Since 2009

+38(044)390-01-21          e-mail: sales@enertec.ua

Чтобы увидеть, как это работает, перейдите на опубликованный сайт.
  • Разделы
  • Все посты
  • Мои посты
alex.tubolets
31 янв. 2018 г.

Мультизадачность

в Code::Blocks

Добрый день. У меня следующий вопрос-задача и хотелось бы увидеть мнение специалиста, как лучше к этому подойти.

Я хочу построить на этих контроллерах всю систему умного дома. Судя по моим изысканиям, это вполне возможно.

Одна из задач - управление освещением, включая диммирование. Т.е. нажимаю на кнопку и, пока удерживаю её, яркость постепенно растет. Вопрос в том, как реализовать, чтобы во время удержания кнопки не блокировались другие сигналы.

Примеры кода (не ругайте за код, это просто чтобы мысль выразить)

void App_Run() {

// TODO: Add here your algorithm

TTimer t;

//Обработка удержания кнопки, которая подключена к первому дискретному входу

if (IO.GetDI(1)==1) t.Start;

while(IO.GetDI(1)==1)

{

if (t.Elapsedms>2000 && t.Elapsedms<10000) IO.SetAO(1,(t.Elapsedms/10));

}

if (IO.GetDI(1)==0 && t.Elapsedms>1 && t.Elapsedms<=2000) IO.SetDO(1,!IO.GetDO(1));

//обработка нажания кнопки на дискретном входе 2

if (IO.GetDI(2)==1) IO.SetDO(2,0);


Проблема в том, что пока удерживается кнопка 1, нажатие кнопки 2 будет игнорироваться, потому что до строчки кода с обработкой нажания не дойдет очередь, пока крутися цикл.

Как я вижу решения принципиально:

1. Использование глобальных переменных. В этом случае App_Run будет использоваться как цикл и обновлять в них текущие состояния входов. Дополнительно, надо бы придумать, как хранить состояния в персистент переменных на случай отключения питания. Использовать персистент для "операционной" работы не хочу, потому что они медленные.

Вопрос здесь только в том, разрешены ли глобальные переменные вообще в целом в этом компиляторе. В документации явного ответа нет.

2. Для каждой кнопки, которая может удерживаться, использовать отдельные потоки. Я вроде нашел, как запускать функции в отдельных потоках и поэкспериментирую. Вопрос в том, насколько корректно в каждой такой функции запускать бесконечный цикл для прослушивания входов. Не загрузит ли это процессор?


3? Может я что-то упустил и есть какой-то другой способ? Например, в тестовом приложении в код:блокс в файлах main.c и useri.c у каждого свой комплект функций App_Init & App_Run. Может можно иметь такие же комплекты для каждой кнопки?

2 комментария
0
Евгений Луценко
01 февр. 2018 г.

Здравствуйте!


Глобальные переменные здесь разрешены и, учитывая специфику задач, даже приветствуются. Объявление глобальных переменных делается вне функций обычным образом (тип, идентификатор и ';'). Можно объявлять глобальные переменные и внутри подключаемых файлов.

Типом может быть и имя класса, в том числе собсвенного.


Чтобы объявить персистент переменную, нужно дописать ключевое слово "persistent" перед объявлением типа. Persistent-переменные дейсвительно медленные и имеют ограниченный ресурс перезаписи. Поэтому напрямую работать с персистент переменными можно только в том случае, когда они используются очень редко.

В "операционной" работе для каждого параметра, значение которого нужно сохранять, следует вводить две переменные: одну обычную и одну персистент.

При инициализации внутри App_Init() следует вычитать все энергонезависимые переменные в обычные. А в процессе работы синхронизировать изменения значения обычных переменных с изменениям персистент. Делать это можно либо отлавливая все возможные точки изменений, либо написав специальную синхронизирующую функцию, которая вызывается периодически или по запросу.

Перед перезаписью персистент-переменной обязательно проверяйте, отличается ли текущее значение от записываемого.


Обратите внимание на то, что при перезаливке прошивки персистент память не трогается.


Работая с персистент-памятью, Вы столкнетесь с проблемой первичной инициализации. При первой загрузке пришивки (или ручном сбросе настроек) на контроллере Вам нужно будет выполнить одни действия (задать персистент из ROM), а при последующих - другие (оставить персистент без изменений). Тут вам поможет отдельная персистент переменная, в которую при инициализации будет записываться системная константа nvmcc_CompileDate (которая в свою очередь будет разной для каждой отдельной прошивки, что обеспечит переинициалицию при перепрошивке новой версией).


persistent long InitDone;


void App_Init(){

if (InitDone != nvmcc_CompileDate){

/* первая инициализация персистент */

InitDone = nvmcc_CompileDate;

}

else{

/* последующие инициализации персистент */

}

/* остальная инициализация */

}


Теперь насчет потоков. Это интересный инструмент, но тут есть определенные ограничения.

Следует различать потоки (threads) и задачи (tasks). В мульзадачном шаблоне прошивка разделена на две задачи (main и useri). Каждая задача имеет свой отдельный файл, свои функции App_Init() и App_Run(). Глобальные переменные задач не пересекаются. Для передачи данных между задачами используются функции чтения/записи по CAN. Задача main используется для общей логики и IO, а useri - для управления дисплеем и клавиатурой. Такая схема позволяет писать приложения, которые можно использовать с внешними удаленными дисплеями.

Свою задачу пользователь создать не может. То есть пользователь может только выбрать стартовый шаблон с одной задачей или двумя.


Внутри задач (а именно, внутри функции инициализации) можно запускать специальные функции - потоки. Каждый поток имеет общие глобальные переменные с родителькой задачей.


Всего, задач и потоков можно открыть лишь 5. Причем две задачи main и useri определены автоматически, еще один поток автоматически создается для реагирования на запросы по modbus.

Таким образом обычно для пользователя остается всего два доступных потока.

Один из них логично использовать для входов/выходов и сопряженных с ними задач быстрого реагирования. Так что на много кнопок потоков не хватит.



Резюмируя, можно посоветовать следующее:

1. Цикл while(IO.GetDI(1)==1) заменить конструкциями через глобальные переменные.

2. Если прошивка будет достаточно нагружена, обработку всех входов/выходов и критических по времени участков можно помеcтить в отдельный поток из main (сначала считываем входы, потом обрабатываем крит блоки, потом посылаем выходы).

3. Теоретически можно приспособить один или два потока для управления 1-2 кнопками. Однако лучше прибегнуть к этому, если реализация предыдущего варианта на практике Вас будет принципиально не устраивать по скорости реагирования.

4. Лучше разделить визуально алгоритм и вычитку входов/выходов. То есть отдельно переводим значения IO в некоторый понятный набор переменных и отдельно используем эти переменные в алгоритме.


0
alex.tubolets
05 февр. 2018 г.

Поэкспериментировал, получилось с обоими вариантами. В принципе, у меня сейчас наверно достаточно инструментов для реализации значительного куска функционала.

Спасибо.

0
2 комментария

© 2009-2017 mcx.support

Designed by Eugene Malyukin