SWITCH-технология для МК. Пример реализации на AVR.

AVR

SWITCH-технология для МК. Пример реализации на AVR.


Disclaimer
Цель написания данной заметки исключительно простая — поделится собственным опытом и наработками применения так называемой SWITCH-технологии при разработке программного обеспечения для МК. В данной заметке приведено краткое описание реализации UART LCD дисплея на ATmega8 и символьный дисплей 2Х16 символов (исходники прилагаются).
Я не являюсь профессиональным программистом или разработчиком устройств на МК, так что возможен «быдлокод» и не самые изящные решения :). Примеры и исходники приведенные в данной заметке могут свободно использоваться любыми представителями гуманоидных цивилизаций (и негуманоидных тоже, но после согласования с автором:)).

Пролог
В процессе освоения МК наверное для каждого наступает момент перехода от простых приложений типа помигать светодиодом, померить температуру или напряжение без особых изысков, к более сложным проектам, включающим работу с различной периферией (UART, LCD дисплей и т.п.), а также реализацию всяческих меню и протоколов взаимодействия с ПК например.
По мере усложнения программ становится все труднее контролировать структуру программы, обеспечивать взаимодействие подпрограмм (функций) между собой, обеспечивать контролируемое (фиксированное) время исполнения кода.
Именно в такой момент я и начал изучать (во многом благодаря материалам с этого ресурса) как строить структуру программы, а также что такое операционные системы для МК. Скажу честно наибольшего понимания работы операционных систем и их частей (планировщиков, очередей задач и таймеров) я достиг именно после освоения той самой SWITCH-технологии (или автоматного программирования). По моему скромному мнению, в любительской практике применение операционных систем для МК слишком сложно, ну не считая может минималистичных реализаций (например от уважаемого DIHALTa).

Что такое SWITCH-технология?
Итак к делу. Основой для моих наработок по применению SWITCH-технологии при разработке программного обеспечения для МК послужили статьи господина Владимира Татарчевского в журнале «Компоненты и технологии» (статьи приложу к заметке вместе с исходниками). Крайне рекомендую к ознакомлению.
Суть самой технологии в том, что все основные функции программы реализуются как конечные автоматы на основе SWITCH конструкции (в терминах С). Для взаимодействия между автоматами используется служба сообщений. Также реализована служба таймеров. Подробнее описывать не буду — смотрите приложенные статьи, там все описано наилучшим образом.

Зачем это нужно?
Основными плюсами применения данной технологии при на писании программ для МК являются:
  • возможность независимо разрабатывать и модифицировать подпрограммы (функции)
  • прозрачный и надежный механизм взаимодействия между подпрограммами (функциями)
  • удобнейшая гибкая служба таймеров
  • относительно компактный, портируемый и легко читаемый код

Пример реализации.
В качестве примера реального использования данной технологии представляю вам UART LCD дисплей на ATmega8 + EONE1602 (LCD дисплей 16Х2).
Данный дисплей можно использовать как с МК, так и с ПК (нужен только преобразователь уровней или USB-UART). Идеей загорелся после первых опытов подключения дисплея к МК и написания собственной библиотеки для работы с ним. Считаю, что такие вещи лучше делать самому, а не использовать готовые решения — множество моментов связанных с реальным программированием МК проясняется в процессе разгребания грабель :).
Итак дисплей воспринимает по UARTу команды и строки для отображения в виде строк ASCII оканчивающихся кодом перевода строки (0x13). Поддерживаются символьные дисплеи в конфигурации 2Х8 и 2Х16.
В программе реализованы следующие функции:
Прием команд и строк по UART на скоростях 9600 или 38400 бод (при использовании внутреннего генератора Меги8 на 8МГц) с использованием кольцевого буфера до 128 байт (размер буфера равный 2^n задается в дефайнах)
Передача от дисплея символов подтверждения приема (можно отключить в дэфах), сообщений об ошибках (реализовано не все, можно отключить в дэфах), информации о прошивке и формате дисплея (можно отключить в дэфах);

