Arduino не находит символ конца строки. Обрабатываем строки на Arduino. Преобразование String в массив char

18 ноября 2015 в 14:39

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

  • Разработка под Arduino

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

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 - функции стандартного ввода-вывода.
Этим не ограничивается функционал. Упомянуто то, что связано с задачей.

№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(" help\r\n statlist statclear\r\n time timeset\r\n 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 = { 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"; // дописать 0 memcpy(outbuf, mybuf, idx); // скопировать idx = 0; return 1; } if (idx >=(SBUF_SZ-1)) { // проверяем на длину внутреннего буфера mybuf[ SBUF_SZ-1 ] = "\0"; // дописать 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, ip, ip, ip);
Подробней о строке формата можно почитать, например,

Текстовые строки могут быть объявлены двумя способами: можно использовать тип данных String, который входит в ядро, начиная с версии 0019; либо объявить строку как массив символов char с нулевым символом в конце. На этой странице описан второй способ. Для получения более подробной информации об объекте String, предоставляющем больше возможностей ценой большего расхода памяти, см. страницу String - объект.

Примеры

Ниже представлены примеры правильного объявления строк.

Char Str1; char Str2 = {"a", "r", "d", "u", "i", "n", "o"}; char Str3 = {"a", "r", "d", "u", "i", "n", "o", " char Str1; char Str2 = {"a", "r", "d", "u", "i", "n", "o"}; char Str3 = {"a", "r", "d", "u", "i", "n", "o", "\0"}; char Str4 = "arduino"; char Str5 = "arduino"; char Str6 = "arduino"; "}; char Str4 = "arduino"; char Str5 = "arduino"; char Str6 = "arduino";

Допускаемые операции при объявлении строк

  • Объявить массив символов без его инициализации (Str1)
  • Объявить массив символов с одним избыточным элементом, компилятор сам добавит требуемый нулевой символ (Str2)
  • Добавить нулевой символ явно (Str3)
  • Инициализировать массив с помощью строковой константы, заключенной в кавычки; компилятор создаст массив необходимого размера с нулевым символом в конце (Str4)
  • Инициализировать массив с помощью строковой константы, явно указав его размер (Str5)
  • Инициализировать массив избыточного размера, оставив место для более длинных строк (Str6)

Нулевой завершающий символ

Как правило, все строки завершаются нулевым символом (ASCII код 0), который позволяет функциям (подобным Serial.print() ) определять длину строки. Без этого символа они продолжали ли бы последовательно считывать байты памяти, которые фактически уже не являлись бы частью строки.

По сути, это означает, что длина вашей строки должна быть на 1 символ больше, чем текст, который вы хотели бы в ней хранить. Именно поэтому Str2 и Str5 должны быть длиной 8 символов, несмотря на то, что слово "arduino" занимает всего 7 - последняя позиция автоматически заполняется нулевым символом. Размер Str4 автоматически станет равным 8 - один символ требуется для завершающего нуля. В строке Str3 мы самостоятельно указали нулевой символ (обозначается "\0").

Следует иметь в виду, что в целом можно объявить строку и без завершающего нулевого символа (например, если задать длину Str2 равной 7, а не 8). Однако это приведет к неработоспособности большинства строковых функций, поэтому не следует намеренно так делать. Такая ошибка может быть причиной странного поведения или появления сторонних символов при работе со строками.

Одинарные или двойные кавычки?

Строки всегда объявляются в двойных кавычках ("Abc"), а символы всегда объявляются в одинарных кавычках ("A").

Перенос длинных строк

Длинные строки можно переносить так:

Char myString = "This is the first line" " this is the second line" " etcetera";

Массивы строк

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

В нижеследующей программе звездочка после указания типа данных char "char*" показывает, что переменная является массивом "указателей". Все имена массивов фактически являются указателями, поэтому звездочка необходима для создания массива массивов. Указатели в C - одна из наиболее сложных вещей для начинающих, но в данном случае глубокого понимания указателей для их эффективного использования вовсе не требуется.

Пример

char* myStrings={"This is string 1", "This is string 2", "This is string 3", "This is string 4", "This is string 5","This is string 6"}; void setup(){ Serial.begin(9600); } void loop(){ for (int i = 0; i < 6; i++){ Serial.println(myStrings[i]); delay(500); } }

Я пришел к программированию в целом, и программированию 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.: Если статья будет принята адекватно, напишу следующую о том, что у меня вышло с улучшением функционала функции парсинга. Ну, с богом! .

Arduino String – основная библиотека для работы со строками в ардуино. С ее помощью существенно упрощается использование массивов символов и строк в скетче. Объект типа String содержит множество полезных функций для создания и объединения строк, преобразований string to int (парсинг чисел) и int to string (форматирование чисел). Строки используются практически в любых проектах, поэтому и вероятность встретить String в скетче очень высока. В этой статье мы постараемся рассмотреть основные методы этого класса и наиболее часто возникающие ситуации.

Стандартным способом работы со строками в языке C является использование массива символов. Это все означало необходимость работы с указателями и понимания адресной арифметики. В ардуино и C++ у программистов появилось гораздо больше возможностей. Все “низкоуровневые” операции по работе со строкой выделены в отдельный класс, а для основных операций даже переопределены операторы. Например, для объединения срок мы просто используем хорошо знакомый знак “+”, а не зубодробильные функции типа malloc и strcpy. С помощью String мы работаем со строкой как с целым объектом, а не рассматриваем его как массив символов. Это позволяет сосредоточиться на логике скетча, а не деталях реализации хранения символов в памяти.

Естественно, у любого “упрощения” всегда есть свои подводные камни. String всегда использует больше оперативной памяти и в некоторых случаях функции класса могут медленнее обрабатываться. Поэтому в реальных больших проектах придется тщательно взвешивать все плюсы и минусы и не забывать, что никто не мешает нам работать со строками в стиле С. Все обычные функции обработки массивов char остаются в нашем арсенале и в arduino.

Создание строк в ардуино с помощью String

В ардуино у нас есть несколько способов создать строку, приведем основные:

  • char myCharStr = “Start”; – массив типа char с завершающим пустым символом;
  • String myStr = “Start”; – объявляем переменную, создаем экземпляр класса String и записываем в него константу-строку.
  • String myStr = String(“Start”); – аналогичен предыдущему: создаем строку из константы
  • String myStr(myCharStr); – создаем объект класса String с помощью конструктра, принимающего на вход массив типа char и создающего из char String.
  • String myStr = String(50); – создаем строку из целого числа (преобразование int to string).
  • String myStr = String(30, H); – создаем строку – представление числа в 16-чной системе (HEX to String)
  • String myStr = String(16, B); – создаем строку – представление числа в двоичной системе (Byte to String).

Каждый раз, когда мы объявляем в коде строку с использованием двойных кавычек, мы создаем неявный объект класса String, являющийся константой. При этом обязательно использование именно двойных кавычек: “String” – это строка. Одинарные кавычки нужны для обозначения отдельных символов. ‘S’ – это символ.

Функции и методы класса String

Для работы со строками в String предусмотрено множество полезных функций. Приведем краткое описание каждой из них:

  • String() – конструктор, создает элемент класса данных string. Возвращаемого значения нет. Есть множество вариантов, позволяющих создавать String из строк, символов, числе разных форматов.
  • charAt() возвращает указанный в строке элемент. Возвращаемое значение – n-ный символ строки.
  • compareTo() – функция нужна для проверки двух строк на равенство и позволяет выявить, какая из них идет раньше по алфавиту. Возвращаемые значения: отрицательное число, если строка 1 идет раньше строки 2 по алфавиту; 0 – при эквивалентности двух строк; положительное число, если вторая строка идет раньше первой в алфавитном порядке.
  • concat() – функция, которая объединяет две строки в одну. Итог сложения строк объединяется в новый объект String.
  • startsWith() – функция показывает, начинается ли строка с символа, указанного во второй строке. Возвращаемое значение: true, если строка начинается с символа из второй строки, в ином случае false.
  • endsWith() – работает так же, как и startsWith(), но проверяет уже окончание строки. Также возвращает значения true и false.
  • equals() – сравнивает две строки с учетом регистра, т.е. строки «start» и «START» не будут считаться эквивалентными. Возвращаемые значения: true при эквивалентности, false в ином случае.
  • equalsIgnoreCase() – похожа на equals, только эта функция не чувствительна к регистру символов.
  • getBytes() – позволяет скопировать символы указанной строки в буфер.
  • indexOf() – выполняет поиск символа в строке с начала. Возвращает значение индекса подстроки val или -1, если подстрока не обнаружена.
  • lastIndexOf() –выполняет поиск символа в строке с конца.
  • length() – указывает длину строки в символах без учета завершающего нулевого символа.
  • replace() – заменяет в строке вхождения определенного символа на другой.
  • setCharAt() – изменяет нужный символ в строке.
  • substring() – возвращает подстроку. Может принимать два значения – начальный и конечный индексы. Первый является включительным, т.е. соответствующий ему элемент будет включаться в строку, второй – не является им.
  • toCharArray() – копирует элементы строки в буфер.
  • toLowerCase() – возвращает строку, которая записана в нижнем регистре.
  • toUpperCase() – возвращает записанную в верхнем регистре строку.
  • toInt() – позволяет преобразовать строку в число (целое). При наличии в строке не целочисленных значений функция прерывает преобразование.
  • trim() – отбрасывает ненужные пробелы в начале и в конце строки.

Объединение строк Arduino

Объединить две строки в одну можно различными способами. Эта операция также называется конкатенацией. В ее результате получается новый объект String, состоящий из двух соединенных строк. Добавить к строке можно различные символы:

  • String3 = string1 + 111; // позволяет прибавить к строке числовую константу. Число должно быть целым.
  • String3 = string1 + 111111111; // добавляет к строке длинное целое число
  • String3 = string1 + ‘А’; // добавляет символ к строке
  • String3 = string1 + “aaa”;// добавляет строковую постоянную.
  • String3 = string1 + string2; // объединяет две строки вместе.

Важно осторожно объединять две строки из разных типов данных, так как это может привести к ошибке или неправильному результату.

Arduino string to int и string to float

Для конвертации целочисленных значений string to int используется функция toInt().

String MyStr = “111”;

int x = MyStr.toInt();

Если нужно конвертировать объект с плавающей запятой, применяется функция atof().

String MyStr = “11.111”;

MyStr.toCharArray(myStr1, MyStr.length()); // копируется String в массив myStr1

float x = atof(myStr1); // преобразование в float

Преобразование int to string

Для создания строки из числа не требуется делать особых телодвижений. Мы можем просто объединить строку и число:

String str = “Строка номер “+ i;

Можем создать объект, используя конструктор

String str = String(50);

Можем объединить оба способа:

String str = “Строка номер “+ String(50);

Преобразование String в массив char

Тип данных Char позволяет объявлять текстовые строки несколькими способами:

  • char myStr1; – в данном случае объявлен массив определенного размера.
  • char myStr2 = {‘a’, b, ‘c’, ‘d’, ‘e’}; – объявлен сам массив. Конечный символ не записанявно, его прибавит сам компилятор.
  • char myStr3 = {‘a’, b, ‘c’, ‘d’, ‘e’’/0’}; – объявлен массив, при этом в конце прописан признак окончания строки.
  • char myStr4 = “abcde”; – инициализация массива строковой постоянной. Размер и завершающий символ добавляются автоматически компилятором.
  • char myStr5 = “abcde”; – инициализация массива с точным указанием его размера.
  • char myStr 6 = “abcde”; – аналогично, но размер указан больше для возможности использования строк большей длины.

Еще раз напомним, что в типе данных char строковые константы нужно записывать в двойные кавычки «Abcde», а одиночные символы – в одинарные ‘a’.

Конвертировать строку в массив сhar array можно при помощи следующего кода:

String stringVar = “111”;

char charBufVar;

stringVar.toCharArray(charBufVar, 20);

Можно сделать обратное преобразованиеchar to string .

char chArray = “start”;

String str(chArray);

Пример преобразования String to const char* . Указание звездочкой char*означает, что это массив указателей.

String stringVar=string (`start);

Char charVar[ sizeof ;

stringVar.toCharArray(charVar, sizeof(charVar));

Заключение о String и ардуино

В этой статье мы рассмотрели основные вопросы использования String для работы со строками arduino. Как показывают примеры, ничего страшного и сложного в этом классе нет. Более того, зачастую мы можем даже не догадываться, что работаем с классом String: мы просто создаем переменную нужного типа, присваиваем ей строку в двойных кавычках. Создав строку, мы используем все возможности библиотеки String: можем без проблем модифицировать строку, объединять строки, преобразовывать string в int и обратно, а также делать множество других операций с помощью методов класса.

В ситуациях, когда скетч большой и перед нами встает дефицит памяти, использовать String нужно осторожно, по возможности заменяя на char*. Впрочем, в большинстве первых проектов начинающего ардуинщика таких ситуаций не много, поэтому рекомендуем использовать String без опаски – это предотвратит появление ошибок адресной арифметики, возникающих при работе с массивами char.




Top