Вот и мы взяли EFM32 за энергопотребление. Но нам был критичен USB, поэтому используем LG. Ну и 4кБ RAM при работе с NANDFLASH со страницами 3кБ тоже были бы грустны :).
У меня вся прошивка собирается в одно приложение, с кооперативной многозадачностью на манер co-routines из FreeRTOS. Поэтому я и сказал про «одно приложение». Это позволяет существенно экономить память.
Второй момент сложился исторически, из-за ошибки планирования на старте проекта. Изначально делали макет для оценки параметров MEMS-микрофонов с цифровым выходом. При этом лепили «из того что было» (EFM32LG), больше интересуясь не системными вещами, а поднятием драйверов переферии и замерами энергопотребления и качества звука.
А потом руководство сказало что надо срочно сделать из этого продающийся диктофон. Т.к. к этому моменту уже была сделана запись звука на NANDFLASH и вычитывание через USBCDC-драйвер, то пришлось срочно доделать, а не начинать разработку софта заново.
Но после самостоятельной реализации псевдомногозадачной RTOS с поддержкой композитного USB-устройства, описанным в данной статье CLI, механизмом событий для межзадачного взаимодействия, динамического изменения частоты MCU и т.д. я дал себе зарок в следующей подобной разработке использовать что-нибудь вроде FreeRTOS.
Про EFM32 c 32кБ — как показал опыт этого вполне достаточно, если все задачи используют один и тот же стек. Для экономии же памяти я отказался от динамического выделения. Вообще всё что можно я регистрирую с помощью линкера =). А что вы делаете на EFM32ZG, если не секрет?
А зачем вам указатель на голову и указатель на конец? У меня они автоматически генерируются линкером, и при этом не занимают места, потому что это просто символы линкера, а не байты в памяти. Это позволяет ещё чуть-чуть уменьшить overhead.
Для секции secname у меня линкер автоматом генерит что-то вроде:
Потом я эти символы объявляю как extern указатели и при линковке они автоматически подменяются на соответсвтующие адреса. Возможно что это свойcтво gcc-arm-embedded, но я беззазрительно им пользуюсь.
У меня пока проект на EFM32 с 32КБ RAM не разросся до таких высот как несколько приложений, поэтому реализовывать команду как приложение не получается =). Вот и изобрёл «на скорую руку» такой огрызок.
Второй момент — когда разработчиков в проекте мало, то затраты на собственную систему сборки/полноценную RTOS не окупаются. А вот быстрая регистрация команд (и такой же механизм для задач в OS) уже зарекомендовала себя, т.к. сильно ускорила разработку аппаратных «fork-ов» от исходной платы. Так из диктофона получились: стерео USB-микрофон, USB-адаптер для управления несколькими реле, переходник USB-I2C для отладки работы с новыми микросхемами, 8-ми канальная микрофонная решётка совместимая с USB-audio драйверами Windows и Linux. Возможно скоро и ещё что-нибудь получится.
Про тестирование Embedded — когда мне пришлось тестировать плату я поступил схожим образом.
Часть тестов выполняются внутри устройства при старте (список модулей я тоже делал через __attribute__ ((section(«name»)))) — здесь я провожу тестирование основных «железных» компонентов, необходимых для запуска платы. Т.к. здесь тестируются жизненно важные компоненты (работоспособность тактового генератора, например), то эти тесты никак отдельно не выделяются, а встроены в код инициализации платы.
А тесты качества и интеграционные выполняются «снаружи», подачей команд через COM-порт. А в качестве framework я выбрал pyunit, выполняющийся на хосте. Вообще автоматизация тестирования на Python мне приглянулась из-за своей интерактивности. Я могу в интерпретаторе писать какой-то код, посылать команды на целевое устройство и сразу же видеть отклик в логах. Мне не надо для этого пересобирать код.
А разве const-данные не автоматически в секцию ro записываются? Я не уверен, но не казалось что в том и прелесть атрибута section, что он сложит все данные в одну секцию. И она ляжет в ro, если в ней есть только константные данные.
Генерация кода билд-системой бывает вполне мила (по опыту общения с DSP/BIOS от TI), но там свои косяки вылезают. Иногда мне очень хотелось подправить тамошнюю систему tconf (textual config).
Перенесли из-за желания сделать команды максимально стандартными, тем самым уменьшив порог вхождения. Как бонус, получили возможность собирать команды из нашей ОС на unix хостах (без изменений), наоборот — тоже получается достаточно хорошо.
Это понятно. Но, как я понимаю, относится к сигнатуре обработчика команд, а не к способу регистрации команд.
При добавлении новой команды нужно сообщать системе сборки о новом файле. Описание лежит рядом с исходником, как например Cat.my и cat.c.
Вариант, если к каждому исходнику всё равно прилагается его описание для MyBuild. Иначе придётся плодить дополнительные файлы. Но даже в этом варианте добавление/удаление новой команды требует редактирования двух файлов.
забыть изменить мануал можно и в текущем файле
Можно, но если этот мануал у тебя идёт рядом с самой функцией, то глазами на него натыкаешься, когда код редактируешь. А вот если у тебя тело функции от мануала отстоит строк хотя бы на 30 — то легко забыть.
А почему решили перенести? У вас это не порождает проблем с синхронизацией между исходниками и системой сборки? Опять же — изолированность изменений в системе контроля версий. Как-то не хочется добавив команду в один исходник изменять ещё скрипт сборки. Мне кажется что это захламляет changelog скрипта.
Для меня первый минус критичен — когда я разрабатываю новый функционал, я добавляю новые файлы. Там появляются новые команды.
Потом я могу решить в какую-то из сборок не включать часть функционала и в makefile закомментировать соотвествующие исходники.
А если сводить все команды в один массив, то придётся постоянно его редактировать. Либо делать автогенерацию этого массива в процессе сборки, но это ИМХО не легче чем мой способ.
А соль моего способа состоит в том, что я могу новые команды добавлять в любом файле просто макросом. Пример:
А вот «продвинутость» вашего shell'a действительно интересна — я часто думал не сделать ли мне историю команд. Но каждый раз забивал, т.к. shell является не ключевой частью устройства, а скрытой от всех кроме разработчика отладочной фичей.
После вашего комментария пошёл гуглить тонкости различия. Не могу сказать, что кристально ясно уловил их, но выделил одно:
Консоль контролирует всю видимую область, т.е. позволяет не только добавлять строку, но и перерисовывать экран целиком.
А Command Line Interpreter осуществляет только ввод-вывод текста, без последующего форматирования и заботы о перерисовке предыдущего.
В случае если требуется только поменять состояние получается куцая функция перехода
Непрозрачно (по крайней мере в вашем примере) соответствие графического представления КА с исходным текстом. Я ведь правильно понимаю, что под функцией state1() скрывается парсер входных данных и все переходы из состояния 1? Тогда сложно эти переходы наглядно увидеть в одном месте кода
в случае добавления ещё одного состояния придётся менять очень много кода разбросанного по файлу — неудобно будет читать diff в системе контроля версий
Если в рамках отладки захотелось добавить текстовый лог вида «Начальное состояние -> сигнал -> конечное состояние» то его придётся добавлять в каждую функцию, потому что нет момента выполнения где доступны все три значения одновременно.
Можно (но не нужно) представить линейным массивом структур из {old_state, signal, new_state, callback}. Если его правильно отсортировать он окажется двумерным. Если не сортировать — поиск нужного перехода из O(1) станет O(n) и будет вызывать боль (кроме случая когда мы экономим память, а таблица разреженная). здесь должен был быть монолог в пользу map<pair<state,event>, pair<state, callback>>, но я не люблю С++ в микроконтроллерах
Это очень зависит от компилятора. Если компилятор развернёт switch в таблицу переходов по адресам — будет примерно так же по времени.
Если switch будет развёрнут как последовательность if-else — то мой вариант будет быстрее, т.к. любой переход будет выполняться за фиксированное время, независимо зависимости от расположения в таблице. А у switch-case придётся выверять последовательность case, так чтобы наиболее частые шли первыми.
Помню как у меня замена
switch (argument)
{
case 0x10: ... break;
case 0x20: ... break;
case 0x50: ... break;
default: ...
}
на
switch (argument >> 4)
{
case 0x1: ... break;
case 0x2: ... break;
case 0x5: ... break;
default: ...
}
дало очень существенный прирост.
Про вызов функции — надо смотреть на соотношение трудоёмкости пролога/эпилога с обработкой switch-case. А ещё принимать во внимание как всё это будет кэшироваться. Вообщем без прямых тестов сказать невозможно.
Вообще для оптиматльной производительности, с шансами, подойдёт как раз упомянутый выше Ragel с генерацией кода на goto.
Ваша правда. Поэтому для чистого поиска строк лучше использовать КМП, а не городить огород, который я развёл. О чём уже указывалось в комментариях выше
Так топик как раз об этом — как удобнее расположить грядки в огороде, чтобы в нём не запутаться.
С описанного вами подхода я как раз начинал — смотрите первый приведённый мною кусок кода «Наиболее интуитивный, но громоздкий код для подобной задачи».
А дальше я рассказываю как его сделать компактнее.
ОФФТОП: читая gcode_parse.c, на который вы ссылаетесь, задался вопросом: мне кажется или
if (DEBUG_ECHO && (debug_flags & DEBUG_ECHO))
полностью эквивалентна более компактной
if (debug_flags & DEBUG_ECHO)
?
Казалось бы что если (debug_flags & DEBUG_ECHO), т.е. (debug_flags & DEBUG_ECHO) != 0, то и DEBUG_ECHO != 0.
Мне кажется, что это справедливо для любого инструмента.
В то же время есть много таких областей где без регулярных выражений гораздо тяжелее, чем с ними.
Как и для любого другого хорошего инструмента.
Не обязательно, можно банально каждый раз сдвигать символы на 1 элемент влево. Вообще, если дело обстоит так, как вы описали выше, и у вас всего 4 возможных символа, то любую строку длины 4 можно представить просто числом от 0 до 15 и сравнение строк свести к сравнению двух чисел.
Это уже посоветовали в комментариях ниже.
Про сложность потенциальную сложность автомата — да, наверное вы правы. Надо совершенствоваться в умении не строить Hammer, когда достаточно телеги.
Второй момент сложился исторически, из-за ошибки планирования на старте проекта. Изначально делали макет для оценки параметров MEMS-микрофонов с цифровым выходом. При этом лепили «из того что было» (EFM32LG), больше интересуясь не системными вещами, а поднятием драйверов переферии и замерами энергопотребления и качества звука.
А потом руководство сказало что надо срочно сделать из этого продающийся диктофон. Т.к. к этому моменту уже была сделана запись звука на NANDFLASH и вычитывание через USBCDC-драйвер, то пришлось срочно доделать, а не начинать разработку софта заново.
Но после самостоятельной реализации псевдомногозадачной RTOS с поддержкой композитного USB-устройства, описанным в данной статье CLI, механизмом событий для межзадачного взаимодействия, динамического изменения частоты MCU и т.д. я дал себе зарок в следующей подобной разработке использовать что-нибудь вроде FreeRTOS.
Про EFM32 c 32кБ — как показал опыт этого вполне достаточно, если все задачи используют один и тот же стек. Для экономии же памяти я отказался от динамического выделения. Вообще всё что можно я регистрирую с помощью линкера =). А что вы делаете на EFM32ZG, если не секрет?
Для секции secname у меня линкер автоматом генерит что-то вроде:
Потом я эти символы объявляю как extern указатели и при линковке они автоматически подменяются на соответсвтующие адреса. Возможно что это свойcтво gcc-arm-embedded, но я беззазрительно им пользуюсь.
Второй момент — когда разработчиков в проекте мало, то затраты на собственную систему сборки/полноценную RTOS не окупаются. А вот быстрая регистрация команд (и такой же механизм для задач в OS) уже зарекомендовала себя, т.к. сильно ускорила разработку аппаратных «fork-ов» от исходной платы. Так из диктофона получились: стерео USB-микрофон, USB-адаптер для управления несколькими реле, переходник USB-I2C для отладки работы с новыми микросхемами, 8-ми канальная микрофонная решётка совместимая с USB-audio драйверами Windows и Linux. Возможно скоро и ещё что-нибудь получится.
Часть тестов выполняются внутри устройства при старте (список модулей я тоже делал через __attribute__ ((section(«name»)))) — здесь я провожу тестирование основных «железных» компонентов, необходимых для запуска платы. Т.к. здесь тестируются жизненно важные компоненты (работоспособность тактового генератора, например), то эти тесты никак отдельно не выделяются, а встроены в код инициализации платы.
А тесты качества и интеграционные выполняются «снаружи», подачей команд через COM-порт. А в качестве framework я выбрал pyunit, выполняющийся на хосте. Вообще автоматизация тестирования на Python мне приглянулась из-за своей интерактивности. Я могу в интерпретаторе писать какой-то код, посылать команды на целевое устройство и сразу же видеть отклик в логах. Мне не надо для этого пересобирать код.
Генерация кода билд-системой бывает вполне мила (по опыту общения с DSP/BIOS от TI), но там свои косяки вылезают. Иногда мне очень хотелось подправить тамошнюю систему tconf (textual config).
Вариант, если к каждому исходнику всё равно прилагается его описание для MyBuild. Иначе придётся плодить дополнительные файлы. Но даже в этом варианте добавление/удаление новой команды требует редактирования двух файлов.
Можно, но если этот мануал у тебя идёт рядом с самой функцией, то глазами на него натыкаешься, когда код редактируешь. А вот если у тебя тело функции от мануала отстоит строк хотя бы на 30 — то легко забыть.
А что вы здесь имели в виду? Что существует указатель по которому можно работать с распределённым массивом, как с обычным? Так это и у меня так.
Вообще создаётся впечатление, что мы идём параллельными, но разными путями.
Потом я могу решить в какую-то из сборок не включать часть функционала и в makefile закомментировать соотвествующие исходники.
А если сводить все команды в один массив, то придётся постоянно его редактировать. Либо делать автогенерацию этого массива в процессе сборки, но это ИМХО не легче чем мой способ.
А соль моего способа состоит в том, что я могу новые команды добавлять в любом файле просто макросом. Пример:
А вот «продвинутость» вашего shell'a действительно интересна — я часто думал не сделать ли мне историю команд. Но каждый раз забивал, т.к. shell является не ключевой частью устройства, а скрытой от всех кроме разработчика отладочной фичей.
Консоль контролирует всю видимую область, т.е. позволяет не только добавлять строку, но и перерисовывать экран целиком.
А Command Line Interpreter осуществляет только ввод-вывод текста, без последующего форматирования и заботы о перерисовке предыдущего.
Вы это имели в виду или что-то другое?
Про скорость ничего сказать не могу.
здесь должен был быть монолог в пользу map<pair<state,event>, pair<state, callback>>, но я не люблю С++ в микроконтроллерахЕсли switch будет развёрнут как последовательность if-else — то мой вариант будет быстрее, т.к. любой переход будет выполняться за фиксированное время, независимо зависимости от расположения в таблице. А у switch-case придётся выверять последовательность case, так чтобы наиболее частые шли первыми.
Помню как у меня замена
на
дало очень существенный прирост.
Про вызов функции — надо смотреть на соотношение трудоёмкости пролога/эпилога с обработкой switch-case. А ещё принимать во внимание как всё это будет кэшироваться. Вообщем без прямых тестов сказать невозможно.
Вообще для оптиматльной производительности, с шансами, подойдёт как раз упомянутый выше Ragel с генерацией кода на goto.
С описанного вами подхода я как раз начинал — смотрите первый приведённый мною кусок кода «Наиболее интуитивный, но громоздкий код для подобной задачи».
А дальше я рассказываю как его сделать компактнее.
ОФФТОП: читая gcode_parse.c, на который вы ссылаетесь, задался вопросом: мне кажется или полностью эквивалентна более компактной ?
Казалось бы что если (debug_flags & DEBUG_ECHO), т.е. (debug_flags & DEBUG_ECHO) != 0, то и DEBUG_ECHO != 0.
В то же время есть много таких областей где без регулярных выражений гораздо тяжелее, чем с ними.
Как и для любого другого хорошего инструмента.
Про сложность потенциальную сложность автомата — да, наверное вы правы. Надо совершенствоваться в умении не строить Hammer, когда достаточно телеги.