Преместване на знаците на текстовия масив на Arduino. Форматиране на низове с sprintf. Определяне на дължината на низа

Текстови низовеможе да се декларира по два начина: можете да използвате типа данни String, който е включен в ядрото от версия 0019; или декларирайте низа като масив от символи с нулев край. Тази страница описва втория метод. За да получите повече подробна информацияЗа String обект, който предоставя повече функционалност на цената на повече памет, вижте страницата String - Object.

Примери

По-долу са дадени примери за правилни декларации на низове.

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 Str5 = "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 = "Това е първият ред" " това е вторият ред" " и така нататък";

Низови масиви

При работа с големи обемитекст (например в проекти, работещи с LCD екран), често е удобно да се използват масиви от низове. Тъй като самите низове са масиви, това на практика е пример за двуизмерен масив.

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

Пример

char* myStrings=("Това е низ 1", "Това е низ 2", "Това е низ 3", "Това е низ 4", "Това е низ 5", "Това е низ 6"); void setup())( Serial.begin(9600); ) void loop())( for (int i = 0; i< 6; i++){ Serial.println(myStrings[i]); delay(500); } }

Колкото и да изучавам Arduino, той никога не престава да ме учудва със своята простота. Например сглобяване и тестване на системата " умен дом", мислех, че издаването на команди от компютъра ще бъде най-трудната част - трябва да приемете низ от сериен порт, разпознайте го, уверете се, че няма грешки... Оказа се обаче, че е достатъчно да прочетете уебсайта Arduino.cc и да тествате няколко примера, тъй като стана ясно, че разработчиците са се опитали да ни предпазят от дълго писане и тъп код. Между другото, в крайна сметка завърших задачата вечерта, като накрая дори си помислих: „каква друга команда мога да добавя?..“

И така, нека приемем, че вече знаете как да програмирате Arduino и можете да разберете своя собствен или код на някой друг. Едно от основните понятия са променливите и техните типове. Е, набързо? byte, int, long, char, string... Последните две са по същество едно и също нещо, защото string е масив от променливи тип char(Сега някой може да възрази, че char е представен като байт номер, но това не е целта.) Така че всичко, получено от серийния порт, трябва да се чете като char:

Char inChar = " "; байт z = 0; докато (Serial.available()) ( inChar[z] = Serial.read(); z++; )

Това е първият пример, който може да ви дойде на ум. Създаваме празен ред, след което, ако има нещо за четене от серийния порт, го попълваме символ по символ. Функцията Serial.available() връща броя байтове, налични за четене, и ако е празна, тогава 0, очевидно. Това може да се използва, за да разберете дължината на дадената команда, въпреки че вече я знаем в дадения пример - това е стойността на променливата z при излизане от цикъла. Да, поредица от интервали (ASCII интервален код - не нула!) е поносима, но все още не е много добра, избягвайте това, ако е възможно. И находчивият читател ще може да се похвали, ако веднага познае какво трябва да се коригира в горния код. За тези, които не са познали, съвет: char inchar е низ с дължина 6 знака. Ако на низ е присвоена стойност, компилаторът ви позволява да избегнете изричното посочване на неговата дължина, така че в примера квадратни скобипразна.

Между другото, не забравяйте да напишете в setup()

Serial.begin(9600);

И във вградения монитор на порта, към който всъщност ще се изпращат команди, посочете скоростта - 9600 бода, в противен случай ще видите пукнатини.

След това какво да правим с получения низ? Тези, които предлагат да сравнят байт по байт низ с известни стойности (и такава идея със сигурност може да хрумне на някого), след като прочетат статията, ще се преместят във времето с 20 години от миналото, имам предвид :).

Търсене в документация Arduino IDEдава две опции за това какво е низ. Това е самият низ като низ от знаци, а String, който е обект? Според Wikipedia, това е „някое същество в виртуално пространство, имащ определено състояние и поведение, имащ зададени стойностисвойства (атрибути) и операции върху тях (методи)". С други думи, променлива с вградени функции, които правят нещо с тази променлива. За да започнете да работите с този обект, нека напишем нещо подобно:

