Организация диспетчера задач посредством указателей на функции
Дисклеймер: Этот код я написал больше двух лет назад, когда решил написать новый вариант своей системы МенюОС, чтобы применить ее в одном проекте. Проект не был реализован, а старой менюОС вполне хватает до сих пор. За исключением одного НО: для проекта, имеющего помимо пары запускаемых из меню приложений еще некоторые фоновые функции, МенюОС становится эталоном быдлокодинга и частоколом из костылей.
Понадобилось мне организовать изменяемый список задач, которые необходимо запускать через определенные промежутки времени. И тут я вспомнил про тот самый старый проект. Открыл, и был слегка удивлен — для кода того времени функции оказались настолько абстрагированы друг от друга, что без изменения запустились и заработали на новом месте. Ну а поскольку менюОС я распространяю под LGPL, то публикую код здесь.
Поставим простую задачу — на 8 светодиодах, подключенных к одному порту организуем бинарный инкремент. Но не используя непосредственно команду инкремента. Пойдем намного более извращенным путем. Исключительно для наглядности задачи.
Суть всего диспетчера — в его гибкости. Всегда должна иметься возможность изменить набор вызываемых функций, либо периода их работы. Поскольку у каждой функции есть свой адрес, то логично предположить, что кому-то как-то и каким-то образом можно скормить указатель на данную функцию. И в C есть такая возможность.
Объявляется указатель на функцию следующим образом:
Работа с указателем на функцию не отличается от работы с любым другим указателем. А вот чтобы впоследствии функцию вызвать, используем
В нашем примере функции типа void без параметров, объявляем массив указателей по количеству «Слотов», а заодно массив, содержащий периодичность исполнения:
Добавим сюда вспомогательные переменные, необходимые для работы нашей логики переключения:
Для периодичной работы нам нужен таймер. Пусть это будет TIMER2, а работать он будет на переполнение с периодом 10мс.
Создаем функцию настройки таймера:
Ну и функции для активации и останова диспетчера, лишними не будут:
Теперь необходим обработчик прерывания.
Подготовка завершена, осталось заиметь функцию, которая будет заносить программу в слот для исполнения:
Тут стоит отметить, что в данной реализации для деактивации слота необходимо ввести нулевое время срабатывания. Мне данный подход кажется вполне логичным.
Теперь жуткая вещь — 8 одинаковых функций для имитации бинарного инкремента. Задача каждой функции — по вызову переключать состояние ножки:
Зачем? а затем! Что мы будем каждую следующую ножку переключать с вдвое большим периодом выполнения имитируя таки бинарный инкремент. Создаем main:
И все!.. Компилируем по F7, потом зашиваем пунктом меню AVR Studio Tools-COM16 (см.Эту статью) И радуемся жизни:
Эти функции практически в оригинальном виде, слегка исправленные и дополненные, станут основой менюОС2. Кто не знает что такое менюОС, прошу сюда:МенюОС
В целом, у нас получился достаточно простой но эффективный диспетчер.
Если кто-то хочет почитать больше про указатели на функцию, то вот первая попавшаяся мне ссылка:
Указатели на функции
Скачать оформленный в библиотеку диспетчер можно по ссылке: task_manager.rar
Понадобилось мне организовать изменяемый список задач, которые необходимо запускать через определенные промежутки времени. И тут я вспомнил про тот самый старый проект. Открыл, и был слегка удивлен — для кода того времени функции оказались настолько абстрагированы друг от друга, что без изменения запустились и заработали на новом месте. Ну а поскольку менюОС я распространяю под LGPL, то публикую код здесь.
Поставим простую задачу — на 8 светодиодах, подключенных к одному порту организуем бинарный инкремент. Но не используя непосредственно команду инкремента. Пойдем намного более извращенным путем. Исключительно для наглядности задачи.
Суть всего диспетчера — в его гибкости. Всегда должна иметься возможность изменить набор вызываемых функций, либо периода их работы. Поскольку у каждой функции есть свой адрес, то логично предположить, что кому-то как-то и каким-то образом можно скормить указатель на данную функцию. И в C есть такая возможность.
Объявляется указатель на функцию следующим образом:
footype (*fooindex)(type param1 *, type param2 *);
Работа с указателем на функцию не отличается от работы с любым другим указателем. А вот чтобы впоследствии функцию вызвать, используем
(*fooindex)();
В нашем примере функции типа void без параметров, объявляем массив указателей по количеству «Слотов», а заодно массив, содержащий периодичность исполнения:
#define PSLOTS 8 //8 слотов для функции по максимальному количеству функций в диспетчере
void (*progslot[PSLOTS])();//слоты для программ
uint16_t TPpresc[PSLOTS][2]={0};//а сюда пишем время срабатывания кратное 10мс.
Добавим сюда вспомогательные переменные, необходимые для работы нашей логики переключения:
uint8_t bol_pin[8]={0};
Для периодичной работы нам нужен таймер. Пусть это будет TIMER2, а работать он будет на переполнение с периодом 10мс.
Создаем функцию настройки таймера:
void TSetup()//установка таймера.
{
TIMSK2 &= ~(1<<TOIE2);//отключили перерывание по переполнению
TCCR2A &= ~((1<<WGM21) | (1<<WGM20));//обычный режим
TCCR2B &= ~(1<<WGM22);
ASSR &= ~(1<<AS2);//режим тактирования от внутреннего источника.
TIMSK2 &= ~(1<<OCIE2A);//отключили прерывание по сравнению, мало ли...
TIMSK2 &= ~(1<<OCIE2B);//отключили прерывание по сравнению, мало ли...
TCCR2B |= (1<<CS22)|((1<<CS21) | (1<<CS20));//прескалер на 1024.
TCNT2 = 156;//как бы 10мс. (9.986 если быть точнее)
}
Ну и функции для активации и останова диспетчера, лишними не будут:
void TStart()//разрешает работу диспетчера
{
TIMSK2 |= (1<<TOIE2);
}
void TStop()//Останавливает работу диспетчера
{
TIMSK2 &= ~(1<<TOIE2);
}
Теперь необходим обработчик прерывания.
ISR(TIMER2_OVF_vect) {
TCNT2 = 156;//маст врайн ани оферфлоу тайм.
for (uint8_t _j=0;_j<PSLOTS;_j++)
{//смотрим, подошло ли время:
if (TPpresc[_j][0]){//Если Слот активен (время не равно нулю)
TPpresc[_j][1]++;//Отсчитываем текущее время слота
if (TPpresc[_j][0]==TPpresc[_j][1]){//Если досчитали
TPpresc[_j][1]=0;//то обнуляем счетчик и
(*progslot[_j])();//выполняем требуемую программу
}
}
}
}
Подготовка завершена, осталось заиметь функцию, которая будет заносить программу в слот для исполнения:
void TReload(uint8_t _pslot, void (*_f)(), uint16_t _ppresc)//записывает функцию в слот
{
progslot[_pslot]=_f;//забиваем слот
TPpresc[_pslot][0]=_ppresc;
}
Тут стоит отметить, что в данной реализации для деактивации слота необходимо ввести нулевое время срабатывания. Мне данный подход кажется вполне логичным.
Теперь жуткая вещь — 8 одинаковых функций для имитации бинарного инкремента. Задача каждой функции — по вызову переключать состояние ножки:
Индусский код!
void foo0(){
bol_pin[0]=!bol_pin[0];
if (!bol_pin[0])
PORTL&=~(1<<PL0);
else
PORTL|=(1<<PL0);
}
void foo1(){
bol_pin[1]=!bol_pin[1];
if (!bol_pin[1])
PORTL&=~(1<<PL1);
else
PORTL|=(1<<PL1);
}
void foo2(){
bol_pin[2]=!bol_pin[2];
if (!bol_pin[2])
PORTL&=~(1<<PL2);
else
PORTL|=(1<<PL2);
}
void foo3(){
bol_pin[3]=!bol_pin[3];
if (!bol_pin[3])
PORTL&=~(1<<PL3);
else
PORTL|=(1<<PL3);
}
void foo4(){
bol_pin[4]=!bol_pin[4];
if (!bol_pin[4])
PORTL&=~(1<<PL4);
else
PORTL|=(1<<PL4);
}
void foo5(){
bol_pin[5]=!bol_pin[5];
if (!bol_pin[5])
PORTL&=~(1<<PL5);
else
PORTL|=(1<<PL5);
}
void foo6(){
bol_pin[6]=!bol_pin[6];
if (!bol_pin[6])
PORTL&=~(1<<PL6);
else
PORTL|=(1<<PL6);
}
void foo7(){
bol_pin[7]=!bol_pin[7];
if (!bol_pin[7])
PORTL&=~(1<<PL7);
else
PORTL|=(1<<PL7);
}
Зачем? а затем! Что мы будем каждую следующую ножку переключать с вдвое большим периодом выполнения имитируя таки бинарный инкремент. Создаем main:
int main(void)
{
DDRL=0xFF;//светодиоды
TSetup();//настройка таймера
TReload(0,*foo0,10);//заносим программы в слоты. Период 100мс.
TReload(1,*foo1,20);
TReload(2,*foo2,40);
TReload(3,*foo3,80);
TReload(4,*foo4,160);
TReload(5,*foo5,320);
TReload(6,*foo6,640);
TReload(7,*foo7,1280);
TStart();//запуск счета
sei();
while(1)
{
}
}
И все!.. Компилируем по F7, потом зашиваем пунктом меню AVR Studio Tools-COM16 (см.Эту статью) И радуемся жизни:
В целом, у нас получился достаточно простой но эффективный диспетчер.
Если кто-то хочет почитать больше про указатели на функцию, то вот первая попавшаяся мне ссылка:
Указатели на функции
Скачать оформленный в библиотеку диспетчер можно по ссылке: task_manager.rar
6 комментариев
Есть намного более простая и легкая реализация подобного диспетчера, называется она "протопотоки".
Но диспетчер в качестве счетчика — это все равно, круто :)
Вообще, суть именно массива указателей на функции изначально была написана как раз для обработчика кнопок.
Когда при запуске приложения, оно пишет свой/свои обработчик(и) кнопок в массив и тот спокойно выполняется.Хитрость заключалась, например, в том, что в менюОС2 появились фоновые приложения, которые настраивали под себя, например, только одну кнопку, при этом мы спокойно гуляем где были. Но об этом в другой статье :)
Добавил архив с библиотекой.
Спасибо за баг-репорт :)