Объект String в Arduino и команды через последовательный порт

Сколько ни изучаю Arduino, она не перестаёт удивлять меня своей простотой. К примеру, собирая и тестируя систему «умного дома», я думал, что подача команд с компьютера будет самой сложной частью — это же надо принимать строку из последовательного порта, распознавать её, следить, чтобы не возникало ошибок… Однако оказалось достаточно почитать сайт Arduino.cc да потестить пару примеров, как стало ясно — разработчики постарались, чтобы оградить нас от написания длинного и унылого кода. К слову сказать, с задачей в итоге я справился за вечер, под конец даже подумывая: «а какую бы ещё команду прикрутить?..»

Итак, предположим, вы уже умеете программировать Arduino и можете разобраться в своём или чужом коде. Одним из основных понятий являются переменные и их типы. Ну-ка навскидку? byte, int, long, char, string… Два последних — по сути одно и то же, ибо string — массив переменных типа char (Кто-нибудь сейчас должен возразить, что char представляется в виде байтового числа, но речь не об этом). Итак, всё, что принимается из последовательного порта, следует читать, как char:

 char inChar[] = "             "; byte z = 0; while (Serial.available()) {   inChar[z] = Serial.read();   z++; }

Это первый пример, который может придти в голову. Создаём пустую строку, затем, если есть, что читать из последовательного порта, посимвольно её заполняем. Функция Serial.available() возвращает количество байт, доступных для чтения, а если там пусто — то 0, очевидно. Этим можно пользоваться, чтобы узнать длину поданной команды, хотя мы и так её узнаем в приведённом примере — это величина переменной z на выходе из цикла. Да, строка из пробелов (ASCII код пробела — не ноль!) — это терпимо, но всё-таки не очень хорошо, по возможности избегайте этого. А догадливый читатель сможет похвалить себя, если сразу догадается, что стоит исправить в вышеприведённом коде. Для тех, кто не догадался — подсказка: char inchar[6] — строка длиной 6 символов. Если строке присваивается значение, компилятор позволяет не указывать явно её длину, поэтому в примере квадратные скобки пустые.

Кстати, не стоит забывать прописать в setup()

 Serial.begin(9600);

А во встроенном мониторе порта, в который, собственно, и будут отправляться команды, указать скорость — 9600 бод, иначе увидите крякозябры.

Далее, что делать с полученной строкой? Те, кто предложат сравнивать побайтово строку с известными значениями (а такая мысль наверняка кому-то может придти в голову) после прочтения статьи переместятся вперёд во времени лет на 20. Из прошлого, я имею в виду 🙂

Поиск по документации Arduino IDE даёт два варианта, что такое string. Это сам string как строка char’ов, и String, являющийся объектом. Что такое объект? Согласно википедии, это «некоторая сущность в виртуальном пространстве, обладающая определённым состоянием и поведением, имеющая заданные значения свойств (атрибутов) и операций над ними (методов)». Другими словами — переменная со встроенными функциями, делающими что-то с этой переменной. Чтобы начать работать с этим объектом, напишем что-нибудь такого вида:

 String input = "";  while (Serial.available()) {   input += Serial.read();    }

Здесь к input будут добавляться всё новые символы, пока буфер не иссякнет. Тогда можно будет анализировать полученную строку, например, так:

 input.toLowerCase(); if(input.startsWith("pause")) {    String toWait =  input.substring(5);    toWait.trim();    int delaytime = toWait.toInt();    if(delaytime>0) {       if(delaytime<10000) {          delay(delaytime);       }    } }  

Код использует очень удобные функции, встроенные в объект String. Это startsWith(), которая возвращает единицу, если строка начинается с того, что записано в скобках, substring(), возвращающая кусок строки, начинающийся в данном случае с 5-го символа (считается, начиная с нуля), trim(), отбрасывающий всё лишнее по краям строки, ну и toInt(), превращающий то, что осталось, в число типа Int. Это число неплохо ещё и проверить на предмет попадания в рамки ожидаемого. В итоге, если дать команду «PauSe 567 «, то МК подождёт ровно 567 миллисекунд.

Про trim() стоит написать отдельно. Он нужен не только для того, чтобы отбросить пробел в начале получившейся строки, но в первую очередь — чтобы избавиться от символов в её конце. Это служебные символы, добавляющиеся при отправке сообщения — NL (новая строка) и CR (возврат каретки). Они нужны как раз для того, чтобы сигнализировать о конце команды, но могут и помешать. Поэтому, несмотря на то, что в мониторе порта можно выбрать, какие из этих символов посылать или не посылать ничего, лучше перестраховаться. Тем более, что делается это в одну строчку кода.

А вот и список функций (методов) объекта String.

  • charAt() — возвращает символ, стоящий на указанном месте

  • concat() — функция конкатенации, т.е слияния двух строк в одну. Правда string1 = string1 + string2 это то же самое, что и string1.concat(string1, string2), а записывается проще и понятнее.

  • equals() — возвращает единицу, если строка посимвольно равна тому, что написано в скобках. Есть ещё equalsIgnoreCase(), который игнорирует регистр (верхний или нижний)

  • endsWith() — который работает аналогично startsWith()

  • indexOf() — возвращающий место в строке символа(или строки) в скобках. Ищет с конца и возвращает -1, если не найдено.

  • length() — выдающий длину строки

  • setCharAt() — требующий место и символ, который надо поставить на это место, например: string1.setCharAt(3, ‘d’) поставит d третьим символом в строке взамен того, что там стояло

  • И ещё несколько других, которые вряд ли вам понадобятся, если вы не в силах залезть на arduino.cc и прочитать о них 🙂

Вот и всё, что хотел рассказать. Надеюсь, эта статья поможет не бояться ООП и научить вашего домашнего робота на Arduino повиноваться сигналам с компа