Въвеждане на низ = ""; докато (Serial.available()) ( input += Serial.read(); )

Тук все повече и повече нови знаци ще се добавят към входа, докато буферът се изчерпи. Тогава ще бъде възможно да се анализира полученият низ, например, така:

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“, тогава MK ще изчака точно 567 милисекунди.

Струва си да напишем отделно за trim(). Необходимо е не само да се премахне интервалът в началото на резултантния ред, но преди всичко да се отървете от символите в края. Това са служебни знаци, добавени при изпращане на съобщение - NL (нов ред) и CR (връщане на каретка). Те са необходими само за сигнализиране за края на командата, но могат и да се намесват. Следователно, въпреки че мониторът на порта може да избере кой от тези знаци да изпрати или да не изпрати нищо, по-добре е да бъдете на сигурно място. Освен това това се прави в един ред код.

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

    charAt()- връща знака на посочената позиция

    concat()- функция за конкатенация, т.е. обединяване на два низа в един. Вярно е, че string1 = string1 + string2 е същото като string1.concat(string1, string2), но е написано по-просто и по-ясно.

    равно на()- връща единица, ако низът е символ по знак равен на написаното в скоби. Има също equalsIgnoreCase(), който игнорира главни и малки букви

    завършва със()- което работи подобно на startsWith()

    индекс на()- връщане на местоположението в низа на знака (или низа) в скоби. Търси от края и връща -1, ако не бъде намерен.

    дължина()- показване на дължината на низа

    setCharAt()- изискване на място и знак, който трябва да бъде поставен на това място, например: string1.setCharAt(3, "d") ще постави d като трети знак в реда вместо това, което беше там

  • И няколко други, които едва ли ще ви трябват, ако не можете да отидете на arduino.cc и да прочетете за тях :)

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

Добър ден! Продължаваме да овладяваме дъската Ardiuno Uno. В последната статия научихме как да показваме информационни съобщения на екрана на компютъра/лаптопа.

Ако си спомняш, ти и аз все още имаме недовършена работа. А именно методът за определяне на входни параметри на функции Сериен. print() иСериен. println().Ние задаваме параметрите на тези функции директно на редовете, където са били извикани, вместо да задаваме параметри чрез променливи.

Първо, нека сглобим отново веригата с два светодиода. След това отваряме програмата, която написахме в урок 5.

Както можете да видите, допълних втория с необходимите редове. Надявам се, че сте изпълнили и тази задача :) Ако не, разширете програмата си.

Не трябва да копирате редове код от текста на статията. Например, кавичките в документ на Verdov може да не се интерпретират като „кавички“ в IDE на Arduino. Също така искам да кажа следното - ако не сте успели да заредите програмата в MK при първия опит, трябва да опитате отново (няколко пъти).

В една от предишните статии вече засегнах въпроси, свързани с използването на . Повтарям. Много по-практично е да правите промени в необходимите параметри, като променяте стойностите на променливите, които се намират в началото на програмата или в отделен файл, отколкото да претърсвате целия код за необходимите стойности.

Досега използвахме променливи, които бяха от целочислен тип. Тези числа са удобни за преброяване или посочване на номера на щифта на микроконтролера. В допълнение, този тип заема много по-малко памет на микроконтролера в сравнение с други типове. Въпреки това, когато четем данни от сензори (например температурен сензор), ще получим стойности, които се състоят от цяло число и дробна част. За да работим с такива числа ни трябва тип плавам(числа с плаваща запетая). Този тип ще бъде описан отделно, сега нека поговорим за типа низови данни низ .

Променлив тип низсе присвояват данни, затворени в двойни кавички.

Нека декларираме променливи от тип String.

  • Низ greenLedText = “”;
  • Низ redLedText = “”;

В програмния код ще заменим текстовите съобщения с имена на променливи. Както можете да видите, когато записвате променливи като входни параметри към функции, не са необходими двойни кавички. Но какво се случва, ако напишем името на една от променливите в двойни кавички? Сега ще проверим...

