12

Микроконтроллеры и язык Си: указатели на функции

Последние пару месяцев я занимаюсь тем, что пишу программку для одного микроконтроллера (для M30624, семейство m16c/62). Хотя называть его "микроконтроллер" - не совсем правильно, поскольку микросхема эта довольно навороченная.

Частенько я программу для контроллера описываю в виде графа состояний. Имеется одна глобальная переменная STATE, в зависимости от значения которой вызываются соответствующие подпрограммы, например вот так:

while(1)
{
int res;
int cmd = getCommand();
switch(STATE)
{
case STATE_1:
res = sub_prog1(cmd);
break;
case STATE_2:
res = sub_prog2(cmd);
break;
...
..
case STATE_N:
res = sub_progN(cmd);
}
STATE = res;
}

В этот раз состояний оказалось многовато, более 20 штук (а точнее - 22). Возможно, я что-то не то сделал, и все это можно было как-то иначе описать, но... пока не хочется, думаю что и так все будет фурыкать. Другое дело, что неохота было такой громадный оператор switch расписывать. Тут я вовремя вспомнил про указатели на функции. Хорошая штука, только на работе, млин, ни одной книжки по Сям не оказалось, а я, как на зло, забыл, как оно называется. Искал в инете по словам "процедурный тип", "функциональный тип" и т.п, и ессно, ничего нужного мне не нашел. На скорую руку написал по памяти:

typedef int (*StateHandler)(int);

По моим представлениям, такое объявление дает понять компилятору, что StateHandler - это указатель на функцию, возвращающую тип int и принимающую один параметр тоже типа int. Как потом оказалось, правильно я его все-таки объявил :) После этого приведенный в начале поста код можно переписать уже вот так:

StateHandler handlers[] = {sub_prog1, sub_prog2, ... sub_progN};

while(1)
{
cmd = getCommand();
STATE = (*(handlers[STATE]))(cmd);
}

По-моему, получается короче, да и работать наверняка будет побыстрее. Главное, чтобы значения переменной STATE шли по-порядку, но имхо, это как раз не проблема.

12 коммент.:

John комментирует...

Не боишься вылететь за пределы буфера? Вернется как-нибудь STATE = -1 или +10000.
Хотя согласен с тобой, с указателями на функции изящно получается.

Lotrex комментирует...

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

Roman комментирует...

Подскажите, где можно купить такой микроконтроллер?

Lotrex комментирует...

Есть на Симметроне (www.symmetron.ru), интернет-магазин на http://www.chipinfo.ru, магазин Chip & Dip (http://chipdip.ru). Магазин http://www.chipinfo.ru вроде как по почте компоненты посылает, поэтому я думаю, Вам лучше к ним обратиться.

Анонимный комментирует...

lnhjknk

Анонимный комментирует...

ага а так еще короче
while(STATE = handlers[STATE](getCommand()));

Анонимный комментирует...

вернее даже так если 0 может возвращатся как состояние
while((STATE = handlers[STATE](getCommand()), 1));

Анонимный комментирует...

ыыы

#define SETUP_STATE_HANDLER \
int(*handler[])(int) = {sub_prog1, sub_prog2, ...}
#define WHILE_STATE_HANDLER \
while((STATE = handlers[STATE](getCommand()), 1))

int main(int argc, char *argv[])
{
SETUP_STATE_HANDLER;
WHILE_STATE_HANDLER;
}

Анонимный комментирует...

а вообще вариант со switch мне больше всего нравится его легче всего исправлять (а значит и меньше шансов сделать ошибку) и вообще не надо беспокоится о неправильных индексах :)

Анонимный комментирует...

или так :)

#define SETUP_STATE_HANDLER(...) \
int(*handler[])(int) = {__VA_ARGS__}

а потом ...

SETUP_STATE_HANDLER(sub_prog1, sub_prog2, sub_progN);
WHILE_STATE_HANDLER;

любую идею можно довести до абсурда )
я бы так не сделал мой выбор switch :)

virspb комментирует...

Я может быть ошибаюсь, но с чего вы взяли, что прямая адресация работает медленнее косвенной?

Lotrex комментирует...

Я не считаю, что прямая адресация работает медленнее косвенной.
Я имел в виду, что оператор
switch(STATE)
{
case STATE_1:
res = sub_prog1(cmd);
break;
case STATE_2:
res = sub_prog2(cmd);
break;
...
..
case STATE_N:
res = sub_progN(cmd);
}

будет выполнятся с такой же скоростью, или медленнее, чем оператор

STATE = (*(handlers[STATE]))(cmd);

Основания для таких мыслей есть. В первом случае прежде чем вызвать процедуру sub_progN (прямая адресация) нужно определить, какую из N процедур вызывать. Если числовые значения всех констант STATE_1, ... STATE_N идут подряд, компилятор может создать код, в котором не будет ни одной операции сравнения, а будут только переходы на функции либо переходы к местам вызова функций. В первом случае фактически будет использоваться та же косвенная адресация. Во втором - будет 1 безусловный переход на STATE_N*k адресов вперед к месту вызова функции + сам вызов функции (тоже 2 операции). Возможно, второй вариант будет работать быстрее, чем косвенная адресация, но я в этом сомневаюсь. Если компилятор не в состоянии оптимизировать оператор switch описанными способами, он будет использовать просто кучу условных переходов, и чем больше вариантов переходов будет, тем медленнее такой switch будет работать.

Отправить комментарий