Все статьиПрограммирование

Как правильно писать скетч для Arduino и ESP — подробный урок

БА
Бексултан Айтен
CTO, Alashed
20 августа 2025 г.
9 мин чтения
Как правильно писать скетч для Arduino и ESP — подробный урок

Подробный урок о том, как правильно писать скетч для Arduino и ESP: структура скетча, комментарии, создание функций, задержки, современные конструкции и оформление кода.

Как правильно писать скетч для Arduino и ESP
Как правильно писать скетч для Arduino и ESP

Всем привет! В этой статье я подробно расскажу, как правильно писать скетч, чтобы он был легко понятен, хорошо работал и легко исправлялся. Погнали =)

1. СТРУКТУРА СКЕТЧА

Скетч разделяется визуально на несколько частей:

1. Подключение библиотек/файлов, объявление констант/переменных. Эта часть находится в самом верху кода, начинается с первой строки и заканчивается там, где начинается функция _setup()_. В этой части указывают большинство переменных и констант, которые будут использоваться в скетче, а также подключаются библиотеки и остальные файлы проекта. Для многих модулей, например, TFT дисплеи, сервоприводы, необходимо создать объект (указать имя и пины) прямо в начале кода после подключения библиотек. Пример первой части кода представлен в строках 1-10 примера кода внизу.

2. Функция _setup()_. Эта функция выполняется _один раз после запуска микроконтроллера_. В этой части обычно инициализируются объекты каких-либо устройств, подключенных к плате или просто инициализируются библиотеки. а также задаются роли для цифровых/аналоговых пинов платы (вход или выход сигнала). Если в проекте используется коммуникация по Serial, то указывается скорость порта специальной функцией. Пример второй части кода представлен в строках 11-20 примера кода внизу.

3. Функция _loop()_. Эта функция выполняется циклично после выполнения функции _setup()_. В этой части скетча выполняются основные вычисления и действия, которые будут повторяться, пока не отключат питание платы. Например, подаются сигналы на пины, отправляются сообщения в Serial, считываются показания с датчиков и т.д. Пример первой части кода представлен в строках 21-30 примера кода внизу.

4. Остальные функции (необязательно, но желательно). Если у вас большой код, состоящий из групп отдельных действий, то рекомендую разделить основной код на функции, описать эти функции по отдельности после основного цикла _loop()_, и в цикле просто вызывать эти функции в нужном порядке. На работу кода это не повлияет, зато будет удобно редактировать код, так как он разделён на функции, а у функций есть имена, чтобы знать что функция делает.

Пример хорошего, удобно воспринимаемого и красивого кода:

```cpp

#include <Servo.h>

const int BUTTON_PIN = 2;

const int SERVO_PIN = 9;

const int DEBOUNCE_DELAY = 50;

int buttonState = 0;

int lastButtonState = HIGH;

int servoAngle = 0;

unsigned long lastDebounceTime = 0;

Servo myServo;

void setup() {

pinMode(BUTTON_PIN, INPUT_PULLUP);

Serial.begin(9600);

myServo.attach(SERVO_PIN);

}

void loop() {

handleButton();

updateServo();

printStatus();

}

void handleButton() {

int reading = digitalRead(BUTTON_PIN);

if (reading != lastButtonState) {

lastDebounceTime = millis();

}

if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {

if (reading != buttonState) {

buttonState = reading;

if (buttonState == LOW) {

servoAngle = (servoAngle + 45) % 180;

}

}

}

lastButtonState = reading;

}

void updateServo() {

myServo.write(servoAngle);

}

void printStatus() {

Serial.print("Button: ");

Serial.print(buttonState == LOW ? "PRESSED" : "RELEASED");

Serial.print(" | Servo angle: ");

Serial.println(servoAngle);

delay(100);

}

`

2. КОММЕНТАРИИ

Для того, чтобы было понятно, что означает какая-либо строка кода, нужно писать комментарии. Комментарии в одну строку обозначаются двумя косыми линиями (слешами), пример:

```cpp

// Комментарий в одну строку

`

Если нужно закомментировать несколько строк, то в начале первой строки напишите слеш со звёздочкой, а в конце последней наоборот, звёздочку со слешем. Пример:

```cpp

/*

Комментарий во много строк

Комментарий во много строк

Комментарий во много строк

*/

`

Комментарии не обрабатываются компилятором, а значит, не занимают место в памяти микроконтроллера. Поэтому комментариев можно писать сколько угодно =)

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

```cpp

void myFunc(){

Serial.println("Эта строка будет работать!");

//Serial.println("А эта не будет!");

/* Serial.println("И эта строка не будет!");

Serial.println("И эта строка не будет!");

Serial.println("И эта строка не будет!"); */

}

`