Поддерживаются следующие команды:
  1. sSTRING — вывод строки на дисплей
  2. eSTRING — эхо строки обратно в порт (может и пригодится кому)
  3. cCOMMAND — команда для дисплея
    • сс — очистка экрана
    • cs{0/1} — очистка строки 1ой или 2ой соответственно
    • co{0/1} — включить/выключить дисплей
    • cb{0-9} — установить уровень подсветки (0-9) (пока не реализовано!)
    • ck{0-9} — установить контраст (0-9) (пока не реализовано!)
    • cpXYY — установит позицию вывода в Х{0/1} строку и в YY{0-15} позицию
    • cxo{0/1} — курсор включить/выключить
    • cxb{0/1} — мигающий курсор включить/выключить
    • cxh — курсор домой (в позицию 0, 0)
    • cxm{0/1} — сдвиг курсора влево/вправо на 1 позицию (0-Л/1-П)

Немного подробностей по программе.

В файлах FSM.c и FSM.h находится код основных функций:
void FSM_LED(void)
— мигание/включение/выключение светодиода (самая важная функция! :) )
void FSM_LCD(void)
— работа с дисплеем, обновление из фрэймбуфера, очистка, включение/выключение
void FSM_UART(void)
— работа с UARTом, прием и передача через кольцевые буферы, первоначальная обработка комманд
void FSM_CMD(void)
— обработка комманд и данных принятых по UART

В файлах UART.c и UART.h находится код обработчика прерываний UARTAа с реализацией работы на кольцевых буферах.
В файлах LCD.c и LCD.h находится код библиотеки работы с дисплеем. Пины к котроым подключен дисплей легко переопределяются в дефайнах. Единственное ограничение: пины данных (DB4-DB7) должны идти 4 подряд в одно порту.

В файлах MESSAGES.c, MESSAGES.h, TIMERS.c и TIMERS.h находятся функции обработки службы сообщений и службы таймеров соответственно.

В main.c и main.h соответственно инициализация периферии, конечных автоматов и естественно главный цикл.
В главном цикле только обработчики службы сообщений и таймеров, а также вызов функций конечных автоматов. И все! В этом и вся прелесть метода — нужно добавить еще функцию или наоборот исключить на время — просто добавляем/комментируем вызов соответствующей функции конечного автомата в главном цикле. Profit!

/*************/
	/* MAIN LOOP */
	/*************/
	while (1) {
		// process timers
		ProcessTimers(&sys_timer);

		// FSMs' processing here
		FSM_LED();
		FSM_LCD();
		FSM_UART();
		FSM_CMD();

		// process messages
		ProcessMessages();
        }


Немного подробностей по работе с сообщениями и таймерами.
Таймеры (глобальные таймеры я пока не использовал, но они реализованы; по вопросам функционала см. первоисточник):
void ProcessTimers(u08 * tick)
— обработчик таймеров; получает указатель на системную переменную, которая инкрементируется по прерыванию таймера (TIMER0 в данной реализации) каждую 1 миллисекунду; таймеры считают от 0 до 65535 и дальше по кругу;
void InitTimers(void)
— начальная инициализация таймеров;
u16  GetTimer(u08 Timer)
— возвращает значение таймера по номеру;
void ResetTimer(u08 Timer)
— сброс таймера в 0 (начало счета)

Сообщения (глобальные сообщения в данной программе не использовал, но они реализованы; по вопросам функционала см. первоисточник):
void InitMessages(void)
— начальная инициализация сообщений;
void ProcessMessages(void)
— обработчик службы сообщений;
void SendMessageWParam(u08 Msg, u08 * ParamPtr)
— послать сообщение с параметром: адрес сообщения и указатель на параметр;
void SendMessageWOParam(u08 Msg)
— послать сообщение без параметров: иногда параметры передавать не нужно, сигналом является сам факт получения сообщения;
u08  GetMessage(u08 Msg)
— получить сообщение: возвращает True (1) если сообщение было послано, иначе False (0);
u08 *GetMessageParam(u08 Msg)
— возвращает указатель на параметр сообщения;

Q: Почему нет более подробного описания исходников и алгоритма?
А: Пробовал описать словами работу кода и вставить куски в текст — как-то у меня не пошло.
Логика работы программы вполне понятна из исходного кода (ну кроме может некоторой магии при работе с дисплеем).

Вот вкратце и все :) Если есть еще вопросы по коду — пишите в каментах, обсудим.

