Командная оболочка для AVR

AVR
Подумал я тут недавно: «а не написать ли мне от нечего делать свою командную оболочку для AVR?» И написал.





На всякий случай, я знаю, что существуют и более продвинутые проекты этого же типа. Но зачастую они слишком тяжеловесны и смахивают на сборник красотулек. Целью же моих изысканий было, во-первых, упражнение в программировании, а, во-вторых, создание по-возможности компактного каркаса командной оболочки – результат (только код оболочки) в скомпилированном состоянии занимает менее килобайта FLASH, а потребление оперативной памяти можно настраивать (по-умолчанию – порядка тридцати байт). Так что, по идее, код должен работать на всех AVR, у которых есть UART.

I. Описание структуры.

Оболочка состоит из нескольких модулей (.c + .h). При этом модуль интерпретатора отделен от модуля ввода/вывода, так что оболочку можно заставить работать через что угодно, а то и вообще устроить пакетные файлы. Ну или портировать, для этого потребуется подредактировать код вызова функции из таблицы, ибо он очень AVR-специфичен.

uart_text_io

Организует ввод/вывод текста через UART. Содержит следующие функции (все синхронные, т.е., явно ожидают реакции пользователя):

void TIO_Init(void);


— инициализация обмена. 8N1, 9600BPS.

uint8_t TIO_CharInput(void);


— ввод символа.

void TIO_CharOutput(uint8_t ch);


— вывод символа.

void TIO_TextOutput(uint8_t *outtxt);


— вывод строки.

void TIO_TextInput(uint8_t *intxt);


— ввод строки. Поддерживает примитивное редактирование с помощью backspace. Ввод завершается нажатием Enter'а.

void TIO_PrintPgmMsg(uint8_t* pgm_msg);


— вывод строки из FLASH.

cmd_util

Содержит вспомогательные утилиты.

uint8_t is_regular_char(char x);


— проверка принадлежности символа к буквам/цифрам.

uint8_t is_digit(char x);


— проверка принадлежности символа к цифрам.

uint8_t str_len(uint8_t* str);


— вычисление длины строки из RAM.

uint8_t str_len_pgm(uint8_t* pgm_str);


— вычисление длины строки из FLASH.

uint8_t str_equal_pgm(uint8_t* str,uint8_t* pgm_str);


— сравнение строки со строкой из FLASH.

uint16_t str_to_uint16(uint8_t *s_num);


— преобразование строки в число. Понимает только десятичную систему, преобразует до первого нечислового символа. Максимальное значение, как ясно из названия и возвращаемого типа, ограничено 65535. В противном случае не обидится, просто выдаст не связанный с реальностью результат.

cmd_func

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


//Function table

void (*sys_func[])(uint8_t* p_arg[],uint8_t num_args) PROGMEM = {

    print_help,
    print_args,
    handle_led

};

//Command line alias table

uint8_t funcname1[] PROGMEM = {"help"};
uint8_t funcname2[] PROGMEM = {"listarg"};
uint8_t funcname3[] PROGMEM = {"led"};

uint8_t *sys_func_names[] PROGMEM = {

    funcname1,
    funcname2,
    funcname3

};


Странное на первый взгляд объявление масива имен команд продиктовано требованиями AVR-libc к объявлению массивов строк во FLASH.

cmd_interp

Ядро оболочки, командный интерпретатор. В самом модуле две функции:


void split_args(uint8_t* cmdline);
uint8_t cmd_exec(uint8_t *cmdline);


Извне доступна только последняя. Она ищет соответствующую команде функцию и запускает ее, передавая ей разобранные параметры. Возвращает 1, если все хорошо и запрошенная функция найдена и выполнена, либо 0, если команда не знакома.

Если кому интересно, разбор командной строки происходит так: сначала в строке ищутся параметры (параметром считается последовательность букв/цифр, перед которой стоит пробел), адреса их стартовых ячеек складываются в массив. Попутно запоминается их количество. Далее все пробелы заменяются нулями. В результате при обращении по адресу, содержащемуся в массиве указателей на параметры, каждый параметр выглядит как строка, заканчивающаяся нулем. Картинка ниже иллюстрирует сказанное.



Описанное преобразование как раз и выполняет функция split_args(), которую cmd_exec() вызывает в начале. Далее производится поиск функции, сопоставленной принятой команде. Если она найдена, то cmd_exec() запускает ее, передавая в качестве аргуметов массив указателей на параметры и их количество.

cmd_shell

Этот модуль собирает все воедино. Содержит функцию

void cmd_process_loop(void);


Она организует диалог с пользователем – показывает заголовок и приглашение, считывает команду, запускает для нее интерпретатор и сообщает об ошибках, буде таковые случатся. Вызывается в main() вместо while (1) (собственно, она его и содержит).