В этом фрагменте кода я представлю все способы добавить пояснения к коду:

```cpp

Serial.println("Hi, dear Arduiner!"); // Комментарий в строке

// Комментарий над строкой

servo.write(90);

/*

Подробный комментарий

Расскажем, например, про особенности

подключения модуля или создания сигнала

Ниже у нас функция подачи ШИМ сигнала, параметры:

- Номер пина с поддержкой ШИМ (для Arduino Uno/Nano это пины 3, 5, 6, 9, 10, 11)

- Уровень сигнала (для Arduino Uno/Nano от 0 до 255)

*/

analogWrite(5, 250);

`

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

```cpp

// === Библиотека для серво ===

#include <Servo.h>

// === Константы (не меняются в коде) ===

const int BUTTON_PIN = 2; // Пин кнопки

const int SERVO_PIN = 9; // Пин сервопривода

const int DEBOUNCE_DELAY = 50; // Задержка для антидребезга (мс)

// === Переменные (меняются в коде) ===

int buttonState = 0; // Текущее состояние кнопки

int lastButtonState = HIGH; // Предыдущее состояние

int servoAngle = 0; // Текущий угол сервы

unsigned long lastDebounceTime = 0; // Таймер антидребезга

// === Объект серво ===

Servo myServo; // Создаем объект сервопривода

// === Функция настроек Setup ===

void setup() {

// Настройка пинов

pinMode(BUTTON_PIN, INPUT_PULLUP);

// Инициализация Serial

Serial.begin(9600);

// Подключение сервопривода

myServo.attach(SERVO_PIN);

}

// === Цикл Loop ===

void loop() {

handleButton(); // Проверка кнопки

updateServo(); // Обновление позиции сервы

printStatus(); // Вывод данных в Serial

}

// === Функция обработки кнопки ===

void handleButton() {

int reading = digitalRead(BUTTON_PIN);

// Если состояние изменилось

if (reading != lastButtonState) {

lastDebounceTime = millis();

}

// Проверка временного интервала для антидребезга

if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {

if (reading != buttonState) {

buttonState = reading;

// Если кнопка нажата (LOW, так как INPUT_PULLUP)

if (buttonState == LOW) {

servoAngle = (servoAngle + 45) % 180; // Изменяем угол на 45°

}

}

}

lastButtonState = reading;

}

// === Функция обновления сервопривода ===

void updateServo() {

myServo.write(servoAngle); // Устанавливаем угол

}

// === Функция вывода статуса ===

void printStatus() {

Serial.print("Button: ");

Serial.print(buttonState == LOW ? "PRESSED" : "RELEASED");

Serial.print(" | Servo angle: ");

Serial.println(servoAngle);

delay(100); // Небольшая задержка для читаемости логов

}

`

3. СОЗДАНИЕ ФУНКЦИЙ

Как вы помните из параграфа 1, я говорил, что большой код надо разделять на функции. Сейчас я расскажу, как создать и подключить функцию.

Создаётся функция вот так. Пишем «void », затем имя функции латинскими буквами и цифрами, начинающееся с буквы. Затем круглые скобки и фигурные скобки. Между фигурных скобок ставим курсор и 2 раза жмём клавишу ENTER для создания переноса строки. Теперь пишем код для этой функции в фигурных скобок.

Если есть необходимость указывать параметры для функции при вызове, можно создать необходимые переменные в круглых скобках. Они специально и созданы для параметров =)

Пример создания функции ниже. Мы создали функцию для управления встроенным светодиодом на плате.

```cpp

void builtinLedControl(bool ledstate){

digitalWrite(LED_BUILTIN, ledstate);

if (ledstate == 0){

Serial.println("Builtin led OFF!");

}

else if (ledstate == 1){

Serial.println("Builtin led ON!");

}

}

`

В идеале, именно для этой функции можно ещё добавить параметр «номер пина». Реализуем:

```cpp

void ledControl(int ledPin, bool ledstate){

digitalWrite(ledPin, ledstate);

if (ledstate == 0){

Serial.println("Led OFF!");

}

else if (ledstate == 1){

Serial.println("Led ON!");

}

}

`

Так вот, функцию мы объявили, теперь нужно её вызвать, потому что без вызова она ничего делать не будет. Вызвать функцию можно в коде любой другой «самодельной» функции, а также в функциях SETUP и LOOP. Реализуем вызов нашей функции ledControl() в коде основного цикла LOOP:

```cpp

void loop(){

ledControl(5, 1); // Вызываем нашу функцию ledControl, в параметрах укажем пин 5 и уровень сигнала 1

}

`