P.S. В процессе создания данного программно-аппаратного решения были использованы следующие ресурсы: Atmega8-16PU; EONE1602; AVR910 Protoss USB ISP programmator; Eclipse IDE + AVR plugin; GCC + AVR lib-c; www.easyelectronics.ru; www.chipenable.ru; internet; Google; brain + 2 straight hands ;)
  • +1
  • 31 октября 2012, 21:53
  • Masakra
  • 2

Комментарии (60)

RSS свернуть / развернуть
Статьи интерестные.
Тебя может заинтересовать еще вот этот фреймворк основаный на конечных автоматах
0
Кстати вот пост в сообществе про этот фреймворк
0
хм. КА на свичах. что тут может быть нового?..
0
Просто делюсь опытом. Все что мне попадалось про КА в программировании МК была в основном теория и куски кода. Я выложил реальный работающий проект — он может сам по себе комуто пригодиться.
0
Простите, но как Вам удается уйти от зависания если входной буфер USART заполнен? Т.е когда
(((RX_buff.tail — RX_buff.head + 256) & (RX_BUFFER_SIZE-1)) будет >= (RX_BUFFER_SIZE-1)).
Вы же в этом случае не считываете UDR, поэтому выйдя из прерывания оно произойдет вновь и вновь?
0
Действительно. Исходники поправил. Спасибо.
На практике программа при переполнении буфера успешно сваливалась в обработчик ошибок — скорее всего потому что AVR после возврата из прерывания выполняет минимум 1 команду, прежде чем снова уйти в прерывание. Так и доползало до обработчика (на глаз было незаметно ;))
0
Автоматы — полезная штука!
Тоже когда то с этой статьи начинал
0
Хм, я часто использую концепцию КА, но я не понимаю, что означают следующий тезисы:

• возможность независимо разрабатывать и модифицировать подпрограммы (функции)
• прозрачный и надежный механизм взаимодействия между подпрограммами (функциями)

Эти моменты – основа структурного программирования, при чем здесь «SWITCH-технология»? Можете, своими словами, объяснить, что Вы имели в виду?
0
По пункту 1:
Имелось ввиду, что при написании кода КА реализующие разные функции (дисплей, уарт и етс.) работают независимо друг от друга. Также при реализации логики работы КА его можно писать частями, при этом код все время рабочий и независим от кода других КА. Подобный подход при классическом построении программы не работает. Опять же порядок вызова функций КА в главном цикле зачастую неважен.
По пункту 2:
Взаимодействие КА между собой происходит через службу сообщений, которая гарантирует что любой автомат получит сообщение в течении следующей итерации главного цикла, при условии конечно что он способен принять это сообщение. Нет необходимости контролировать момент изменения флаговых переменных, практически отпадает вопрос синхронизации между КА.

Примерно это я и имел ввиду.
0
Взаимодействие КА между собой происходит через службу сообщений, которая гарантирует что любой автомат получит сообщение в течении следующей итерации главного цикла, при условии конечно что он способен принять это сообщение. Нет необходимости контролировать момент изменения флаговых переменных, практически отпадает вопрос синхронизации между КА.
А чем это лучше простого выставления флага и проверки его в главном цикле? Также на следующем проходе флаг будет обнаружен, причем без всяких лишних накруток кода в виде «службы сообщений» и затрат ресурсов на нее…

Это для программиста — вроде как удобно, что что-то делается за него, но контроллеру это — молотить лишние команды кода.

Чем больше посредников — тем дороже товар…
+1
Я постепенно перешёл от флагов к callback-методам =) Может, оно и тратит тоже немного лишнего кода и времени, но зато удобнее представимо: наступило событие, вызвалась соответствующая функция. И уже она, если так надо, выставит нужный флаг или обработает событие сама, если там дел ни о чём и надо срочно. И что здорово, от железа алгоритм уже не зависит. Какие он там регистры тягать будет, в какие прерывания попадать, арм это или авр — личное дело этих низкоуровневых функций. В службе же сообщений особой выгоды не вижу.з.
Грубо говоря:

void protocol_onTimeout(void)
{
    // ...
}

void uart_onReceiveChar(uint8_t Char)
{
    // ...
}

