ПМК 3 курс Лекции Лекция 3 Основы программирования Arduino - BysonHunter/Tavrich_college GitHub Wiki
🏠 Главная · Программирование микроконтроллеров · 3 курс · Лекции
Дисциплина: МДК.02.02 «Программирование микроконтроллеров»
Специальность: 09.02.01 «Компьютерные системы и комплексы»
Раздел: Введение в архитектуру микроконтроллеров и основы программирования
Тема: среда Arduino и основы языка программирования C/C++ для микроконтроллеров
Программа Arduino называется скетчем. Скетч фактически является программой на C/C++ с библиотеками и служебным кодом Arduino Core.
Минимальная программа содержит две обязательные функции:
void setup() {
// выполняется один раз после включения или reset
}
void loop() {
// выполняется снова и снова
}
// однострочный комментарий
/*
многострочный комментарий
*/
#include <LibraryName.h>
void setup() {
// инициализация
}
void loop() {
// основной алгоритм
}Комментарии не входят в машинный код программы после компиляции и применяются для пояснения алгоритма.
После большинства операторов ставится ;:
int value = 10;
value++;
digitalWrite(13, HIGH);После заголовка функции, if, for, while и других конструкций с блоком { ... } точка с запятой обычно не ставится.
Объявление переменной:
int myVal;Объявление с начальным значением:
int myVal = 2300;Общая форма:
<тип> <имя>;
<тип> <имя> = <значение>;

В исходном материале размеры типов приведены для классических Arduino на AVR, например Arduino Uno/Nano с ATmega328P. На 32-разрядных контроллерах ARM Cortex-M, ESP32 и других платформах размеры некоторых типов отличаются.
Например:
- на AVR
intобычно 16-битный; - на многих 32-битных МК
int— 32-битный; - на AVR
doubleобычно имеет ту же точность, что иfloat; - на многих 32-битных платформах
double— 64-битный.
Для аппаратно-зависимого кода удобно использовать типы фиксированной ширины:
uint8_t
int16_t
uint32_tОбъявляется вне функций:
int counter = 0;
void setup() {
}
void loop() {
counter++;
}Доступна функциям программы в пределах области видимости.
Создается внутри функции или блока:
void myFunction() {
int value = 25;
}После выхода из блока локальная автоматическая переменная перестает быть доступной.
Следует предпочитать локальные переменные там, где глобальный доступ не требуется: это уменьшает количество скрытых зависимостей в программе.
Константа с типом:
const int ledPin = 13;Препроцессорная подстановка:
#define LED_PIN 13Для обычных числовых и логических констант в C++ чаще удобнее const или constexpr, поскольку они имеют тип и лучше контролируются компилятором.
Пример:
const unsigned long BLINK_INTERVAL = 500;Основные операторы:
| Оператор | Назначение |
|---|---|
+ |
сложение |
- |
вычитание |
* |
умножение |
/ |
деление |
% |
остаток от целочисленного деления |
++ |
увеличить на 1 |
-- |
уменьшить на 1 |
Составные операции:
x += a;
x -= a;
x *= a;
x /= a;Стандартные математические функции, доступные в Arduino:
pow(x, a);
sq(x);
sqrt(x);
abs(x);
sin(x);
cos(x);
tan(x);
round(x);
ceil(x);
floor(x);int a = 5 / 2; // 2
float b = 5.0 / 2.0; // 2.5Тип операндов определяет способ выполнения операции.
При работе с последовательным портом важно отличать число от символа.
Например, символ '1' хранится не как число 1, а как код ASCII 49.

char c = Serial.read();
if (c == '1') {
// пользователь отправил символ 1
}Если известно, что пришла одна десятичная цифра:
int digit = Serial.read() - '0';Простейшая форма:
if (condition) {
// выполняется, если condition истинно
}Ветвление:
if (condition) {
// да
} else {
// нет
}Несколько вариантов:
if (value < 10) {
// ...
} else if (value < 20) {
// ...
} else {
// ...
}== равно
!= не равно
> больше
< меньше
>= больше или равно
<= меньше или равно
&& логическое И
|| логическое ИЛИ
! логическое НЕ
Пример:
if (temperature > 30 && fanEnabled) {
digitalWrite(FAN_PIN, HIGH);
}Они работают не с логическими условиями целиком, а с отдельными битами целого числа:
& AND
| OR
^ XOR
~ NOT
<< сдвиг влево
>> сдвиг вправо
В исходной лекции рассматривается программа, которая меняет задержку мигания встроенного светодиода в зависимости от команды, принятой с компьютера.

Алгоритм этой программы удобно представить так:

Главная идея:
-
'1'— медленное мигание; -
'2'— средняя скорость; -
'3'— быстрое мигание.
В учебном примере используется delay(), потому что он хорошо демонстрирует последовательность выполнения. Для реальных многозадачных скетчей ниже рассмотрен более удобный вариант на millis().
Когда одна переменная сравнивается с несколькими фиксированными вариантами, switch часто делает программу понятнее длинной цепочки if ... else if.

