Пару слов введения:
Делаю систему контроля на базе AtMega32. Цель — отслеживать значение датчиков температуры и давления, управление нагрузкой и сброс отладочных логов в компьютер.
Плюс экранчик 2х16 символов и клавиатура на 7 клавиш. Аппаратную часть использовал готовую — набор NM8036 от МастерКита. А вот с программной частью засада: стандартный алгоритм, уже прошитый в наборе, примитивен и универсален, исходных кодов прошивки нет, обновления выходят в зашифрованном виде. Пришлось писать самому.
Первоначально рисовал «быстро и грязно» — только чтобы работало. Затем система эволюционировала — усложнялись алгоритмы работы, разрастался интерфейс, увеличилось количество параметров, появилась необходимость обучить другого человека работе с системой. Вносить изменение не порушив старый код становилось все сложнее и сложнее. Плюс некоторые архитектурные недоработки, заложенные с самого начала, очень осложняли жизнь.
На форумах, посвященных программированию микроконтроллеров, наткнулся на упоминание об rtos — операционных системах реального времени. Почитал, скачал пару бесплатных, попробовал и принял решение: написать свое.
Извечный вопрос: почему с нуля, а не взять готовое. Та потому что готовые универсальные системы становятся монстрообразными. Там, как в кухонном комбайне, есть все, но радости это не приносит. Все равно буду использовать 10% заложенных функций. А если так, то зачем грузить больше?
Итак, начнем с того, что есть в существующих rtos и мне не нужно:
Подумав еще немного, остановился на архитектуре event-driven («события рулят»).
Для этого ввел такие сущности:
Какой сразу возникает плюс:
— обработчик аппаратных прерываний усыхает до десятка инструкций — создать определенное событие. Соответственно, нет необходимости запрещать прерывания в критичных секциях — задержка из-за прерываний редко превысит 1 мксек, чего вполне хватит даже при программной реализации протоколов типа 1-wire.
Итак, за дело. Сначала немного структур:
Возможно, я нарушу первую сотню правил по наименованию переменных и функций. Ну что ж, не пинайте больно :)
Ну и так исторически сложилось, что я события называю сообщениями — как по мне, так эти сущности в данной системе эквипенисуальны.
Едем дальше. Работа с обработчиками событий:
Работа с событиями:
И работа с таймерами:
Как ни странно, этого хватает для дальнейшей легкой разработки приложения. Ну об этом — дальше. :)
Делаю систему контроля на базе 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.
Как ни странно, этого хватает для дальнейшей легкой разработки приложения. Ну об этом — дальше. :)