II. Настройки.

В cmd_func.h указано количество доступных команд/функций:

#define FUNC_NUM        3


В cmd_shell.h можно настроить максимальную длину строки, которую позволит ввести оболочка:

#define SHELL_INPUT_LEN		20


а также максимальное количество аргументов команды:

#define SHELL_ARGS_MAX		5


В cmd_shell.c расположены сообщения, выдаваемые оболочкой.

Сообщение о том, что команда не найдена:


uint8_t msg_cmd_err[] PROGMEM = {"Input error - unknown command.\r\n"};


Приглашение командной строки:


uint8_t msg_con[] PROGMEM = {"cmd: "};


— можно поменять на C:\ или root@ATmega48:~#, по вкусу.

Приглашение, выдаваемое в начале:


uint8_t msg_start[] PROGMEM = {"AVR command shell v1.0 by YS\r\n\r\n"};


Разделитель команд/результатов выполнения команд:


uint8_t msg_newline[] PROGMEM = {"\r\n"};


III. Как описать команду.

Интерпретатор ищет соответствие команде в двух массивах. Первый – таблица функций.


//Function table
void (*sys_func[])(uint8_t* p_arg[],uint8_t num_args) PROGMEM = {

    print_help,
    print_args,
	handle_led

};


Тут все просто – дописали функцию в cmd_func.c и дописали ее имя в массив sys_func[].

Второй массив – таблица имен команд, которые эти функции реализуют.


//Command line alias table
uint8_t funcname1[] PROGMEM = {"help"};
uint8_t funcname2[] PROGMEM = {"listarg"};
uint8_t funcname3[] PROGMEM = {"led"};

uint8_t *sys_func_names[] PROGMEM = {

    funcname1,
    funcname2,
	funcname3

};


Как уже говорилось, эти масивы неспроста объявлены так экзотично – это требование компилятора. Дописали имя созданной функции в sys_func, и тут же создаем строку-команду для вызова этой функции и добавляем ее в массив sys_func_names[]. Ну и надо не забыть указать общее количество функций в дефайне, описанном выше.

Когда пользователь введет команду, интерпретатор попытается найти ее в sys_func_names[] и вызвать функцию из sys_func[] с тем же номером. Кстати, сам вызов выглядит очень забавно:


((void (*)(uint8_t**,uint8_t))pgm_read_word(&(sys_func[i])))(arg_ptr,args_num);


Это вызов функциии по адресу, который лежит в массиве, который расположен во FLASH. Почти haskell…

Сама функция-обработчик команды имеет вид


void <имя>(uint8_t* p_arg[],uint8_t num_args)
{
  ...
}


Ей передается массив указателей на параметры (строки с нулем в конце) и их количество. Функция имеет доступ ко всем возможностям uart_text_io.h, cmd_util.h, avr/io.h, avr/interrupt.h. Я решил не вводить возвращаемого значения, потому что здесь мне не видится в нем особого смысла. Можно было бы заставить интерпретатор анализировать возвращаемое значение и сообщать результат выполения, но функциям и так доступен весь арсенал ввода-вывода, так что я решил ограничить роль командного интерпретатора разбором параметров и запуском функции, запрошенной пользователем.

IV. Демонстрация.

Для демонстрации было разработано несколько функций. Первая, конечно, help – какая же облочка без команды help? Остальное ясно из вывода этой первой команды:


AVR command shell v1.0 by YS

cmd: help

  AVR command shell v1.0
  Currently supports three commands:
  help - display this help;
  listarg - lists its arguments;
  led <color> <state> - control LEDs.
   color: green/blue;
   state: on/off/blink/noblink

cmd:


Остается только добавить, что зеленый светодиод подключен к PB1, а синий – к PB2.

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

Ну и, конечно, видео:



Файлы проекта приложены к статье. Для тех, кому хочется поковырять интерфейс, но лень возиться с железом, приложена модель в Proteus.

Дисклаймер:

Данная оболочка написана just4fun, главным образом для упражнения автора в программировании. Потому я НЕ ГАРАНТИРУЮ отсутствия в ней ошибок вплоть до критических. Вы можете делать с кодом все что угодно и использовать его как угодно, но только на свой страх и риск. Автор не несет никакой ответственности за любые результаты использования этого кода.
  • +8
  • 02 февраля 2012, 21:50
  • _YS_
  • 1
Файлы в топике: AVR_CMD.zip

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

RSS свернуть / развернуть
Есть ещё такой вариант, с поддержкой редактирования.
0
  • avatar
  • Resp
  • 02 февраля 2012, 22:41
Угу, неплохо. Только по памяти моя реализация использует в разы меньше. Если совсем точно — 976 байт, если закомментировать код всех функций. А там даже в урезанном варианте почти два килобайта.