// ...
timer_AddFunction(10, &protocol_onTimeout);
uart_SetRxHandler(&uart_onReceiveChar);
0
Я постепенно перешёл от флагов к callback-методам =) Может, оно и тратит тоже немного лишнего кода и времени, но зато удобнее представимо: наступило событие, вызвалась соответствующая функция. И уже она, если так надо, выставит нужный флаг или обработает событие сама, если там дел ни о чём и надо срочно.
А каким образом определяете наступление события? Не все же они на прерываниях висят.
Значит, все равно надо в главном цикле (или циклическом прерывании системного таймера) проверять, например, ноги портов, или еще чего…

Не вижу принципиальных отличий от типичного флагового автомата.

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

Ничего лишнего, все при деле. И предельно прозрачный алгоритм, без промежуточных прослоек. Раньше писал это на ассемблере, в последние годы — на Паскале, но сам принцип построения программы — все тот же (интуитивно нащупанный путем проб и ошибок когда-то).

По сути, и большинство модных сейчас микроконтроллерных «RTOS», при их ближайшем рассмотрении, оказываются всего лишь все теми же флаговыми автоматами (если их набор задач задан при программировании, а не формируется извне произвольно, как, например, загрузкой исполняемых файлов в компьютерах).
Разница лишь в том, что эти «RTOS» всего лишь предоставляют программисту уже готовую часть флагового автомата, осуществляющего проверку флагов и переключение задач по флагам или событиям, с ограничением времени работы отдельных задач по времени, контролируемому независимым от самих задач таймером…

Хотя нормально сделанный флаговый автомат сделает всю ту же работу, что и программы со «RTOS», более просто, прозрачно, эффективно и экономно.

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

Все равно любая программа микроконтроллера с фиксированным списком задач, заложенных при написании, в целом является ничем иным, как флаговым автоматом, с конечным и вполне определенным количеством состояний.

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

Привыкают писать программы абы как, надеясь, что компилятор и RTOS за них все распараллелят и оптимизируют.

Что и приводит в итоге к бесконечной гонке за мегабайтами и мегагерцами, вместо написания эффективно работающего кода.
0
Отличий, и правда, не слишком много =) Просто код расположен в исходниках чуть иначе, а по сути это те же флаги. Ну и зачастую это даёт переносимый алгоритм. В тех же АВРках таймеры всё же отличаются по настройке в разных контроллерах, уарты и прочая периферия тоже. То есть даже не по настройке, а по названиям регистров и управляющих битов. При таком подходе можно на это забить, написав для каждого контроллера свой модуль уарта/таймера и т.д, который потом и добавлять, никак не редактируя, в проект.

Ещё я очень люблю, что область видимости переменных была как можно меньше — только в пределах нужного куска кода, если это возможно. При этом всем остальным модулям плевать, как называются мои функции и переменные в главном цикле. Я могу использовать какие хочу и даже с модификатором static. С флагами такое не очень покатит: или прерывания надо пихать в тот модуль, где флаги используются (что на переносимости сказывается нехорошо), или делать флаги глобальными и видимыми через extern низкоуровневым драйверам, чтоб они могли ими управлять. А это снижает универсальность этих модулей — надо следить, как переменные называть, делать h-файлы одинаковые во всех проектах, где эти флаги выставлены на обозрение с помощью extern, бе. При этом всё жёстко завязано на тип и значение флагов. Захочешь сделать флаг не переменной, а битом в каком-то регистре — фиг, придётся переписывать код в нескольких файлах. А так вся логика сидит в одном, подчиняясь сигналам, пришедшим из низкоуровневого модуля. А так как вызывается функция, в ней я могу выставить флаги так, как пожелаю. Хоть один, хоть два, хоть десять. Могу что-то проверить, могу сделать какое-то действие, не дожидаясь очереди в главном цикле. Что захочу.

А выставляются они где на прерываниях, где в основном коде. По-всякому, как и у вас. Зависит от того, какие события обрабатываются и какая скорость реакции нужна =)

Хотя, конечно, кое-что работает и на флагах, где это уместно и не требует мгновенной реакции. Но это всё тоже упрятано в функции (для внешних модулей), никаких переменных модули на обозрение не выставляют:
bool dmx_Received(void);


Естественно, в функции этой флаг и сбрасывается, если он есть =)
if(dmx_Received())
{
    dimmer_Update(dmx_Data());
}


вместо такого:
extern bool dmx_received;
extern uint8_t * dmx_buffer;
// ...
if(dmx_received)
{
    dmx_received = false;
    dimmer_Update(dmx_buffer);
}


