Об'єкт 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 підкорятися сигналам з компа