Простой парсер для микроконтроллера

15/10/2013 - 16:30 Павел Бобков

Введение

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


   Парсер принимает в буфер поток символов и разбирает их на слова. Под словами в данном случае подразумевается один или несколько символов отделенных пробелами. Количество пробелов неважно, все они заменяются в буфере одним символом окончания строки ('\0'). Прием данных выполняется до первого символа перевода каретки ('\r'). Если внутренний буфер переполняется, данные перестают приниматься, но символ перевода каретки все равно ожидается.

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

   Далее нам нужно только написать интерпретатор принятых слов. Для этого в программном модуле парсера есть несколько дополнительных функций - функции для сравнения строк и функции для перевода строки в число.

Особенности

- не "привязан" к AVR,
- как обычно работа с тремя компиляторами: IAR, GCC и CodeVision,
- компактный код,
- простота использования.

Подключение к проекту

1. Переписываем файлы библиотеку в папку проекта.
2. Подключаем parser.c к проекту внутри среды разработки.
3. Инклюдим заголовочный файл parser.h к сишному файлу, в котором будут использоваться его функции. 
4. Настраиваем конфигурацию в файле parser.h
5. Описываем функцию PARS_Handler(..)
6. Прописываем в свой код вызов функций парсера.

Настройка конфигурации

Нужно задать два параметра:

- вместимость приемного буфера SIZE_RECEIVE_BUF. Он определяет размер строки, который может принять парсер. 

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

Пользовательские функции

void PARS_Init(void) - инициализирует внутренние переменны. Вызывается в начале main`a.

Пример:


//файл main.c
...
int main( void )
{
   char sym;

   PARS_Init();
   USART_Init(USART_DOUBLED, 57600);
...


void PARS_Parser(char sym) - принимает последовательность символов и анализирует их.

Пример:

//файл main.c
...
while(1){

   /*буфер UART`a не пустой,
   передаем символ парсеру*/
   if (USART_GetRxCount()){
      sym = USART_GetChar();
      PARS_Parser(sym);
   }


extern void PARS_Handler(uint8_t argc, char *argv[]) - обработчик результата парсера. Эта функция вызывается парсером после обработки последовательности, но при условии приема хотя бы одного слова. Вызывать эту функцию самому не нужно.

   Она определена как внешняя (extern) и должна быть описана пользователем. Функция принимает два параметра: argc - количество слов, argv[] - массив указателей на слова. Рисунок ниже поясняет, что содержат эти переменные.


Пример:


//файл main.c

...
//команды
__flash char com1[] = "ledon";
...


//обработка принятой последовательности
void PARS_Handler(uint8_t argc, char *argv[])
{
   //если принята первая команда включить светодиод
   if (PARS_EqualStrFl(argv[0], com1)){
   LED_On();
}
...

}


uint8_t PARS_EqualStr(char *s1, char *s2) - сравнивает две строки, хранящиеся в ОЗУ. Если строки равны возвращает 1.

uint8_t PARS_EqualStrFl(char *s1, char __flash *s2) - сравнивает строку, хранящуюся в ОЗУ, со строкой из флэш памяти. Если строки равны возвращает 1.

uint8_t PARS_StrToUchar(char *s) - переводит строку в число типа uint8_t и возвращает его. Если передать строку, содержащую большее значение, то произойдет переполнение.

uint16_t PARS_StrToUint(char *s) - переводит строку в число типа uint16_t и возвращает его. Если передать строку, содержащую большее значение, то произойдет переполнение.