Только в крайнем случае, когда скорости катастрофически не хватает, от всего этого я могу отказаться. Но пока таких критичных задач особо и не возникало.

Кстати, RTOS недооценивать не стоит =) На восьмибитниках оно, конечно, излишне, но на армах с аппаратной поддержкой ОС оно может стать хорошим подспорьем. Все алгоритмы разбегутся по процессам, не загромождённые левым кодом от соседних алгоритмов. Хотя не скажу, что я их где-то использовал пока что.
0
Я большинство флагов собираю побитно в байты, которые называю «слово состояния программы». Значения выбираю так, чтобы в исходном состоянии (при старте программы) там были все нули. Кроме наглядности, упрощает и обработку. Достаточно проверить байт флагов на 0, — и можно пропустить целый блок побитовых проверок для разных задач. Не 0 — смотрим тогда, что в нем имеем…

В более — менее сложных программах — имею обычно 2-3 и более таких байта состояния с флагами, сгруппированными по важности и старшинству. Следующий байт обычно содержит флаги, детализирующие отдельные процессы, общая информация о которых (типа в работе — нет), находится в предыдущих байтах. Также в отдельные байты собираю, например, битовые флаги радиоканала, и так далее.

Работаю больше с PICами, а там битовые операции очень удобны, независимо, регистр это, спецрегистр, порт, или переменная. В МикроПаскале компилируется в минимум команд, мало отличаясь от ассемблера.

Вот с AVR — с битами посложнее, из за разнобоя с типами адресации и областью действия разных однотипных команд…
Иной раз проще под флаг целый байт отдать.
0
Ага, согласен, группировать флаги бывает удобно, если они одного назначения — флаги ошибок, например. Да, я так делаю иногда в армах, в авр редко… Там обычно bool.

Хорошо бы, если бы в виде статьи написали про свои техники те, у кого они есть =)
0
А зачем? что бу каждый посрал в комменты «фу, свитч», «Фу, асм», «Фу, <вставьте свой вариант>»?
0
Ну например просто рассказать о своем опыте. Я например зарегистрировался не так давно, и только когда возникло желание поделиться достигнутым результатом. А до этого читал материалы с ресурса без возможности «посрать в каментах», и многие оказались полезны для меня в процессе изучения МК.
0
Прелдставленный пример, найболее вероятно, в прерывании и обрабатыавает все. И цепочка «прерывание-установка флага-проверка всех флагов в мейне-выполнение программы» в данном конкретном случае попросит больше ресурсов (разве что 1 бит флага, вместо 2-4 байт указателя в оперативке).
Описанный вами подход лучше, когда у нас одноуровневые прерывания. Когда же имеется NVIC — смысла меньше.
Всему своё место.
0
А чем это лучше простого выставления флага и проверки его в главном цикле?
Как минимум это лучше отсутствием глобальных переменных и возможностью накапливать сообщения, при необходимости. Скажем, если по прерыванию ставить флаг, то два одинаковых прерывания будут выглядеть как одно и нет способа определить, что прерываний было несколько. С посылкой сообщений все проще — на каждое прерывание по сообщению и никто никуда не теряется.
+1
из анекдота:
Взял рыбак свою подругу на рыбалку.
Положил на берегу удочку а сам отлучился ненадолго по надобности.
Подруга сидит на берегу в панаме, читает книжку.
Подходит рыбнадзор и говорит типа девушка я вас оштрафую за ловлю рыбы в неположенном месте. Она — за что я же рыбу не ловлю. Он — как же не ловите вот у вас на берегу и инструмент лежит и все возможности есть.
Она — тогда я напишу на вас заяву за изнасилование. Он — но я же не насилую. Она — но инструмент и все возможности у вас есть для этого.

Вот и тут написано что есть возможность независимо разрабатывать и модифицировать…
хихихи…
пусть тот кто скажет что это девочка первым кинет в меня камень
0
Все верно написано. К примеру, есть автомат работы шагового двигателя и обмена по UART.
шаговый — выводит положение вала на заданный шаг по переменной step, а UART отправляет принятые данные в переменную rx_char, если сброшен флаг rx_f. Первыми шагами автоматов будет инициализация -шаговик устанавливается на нулевой шаг, а UART инициализирует регистры, далее автоматы в рабочих циклах.
Автоматы настраиваются и модифицируются НЕЗАВИСИМО от других. Взаимодействие между ними выносится во внешние функции (здесь для простоты прямо в main).