Для сравнения собирал в двух вариантах: в урезанном — без авто-дополнений, без истории ввода и обработки курсорных стрелок и полном, вот результат
ARM AVR
урезанный 1,5Кб 1,6Кб
полный 3,1Кб 3,9Кб

да и ко всему прочему

измерения проводились только для самой библиотеки, ни обработка USART (USB-CDC для АRM), ни интерпретатор не учитывались, т.к. это уже оболочка (shell).

А у меня 976 байт (даже меньше, просто лень было при тесте закомментировать строки определения команд) со всем.

С RAM там тоже худо.

Что касается ОЗУ, тут все просто, библиотеке под внутренние переменные нужно байт ~30, плюс буфер для командной строки — определяете сами в конфиге его длину, плюс буфер для истории ввода — поставьте сколько не жалко (но не более 256 байт в этой реализации).

У меня весь готовый пример использует 32 байта. Это и библиотека, и демо-функции.
0
Почитал историю терминалов по ссылке из ссылки. Ужаснулся :)
0
Здорово! Порвал «конкурента» как тузик тряпку :) Мне до такого еще как до Китая пешком, я в AVR пока только асм осваиваю.
0
  • avatar
  • yars
  • 02 февраля 2012, 23:35
Достаточно забить на асм и поучить С. Тогда Китай окажется значительно ближе :)
0
Ну, асм-то все же знать надо. Но после его осознания и правда стоит переключиться на С, в порядке логического продолжения. :)
0
Знать асм и «освоить» это несколько разные уровни с разными затратами времени и сил. Да, знать, конечно, полезно. И, в свое время, асм я тоже поковырял изрядно. Но тогда это имело смысл, компиляторы слабые, асм, вобщем, простой, поведение проца предсказуемо, а всякие заморочки типа предсказания переходов и многоуровневого кеша отсутствовали как класс. Сейчас я бы забил. Даже для выгрызания отдельных байтов это не очень полезно, а точки зрения производительности вполне может оказаться вредным — слишком много факторов надо держать в голове в процессе написания, что бы писать действительно эффективный код, а о том, сколько при этом остается времени на, собственно, решение задачи, лачше не вспоминать, что бы не расстраиваться. Вот с линковкой досконально разобраться, это, безусловно, нужно (как раз с точки зрения байтовыгрызания).
0
Ну, я еще не видел AVR с предсказанием ветвлений… :)
0
У AVR32 есть такая хрень.
0
Ну, это только в случае урезанного варианта, который, как я понял, у автора по ссылке не сильно отличается от моего, т.е., без стрелок, без автодополнений и прочих красотулек. Вот этот самый чистый код занимает у него 1.6КБ против моих 976 байт.

Кстати, перечитал ту статью, и мне начало казаться, что у нас вообще немного разный подход — как я понял, он сделал основной акцент на ввод и редактирование, а на интепретатор как-то подзабил. Поправьте, если не прав. Меня смущает вот эта цитата:

Надо сказать, что измерения проводились только для самой библиотеки, ни обработка USART (USB-CDC для АRM), ни интерпретатор не учитывались, т.к. это уже оболочка (shell).


Ввод не учитывал, интерпретатор не учитывал, так что же он мерял? У меня с вводом, с выводом, с интепретатором (кстати, может, мы называем интепретатором разные вещи?) и зачатками API получается никак не больше килобайта. А если оставить только один пустой обработчик команды, так и вообще 922 байта чистого каркаса.

Но в полной версии та оболочка, конечно, в разы удобнее — автодополнение, история, полноценное редактирование. Зато моя пойдет даже с двумя килобайтами памяти, и под обработчики останется больше килобайта. :)
0
А как все это, можно применять в народном хозяйстве?
0
  • avatar
  • Aneg
  • 03 февраля 2012, 02:15
То есть как это как??? Рулить че-нибудь. :) И это только один из вариантов, включайте фантазию.
0
Так это некий аналог микросхем от FTDI, ну например FT2232HL или от Cypress — CY7C68013A, только у тебя работа происходит через UART.
А слабо сделать, что бы также, но по USB бегало?
0
Често скажу — пока слабо. Но я изучу…
0
Честно, очепяточка вышла.
0
Причем тут эти микросхемы? Речь идет о типаконсоли (CLI — Command-Line Interface) для AVR-ок. Т.е. командная оболочка, как command.com в DOS'е. А работать оно может через любой интерфейс. Хоть через прикрученные к контроллеру же клаву и экранчик. Просто через терминал на UART'е проще всего.
А вот FT232 — это преобразователь интерфейса передачи данных, с USB на UART. CY7C68013 и вовсе (почти) обычный микроконтроллер.

А через USB можно. Прикрутить CDC на V-USB и подправить функции ввода/вывода символов на работу с ним.
0
Хоть через прикрученные к контроллеру же клаву и экранчик.

