На цій сторінці зібрано перелік практично всіх стандартних команд для Arduino, доповнений стислими поясненнями та прикладами використання. Деякі відомості взято з пошукових джерел, наприклад, Google, особливо щодо специфіки мови, інші напрацювання отримано шляхом експериментів. Детальнішу інформацію про застосування цих команд можна знайти у навчальних посібниках або в офіційній документації.
Структура скетча
Синтаксис, структура коду
/* */
Багаторядковий коментар.
/* цей код не
компілюється */
//
Однорядковий коментар.
// цей код
// не компілюється
;
Ставиться в кінці кожної дії.
void setup() {}
Функція, вміст якої виконується один раз під час запуску мікроконтролера.
void loop() {}
Функція, вміст якої виконується (або намагається виконуватися) «по колу» протягом усього часу роботи мікроконтроллеру.
#include
Директива, що дає змогу підключати до проєкту додаткові файли з кодом.
#include <Servo.h> // подключает библиотеку Servo.h
#include "Servo.h" // подключает библиотеку Servo.h
У чому відмінність <> і «»? Коли вказуємо назву «в лапках», компілятор спочатку шукає файл у папці зі скетчем, а потім у папці з бібліотеками. При використанні <галочок> компілятор шукає файл тільки в папці з бібліотеками.
#define
Директива, що дає команду препроцесору замінити вказану назву на вказане значення. Найчастіше таким чином оголошують константи:
#define MOTOR_PIN 10 // пін мотора 10
#define LED_PIN 3 // пін світлодіода 3
Після компіляції всі слова MOTOR_PIN, що трапляються в тексті програми, будуть замінені на цифру 10, а LED_PIN – на цифру 3. Такий спосіб зберігання констант не використовує оперативну пам’ять мікроконтролера. Також define дає змогу робити т.зв. макро функції. Наприклад Ардуїнівська функція sq (квадрат) є макро, який під час компіляції перетворюється на множення:
#define sq(x) ((x)*(x))
#if, #elif, #else, #endif
Директиви препроцесору, що дають змогу включати або виключати ділянки коду за умовою
#define TEST 1 // визначаємо TEST як 1
#if (TEST == 1) // якщо TEST 1
#define VALUE 10 // визначити VALUE як 10
#elif (TEST == 0) // TEST 0
#define VALUE 20 // визначити VALUE як 20
#else // якщо ні
#define VALUE 30 // визначити VALUE як 30
#endif // кінець умови
При помощи условной компиляции очень удобно собирать и настраивать сложные проекты с кучей настроек и библиотек, подключаемых “по условию”. Например:
#define DEBUG 1
void setup() {
#if (DEBUG == 1)
Serial.begin(9600);
Serial.println(«Hello!»);
#endif
}
Якщо параметру DEBUG встановити 1, то буде підключено бібліотеку Serial, якщо 0 – то ні. Таким чином отримуємо універсальний оптимізований проєкт із налагодженням.
#ifdef, #ifndef
Умовні директиви препроцесору, дають змогу вмикати або вимикати ділянки коду за умовою: ifdef – чи визначено? ifndef – чи не визначено?
#define TEST // визначаємо TEST
#ifdef TEST // якщо TEST визначено
#define VALUE 10 // визначити VALUE як 10
#else // якщо закоммент. #define TEST
#define VALUE 20 // визначити VALUE як 20
#endif // кінець умови
goto
Оператор переходу в іншу частину коду за міткою. Не рекомендується до використання, завжди можна обійтися без нього. Як приклад використання – вихід із купи умов
for (byte r = 0; r < 255; r++) {
for (byte g = 255; g > -1; g--) {
for (byte b = 0; b < 255; b++) {
if (analogRead(0) > 250) {
// піти з порівнянь
goto bailout;
}
// ще код
}
}
}
bailout:
// перенеслися сюди
return
Оператор переривання функції, він же оператор повернення значення з функції.
Умови (if, switch)
if, else if, else
Оператор порівняння та його друзі.
// при виконанні однієї дії {} необов'язкові
if (a > b) c = 10; // якщо a більше b, то c = 10
else c = 20; // якщо ні, то c = 20
// замість порівняння можна використовувати лог. змінну
boolean myFlag, myFlag2;
if (myFlag) c = 10;
// складні умови
if (myflag && myFlag2) c = 10; // якщо обидва прапори true
// при виконанні двох і більше {} обов'язкові
if (myFlag) {
с = 10;
b = c;
} else {
с = 20;
b = a;
}
byte buttonState;
if (buttonState == 1) a = 10; // якщо buttonState 1
else if (buttonState == 2) a = 20; // якщо ні, але якщо buttonState 2
else a = 30; // якщо і це не вірно, то ось
?
Скорочений запис умови: (логіка) ? правда : брехня.
int с = (a > b) ? 10 : -20; // якщо a > b, то с = 10. Якщо ні, то с = -20
boolean flag = true;
Serial.println( (flag) ? («прапор піднято») : («прапор опущено») );
switch.. case
Оператор вибору, замінює конструкцію з else if.
switch (val) {
case 1:
// виконати, якщо val == 1
break;
case 2:
// виконати, якщо val == 2
break;
default:
// виконати, якщо val ні 1 ні 2
// default опціональний
break;
}
Оператор break дуже важливий, дає змогу вийти з switch. Але можна використовувати так:
switch (val) {
case 1:
case 2:
case 3:
case 4:
// виконати, якщо val == 1, 2, 3 або 4
break;
case 5:
// виконати, якщо val == 5
break;
}
Циклы (for, while)
for
Цикл – «лічильник». for (ініціалізація; умова; інкремент).
for (int i = 0; i < 10; i++) {
a = i; // a прийме значення від 0 до 9 на кожній ітерації
Serial.println(a); // виведення в порт
}
// для однієї дії {} не потрібні
for (int i = 0; i < 10; i++)
Serial.println(i); // виведення в порт
Також використовується для створення замкнутих циклів, оскільки налаштування for необов’язкові. Вихід тільки через break або goto
for (;;;); // крутимося в циклі вічно
while
Цикл із передумовою.
while (a < b) {
// виконується, поки a менше b
}
Може бути використаний для створення замкнутого циклу, вихід тільки через break або goto
while (true) {
// крутимося в циклі вічно
}
do.. while
Цикл із постумовою.
do {
// виконується, поки a менше b
} while (a < b);
Відрізняється від while тим, що гарантовано виконається хоча б один раз
continue
Пропускає всі дії, що залишилися в тілі циклу, і переходить до наступної ітерації
break
Виходить із циклу
Оператори
Кома ,
Кома теж є оператором, використовується в таких випадках:
- Перерахування елементів у масивах
- Перерахування аргументів у функціях
- Виконання послідовності дій (зробити це І це)
Розглянемо третій випадок тут:
// оголосити a і b і дати їм значення
int a = 5, b = 10;
// присвоїти 3 до b
// додати 1 до b
// прирівняти до a
a = (b = 3, ++b); // a == 4
// оголосити i і j
// додавати i+1 і j+2
for (byte i = 0, j = 0; i < 10; i++, j += 2) {
// тут i змінюється від 0 до 9
// і j змінюється від 0 до 18
}
Арифметичні
Арифметичні оператори – найпростіші та найзрозуміліші з усіх
- = присвоювання
- % залишок від ділення
- * множення
- / ділення
- + додавання
- – віднімання
Порівняння та логіка
- == рівність (a == b)
- != нерівність (a != b)
- = більше або дорівнює
- <= менше або дорівнює
- більше
- < менше
- ! логічне НЕ, заперечення. Аналог – оператор not
- && логічне І. Аналог – оператор and
- || логічне АБО. Аналог – оператор or
Складові оператори
- ++ (плюс плюс) інкремент: a++ рівносильно a = a + 1
- — (мінус мінус) декремент: a — рівносильно a = a – 1
- += складене додавання: a += 10 рівносильно a = a + 10
- -= складене віднімання: a -= 10 рівносильно a = a – 10
- *= складене множення: a *= 10 рівносильно a = a * 10
- /= складене ділення: a /= 10 рівносильно a = a / 10
- %= додати залишок від ділення: a %= 10 рівносильно a = a + a % 10
- &= складене бітове І: a &= b рівносильно a = a & b
- ^= складене виключне АБО: a ^= b рівносильно a = a ^ b
- |= складене АБО: a |= b рівносильно a = a | b
Бітові операції
- & бітове І
- << бітове зрушення вліво
- >> бітове зрушення вправо
- ^ бітове виключне АБО (аналогічний оператор – xor)
- | бітове АБО
- ~ бітове НЕ
Покажчики та посилання
- & – повертає адресу даних у пам’яті (адреса першого блоку даних)
- *- повертає значення за вказаною адресою
- -> – оператор непрямого звернення до членів і методів (для покажчиків на структури і класи). Є коротким записом конструкції через покажчик: a->b рівносильно (*a).b
Робота з даними
Типи даних, змінні
Змінна – елементарна комірка для зберігання даних (цифр). Змінні різних типів мають різний «розмір комірки» і мають різний ліміт на розмір числа.
Назва | Альтернативна назва | Розмір | Діапазон | Особливість |
---|---|---|---|---|
boolean | bool | 1 байт | 0 або 1, true або false | Логічна змінна. bool на Arduino також займає 1 байт, а не біт! |
char | – | 1 байт | -128… 127 | Зберігає номер символу з таблиці ASCII |
– | int8_t | 1 байт | -128… 127 | Цілі числа |
byte | uint8_t | 1 байт | 0… 255 | Цілі числа |
int | int16_t, short | 2 байти | -32 768… 32 767 | Цілі числа |
unsigned int | uint16_t, word | 2 байти | 0… 65 535 | Цілі числа |
long | int32_t | 4 байти | -2 147 483 648… 2 147 483 647 | Цілі числа |
unsigned long | uint32_t | 4 байти | 0… 4 294 967 295 | Цілі числа |
float | – | 4 байти | -3.4E+38… 3.4E+38 | Числа з плаваючою комою (десяткові дроби). Точність: 6-7 знаків |
double | – | 4 байти | -1.7E+308… 1.7E+308 | Для AVR те саме, що float. В інших випадках — 8 байтів |
– | int64_t | 8 байтів | -(2^64)/2… (2^64)/2-1 | Цілі числа |
– | uint64_t | 8 байтів | 0… 2^64-1 | Цілі числа |
Існує ще кілька спеціальних типів даних для символів.
wchar_t – 16 бітний символ
- char16_t – 2-х байтний char
- char32_t – 4-х байтний char
Також є таке поняття, як перевизначення типів даних (не створюючи нових типів), для цього використовується ключове слово typedef. Typedef працює таким чином: typedef <тип> <ім’я>; – створити новий тип даних <ім’я> на основі типу <тип>. Приклад:
typedef byte color;
Створює тип даних під назвою color, який буде абсолютно ідентичний типу byte (тобто приймати 0-255). Тепер із цим типом можна створювати змінні:
color R, G, B;
Створили три змінні типу color, який той самий byte, тільки в профіль. Це все!
Структури
Структура (struct) – дуже складний тип даних: сукупність різнотипних змінних, об’єднаних одним ім’ям.
struct <ярлик> {
<тип> <ім'я змінної 1>;
<тип> <ім'я змінної 2>;
<тип> <ім'я змінної 3>;
};
Ярлик буде новим типом даних, і, використовуючи цей ярлик, можна оголошувати вже безпосередньо саму структуру:
<ярлик> <ім'я структури>; // оголосити одну структуру
<ярлик> <ім'я структури1>, <ім'я структури2>; // оголосити дві структури типу <ярлик>
<ярлик> <ім'я структури>[5]; // оголосити масив структур
Також є варіант оголошення структури без створення ярлика, тобто створюємо структуру, не оголошуючи її як тип даних зі своїм ім’ям.
struct {
<тип> <ім'я змінної 1>;
<тип> <ім'я змінної 2>;
<тип> <ім'я змінної 3>;
} <ім'я структури>;
- Звернення до члена структури відбувається ось за такою схемою: <ім’я структури>.<ім’я змінної> і дає змогу змінювати або читати значення.
- Якщо дві структури мають однакову структуру (оголошені одним ярликом), то можна одну структуру просто прирівняти до іншої, всі змінні запишуться відповідно на свої місця.
- Ще одним зручним варіантом є присвоювання значення ось таким чином: <ім’я структури> = ( <ярлик> ) { <значення змінної 1>, <значення змінної 2>, <значення змінної 3> };
Перечисления
Перерахування (enum – enumeration) – тип даних, що являє собою набір іменованих констант, потрібен насамперед для зручності програміста.
Оголошення перерахування чимось схоже на оголошення структури:
enum <ярлик> {<ім'я1>, <ім'я2>, <ім'я3>, <ім'я4>, <ім'я5>};
Таким чином ми оголосили ярлик. Тепер, використовуючи цей ярлик, можна оголосити саме перерахування:
<ярлик> <ім'я перерахування>;
Так само як і у структур, можна оголосити перерахування без створення ярлика (навіщо нам зайвий рядок?):
enum {<ім'я1>, <ім'я2>, <ім'я3>, <ім'я4>, <ім'я5>} <ім'я перерахування>;
Створене таким чином перерахування є змінною, яка може приймати зазначені для неї <імена>, також із цими іменами її можна порівнювати. Тепер найголовніше: імена для програми є числами, починаючи з 0 і далі по порядку збільшуючись на 1. В абстрактному прикладі вище <ім’я1> дорівнює 0, <ім’я2> дорівнює 1, <ім’я3> дорівнює 2, і так далі. Крім зазначених імен, перерахуванню можна прирівняти і число безпосередньо, але як би навіщо.
Класи
Класи в С++ – це основний і дуже потужний інструмент мови, більшість «бібліотек» є класами. Ієрархія така:
- Клас
- Об’єкт
- Властивості та методи
Клас оголошується таким чином:
class /*ім'я класу*/
{
private:
// список властивостей і методів для використання всередині класу
public:
// список методів доступних іншим функціям і об'єктам програми
protected:
// список засобів, доступних під час успадкування
};
Масиви
Для оголошення масиву достатньо вказати квадратні дужки після імені змінної, тип даних – будь-який.
// вказуємо кількість комірок і все, далі можна з ними працювати
int myInts[6];
// вказуємо вміст комірок, компілятор сам порахує їхню кількість
int myPins[] = {2, 4, 8, 3, 6};
// вказуємо і те, і те, кількість клітинок у [ ] має збігатися з { } або бути більшою!
float Sens[3] = {0.2, 0.4, -8.5};
// зберігаємо символи
char message[6] = «hello»;
// пам'ятаємо, що порядок комірок починається з нуля!
myInts[0] = 10; // записати 10 у клітинку 0 масиву myInts
Рядки (об’єкт String)
String – дуже потужний інструмент для роботи з рядками, тобто текстовими даними. Оголосити рядок можна кількома способами:
String string0 = «Hello String»; // заповнюємо словами в лапках
String string1 = String(«lol “) + String(”kek»); // сума двох рядків
String string2 = String('a'); // рядок із символу в одинарних лапках
String string3 = String(«This is string»); // конвертуємо рядок у String
String string4 = String(string3 + « more»); // складаємо рядок string3 з текстом у лапках
String string5 = String(13); // конвертуємо з числа в String
String string6 = String(20, DEC); // конвертуємо з числа із зазначенням базису (десятковий)
String string7 = String(45, HEX); // конвертуємо з числа із зазначенням базису (16-ричний)
String string8 = String(255, BIN); // конвертуємо з числа із зазначенням базису (двійковий)
String string9 = String(5.698, 3); // з float із зазначенням кількості знаків після коми (тут 3)
// можна формувати назву зі шматочків, наприклад для роботи з файлами
#define NAME «speed»
#define TYPE «-log»
#define EXT «.txt»
// при складанні досить вказати String 1 раз для першого рядка
String filename = String(NAME) + TYPE + EXT; // filename дорівнюватиме speed-log.txt
// доступ до елемента рядка працює за таким самим механізмом, як масив
string1[0] = «a»;
// тепер замість Hello String у нас aello String
Рядки можна порівнювати, складати і віднімати, також для роботи з ними є купа функцій:
charAt()
myString.charAt(index);
Повертає елемент рядка myString під номером index. Аналог – myString[index];
setCharAt()
myString.setCharAt(index, val);
Записує в рядок myString символ val на позицію index. Аналог – myString[index] = val;
compareTo()
myString.compareTo(myString2);
- Повертає від’ємне число, якщо myString йде перед myString2.
- Повертає додатне число, якщо myString йде після myString2.
- Повертає 0, якщо рядки однакові.
concat()
myString.concat(value);
Приєднує value до рядка (value може мати будь-який числовий тип даних).
Повертає true у разі успішного виконання, false при помилці.
Аналог – додавання: myString + value.
endsWith()
myString.endsWith(myString2);
Перевіряє, чи закінчується myString символами з myString2.
У разі збігу повертає true.
startsWith()
myString.startsWith(myString2);
Перевіряє, чи починається myString символами з myString2.
У разі збігу повертає true.
equals()
myString.equals(myString2);
Повертає true, якщо myString збігається з myString2.
Регістр літер має значення.
equalsIgnoreCase()
myString.equalsIgnoreCase(myString2);
Повертає true, якщо myString збігається з myString2. Регістр літер не має значення.
indexOf()
myString.indexOf(val);
myString.indexOf(val, from);
Шукає і повертає номер (позицію) значення val у рядку, шукає зліва направо, повертає номер першого символа у збігу.
val може бути char або String, тобто можна шукати в рядку інший рядок або символ.
Можна почати пошук із заданої позиції from.
Якщо val не знайдено в рядку, повертає -1.
lastIndexOf()
myString.lastIndexOf(val);
myString.lastIndexOf(val, from);
Шукає і повертає номер (позицію) значення val у рядку, шукає справа наліво, повертає номер першого символа у збігу (зліва направо).
val може бути char або String, тобто можна шукати в рядку інший рядок або символ.
Можна почати пошук із заданої позиції from.
Якщо val не знайдено в рядку, повертає -1.
length()
myString.length();
Повертає довжину рядка у кількості символів.
remove()
myString.remove(index);
myString.remove(index, count);
Видаляє з рядка символи, починаючи з index і до кінця, або до вказаної кількості count.
replace()
myString.replace(substring1, substring2);
У рядку myString замінює послідовність символів substring1 на substring2.
String myString = "babushka Misha";
myString = myString.replace("babushka", "dedushka");
System.out.println(myString); // Виведе: dedushka Misha
reserve()
myString.reserve(size);
Резервує в пам’яті кількість байтів size для роботи з рядком.
c_str()
myString.trim();
Перетворює рядок у формат “C” (null-terminated string) і повертає вказівник на отриманий рядок.
substring()
myString.substring(from);
myString.substring(from, to);
Повертає частину рядка, що міститься в myString
, починаючи з позиції from
і до кінця, або до позиції to
.
String myString = "lol kek 4eburek";
String chebur = myString.substring(8);
// рядок chebur містить "4eburek"
toCharArray()
myString.toCharArray(buf, len);
Розподіляє рядок в масив — буфер buf
(типу char[]
), починаючи з початку і до довжини len
.
getBytes()
myString.getBytes(buf, len);
Копіює вказану кількість символів len
(до unsigned int
) в буфер buf
(типу byte[]
)
toFloat()
myString.toFloat();
Повертає вміст рядка у тип даних float
toDouble()
myString.toDouble();
Повертає вміст рядка у тип даних double
toInt()
myString.toInt();
Цей метод перетворює рядок myString
на значення типу int
String myString = "10500";
int val = myString.toInt();
// val теперь 10500
toLowerCase()
myString.toLowerCase();
Цей метод змінює рядок myString
, перетворюючи всі його символи на малі літери. Наприклад, якщо було “AAAAA”, стане “aaaaa”.
toUpperCase()
myString.toUpperCase();
Цей метод змінює рядок myString
, перетворюючи всі його символи на великі літери. Наприклад, якщо було “aaaaa”, стане “AAAAA”
Специфікатори змінних
const
— константа, змінна, яку не можна змінити (інакше буде помилка). const int val = 10;static
— дозволяє оголосити локальну змінну всередині функції, і ця змінна не буде повторно ініціалізована при кожному виклику функції. Така собі локальна глобальна змінна.volatile
— вказує компілятору, що змінну не слід оптимізувати, оскільки її значення може змінюватися зовні (наприклад, апаратно або в обробнику переривання). Такий специфікатор слід використовувати для змінних, значення яких можуть змінюватися в обробниках переривань або іншими потоками.extern
— вказує компілятору, що ця змінна оголошена в іншому файлі програми, але ми хочемо використовувати саме її, а не створювати нову змінну з таким самим ім’ям у цьому файлі. Це дозволяє читати та змінювати змінні, створені в інших файлах (або бібліотеках).
Перетворення типів даних
Змінні різних типів даних можуть бути перетворені одна в одну — для цього достатньо вказати потрібний тип даних у дужках перед змінною, яку потрібно перетворити:
(тип_даних)змінна.
Результатом буде значення з новим типом даних, але тип самої змінної не зміниться (перетворення працює лише в межах однієї операції).
// змінна типу byte
byte val = 10;
// передаємо якійсь функції, яка очікує int
sendVal( (int)val );
Таким чином можна перетворювати звичайні змінні, вказівники та інші типи даних.
А щодо рядків — їх ми вже розглядали вище.
- toInt()
- toFloat()
- toCharArray()
Інколи можна зустріти перетворення типів за допомогою оператора cast
.
reinterpret_cast
— Приведення типів без перевірки, безпосереднє вказівка компілятору. Застосовується лише в разі повної впевненості програміста у своїх діях. Не знімаєconst
іvolatile
, застосовується для приведення вказівника до вказівника, вказівника до цілого і навпаки.- static_cast — Перетворює вирази одного статичного типу в об’єкти і значення іншого статичного типу. Підтримується перетворення числових типів, вказівників і посилань по ієрархії наслідування як вгору, так і вниз. Перетворення перевіряється на рівні компіляції, і в разі помилки приведення типів буде виведено повідомлення.
dynamic_cast
— Використовується для динамічного приведення типів під час виконання. У разі неправильного приведення типів для посилань викликається виключна ситуаціяstd::bad_cast
, а для вказівників буде повернуто 0.- const_cast — найпростіше приведення типів. Знімає
const
іvolatile
, тобто константність і відмову від оптимізації компілятором змінної. Це перетворення перевіряється на рівні компіляції, і в разі помилки приведення типів буде виведено повідомлення.
Як користуватися: на прикладі попереднього прикладу
// переменная типа byte
byte val = 10;
// передаём какой-то функции, которая ожидает int
sendVal( static_cast<int>(val) );