Общая форма:
switch (value) {
case 1:
// ...
break;
case 2:
// ...
break;
default:
// ...
break;
}break завершает текущую ветку. Если его убрать, выполнение может продолжиться со следующего case.
delay(1000);Останавливает выполнение текущего потока скетча примерно на 1000 мс.
Это удобно для простейших примеров, но во время такой задержки код в loop() не может выполнять другие задачи.
delayMicroseconds(20);Создает короткую задержку в микросекундах. Допустимые интервалы и точность зависят от платформы Arduino Core и частоты микроконтроллера.
Возвращает число миллисекунд с момента запуска:
unsigned long now = millis();На классическом AVR значение unsigned long переполняется примерно через 49,7 суток.
Возвращает время в микросекундах. Для Arduino Uno разрешение традиционно составляет несколько микросекунд, а переполнение происходит примерно через 70 минут. На других платах характеристики могут отличаться.

Рекомендуемый шаблон:
if (millis() - lastTime >= interval) {
lastTime = millis();
// периодическое действие
}Преимущество: основной цикл продолжает работать и может одновременно читать кнопки, Serial и датчики.
Используется, когда удобно явно задать счетчик:
for (byte i = 0; i < 100; i++) {
Serial.println(i);
}
Структура:
for (инициализация; условие; изменение)
Сначала проверяет условие:
while (condition) {
// тело
}
Если условие изначально ложно, тело не выполнится ни разу.
Сначала выполняет тело, затем проверяет условие:
do {
// тело
} while (condition);Поэтому тело выполняется минимум один раз.
Немедленно завершает цикл:
for (byte i = 0; i < 100; i++) {
if (i > 50) {
break;
}
Serial.println(i);
}Пропускает оставшуюся часть текущей итерации:
for (byte i = 0; i < 100; i++) {
if (i > 50 && i < 60) {
continue;
}
Serial.println(i);
}Библиотека содержит готовые классы и функции для работы с устройством или алгоритмом.
В исходном материале показан классический способ добавления ZIP-библиотеки. В современных версиях Arduino IDE также удобно использовать Library Manager.



В скетче:
#include <LibraryName.h>Перед использованием сторонней библиотеки желательно проверить:
- для каких плат она предназначена;
- ее версию;
- примеры;
- наличие документации;
- совместимость с используемым Arduino Core.
Arduino может обмениваться данными с ПК через USB. На компьютере это обычно отображается как виртуальный последовательный порт.
На Arduino Uno аппаратный UART микроконтроллера связан с выводами:
-
D0 / RX; -
D1 / TX.
Поэтому при активном обмене через аппаратный Serial использование этих контактов для другой периферии может создавать конфликт.
Запуск:
void setup() {
Serial.begin(9600);
}Скорость в мониторе порта должна соответствовать скорости, указанной в Serial.begin().

Типичный шаблон:
if (Serial.available() > 0) {
char c = Serial.read();
Serial.println(c);
}Входящие данные накапливаются в буфере. Размер буфера зависит от Arduino Core и конкретной платформы; число 64 байта, приведенное в исходном материале, характерно для ряда классических AVR-конфигураций, но не является универсальным правилом.

Serial.print("Value = ");
Serial.println(value);Форматирование:
Serial.println(value, DEC);
Serial.println(value, HEX);
Serial.println(value, OCT);
Serial.println(value, BIN);Для вещественного числа второй параметр может задавать количество знаков после десятичной точки:
Serial.println(3.14159, 2); // 3.14
Особенно важно правильно понимать:
Serial.flush();В современных Arduino Core эта функция ожидает завершения передачи исходящих данных. Она не предназначена для очистки входного буфера.
Если нужно удалить непрочитанные входящие данные, их можно явно прочитать:
while (Serial.available() > 0) {
Serial.read();
}
Настройка режима:
pinMode(pin, INPUT);
pinMode(pin, OUTPUT);
pinMode(pin, INPUT_PULLUP);Управление выходом:
digitalWrite(pin, HIGH);
digitalWrite(pin, LOW);Чтение входа:
int state = digitalRead(pin);INPUT_PULLUP включает внутренний подтягивающий резистор. Типовая кнопка подключается между входом и GND; тогда:
- кнопка отпущена →
HIGH; - кнопка нажата →
LOW.
Это важно учитывать в условии программы.

