Pull to refresh

Почти ОС реального времени: event-driven

Programming microcontrollers *
Пару слов введения:
Делаю систему контроля на базе AtMega32. Цель — отслеживать значение датчиков температуры и давления, управление нагрузкой и сброс отладочных логов в компьютер.

Плюс экранчик 2х16 символов и клавиатура на 7 клавиш. Аппаратную часть использовал готовую — набор NM8036 от МастерКита. А вот с программной частью засада: стандартный алгоритм, уже прошитый в наборе, примитивен и универсален, исходных кодов прошивки нет, обновления выходят в зашифрованном виде. Пришлось писать самому.

Первоначально рисовал «быстро и грязно» — только чтобы работало. Затем система эволюционировала — усложнялись алгоритмы работы, разрастался интерфейс, увеличилось количество параметров, появилась необходимость обучить другого человека работе с системой. Вносить изменение не порушив старый код становилось все сложнее и сложнее. Плюс некоторые архитектурные недоработки, заложенные с самого начала, очень осложняли жизнь.

На форумах, посвященных программированию микроконтроллеров, наткнулся на упоминание об rtos — операционных системах реального времени. Почитал, скачал пару бесплатных, попробовал и принял решение: написать свое.



Извечный вопрос: почему с нуля, а не взять готовое. Та потому что готовые универсальные системы становятся монстрообразными. Там, как в кухонном комбайне, есть все, но радости это не приносит. Все равно буду использовать 10% заложенных функций. А если так, то зачем грузить больше?

Итак, начнем с того, что есть в существующих rtos и мне не нужно:
  • вытесняющая многозадачность (много ресурсов на хранение и переключение контекста);
  • многопотоковость (все задачи разрабатываю я, ресурсов кристалла не много, поэтому изолированные потоки в данной разработке — только усложнение);
  • система приоритетов задач (не так уж много задач и не такие уж они критичные ко времени реакции);
  • гарантированное время отклика (не то, чтобы совсем не надо было, но задачи в основном неприхотливые ко времени. Главное — чтобы обработали).

Подумав еще немного, остановился на архитектуре event-driven («события рулят»).
Для этого ввел такие сущности:
  • событие (сообщение): может инициироваться по таймеру или непосредственно, вызовом соответствующей функции;
  • обработчик события: функция, которая вызывается при возникновении события. На одно событие может быть повешено несколько обработчиков и они будут вызываться последовательно;
  • таймер: можно включить таймер, по истечении которого будет однократно генерироваться заданное событие;
  • очередь сообщений: кольцевой буфер сообщений FILO.
  • диспетчер таймеров: функция, вызываемая по прерыванию таймера;
  • диспетчер событий: функция, которая крутится в главном цикле.


Какой сразу возникает плюс:
— обработчик аппаратных прерываний усыхает до десятка инструкций — создать определенное событие. Соответственно, нет необходимости запрещать прерывания в критичных секциях — задержка из-за прерываний редко превысит 1 мксек, чего вполне хватит даже при программной реализации протоколов типа 1-wire.

Итак, за дело. Сначала немного структур:

#define maxHandlers 64
#define maxMessages 64
#define maxTimers  64

#define MSG_ADC_CYCLE    1
#define MSG_KEY_PRESS    2
#define MSG_KEY_REPEAT    3
#define MSG_LCD_REFRESH    4
#define MSG_BRESENHAM    5
#define MSG_MENU_SELECT    6
#define MSG_MENU_CANCEL    7
#define MSG_1W_TEMP      8
#define MSG_CHECK_TEMP    9
#define MSG_DISP_REFRESH  10
#define MSG_TIMER_SEC    11

typedef unsigned char msg_num; // тип сообытия - мне пока хватает одного байта
typedef int msg_par; // тип параметра события
typedef unsigned char (*handler)(msg_par); // описание функции-обработчика

// структура записи из списка обработчиков
typedef struct{
  msg_num msg; // обрабатываемое событие
  handler hnd; // собственно сам обработчик
} iHandler;

// структура события из буфера событий
typedef struct{
  msg_num msg; // номер события
  msg_par par; // параметр
} iMessage;

// структура таймера
typedef struct{
  msg_num msg; // номер генерируемого сообщения
  msg_par par; // его параметр
  unsigned int time; // таймер в условных тиках (сейчас 10 мсек)
} iTimer;

iTimer    lTimer[maxTimers]; // список таймеров
iHandler  lHandler[maxHandlers]; // список обработчиков

iMessage  lMessage[maxMessages]; // буфер сообщений
unsigned int lMesPointer=0,hMesPointer=0; // указатели на начало и конец буфера

* This source code was highlighted with Source Code Highlighter.


Возможно, я нарушу первую сотню правил по наименованию переменных и функций. Ну что ж, не пинайте больно :)
Ну и так исторически сложилось, что я события называю сообщениями — как по мне, так эти сущности в данной системе эквипенисуальны.

