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

Итак. Вот примерный алгоритм, которому я следовал:

  1. Идем на arduino.ru и высматриваем в колонке типов все, связанное с символами.

  2. Решаем, какую форму представления будем использовать (Я остановился на классе String, т.к. имел неприятный опыт с месивом массивом).
  3. Судорожно пытаемся написать свою функцию с преферансом и профурсетками
  4. Ищем подробное описание класса.
  5. Ищем нужные операторы.
  6. Пишем!

А алгоритм работы основного тела программы прост:

  1. Циклично проверяем, есть ли в буфере com порта доступный для чтения байт, если есть, читаем.
  2. Если принятый байт — символ переноса строки ('\n'), то вызываем самописную функцию парсинга, если же нет, то добавляем принятый байт в созданную переменную типа String.
  3. Парсим, наконец, строку.
    На самом деле, т.к. практическое использование парсинга строк у меня не горит, то данная функция носит всего лишь демонстративный характер, сравнивая полученную строку с уже записанной в памяти константной строкой. Итак, используя оператор equals сравниваем принятую строку с записанной, и, если оператор возвращает true, выходим из функции обработчика, если же нет, сравниваем со следующей. Если строки эквивалентны, то опять же, выходим из функции-обработчика, возвращая результат. Ну а уж если и это условие не сработает, то все равно выйдем из функции и скажем, что что строка не верна.
  4. В зависимости от принятого результата с помощью switch case выбираем нужный.
  5. Обнуляем принятую строку, чтобы потом начать собирать ее заново.

А вот, наконец-таки, код:
#define led 13
String input_string = "";
const String Led_off = "switch led off";
const String Led_on = "switch led on";
bool led_running;

void setup() {
  Serial.begin(9600);
}

void loop() {
  while (Serial.available() > 0) {
    char c = Serial.read();
    if (c == '\n') { 
      Serial.print("Input_string is: ");
      Serial.println(input_string);
      switch ( parse(input_string, Led_off, Led_on) ) {
        case 10:
          led_running=false;
          Serial.println("Switching off is done");
          break;
        case 11:
          led_running=true;
          Serial.println("Switching on is done");
          break;
        case 0:
          Serial.println("invalid String");
          break;
      }
      input_string = "";
      digitalWrite(led, led_running);
    } else {
      input_string += c;

    }
  }
}

byte parse(String input_string, const String Led_off, const String Led_on) {
  if (input_string.equals(Led_off) == true) {
    return 10;
  }
  else if (input_string.equals(Led_on) == true) {
    return 11;
  }
  else return 0;
}


Так, я не понял, что за дела? Почему не загорается светодиод? Ах да, как же это я запамятовал, в void setup нужно добавить:

pinMode(led, OUTPUT);



P.S.: Немаловажно установить монитор com порта в Arduino IDE в режим «Новая строка», т.к. в любом другом посылаемая строка не будет сопровождаться символом ее окончания '\n'.

P.P.S.: В холиваре по поводу того, что ардуино нинужна — участвовать не собираюсь, изучая основы программирования и алгоритмизации я ничего дурного не сделал.