Для Arduino Uno:
int adc = analogRead(A0);ATmega328P имеет 10-битный АЦП, поэтому функция обычно возвращает:
0 ... 1023
Напряжение, соответствующее коду, зависит от опорного напряжения АЦП.
Линейно пересчитывает диапазон:
long percent = map(adc, 0, 1023, 0, 100);map() работает с целочисленной арифметикой.
Ограничивает значение:
value = constrain(value, 0, 255);На классических Arduino AVR эта функция обычно формирует ШИМ, а не настоящее аналоговое напряжение:
analogWrite(9, 128);128 соответствует примерно 50% заполнения PWM.
Для Uno/Nano с ATmega328P PWM традиционно доступен на выводах:
3, 5, 6, 9, 10, 11
Для других плат список выводов необходимо смотреть в документации.
Функция без аргументов и без возвращаемого результата:
void hello() {
Serial.println("HELLO!");
}Вызов:
hello();Функция с параметром:
void printValue(int value) {
Serial.println(value);
}Функция, возвращающая результат:
int mySum(int a, int b) {
return a + b;
}
Функции позволяют:
- уменьшать повторение кода;
- разделять программу на логические модули;
- упрощать тестирование и сопровождение.
random(max);
random(min, max);Верхняя граница в random(min, max) не включается.
long x = random(0, 100); // 0 ... 99Начальное состояние генератора:
randomSeed(value);В учебных проектах иногда используют шум с неподключенного аналогового входа:
randomSeed(analogRead(A0));Это дает лишь простой источник изменяющегося значения и не делает генератор криптографически безопасным.
Объявление:
int values[6];С начальным содержимым:
int pins[] = {2, 4, 8, 3, 6};Индексация начинается с нуля:
pins[0] → 2
pins[1] → 4
...
pins[4] → 6
Заполнение массива:
byte values[50];
for (byte i = 0; i < 50; i++) {
values[i] = random(100);
}Нельзя обращаться за пределы массива: это может повредить другие данные в SRAM.
Прерывание позволяет микроконтроллеру быстро реагировать на внешнее событие, не проверяя вход на каждой итерации loop().
В современном Arduino-коде удобно записывать:
attachInterrupt(
digitalPinToInterrupt(2),
onButton,
FALLING
);Для Arduino Uno/Nano на ATmega328P внешние прерывания доступны на D2 и D3.
Основные режимы:
LOW
CHANGE
RISING
FALLING

Он должен быть максимально коротким.
Плохая практика:
void onButton() {
delay(1000);
Serial.println("INTERRUPT");
}Предпочтительно установить флаг:
volatile bool buttonEvent = false;
void onButton() {
buttonEvent = true;
}А обработку выполнить в loop().
Переменные, которыми обмениваются основной код и ISR, часто объявляют как volatile. Для многобайтных данных могут дополнительно потребоваться меры атомарности.
- Arduino-программа строится вокруг
setup()иloop(). - Размеры типов данных зависят от архитектуры микроконтроллера.
-
if,switch,for,whileиdo...whileформируют основную управляющую логику. -
delay()прост, но блокирует выполнение основного алгоритма. - Для параллельной обработки нескольких задач предпочтительнее таймеры на
millis(). -
Serialиспользуется для обмена с ПК и отладки. -
digitalRead/digitalWriteработают с логическими уровнями. -
analogReadвыполняет аналого-цифровое преобразование. -
analogWriteна Arduino Uno/Nano формирует PWM, а не постоянное аналоговое напряжение. - Функции и массивы помогают строить структурированные программы.
- ISR должна выполняться быстро.
- При программировании необходимо учитывать особенности конкретной платы и Arduino Core.
- Для чего нужны
setup()иloop()? - Чем локальная переменная отличается от глобальной?
- Почему размер
intнельзя считать одинаковым на всех Arduino-платформах? - Чем
constотличается от#define? - В чем различие
=и==? - Чем
&&отличается от&? - Что вернет
Serial.read(), если пользователь отправил символ7? - Когда удобнее использовать
switch, а когдаif? - Почему длительные
delay()нежелательны? - Почему выражение
millis() - lastTime >= intervalустойчиво к переполнению? - Чем
whileотличается отdo...while? - Для чего нужны
breakиcontinue? - Что делает
Serial.available()? - Для чего применяется
Serial.flush()? - Как работает
INPUT_PULLUP? - Какой диапазон возвращает 10-битный ADC?
- Что фактически генерирует
analogWrite()на Arduino Uno? - С какого индекса начинается массив?
- Почему обработчик прерывания должен быть коротким?
- Для чего используется
volatileпри работе с ISR?
Напишите программу для Arduino Uno/Nano:
- кнопка подключена к D2 и GND в режиме
INPUT_PULLUP; - светодиод используется встроенный;
- короткое нажатие меняет режим мигания:
- режим 1 — 1000 мс;
- режим 2 — 500 мс;
- режим 3 — 100 мс;
- для мигания нельзя использовать
delay(); - текущий режим выводится в Serial Monitor;
- прерывание должно только фиксировать факт нажатия, а смена режима выполняется в
loop().
Такое задание объединяет темы:
GPIO + INPUT_PULLUP + interrupts + millis() + Serial + state machine.
Sketch — программа Arduino.
setup() — однократная инициализация.
loop() — основной повторяющийся цикл.
Variable — переменная.
const — типизированная константа.
ASCII — система кодирования символов.
if / switch — операторы ветвления.
for / while / do while — циклы.
millis() — системный счет времени в миллисекундах.
Serial — последовательный интерфейс Arduino API.
GPIO — цифровые входы/выходы.
ADC — аналого-цифровой преобразователь.
PWM — широтно-импульсная модуляция.
ISR — обработчик прерывания.
volatile — квалификатор для данных, способных изменяться вне обычного потока выполнения.
← Лекция 2. Микроконтроллеры семейства ARM Cortex-M: архитектура и периферия · Лекция 4. Подключение и управление LCD1602 и сервоприводом →