Хм… А идея ведь! :D

Таки да, во вводе-выводе зависят от I/O устройства только TIO_CharInput и TIO_CharOutput. Если поправить их на ввод с чего_угодно и вывод на что_угодно, вся консоль автоматом переедет на эти устройства.
0
Так это некий аналог микросхем от FTDI

Ненене, это некий аналог MS-DOS command shell. :) А уж через что команды передавать — дело фантазии применяющего.
0
Плюсадин. Это очень красиво, но непонятно зачем нужно.
0
Примеры применения есть в статье из ссылки в первом коментарии.

Ну, например, пишете кофеварке через Ethernet

cmd: make coffee w_cream


а она кофе со сливками наливает. :)
0
Мой код — всего лишь шаблон, который позволяет реализовать свой набор команд, не заморачиваясь на разбор командной строки и обработку ввода/вывода.
0
А как все это, можно применять в народном хозяйстве?


Примеры применения описаны в статье по ссылке из первого комментария. :)
0
Прикручиваешь к консольке виртуальную машину обрабатывающую комманды и получаешь что угодно.
0
Угу. В принципе, можно ничего и не прикручивать, а просто описывать реакции на команды — интерпретатор уже написан, он сам вызывает обработчики для команд.
0
Кстати, друг ради прикола скормил этот проект PVS-Studio. Анализатор ошибок в коде не нашел. Только два предупреждения — ему не понравился pgm_read_byte (инструмент для x86 споткнулся об AVR-ассемблер).
0
  • avatar
  • _YS_
  • 03 февраля 2012, 16:31
Но, тем не менее, дисклаймер остается в силе. :)
0
Ну, стоит отметить, этот инструмент предназначен для совсем другого кода =) С++, динамическое выделение памяти, х64 и параллельные процессы…

Для непосредственно АВР я не видел статических анализаторов… Да и для армов их немного и платные все аж жуть.з.

А статанализаторы общего назначения в силу специфики кода помогут мало — макисимум, выход за пределы статического буфера и что-то в таком духе.
0
Ну во, сразу спойлерить. :)
0
Можете написать подробнее как проверяли? По шагам :)
0
Я не проверял. :)
0
А, неправильно прочитал :) Друга здесь нет? Такие сведения были бы полезны. Вон на Хабре сокрушались, что нет мануалов прикручивания PVS-Studio для проверки таких проектов.
0
Не, тут его, вроде нет. Но он это прочитал.

а что прикручивать? затолкал в VS 2010 в проект всё что надо (все заголовочные и исходные файлы), ну и ткнул «проверь»
0
Спасибо! Надо будет попробовать.
0
Какой софт используешь со стороны компа? Гипертерминал?
0
  • avatar
  • Aneg
  • 03 февраля 2012, 17:05
На видео видно, PuTTY.

На открывающей картинке — скрин виртуального терминала Протеуса.
0
труЪ.
очень полезная на мой взгляд вещь. попробуемс прикрутить в своих проектах.
Спасибо.
0
  • avatar
  • NCCat
  • 03 февраля 2012, 20:21
Да не за что, пользуйтесь на здоровье. :)
0
а сие под RTOSом можно ли как-ото использовать?
0
Ну, можно запустить cmd_process_loop() отдельным потоком. Ну и, возможно, поправить TIO_CharInput() и TIO_CharOutput(), если RTOS имеет что-то против прямого доступа к UART — по дефолту модуль ввода/вывода монопольно узурпирует UART.
0
Да, еще надо иметь в виду, что функции I/O должны быть синхронными, т.е., не должны возвращать управление, пока пользователь не среагирует.
0
то есть? МК ничего не сделает и никак не прореагирует пока юзер не введёт параметры?
0
Естесственно. Это же консоль.

Это, конечно, не отменяет фоновых задач на прерываниях. Например, у меня в примере светодиод асинхоронно в прерывани мигает, а команда включает/выключает таймер.
0
А Вы можете это кошерное дело совместить с TCP/IP стеком?
0
Не совсем понял идею.

Вообще я с TCP/IP не работал. Да и зачем AVR Ethernet?
0
а то есть вместо HTTP сервака что-то вроде SSH. подключился удалённо, команды ввёл и всё. с логином и паролем
0
Так у меня тут не HTTP, у меня вообще простенькая консоль через UART.
0
так я даю вектор применения энтузиазма! :)
0
А-а-а, это. Не, спасибо. :)

с логином и паролем


Зачем авторизация? Кругом друзья, бояться некого. :D
0
я ещё это к тому что можно вместо всяких там WEB-серверов использовать тоже самое что и для уарта но на WEB. то есть не спец разъём и тантрический секс с дополнительными проводами а питание и сеть и минимум деталей.
сори за сумбур
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.