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

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


Комментарии   

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


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

C уважением, Владимир.
Ответить | Ответить с цитатой | Цитировать
# freewind 19.02.2015 11:34
if ((*s57)) {return (0);}
а 0 нас уже не интересуют?... s
Ответить | Ответить с цитатой | Цитировать
# freewind 19.02.2015 11:35
Код:
if ((*s<49) || (*s>57)) {return (0);}

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

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

Защитный код
Обновить