И вот так выглядит пример цельного рабочего кода с нашей функцией:

```cpp

void setup(){

pinMode(5, OUTPUT); // Делаем пин 5 выходом сигнала

}

void loop(){

ledControl(5, 1); // Вызываем нашу функцию ledControl, в параметрах укажем пин 5 и уровень сигнала 1

}

void ledControl(int ledPin, bool ledstate){ // Объявляем функцию ledControl

digitalWrite(ledPin, ledstate);

if (ledstate == 0){

Serial.println("Led OFF!");

}

else if (ledstate == 1){

Serial.println("Led ON!");

}

}

`

4. ЗАДЕРЖКИ

Часто люди используют в роли задержки функцию _delay()_, однако её можно использовать не во всех случаях, сейчас разберёмся почему.

Функция _delay()_ не просто делает задержку, а останавливает полностью выполнение всего кода на время. Если вам нужно просто мигать светодиодом, тогда эта функция вполне подойдёт. Но если вам нужно одновременно мигать светодиодом и обрабатывать запросы веб-сервера, тогда уже не получится, так как веб-сервер должен постоянно работать, а с функцией _delay()_ веб-сервер будет работать около 1 миллисекунды останавливать работу на время свечения/не свечения светодиода. Надеюсь вы поняли =)

В такой непростой ситуации приходит на помощь функция _millis()_. Она, конечно, не работает так, как _delay()_, она просто возвращает количество миллисекунд, прошедшее со времени её первого вызова. С помощью несложного расчёта можно сделать аналог _delay()_. Пример кода мигания светодиодом с _millis()_:

```cpp

int timer = 1000; // Время паузы в мс

int ledPin = 13; // Пин светодиода. Если нужно, замените на нужный номер

bool ledState = 0; // Служебная переменная состояния светодиода

unsigned long interv = 0; // Служебная переменная специального вида для расчёта времени

void setup(){

pinMode(ledPin, OUTPUT); // Делаем пин светодиода выходом сигнала

}

void loop(){

if (millis() - interv >= timer){ // Если настало время...

interv = millis(); // Приравниваем служебную переменную с millis

ledState = !ledState; // Меняем переменную состояния светодиода

digitalWrite(ledPin, ledState); // Включаем/выключаем светодиод

}

}

`

С использованием такой схемы можно мигать светодиодом, не останавливая очень важные скрипты =)

5. ИСПОЛЬЗУЙТЕ СОВРЕМЕННЫЕ КОНСТРУКЦИИ

У некоторых функций есть современные аналоги, упрощающие написание кода. Сейчас рассмотрим некоторые из них.

IF...ELSE

Если вам нужно проверить, равна ли переменная 1 (да/истина) или 0 (нет/ложь), то не обязательно указывать число, с которым сравниваем переменную (1 или 0):

```cpp

// Старый вариант сравнения с 1 или 0

if (select == 1){

// делаем что-то если select равна 1

}

else if (select == 0){

// делаем что-то если select равна 0

}

`

Можно не писать 1 или 0, а использовать вот такой вариант:

```cpp

// Новый вариант сравнения с 1 или 0

if (select){

// делаем что-то если select равна 1

}

else if (!select){

// делаем что-то если select равна 0

}

`

В первом случае, если select равна 1, компилятор знает, что так сравнивается переменная с 1. Во втором случае, если select равна 0, компилятор знает, что переменная сравнивается с 0. На объём самой программы в памяти микроконтроллера это не повлияет, но зато код выглядит современнее =)

SWITCH-CASE вместо IF...ELSE

Если вам нужно сравнить значение переменной с определёнными значениями, например, кодами кнопок ИК пульта, тогда следует использовать конструкцию SWITCH-CASE. Она занимает меньше места в коде и выглядит современнее.

Сравните, конструкция с IF...ELSE:

```cpp

if (ircode == 000000){

// делаем что-то при получении кода 000000

}

else if (ircode == 111111){

// делаем что-то при получении кода 111111

}

else if (ircode == 222222){

// делаем что-то при получении кода 222222

}

// ...и ещё штук 10-20 таких конструкций, сколько там у пульта кнопок =)

`

И современная конструкция SWITCH-CASE:

```cpp

switch (ircode){

case 000000:

// делаем что-то при получении кода 000000

break; // Обязательная функция break, обозначает конец кейса

case 111111:

// делаем что-то при получении кода 111111

break;

case 222222:

// делаем что-то при получении кода 222222

break;

// такие конструкции повторяем для всех нужных значений

}

`

6. ОФОРМЛЕНИЕ СКЕТЧА