Пример использования

   Для демонстрации написал тестовый проект: микроконтроллер принимает по UART`у команды и управляет портом, к которому подключено 8 светодиодов. Для зажигания светодиода нужно подать команду "set n", где n число от 0 до 7. Для выключения светодиода - "clear n". В ответ на каждую команду микроконтроллер отвечает "error", "ok" или " large value".

   Весь код комментировать не буду, расскажу только про функцию void PARS_Handler(uint8_t argc, char *argv[]). Эту функцию вызывает парсер после приема символа перевода каретки и передает ей количество слов и массив указателей. Пользователь должен сам описать логику работы этой функции.

   В моем примере я беру, грубо говоря, первое принятое слово и сравниваю его с заданными строками - "set" и "clear". Если принятое слово совпадает с одной из команд, то проверяю есть ли второе слово. Если оно есть, перевожу строку в число и "дергаю" соответствующий вывода порта.



...

/*команды*/
__flash char comSet[]   = "set";
__flash char comClear[] = "clear";

/*ответы*/
__flash char error[]           = "error\r";
__flash char ok[]              = "ok\r";
__flash char largeValue[]   = "large value\r";
__flash char start[]           = "iar start\r";

/*обработчик команд*/
void PARS_Handler(uint8_t argc, char *argv[])
{
   uint8_t value = 0;
   char __flash *resp = error;

   if (PARS_EqualStrFl(argv[0], comSet)){
      if (argc > 1){
         value = PARS_StrToUchar(argv[1]);
        if (value <= 7){
           PORTX |= (1<<value);
           resp = ok;
        }
        else{
           resp = largeValue;
       }
     }
  }
  else{
     if (PARS_EqualStrFl(argv[0], comClear)){
        if (argc > 1){
           value = PARS_StrToUchar(argv[1]);
           if (value <= 7){
              PORTX &= ~(1<<value);
              resp = ok;
           }
           else{
              resp = largeValue;
           }
      }
    }
  }
  USART_SendStrFl(resp);
}

...

Файлы

Проекты для Atmel Studio 6, WinAVR, CodeVision, IAR и Proteus.

Parser-IAR.rar
Parser-CV.rar
Parser-Winavr.rar
Parser-AS6.rar
Parser-Proteus.rar


Comments   

# Peter 2013-10-15 20:04
В обработчике прерывания по приему байта считывайте UDR всегда, а не только когда if (rxCount < SIZE_BUF_RX){data=UDR;
Флаг RxC надо ведь сбрасывать, а то программа будет нырять в прерывание по приему байта после каждой инструкции.
Reply | Reply with quote | Quote
# Pashgan 2013-10-15 20:37
Спасибо. Дельное замечание.
Reply | Reply with quote | Quote
# foxit 2013-10-15 20:31
Спасибо.
Как применить этот парсер для разбора строк NMEA?
Reply | Reply with quote | Quote
# Pashgan 2013-10-15 20:42
Нужно изменить в коде разделитель с пробела на запятую. И код конца строки исправить. Тогда он будет разбивать строку NMEA на отдельные составляющие. Может быть еще понадобиться добавить "ловлю" первого символа.
Reply | Reply with quote | Quote
# foxit 2013-10-16 08:07
Quoting Pashgan:
Может быть еще понадобиться добавить "ловлю" первого символа.


А как добавить?
Reply | Reply with quote | Quote
# JoJo 2013-10-16 08:29
Разделитель поменяй на запятую в parser.c
Code: if (sym != ','){
И он будет дербанить тебе строку на части. А по первому слову ясно, что пришло в остальной части. Просто сравниваешь argv[0] c $GPRMC или каким то другим.
Reply | Reply with quote | Quote
# Pashgan 2013-10-16 08:54
Да, по идее так должно работать.
Reply | Reply with quote | Quote
# foxit 2013-10-16 09:03
Попробую.
Reply | Reply with quote | Quote
# foxit 2013-10-17 18:46
Еще вопрос.
arg[0]= $GPRMC
arg[1]= 123456
Как разложить arg[1] на часы минуты секунды?
Reply | Reply with quote | Quote
# JoJo 2013-10-18 01:46
argv[1][0], argv[1][1] - часы
argv[1][2], argv[1][3] - минуты
...
Reply | Reply with quote | Quote
# Константин112 2014-07-15 18:47
Очень выручили. Подключил и все сразу заработало!
Reply | Reply with quote | Quote
# ArtTheft 2014-08-19 16:12
А можете подсказать как это все подправить под CooCoxIde?

C уважением, Владимир.
Reply | Reply with quote | Quote
# freewind 2015-02-19 11:34
if ((*s57)) {return (0);}
а 0 нас уже не интересуют?... s
Reply | Reply with quote | Quote
# freewind 2015-02-19 11:35
Code:
if ((*s<49) || (*s>57)) {return (0);}

...
Code:
*s<48
Reply | Reply with quote | Quote
# гость 2015-02-25 19:40
У меня в протеусе что-то ничего не работает. Или я в терминале неправильно команды даю? Пишу set 1, нажимаю Enter и ничего не происходит.
Reply | Reply with quote | Quote
# Pashgan 2015-02-25 19:57
Я не знаю. Проект, который я выкладывал, работал и в протеусе и в железе. Проверь скорость обмена, тактовую частоту, работает ли вообще USART.
Reply | Reply with quote | Quote
# swardik 2015-05-28 20:34
У кого нить в железе работает?
В протеусе норм, а в железе не пашет:(
Проверял с кварцем 11059200 Гц
Reply | Reply with quote | Quote
# SenyaDomashny 2015-10-05 10:49
Очень дельная статья. Может ли кто дать рекомендации по реализации обратной задачи - как собрать данные в одну строку. Именно, как можно проще, чтобы моему msp430 особо не напрягаться. Я так понял, использование функций из string.h типа strcat или ctrcpy не приветствуется
Reply | Reply with quote | Quote
# SenyaDomashny 2015-10-12 09:38
Важный вопрос при разборе строки! Если подряд идут две запятые, значит, параметр нулевой. Данный парсер это пропускает и просто собирает все данные в кучу. Как это учесть?
Reply | Reply with quote | Quote
# V@so 2016-12-06 17:59
Привет, залил библиотеку на 48ю мегу, пришлось немного поправить регистры UARTа пока компилятор не перестал ругаться, но почему то не хочет работать как надо, в терминале при старте он выдает нам строку, а если я ему кидаю команду то ничего не происходит((
Reply | Reply with quote | Quote

Add comment