По мотивам «Обрабатываем строки на Arduino»

в 11:39, , рубрики: arduino, парсинг, программирование микроконтроллеров

Прочитал сегодня пост на Geektimes, и хочу поделиться своим опытом. Не хочу обсуждать плюсы и минусы Arduino, но условия проекта, в котором применил нижеописанное — должно быть сделано под неё. Суть — нужно предоставить пользователю терминал для управления устройством. Соотвественно, не малая часть работы является работой со строками. Применять или нет предложенное решение — пусть каждый решает сам.

От класса String решил отказаться: ошибка линкера. Она у меня появлялась исключительно при попытке использовать класс String.

Что требовалось? Вывод информации и обработка введённых пользователем строк. Например:

Ethernet controller — ok
STATIC mode
>time
2015-11-16 22:35:27

Собственно, надо сравнить стоки. Нет, сначала надо разбить текст на фрагменты разделителем ( например пробел), но потом всё равно сравнить строки. Поскольку команд было «раз, два — и обчёлся», то разбивку текста на фрагменты убрал. Из-за указанной выше ошибки класс String использовать не получалось, то как можно по другому? Arduino использует библиотеку AVR-libc, то резонно в первую очередь обратиться к ней.
Что имеем?

  1. stdlib.h — функции взаимного преобразования чисел и строк (в обе стороны).
  2. string.h — функции работы со строками. Основной наш интерес.
  3. stdio.h — функции стандартного ввода-вывода.

Этим не ограничивается функционал. Упомянуто то, что связано с задачей.

Итак, №1 рекомендую для ознакомления — вдруг пригодится отдельно где-то. Сам по себе используется только для работы string.h.

№2 — используем функции memset для заполнения или очистки буфера, memcmp — для сравнения. strcmp не использую, так как нужно явно ограничивать длину сравниваемого фрагмента. №3 — для форматного чтения и вывода: sprintf, sprint_P, sscanf, sscanf_P. Функции с суффиксом _P отличаются тем, что строку форматирования берут из памяти программ PROGMEM, он же макрос F() в библиотеках Arduino.

Кстати

Кстати, если полноценно реализовать функции ввода-вывода отдельного символа getc и putc, то получите стандартные потоки ввода, вывода, ошибок и для работы с файлами, если таковые у вас есть. Часто можно обойтись, переопределив макросы putchar() и getchar(), работающие со стандартным вводом и выводом.

У меня сравнение строк выглядит так:

if ( memcmp(str ,"statlist" ,8)==0 ) {
  // your code here
}

Пожалуй, стоит оговориться, что сравниваются начала строк. Для поиска фрагментов можно использовать memmem.

строки для Си

строки для Си str, они же char * — это ссылка на начало последовательности char, последняя из которых имеет значение 0x00. А значит, их надо где-то разместить. Например, в массиве. Или использовать malloc, calloc, free. Что не даёт делать ошибок подразумевает переложение ответственности на программиста за их размещение и контроль длинны.

То есть поиск команды может выглядеть так:

  if ( memcmp(str ,"statclear", 9)==0 ) {
	memset(journal, 0, sizeof(jrn_rec_t)*JRN_REC_NUM );
    Serial.println( F("ok") );
  }else if ( memcmp(str ,"statlist" ,8)==0 ) {
    funcStatlist();
  }else if ( memcmp(str ,"cfgshow", 7)==0 ) {
	funcCfgShow();
  }else if ( memcmp(str ,"timeset", 7)==0 ) {
    funcTimeSet( str); // setup date and time YYYY-MM-DD hh:mm:ss
  }else if ( memcmp(str ,"cfgset", 6)==0 ) {
	funcCfgSet( str); //funcPingdel( str);
  }else if ( memcmp(str ,"time", 4)==0 ) {
    funcTime();	// print date and time from RTC 
  }else if ( memcmp(str ,"help", 4)==0 ) {
    // print short help
    Serial.println( F(" helprn statlist statclearrn  time timesetrn cfgshow cfgset") );
  }else{
    Serial.print( F("unknow cmd> "));
    Serial.println( str);
  } 

Неочевидный момент

Команды, они же строки, с большей длинной должны идти первыми в приведённом фрагменте. Задумайтесь, почему?

Строки «собираю» следующим образом: читаю байты с порта, пока не превышена допустимая длинна строки или пока не встречен один из символов перевода строки r или n.

чтение строки

Лучше доработать бы… Пока как есть. Вызывается всё время в основном кольце. Если нет работы — максимально быстро на выход, возвращаем false. Если набрали новую строку — true.

bool readln( HardwareSerial &uart, char *outbuf)
// return true when find CR, LF or both and if size limit
{
static char mybuf[SBUF_SZ] = { 0 };  
static char idx = 0;
  while (uart.available()) {
    if ( uart.peek()!= 'r' && uart.peek()!= 'n' ) {
      mybuf[ idx++ ] = uart.read();
    } else {// если CR
      uart.read();
      if ( uart.peek()=='n' || uart.peek()=='r' ) uart.read();
      if ( idx == 0 ) {
        return 0;
      }
      mybuf[ idx++ ] = ''; // дописать 0
      memcpy( outbuf, mybuf, idx);  // скопировать
      idx = 0;
      return 1;
    }
    if ( idx >=(SBUF_SZ-1) ) { // проверяем на длину внутреннего буфера
      mybuf[ SBUF_SZ-1 ] = ''; // дописать 0
      memcpy( outbuf, mybuf, 32);  // скопировать
      idx = 0;
      return 1;
    }
  }
  return 0;
}

Ещё очень полезен форматный ввод-вывод. Например, разбор строки с ведённой датой и временем выглядит так:

sscanf_P(str, (const char *)F("%*s %d-%d-%d %d:%d:%d"), &y, &m, &d, &hh, &mm, &ss)

Получение строки для вывода IP:

sprintf_P(buff, (const char *)F("Your IP: %d.%d.%d.%d"), ip[0],  ip[1], ip[2], ip[3]);

Подробней о строке формата можно почитать, например, здесь scanf и здесь (printf).

Вот собственно и всё. Надеюсь, кому-то данный материал поможет «отвязаться» от Arduino или просто лучше и за меньшее время писать свои программы. Но более типичная ситуация — обойти ограничения Wiring.

Автору первоначальной статьи спасибо, хотя бы за то, что заставил меня набрать этот материал.

Автор: Hoksmur

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js