#include «step_auto.h»
#include «UART_auto.h»
void main()
{
while(1)
{
UART_auto();
if(rx_f) {step=rx_char; rx_f=0;} /*Если есть принятые данные, отправляем их шаговому автомату */
step_auto();
}
}
0
А еще мне кажется там есть возможность использовать оператор goto — тогда это уже не структурное программирование

вот мы шуточно кое что и разрешили уже
0
По ходу прозрачного и надежного механизьма

полагаю ноги растут отсюда

при использовании подхода с КА считается, что известен алгоритм функционирования объекта управления и по нему можно синтезировать точный алгоритм логического управления, обеспечивающий заданное поведение
0
Вы конечно правы — просто чуть пошутить захотелось!
0
на Protothreads это все реализуется проще и исходники не засорены бесконечными switch-ами.
0
FSM_UART кроме приема данных анализирует поток и в зависимости от данных отправляет сообщения в FSM_CMD. Можно ли использовать автомат в другой программе? нет. Он только для FSM_CMD.

FSM_CMD жестко завязан на UART и LCD. Можно ли использовать UART для других целей а поток команд формировать из SPI? нет. Автомат только для работы FSM_UART и LCD.

FSM_LCD. Судя по названию, весь вывод на индикатор должнен идти через него, а от только для включения-выключения и очистки экрана.
0
Ну к FSM_LED'то у вас претензий нет? :)
FSM_LCD полностью обеспечивает работу с дисплеем. Единственное что вынесено из автомата это функция обновления экрана — просто так удобней (да завязана она больше на железо).

А в остальном: кто вам сказал, что название функции должно определять ее работу?
И по вопросу переносимости кода КА — а зачем? КА штука специфическая, реализует алгоритм работы прграммно-аппаратной конкретной конфигурации.
Вот библиотеки LCD, TIMERS и MESSAGES и UART (там правда посылаются сообщения для КА) вполне себе переносимы.

ЗЫ. Поправил и перезалил исходники.
0
С независимыми автоматами удобнее и понятнее работать. Да и вообще это главное преимущество SWITCH-технологии в сравнении с классическим подходом. Почему название должно определять работу? — опять же удобство. Если я меняю дисплей, то я должен менять автомат FSM_LCD а не перелопачивать код в FSM_CMD.
0
Отчасти я согласен с вашей позицией. Мой код приведен в качестве примера и может не отражать всех преимуществ SWITCH-технологии.
Однако, это вполне законченный проект на котором можно посмотреть реализацию теории на практике.
По поводу качества кода есть упоминание в дисклаймере ;)
0
единственный момент, который мне остался не понятен — зачем понадобилось обзывать этот подход «switch-технологией», при том, что используются конечные автоматы и посылка сообщений. Конечные автоматы вовсем не обязательно реализовывать таким неудобным способом как switch (для сложных автоматов таблицы гораздо удобнее, а то и вовсе описание через грамматики), а тут на этом делается, почему-то, акцент.
0
  • avatar
  • evsi
  • 02 ноября 2012, 20:50
Тоже использую switch для автоматов, но без сообщений, каждый автомат работает со своими переменными, а в основном цикле main организовано их взаимодействие. Таблицы и грамматики это больше для парсинга выражений, или перекодирования(например для манчестерского кода), а switch для управляющих алгоритмов больше подходит
0
switch плохо читается и тяжело модифицируется, независимо от типа алгоритма. Таблицы в этом отношении значительно удобнее, да и модульность сильно повышается. Альтернативный способ организации таблиц — использование динамических виртуальных методов. Увы, эта весьма полезная фишка не попала в стандарт (хотя борланд очень старался), но при желании она реализуется руками. Создавалась она, в свое время, для обработки сообщений в гуе, что, по сути, ровно та же задача, для которой применяется и описываемая в топике технология. И создавались они как раз для того, что бы убрать длинные switch-конструкции из кода.
Грамматики это просто следующий уровень, когда состояний становится много, а условия перехода из состояния в состояние усложняются. По сути это просто применение более высокоуровневого средства для описания переходов и действий.