При написании кода обязательно нужно использовать табуляцию — это такой вид отступа, вставляется клавишей TAB, примерно равен 4-7 пробелов. Табуляцией нужно отодвигать содержимое функций и других элементов. Пример кода без табуляций:

```cpp

void setup(){

pinMode(2, OUTPUT);

pinMode(3, INPUT_PULLUP);

servo.attach(9);

byte map[5][8]{

1, 1, 1, 1, 1, 1, 1, 1,

1, 0, 0, 0, 0, 1, 1, 1,

1, 1, 1, 0, 0, 0, 1, 1,

1, 0, 0, 0, 0, 0, 1, 1,

1, 1, 1, 1, 1, 1, 1, 1,

}

}

`

Без отступов получается не красиво и не понятно что и где. Вот пример кода с табуляциями в правильных местах:

```cpp

void setup(){

pinMode(2, OUTPUT);

pinMode(3, INPUT_PULLUP);

servo.attach(9);

byte map[5][8]{

1, 1, 1, 1, 1, 1, 1, 1,

1, 0, 0, 0, 0, 1, 1, 1,

1, 1, 1, 0, 0, 0, 1, 1,

1, 0, 0, 0, 0, 0, 1, 1,

1, 1, 1, 1, 1, 1, 1, 1,

}

}

`

Так выглядит гораздо лучше =)

7. КАК ПРАВИЛЬНО УКАЗЫВАТЬ ФУНКЦИИ и т.д.

Сейчас рассмотрим правильно написание элементов скетча, таких как функции, переменные, константы и другие.

ПЕРЕМЕННЫЕ

**Переменная** — виртуальный «контейнер», значение которого **может меняться** далее в скетче.

>

Например, переменная может содержать число для дальнейших вычислений и изменения его. Для проекта калькулятора сразу объявляются все переменные, в которые будет записываться число, введённое пользователем.

Переменные указываются в начале кода вот по этой схеме:

`

тип_переменной имя_переменной = значение;

`

Примеры правильно указанных переменных:

```cpp

int chislo = 256; // Значение числовой переменной указывается просто вот так

String slovo = "Hi everyone"; // Значения переменных строкового типа указываются в кавычках

bool logic = false; // Переменные типа BOOL могут принимать значения только TRUE/1 либо FALSE/0

`

КОНСТАНТЫ

**Константа** — виртуальный контейнер, значение которого **не может измениться** в скетче.

>

Например, константами задают имена пинам, чтобы в скетче можно было указывать имя пина вместо его номера для удобства.

Константы стандартного вида указываются также как и переменные, но в самом начале нужно написать слово CONST. Примеры:

```cpp

// Вот так можно создать константу для указания имени пина

#define LEDPIN 3

const int chislo = 256; // Значение числовой константы указывается просто вот так

const String slovo = "Hi everyone"; // Значения констант строкового типа указываются в кавычках

`

ФУНКЦИИ

Функции, как мы разобрались ранее, это фрагмент кода с именем, который можно вызывать для выполнения, когда необходимо. Функции создаются так:

```cpp

void myFunction(){

// Код этой функции

}

// Функция с параметрами:

void myFuncParams(int speed; String word;){ // Параметры указываем в круглых скобках в виде переменных без значений

// Код функции, с использованием переменных из параметров

}

`

Вызываются функции вот так:

```cpp

void loop(){

myFunction(); // Функция без параметров вызывается так

myFuncParams(50, "Hi everyone"); // Функция с параметрами. Параметры указываем в скобочках.

}

`

ЗНАКИ ПРЕПИНАНИЯ

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

1. В конце каждой функции обязательно пишем точку с запятой «;», обозначающую завершение действия.

2. Параметры функции указываются через запятую, в порядке их указания при создании функции.

3. Все значения строкового типа указываются в двойных кавычках «».

4. Код функции указывается в фигурных скобках {}.

5. Параметры функции указываются в круглых скобках ().

6. Комментарии указывают двумя слешами // или **слешами со звёздочками /\* комментарий \*/**.

7. Присваивание значения переменной — один знак «=».

8. Операторы сравнения (указаны в кавычках):

- Больше — «>»

- Меньше — «<»

- Не больше (меньше или равно) — «<=»

- Не меньше (больше или равно) — «>=»

- Равно — «==»

- Не равно — «!=»

На этом пока всё. Спасибо за внимание, пишите правильный код! Удачи =)

Попробуйте Alashed бесплатно

Подключите школу к пилоту. Генерируйте КМЖ за 2 минуты, ведите CodeStudio уроки, заказывайте оборудование — всё в одном месте.

Попробовать бесплатноДемо