Обновить
17
Bulashevich Dmitrii@Corviniol

Embedded Developer

18
Подписчики
Отправить сообщение
Вот и мы взяли 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 у меня линкер автоматом генерит что-то вроде:
SECTIONS {
    .rodata : {
         _start_secname = .;
         *(.secname)
         _stop_secname = .;
    }
}

Потом я эти символы объявляю как extern указатели и при линковке они автоматически подменяются на соответсвтующие адреса. Возможно что это свойcтво gcc-arm-embedded, но я беззазрительно им пользуюсь.
У меня пока проект на EFM32 с 32КБ RAM не разросся до таких высот как несколько приложений, поэтому реализовывать команду как приложение не получается =). Вот и изобрёл «на скорую руку» такой огрызок.
Второй момент — когда разработчиков в проекте мало, то затраты на собственную систему сборки/полноценную RTOS не окупаются. А вот быстрая регистрация команд (и такой же механизм для задач в OS) уже зарекомендовала себя, т.к. сильно ускорила разработку аппаратных «fork-ов» от исходной платы. Так из диктофона получились: стерео USB-микрофон, USB-адаптер для управления несколькими реле, переходник USB-I2C для отладки работы с новыми микросхемами, 8-ми канальная микрофонная решётка совместимая с USB-audio драйверами Windows и Linux. Возможно скоро и ещё что-нибудь получится.
наоборот — зачем нужен линкер-скрипт, если мы уже указали квалификатор const? Разве gcc не автоматом складывает const в .rodata?
Про тестирование 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 — то легко забыть.
Спасибо! Особенно порадовала реализация ARRAY_SPREAD_SIZE.
но в исходниках массив представляется именно Си-шным массивом
А что вы здесь имели в виду? Что существует указатель по которому можно работать с распределённым массивом, как с обычным? Так это и у меня так.

Вообще создаётся впечатление, что мы идём параллельными, но разными путями.
А почему решили перенести? У вас это не порождает проблем с синхронизацией между исходниками и системой сборки? Опять же — изолированность изменений в системе контроля версий. Как-то не хочется добавив команду в один исходник изменять ещё скрипт сборки. Мне кажется что это захламляет changelog скрипта.
Для меня первый минус критичен — когда я разрабатываю новый функционал, я добавляю новые файлы. Там появляются новые команды.
Потом я могу решить в какую-то из сборок не включать часть функционала и в makefile закомментировать соотвествующие исходники.
А если сводить все команды в один массив, то придётся постоянно его редактировать. Либо делать автогенерацию этого массива в процессе сборки, но это ИМХО не легче чем мой способ.
А соль моего способа состоит в том, что я могу новые команды добавлять в любом файле просто макросом. Пример:
#include "commands.h" //здесь описан макрос REGISTER_COMMAND

REGISTER_COMMAND("command1_name", function_obrabotka1);
REGISTER_COMMAND("cmd2", func2);

А вот «продвинутость» вашего shell'a действительно интересна — я часто думал не сделать ли мне историю команд. Но каждый раз забивал, т.к. shell является не ключевой частью устройства, а скрытой от всех кроме разработчика отладочной фичей.
После вашего комментария пошёл гуглить тонкости различия. Не могу сказать, что кристально ясно уловил их, но выделил одно:

Консоль контролирует всю видимую область, т.е. позволяет не только добавлять строку, но и перерисовывать экран целиком.
А Command Line Interpreter осуществляет только ввод-вывод текста, без последующего форматирования и заботы о перерисовке предыдущего.

Вы это имели в виду или что-то другое?
Подход тоже имеет право на жизнь, но:
  1. В случае если требуется только поменять состояние получается куцая функция перехода
  2. Непрозрачно (по крайней мере в вашем примере) соответствие графического представления КА с исходным текстом. Я ведь правильно понимаю, что под функцией state1() скрывается парсер входных данных и все переходы из состояния 1? Тогда сложно эти переходы наглядно увидеть в одном месте кода
  3. в случае добавления ещё одного состояния придётся менять очень много кода разбросанного по файлу — неудобно будет читать diff в системе контроля версий
  4. Если в рамках отладки захотелось добавить текстовый лог вида «Начальное состояние -> сигнал -> конечное состояние» то его придётся добавлять в каждую функцию, потому что нет момента выполнения где доступны все три значения одновременно.

  5. Про скорость ничего сказать не могу.
Можно (но не нужно) представить линейным массивом структур из {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, когда достаточно телеги.
Спасибо, покурю. Раньше пользовался только flex — но мне кажется что он слишком заточен на работу именно с текстом.

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург и область, Россия
Зарегистрирован
Активность