Нека изтеглим програмата и да видим резултата. Единият ред се показва правилно, но вторият, вместо да показва необходимата информация, показва името на променливата. Всичко е заради двойните кавички.

Правилото е следното: ако искате да изведете низ директно от скоби, поставете двойни кавички; ако искате да изведете информация, която е присвоена на променлива от низ, не поставяйте кавички.

Нека създадем локална променлива "greeting" в невалиденнастройвам. Защо местни? Първо, заема значително по-малко място в паметта на микроконтролера. Второ, напразно обсъждахме проблемите на локалните и глобалните променливи; трябва да приложим теорията на практика. Защо създаваме „локална зона“ специално в невалиденнастройвам, и всичко това, защото се изпълнява само веднъж.

Ние декларираме и присвояваме стойност на променлива.

низпърви_съобщение = “Здравейте";

Едно „здравей“ няма да е достатъчно, необходимо е нещо друго. Нека напишем нещо като... 🙂 и го разделим на две съобщения.

  • String second_message = “Аз съм Мигалка”;
  • String third_message = “Искам blinky”;

Извикванията на функциите трябва да се правят след реда за активиране (инициализация) на серийния порт.

  • Serial.println(първо_съобщение);
  • Serial.println(второ_съобщение);


Нека изтеглим програмата и да видим резултата. Както е планирано, поздравът се показва веднъж. Но някак си не е хубаво да се съгласим? 🙂 За да бъдат второто и третото съобщение на един ред, има няколко опции:

  • Замяна на функция println(второ_съобщение) На Serial.print(второ_съобщение) . За да сте сигурни, че третото съобщение не се припокрива с второто след... „Мигалка“, поставете интервал.
  • Свържете второто и третото съобщение заедно. сума_съобщение =второ_съобщение +трети_съобщение;

Избираме втория вариант. Нека първо декларираме нова низова променлива низсума_съобщение;

Замяна на името « второ_ съобщение"На « сума_ съобщение". Ако тизамени функцията Сериен.println(второ_съобщение) На Сериен.печат ( второ_съобщение),променете всичко обратно, така че новото съобщение да започва на нов ред и редът " Serial.println(трето_съобщение); » изтрит поради безполезност.

Нека изтеглим програмата и да видим резултата.

18 ноември 2015 г. в 14:39 ч

Въз основа на „Обработка на низове на Arduino“

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

Какво се изискваше? Показване на информация и обработка на низове, въведени от потребителя. Например:

Ethernet контролер - ок
СТАТИЧЕН режим
>време
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() V Ардуино библиотеки.

Между другото

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


Моето сравнение на низове изглежда така:

If (memcmp(str , "statlist" ,8)==0) ( // вашия кодтук)
Може би си струва да се спомене, че началото на линиите се сравнява. За търсене на фрагменти можете да използвате memmem.

низове за C

низове за C ул, те са символ *- това е връзка към началото на поредицата въглен, последното от които има значение 0x00. Това означава, че те трябва да бъдат поставени някъде. Например в масив. Или използвайте malloc, calloc, Безплатно. Какво ви предпазва от грешки означава прехвърляне на отговорността на програмиста за тяхното разположение и контрол на дължината.


Тоест търсенето на команда може да изглежда така:

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); // дата и час за настройка ГГГГ-ММ-ДД чч:мм:сс )else if (memcmp(str ,"cfgset", 6)==0) ( funcCfgSet( str); // funcPingdel(str); else if (memcmp(str), 4)==0) ( funcTime(); // отпечатване на дата и час от RTC) else if (memcmp(str," помощ", 4)==0) ( // отпечатване на кратка помощ Serial.println(F(" помощ\r\n statlist statclear\r\n time timeset\r\n cfgshow cfgset")); )else( Serial. print(F("unknow cmd> "));

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

Командите или редовете с по-голяма дължина трябва да са на първо място в горния фрагмент. Помислете защо?


„Събирам“ редовете по следния начин: чета байтове от порта, докато се превиши допустимата дължина на реда или докато не се срещне някой от символите за подаване на ред \r или \n.

линия за четене