Едем дальше. Работа с обработчиками событий:
// установка обработчика события
// вызывается: setHandler(MSG_KEY_PRESS, &checkKey);
void setHandler(msg_num msg, handler hnd) {
  unsigned char i,j;
  i=0; j=0;
  while (i<maxHandlers) {
    if (lHandler[i].msg==0) { // ищем свободное место
      lHandler[i].hnd = hnd; // и регистрирем обработчик
      lHandler[i].msg = msg;
      break;
    }
    i++;
  }
}

// снятие обработчика события
// вызывается: killHandler(MSG_KEY_PRESS, &checkKey);
void killHandler(msg_num msg, handler hnd) {
  unsigned char i,j;
  i=0; j=0;
  while (i<maxHandlers) {
    
    if ((lHandler[i].msg==msg) && (lHandler[i].hnd==hnd)) {
      lHandler[i].msg = 0; // если нашли нужный, очищаем
    }
    
    if (lHandler[i].msg != 0) {
      if (i != j) { // сдвигаем все записи к началу списка, чтобы дырок не было
        lHandler[j].msg = lHandler[i].msg;
        lHandler[j].hnd = lHandler[i].hnd;
        lHandler[i].msg = 0;
      }
      j++;
    }
    i++;
  }
}

* This source code was highlighted with Source Code Highlighter.


Работа с событиями:

// занести событие в очередь
// пример вызова: sendMessage(MSG_KEY_PRESS, KEY_MENU)
void sendMessage(msg_num msg, msg_par par) {
  hMesPointer = (hMesPointer+1) & (maxMessages-1); // сдвигаем указатель головы
  
  lMessage[hMesPointer].msg = msg; // заносим событие и параметр
  lMessage[hMesPointer].par = par;
  if (hMesPointer == lMesPointer) { // догнали начало очереди, убиваем необработанное сообытие
    lMesPointer = (lMesPointer+1) & (maxMessages-1);
  }
};

// обработка событий
void dispatchMessage() {
  char i;
  unsigned char res;
  
  if (hMesPointer == lMesPointer) { // если пустая очередь - возврат
    return;
  }
  
  lMesPointer = (lMesPointer+1) & (maxMessages-1); // сдвинем указатель
  
  msg_num msg = lMessage[lMesPointer].msg;
  msg_par par = lMessage[lMesPointer].par;
  
  if (msg==0)
    return;
  
  for(i=maxHandlers-1; i>=0; i--) { // просматриваем обработчики с конца
    if (lHandler[i].msg==msg) { // последний занесенный имеет приоритет
      res = lHandler[i].hnd(par); // вызываем обработчик
      if (res) { // если боработчик вернул 1, перываем обработку события
        break;
      }
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


И работа с таймерами:
// установить таймер
// пример вызова: setTimer(MSG_LCD_REFRESH, 0, 50);
void setTimer(msg_num msg, msg_par par, unsigned int time) {
  unsigned char i,firstFree;
  firstFree = maxTimers+1;
  if (time == 0) {
    sendMessage(msg, par);
  } else {
  
    for (i=0; i<maxTimers; i++) { // ищем установленный таймер
      if (lTimer[i].msg == 0) {
        firstFree = i;
      } else { // если нашли - обновляем время
        if ((lTimer[i].msg == msg) && (lTimer[i].par == par)) {
          lTimer[i].time = time;
          return;
        }  
      }
    }
    if (firstFree <= maxTimers) { // иначе - просто добавляем новый
      lTimer[firstFree].msg = msg;
      lTimer[firstFree].par = par;
      lTimer[firstFree].time = time;
    }
  }
}

// убить таймер
// особенность - убивает все установленные таймеры на данное событие,
// не зависимо от параметра события
void killTimer(msg_num msg) {
  unsigned char i;
  for (i=0; i<maxTimers; i++) {
    if (lTimer[i].msg == msg) {
      lTimer[i].msg = 0;
    }
  }
}

// диспетчер таймеров
void dispatchTimer() {
  unsigned char i;
  msg_num msg;
  msg_par par;
  
  for (i=0; i<maxTimers; i++) {
    if (lTimer[i].msg == 0)
      continue;
    
    if (lTimer[i].time == 0) { // если пришло время
      msg = lTimer[i].msg;
      par =lTimer[i].par;
      lTimer[i].msg = 0;
      sendMessage(msg, par); // создаем событие
    } else {
      lTimer[i].time--; // иначе просто уменьшаем время
    }
  }
}

* This source code was highlighted with Source Code Highlighter.


Как ни странно, этого хватает для дальнейшей легкой разработки приложения. Ну об этом — дальше. :)
Tags:
Hubs:
Total votes 75: ↑72 and ↓3 +69
Views 7.6K
Comments Comments 36