P.S. switch для управляющих алгоритмов подходит ровно до того момента, пока состояний мало, как только их становится много, читать и модифицировать код становится весьма неудобно. разработчики гуевого софта столкнулись с этой проблемой десятки лет назад и нашли решение. не вижу причины, по которой нужно еще раз бегать по этим же граблям.
0
Для повышения читабельности switch'ей в исходниках используются задефайненые константы с осмысленным названием вместо «магических чисел». Это как бы общая рекомендация для повышения читаемости кода вне зависимости от от используемого языка.
По поводу неприменимости switch'а при усложнении алгоритма управления — ну это как говорить что, в отличии от байдарки или шлюпа, весельная тяга неэффективна и практически неприменима на космических кораблях ввиду очень малой плотности межзвездного газа.
Но у нас-то в основном байдарки :)
0
О каком решении для ГУИ вы говорите?
0
А он о каком-то решении для GUI вообще говорит? Единственное, что упомянуто из относящегося к GUI — борландовский VCL (а может и какая-то из более ранних технологий, такие либы как OWL я не ковырял и как они устроены не знаю).
0
Название не мое, ссылка и материалы автора есть в заметке и в приложенных файлах. Данное название мне попадалось несколько раз, в том числе, и на английском — так что можно считать устоявшимся термином.
Почему switch — такие конструкции удобнее для понимания и освоения начинающими (я к таким и отношусь). И заметку я писал вообщем то для начинающих. Профи и так уже изобрели свои велосипеды или используют готовые фрэймворки / РТОСы.
Естественно данный материал охватывает толику применения КА в программировании.
По личным ощущениям, переход на данную технику написания программ для МК позволил легко реализовать идеи, которые раньше вязли в мегаклубках функций и проблеммах организации взаимодействия между ними. Плюс вопрос разделения процессорного времени — в неочень большом проекте можно с достаточной точностью просчитать время исполнения каждого сотояния КА, а учитывая четкую логику взаимодействия между собой и общее время исполнения кода в главном цикле. Иногда это очень даже важно.
0
Название не мое, ссылка и материалы автора есть в заметке и в приложенных файлах. Данное название мне попадалось несколько раз, в том числе, и на английском — так что можно считать устоявшимся термином.
Вот только поиском, почему-то, английский вариант никак не находится.

Почему switch — такие конструкции удобнее для понимания и освоения начинающими (я к таким и отношусь).
Это до первой необходимости расщирить или существенно изменить код (скажем, изменить диаграмму переходов между состояниями).

Я совершенно не имею ничего против (и даже наоборот) такого подхода при написания программ для МК, просто я вижу как народ изобретает велосипед и дает ему новое имя. Такая методика написания программ была известна очень давно, а до определенного момента она была единственной эффективной методикой написания сложных многопользовательских сервисов под юниксы.
0
Ждем статью про использование табличных автоматов
+1
да хоть в вике все более чем описано. а вообще, эта тема живет, как ВНЕЗАПНО© оказалось, с далекого 91-го года…
так что гугл и книжки в помощь.
0
Ждать придется долго. У меня и так список проектов, которые я делаю и статей, которые нужно (как минимум хотелось бы) написать полон чуть более чем полностью. А это, все-таки, хобби, а не работа, и времени получается уделить совсем не много. К тому я сейчас в длительной командировке в другой стране. Такие вот расклады…
0
Солидарен с AlexX1810
Книжки-книжками, а чуток практики никому не помешает… :)
0
а автора почитать? благо он жив и вполне себе доступен на форумах и открыт для общения?
ЕМНИП, Шалыто периодически на электрониксе светится.
0
а автора почитать?