P.P.P.S.: Если статья будет принята адекватно, напишу следующую о том, что у меня вышло с улучшением функционала функции парсинга. Ну, с богом!.

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


  1. vladikas
    16.11.2015 10:50

    Буду следить за публикациями.


  1. ZloAlien
    16.11.2015 11:38
    +1

    Не могу не упомянуть протокол «Firmata». (http://robocraft.ru/blog/arduino/283.html)


  1. ToSHiC
    16.11.2015 11:51
    +1

    Очень рекомендую изучить тему текстовых парсеров на основе конечных автоматов. Например, можно попробовать написать простейший парсер (для начала консольный, а не для ардуины) на связке bison + flex.


  1. Alexeyslav
    16.11.2015 12:00
    +2

    Холивары холиварами… но последствия адрдуины уже имеются.
    Что если вашей программе на вход скормить очень длинную строку? Памяти в контроллере мало, и она совмещена со стеком… быстро переполняется и разрушает стек с соответствующими последствиями.
    Надо добавлять контроль максимальной длины строки, для защиты от непреднамеренных ошибок хотябы. Если набрали в строку 32{подставить своё значение не больше свободного объёма RAM} символа а перевода строки всё ещё нет — дальнейшие символы надо игнорировать во избежание разрушения стека — что-то пошло не так.


    1. proudmore
      16.11.2015 12:14

      Спасибо за взгляд со стороны и вскрытую уязвимость. Там, где я думал применять эту конструкцию, он бы себя не проявил. Ну, а на счет последствий… Вот вы беретесь утверждать, что мир МК юзерфрендли для абсолютных нубов? Я, лично, сомневаюсь. Я бы и не подумал даже сюда лезть. Однако сейчас я уже могу писать быдлокод — прогресс налицо. И, да, отказываться от Arduino IDE я таки собираюсь.


      1. Alexeyslav
        16.11.2015 14:05

        Не обязательно отказываться от Arduino IDE, надо просто изучить как она работает. В дальнейшем она может сэкономить массу времени на создании пилотных версий проектов, каких-то единичных вещей которые должны отработать минуту-десять-час и больше не понадобятся.
        Достаточно уметь работать без неё, изучить работу голого контроллера чтобы понять как на железо ложится программа написанная в среде ардуино и т.д.
        Я так думаю едва ли стоит переходить на чистый С для МК если вы не собираетесь становится профессиональным программистом. Всё то же самое можно делать и в Arduino IDE, просто не использовать универсальные громоздкие обёртки.


        1. proudmore
          16.11.2015 14:12
          +2

          Мне 19 лет, и то, чем я сейчас занимаюсь, мне нравится. Я пойду учится делать это профессионально. Хочу остаться в этой сфере.


    1. k12th
      16.11.2015 12:14
      +1

      Тут дело не в ардуине — это стандартный недосмотр, который может допустить кто угодно (привет, HeartBleed).


      1. ToSHiC
        16.11.2015 12:48

        На самом деле конкретно тут и не нужно строку в ОЗУ хранить, достаточно 2-3 байт для хранения состояния парсера, ну и сотня-другая байт флешки для хранения кода парсера.


        1. k12th
          16.11.2015 12:52
          +1

          Я к тому, что наезд на ардуино тут необоснованный:) Конечно, можно оптимальнее и безопаснее сделать.


        1. Hoksmur
          19.11.2015 08:37

          ToSHiC, а набросали бы тоже материал? Любопытно, может и я для себя что нового увижу.


    1. r00tGER
      16.11.2015 14:32

      Насколько помню, ардуиновский обработчик последовательного порта, просто перестанет писать в буфер, размер которого можно менять, кстати.
      В стеке ничего не потрется.
      А вот обработка «не ожидаемых» пакетов полностью на разработчике.


      1. Alexeyslav
        16.11.2015 23:16

        Это другой буфер, который наполняется если данные долго не востребованы.


  1. r00tGER
    16.11.2015 14:15

    Я так понимаю, вы сделали свой протокол. Но, зачем в текстовом виде?
    Почему не работать двоичными данными. Тогда не будет жутких обработок строк.

    Например нужно записать значение в аналогАут:
    0х01 0х01 0хАА — всего три байта — команда, порт и значение.
    А теперь представьте, насколько быстрее сравнение (свитч туда-же) работают с байтам.

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


    1. proudmore
      16.11.2015 14:18

      Я хочу попробовать связать контроллер с вебом посредством отправки и принятия GET запросов. Следовательно, работа со строками необходима :)
      Но в Вашем направлении тоже стоит предпринять какие-то шаги, буду иметь ввиду.


      1. r00tGER
        16.11.2015 14:26

        Ну, урл в любом случае стринговый :)
        Хотя-бы простой парсер ресурса и параметров, конечно нужен.


        1. proudmore
          16.11.2015 14:30

          Можно и по ip заходить же ) Если учитывать, что дальше локальной сети моей квартиры это не уйдет, то вполне себе норм :)


        1. proudmore
          16.11.2015 14:37

          Да и не стал бы я писать сюда с самодельным протоколом работы с двоичными данными :) Как вы правильно сказали, и так навалом всего.


      1. apple01
        18.11.2015 22:26

        Посмотрите мою статью в которой я управляю ардуиной методом GET запросов с последующим парсингом комманд
        http://habrahabr.ru/post/259579/


    1. Actor
      17.11.2015 05:03

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

      | 00 | 0 | 0 | 0010 |
        |    |   |     |
        |    |   |     Номер порта, с которым что то делаем
        |    |   Бит действия, GET / SET
        |    Режим порта HIGH / LOW
        Еще что либо
      
      



  1. GarryC
    16.11.2015 15:46
    +1

    Каждый программист должен хотя бы 1 раз в жизни написать свой 1)парсер 2)макропроцессор 3)printf.
    Вы на правильном пути )


    1. proudmore
      16.11.2015 16:34

      Но, видимо, в самом самом его начале, т.к. что такое макропроцессор и printf я даже понятия не имею :)
      Но спасибо )


  1. geovas333
    16.11.2015 19:17

    Что-то вспомнилось, как я будучи студентом 3-го курса пытался впихнуть в attiny2313 (с 2кб памяти) программу способную определять скорость вращения двигателя и выводить результат на ЖК экран. Я тогда так увлекся оптимизацией кода по выводы данных на экран, что совсем позабыл про цель работу. В конечном итоге скорость определялась мягко говоря не очень… Кто бы что ни говорил, но Arduino (а точнее atmega328p и более старшие модели), помогает решать многие проблемы одним простым sprintf-ом, например, оставляя время на более важные проблемы.


  1. AlanDrakes
    17.11.2015 07:14

    Может, попробуете посмотреть в сторону готовой библиотеки MicroRL?
    Помнится, я писал аналогичные «затычки» как для парсинга строк, так и для вывода на экран (странное то, что сначала — на ЖК экран, а уже затем — в консоль), но решил наконец перейти на готовое. Или на сублиматы — подготовленное к использованию.


    1. proudmore
      17.11.2015 11:33

      Не знал о существовании такой, спасибо!