Главная > AVR > Командная оболочка для 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   - control LEDs.
   color: green/blue;
   state: on/off/blink/noblink

cmd:

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

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

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

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

Дисклаймер:

Данная оболочка написана just4fun, главным образом для упражнения автора в программировании. Потому я НЕ ГАРАНТИРУЮ отсутствия в ней ошибок вплоть до критических. Вы можете делать с кодом все что угодно и использовать его как угодно, но только на свой страх и риск. Автор не несет никакой ответственности за любые результаты использования этого кода.

Рубрики:AVR
  1. Adronik
    27/01/2016 в 17:32

    Приветствую, YS !
    Натолкнулся на вашу статью, очень занимательно.
    Прошло много времени с момента публикации, попытался запустить в AVR Studio 6.2. Компилятор с руганью сыпит ошибки. В основном вида » must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’ «, которые лечатся установкой ключевого слова const.
    Заборол все ошибки кроме двух:

    Error 4 variable ‘sys_func’ must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’ C:\LCD\cmd_func.c 75 8 SmartLCD

    Error 5 variable ‘sys_func_names’ must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’ C:\LCD\cmd_func.c 89 16 SmartLCD

    Пытался приткнуть слово const, но как то не срослась.

    У вас случайно нет свежего переиздания для компилятора Stud 6.2 ?
    Или подскажите куда подоткнуть, что б запела ?

    • YS
      27/01/2016 в 22:04

      Я не использую многогигабайтное порождение под названием Atmel Studio 6, потому версии кода под нее нет. Я пишу под AVR либо в AVR Studio 4, либо в Code::Blocks. На самом деле, конечно, корректнее говорить о версии компилятора, а не среды; его я тоже не обновлял по причине отсутствия необходимости (работает — не трогай).

      По вопросу — похоже что они поменяли соглашение о хранении данных во FLASH. Прочтите мануал на компилятор/стандартную библиотеку, посмотрите, что они там сделали.

      Как вы притыкали const? Так?

      const void (*sys_func[])(uint8_t* p_arg[],uint8_t num_args)

      Это объявление означает неконстантный указатель на константные данные. Попробуйте так:

      const void (* const sys_func[])(uint8_t* p_arg[],uint8_t num_args)

      Это указатель-константа на данные-константу. Может так прокатит… Но вообще лучше изучите, что там и как. Может там еще что-то поменялось. Помните: даже если код на Си компилируется без ошибок (и даже без предупреждений), это еще не гарантия того, что он будет корректно работать.

  1. 07/04/2013 в 18:43

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s