Вы о ком? У автора _этой_ статьи — switch используется, а я про табличные автоматы
0
ну, блин. полторы минуты гуглежа:
#include <stdio.h>
enum states { before = 0, inside = 1, after = 2 };
struct branch {
    enum states new_state:4;
    int should_putchar:4;
};
struct branch the_table[3][3] = {
    /*             ' '          '\n'         others      */
    /* before */ { {before, 0}, {before, 1}, {inside, 1} },
    /* inside */ { {after,  0}, {before, 1}, {inside, 1} },
    /* after  */ { {after,  0}, {before, 1}, {after,  0} }
};
void step(enum states *state, int c)
{
    int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2;
    struct branch *b = & the_table[*state][idx2];
    *state = b->new_state;
    if(b->should_putchar) putchar(C);
}
int main(void)
{
    int c;
    enum states state = before;
    while((c = getchar()) != EOF)
        step(&state, c);
    return 0;
}
0
Вот только что оно делает я с полпинка сказать затрудняюсь.
0
ну ведь линк на вику есть, откуда скопипащено.
печатает каждое первое слово строки.
0
Интересно, как можно красиво переписать код шелла в виде автомата. Там же поиск по таблице, парсинг параметров, выполенение функции, вывод через медленный канал с помощью printf. Все это надо как-то упаковать в состояние. Кажется проще вытеснялку написать, чем сделать это.
0
что за код шелла?
0
Такой механизм конфигурирования и отладки. Подклюаемся к устройству через uart и пишем команды, читаем ответы, смотрим на побочные эффекты. Устройство при этом работает в нормальном режиме, можно вмешатся в его работу, имитировать какое-то событие, изменить значения каких-то констант, и помотреть на результат.

Для этого на строне МК есть код которые парсит ввод из uart и выполняет команды, делает вывод обратно в uart.

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

Сделать из нее быстро работающий автомат мне кажется сложным, не вижу хороших путей.
0
Правильное определение наверно вот здесь.

en.wikipedia.org/wiki/Shell_(computing)
0
а зачем быстро? основное ограничение — скорость канала передачи. это 115200. тормоза те еще. складывать вывод в буфер, и пусть уарт потихоньку отправляет.
0
Есть основные задачи. У меня регулярное событие с частотой 1кГц требует обработки занимающей 50%-90% времени. Один пропуск события можно считать фэйлом в работе устройства.

Буфер не резиновый, как быть если надо выводить столбец каких-то захваченных данных который никак не полезет в буфер в текстовом виде?

Да и надо проверить, что парсинг, поиск по таблицам, выполнение любой из функции не занимают более 10%-50% времени, чтобы основное событие гарантированно было обработанно вовремя.

Да, можно принять последнее условие, и обрубыть вывод если он не уместился в буфер. Но мне как всегда хочется найти решение лучше.
0
ну, в таком случае просто скорость канала связи должна быть гарантированно больше частоты событий. в противном случае только компромисс той или иной степени запущенности.
0
Событие 1кГц, это 1мс период. 90% ваша обработка, 10%-шелл, значит на каждое состояние запас времени 100мкс. Вполне достаточно
0
Что-то вроде этого:

void cmd_shell_auto()
{
  static st=0;
  char tmp=get_rx_buf(); //байт из буфера UART
  static int addr=0,len; //переменные для работы
  switch(st)
  {
    case 0:
       if(tmp==get_mem_dump) st=1; //команда дамп памяти
       if(tmp==annihilation) st=21;
    break;
    case 1:
       addr=tmp<<8; st=2; //старшая часть адр
       break;
    case 2:
       addr|=tmp; st=3; //младшая часть адр
       break;
    case 3:
       len=tmp; st=4; //длина дампа
       break;
    case 4:
      send_tx_buf(*(char*)addr++); //отправка в буфер UART
      if(!len--) st=0; //если вся длина отправлена-исходное состояние
      if(tmp==abort_com) st=0; // если пришла команда прервать - исходное состояние
      break;
    case 21:
      //do something
    break;
  }
}
0
Логика работы получилась для ждущей get_rx_buf(). Надо еще проверку и на switch идти только если в rx что-нибудь пришло или по tx передалось
0
Слишком запутанно. Я сейчас подумал, а можно же просто вызывать в некоторых местах некую shedule() которая будет выполнять другие задачи если они вдруг появились.
0
Получается такой стек вызовов,


main ->
 for(;;) shedule() ->
  task_shell() ->
   cmd11() ->
    printf() ->
     putc() ->
      shedule() ->
       task_event()
        ...
0
Чужой код — потемки, но суть проста. Исходное состояние (st=0) — проверяем на совпадение с кодами команд (в примере одна команда get_mem_dump), потом принимаем параметры команд(st=1..3), затем выполнение команды(st=4) и возврат в исходное состояние(st=0). Можно и без буферизации UART
0
тоже когда-то использовал такую технологию. но практика показала, что сложность конструкции растёт нелинейно. в итоге пришёл к тому, что надо RTOS и несколько потоков в ней. Да, расход на стек. Но в автоматах та же память расходуется под сохранение контекста.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.