Организация программ. Событийная система (Event driven system)

 Вопросы организации программ встраиваемых систем довольно скудно освещены в отечественной литературе. Поэтому у начинающих программистов микроконтроллеров рано или поздно возникают проблемы при написании  больших проектов.
   Стандартный подход к построению микроконтроллерных программ сводится к использованию бесконечного цикла, внутри которого происходит опрос флагов, и вызываются разные функции. Однако, такая программа не наглядна, и ее сложно модифицировать.
   Организация программы в виде событийной системы (event driven system) лишена этих недостатков, позволяет реализовывать сложную логику, и не так “тяжеловесна” как операционные системы (RTOS). В этой статья мы разберемся с принципом ее работы и рассмотрим простой вариант ее реализации.

   Теория

   Событийная система представляет собой набор конечных автоматов,  вызываемых диспетчером для обработки очереди событий. Звучит несколько заумно, но на деле все довольно просто.

блок схема событийной системы

   Событие - это нажатие кнопки, наступление прерывания, окончание вычисления формулы и т.п. вещи. Термин этот вроде бы настолько очевиден, что сложно дать ему строгое определение. В одной книге я встречал определение события как местоположения в пространстве-времени имеющее значение для системы. В этом что-то есть.
   Каждое событие в системе имеет свой код, который помещается в кольцевую очередь в случае его наступления.
   Очередь – это структура данных, основанная на принципе “первый вошел, первый вышел” (first in, first out - FIFO) или “кто первый встал, того и тапочки”. То есть событие, попавшее в очередь первым, будет первым и обработано. С этой структурой мы уже имели дело в статье про USART, только там я называл ее  кольцевой буфер.
   Диспетчер извлекает событие из очереди и вызывает для него соответствующий обработчик. Когда обработчик заканчивает работу,   управление снова передаются диспетчеру. Так повторяется до тех пор, пока очередь не опустеет.
  Обработчик события это функция, которая чаще всего построена в виде конечного автомата (Finite State Machine или FSM). Автомат можно определить как виртуальное устройство, умеющее выполнять определенную работу. Он характеризуется таким понятием, как состояние. В зависимости от состояния , автомат по разному реагирует на входные воздействия и выполняет разную работу. Число возможных состояний автомата может быть велико, но оно всегда конечно (поэтому автомат так и называют). Текущее состояние автомата – результат воздействий на него в прошлом, а набор правил, по которым происходит смена состояний автомата называется функцией переходов. Существует много вариантов реализации автоматов, в простейшем случае он строится на операторе switch. Тема автоматов довольно интересна, но выходит за рамки этой статьи. В будущем обязательно напишу что-нибудь по этому вопросу.

 Программная реализация

  Практический пример будем рассматривать в следующей статье, а в этом разберем только заготовку для простой событийной системы.
   Из приведенного выше рисунка понятно, что костяк системы составляют – кольцевой буфер/очередь, диспетчер и обработчики. Для заготовки нужен только буфер и диспетчер. Обработчики вещь индивидуальная и от проекта к проекту меняются.

Кольцевой буфер и как он используется

   Не буду приводить код кольцевого буфера, он уже подробно разбирался.
Для работы с кольцевым буфером у нас есть две основные функции:

void ES_PlaceEvent(unsigned char event) – положить событие в буфер
unsigned char ES_GetnEvent(void) – взять событие из буфера

Функция ES_PlaceEvent вызывается источником события. Чаще всего это происходит в прерываниях. Например так:

//коды событий
...
#define EVENT_TIMER0  0x01


#pragma vector = TIMER0_COMP_vect
__interrupt void Timer0CompVect(void)
{
    ES_PlaceEvent(EVENT_TIMER0);
}

   Функция ES_GetEvent вызывается из main`а в бесконечном цикле while. Она возвращает код события и если он не равен нулю, то запускается диспетчер.

Диспетчер

   Диспетчер должен вызывать для каждого события свой обработчик. Его можно построить на операторе switch, но мне больше нравиться диспетчер с массивом функций. Он простой, как три копейки, но не очень безопасный.

void ES_Dispatch(unsigned char event)
{
  void (*pFunc)(void);           //переменная – указатель на функцию
  pFunc = FuncAr[event-1];     //присваиваем ей значение из массива
  pFunc();                             //вызываем функцию
}

   FuncAr[] – это массив указателей на функции-обработчики событий. Код события используется в качестве индекса, а значит функции-обработчики событий должны размещаться в массиве FuncAr[] в определенном порядке. Диспетчер не безопасный, потому что если передать ему событие, код которого больше размерности массива, то случится какой-нибудь коллапс.
   Массив указателей на функции должен быть объявлен до диспетчера.

//пустая функция, ничего не делает
void EmptyFunc(void)
{

}

//массив указателей на функции-обработчики

__flash void (*FuncAr[])(void) =
{
  EmptyFunc
  //потом добавим сюда указатели на обработчики
};

Пока что массив содержит указатель на пустую функцию EmptyFunc() – это  просто для наглядности.
Вот собственно и все содержимое заготовки.

event-system.c

Функция main()

Функция main() будет выглядеть так:

//подключение заголовочных файлов

//макроопределения


unsigned char event = 0;
int main(void)
{

//инициализация периферии
//разрешение прерываний


  while(1){
     event = ES_GetEvent();
     if (event)
        ES_Dispatch(event)
  }
return 0;
}

Файлы

Заготовка для IAR`aЗаготовка для CodeVision

Добавить комментарий

При добавлении в комментарий Си кода, заключайте его между тегами [code] [/code]. Иначе он будет отображаться некорректно.


Защитный код
Обновить