По-добре да го финализираме... Засега е както е. Обажда се през цялото време на главния пръстен. Ако няма работа, излезте възможно най-бързо и я върнете невярно. Ако сте въвели нов ред - вярно.

Bool readln(HardwareSerial &uart, char *outbuf) // връща true при намиране на CR, LF или и двете и ако ограничение на размера ( static char mybuf = ( 0 ); static char idx = 0; while (uart.available()) ( if (uart.peek()!= "\r" && uart.peek()!= "\n") ( mybuf[ idx++ ] = uart.read(); ) else (// if 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; if (idx >=(SBUF_SZ-1)) (// проверка на дължината на вътрешния буфер mybuf[SBUF_SZ-1] = "\0"; // добавяне на 0 memcpy(outbuf, mybuf, 32); // копиране на idx = 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("Вашият IP: %d.%d.%d.%d"), ip, ip, ip, ip);
Можете да прочетете повече за форматиращия низ, например,

Дойдох до програмирането като цяло и Програмиране на Arduinoв частност, пълна нула, преди около два месеца. Сега, в хода на настоящите ми дейности, почувствах нуждата да овладея обработката на низове на Arduino. Едно обикновено пътуване до Google за информация не ме зарадва със статия, в която всичко е написано просто и ясно за манекени. И затова съм тук, за да говоря за това как беше реализиран анализът на низове от серийния порт и какво срещнахме по пътя подводни скали. За тези, които се интересуват, моля, обърнете се към кат.

Така. Ето примерен алгоритъм, който следвах:

  1. Отиваме на arduino.ru и търсим в колоната тип за всичко, свързано със символи.
  2. Ние решаваме каква форма на представителство ще използваме (спрях се на клас String, защото имах неприятно преживяване с каша масив).
  3. Трескаво се опитваме да напишем собствена функция с предпочитания и професионални мрежи
  4. Търся клас.
  5. Търсим необходимите оператори.
  6. Да пишем!
А алгоритъмът на основното тяло на програмата е прост:
  1. Проверяваме циклично дали има в буфера com портчетим байт, ако има такъв, прочетете.
  2. Ако полученият байт е знак за прекъсване на ред ("\n"), тогава извикваме самостоятелно написаната функция за анализиране, ако не, тогава добавяме получения байт към създадения тип променливаниз.
  3. Накрая анализирайте низа.

    Всъщност, защото практическа употребатогава анализът на низове не работи за мен тази функцияе само демонстративен по природа, сравнявайки получения низ с постоянен низ, който вече е записан в паметта. И така, използвайки оператора equals, сравняваме получения низ с написания и ако операторът върне true, излизаме от манипулаторната функция; ако не, го сравняваме със следващия. Ако низовете са еквивалентни, тогава отново излизаме от функцията манипулатор, връщайки резултата. Е, ако това условие не работи, тогава пак ще излезем от функцията и ще кажем, че редът не е верен.

  4. В зависимост от приетия резултат от използването случай на превключванеизберете този, от който се нуждаете.
  5. Нулираме получения низ, за ​​да можем след това да започнем да го събираме отново.

И накрая, ето кода:

#define led 13 String input_string = ""; const String Led_off = "изключване на светодиода"; const String 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 е: "); Serial.println(input_string); switch (parse(input_string, Led_off, Led_on)) (случай 10: led_running=false; Serial.println("Изключването е извършено"); break; case 11: led_running=true; Serial.println("Включването е извършено"); case 0: Serial.println("input_string"); 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 трябва да добавите:

PinMode(led, OUTPUT);

P.S.: Важно е да настроите монитора на com порта в Arduino IDE на „ Нова линия“, защото във всеки друг случай изпратеният низ няма да бъде последван от своя краен символ "\n".

P.P.S.: Няма да участвам в holivar за това, че Arduino е ненужен; не съм направил нищо лошо, докато изучавам основите на програмирането и алгоритмизацията.

P.P.P.S.: Ако статията се приеме адекватно, ще напиша следващата за това какво направих с подобряването на функционалността на функцията за анализ. Е, с Бог! .