Функции

Когда скетч растёт, в loop() быстро смешиваются чтение датчиков, расчёты, индикация и команды приводам. Функции позволяют дать каждой законченной операции имя и вызывать её там, где она нужна. Код становится похож на описание поведения робота, а детали остаются внутри небольших блоков.
Функция — именованный фрагмент программы. Она может принимать входные значения через параметры, выполнять действия и возвращать результат. setup() и loop() тоже являются функциями.
Зачем это нужно
Допустим, робот должен остановиться перед препятствием. Для этого программа читает дальномер, сравнивает расстояние с порогом, управляет драйвером и включает индикатор. Если всё записать одним длинным блоком, любое изменение потребует искать нужные строки среди множества соседних действий.
После разделения верхний уровень читается проще:
void loop() {
const int distance = readDistance();
if (distance < 20) {
stopMotors();
setWarning(true);
} else {
moveForward(140);
setWarning(false);
}
}
Имена показывают намерение. Реализация чтения датчика и управления драйвером хранится в определениях функций. Такой подход снижает число повторов, помогает проверять части программы отдельно и уменьшает риск исправить одну копию кода, забыв о другой.
Главная идея
Функцию можно представить как небольшой узел обработки:
аргументы → функция → результат или действие
Например, maxSpeed(120, 180) получает два значения и возвращает большее. setWarning(true) получает состояние и изменяет физический выход, но ничего не возвращает.

На схеме пустые округлые блоки — две независимые функции. Левая ветвь управляет светодиодом через последовательный резистор, правая передаёт команду драйверу двигателя. Общий графитовый провод — общий возврат цепей, а не связь между функциями.
Из чего состоит определение
Простейшая функция без параметров и результата выглядит так:
const int statusLedPin = 9;
void turnStatusLedOn() {
digitalWrite(statusLedPin, HIGH);
}
| Часть | Пример | Значение |
|---|---|---|
| тип результата | void | функция не возвращает значение |
| имя | turnStatusLedOn | используется при вызове |
| параметры | () | входных аргументов нет |
| тело | { ... } | выполняемые команды |
Вызов содержит имя и круглые скобки:
void loop() {
turnStatusLedOn();
}
Точка с запятой завершает выражение вызова. В определении после закрывающей фигурной скобки она обычно не ставится.
Параметры: одна функция для разных команд
Параметр получает значение при каждом вызове. Вместо функций setSpeedSlow, setSpeedMedium и setSpeedFast можно создать одну:
const int motorPwmPin = 5;
void setMotorPower(int pwmValue) {
pwmValue = constrain(pwmValue, 0, 255);
analogWrite(motorPwmPin, pwmValue);
}
void setup() {
pinMode(motorPwmPin, OUTPUT);
}
void loop() {
setMotorPower(80);
delay(500);
setMotorPower(180);
delay(500);
}
Здесь pwmValue — локальная переменная-параметр. Конкретные значения 80 и 180 — аргументы вызовов. Диапазон 0…255 характерен для классического вызова analogWrite() с разрешением по умолчанию на многих платах, но доступные PWM-пины, разрешение и частота зависят от платы. Мотор должен подключаться через подходящий драйвер, а не к GPIO напрямую.
Функция может принимать несколько параметров:
void setWheelPower(int leftPwm, int rightPwm) {
analogWrite(5, constrain(leftPwm, 0, 255));
analogWrite(6, constrain(rightPwm, 0, 255));
}
Порядок аргументов должен совпадать с порядком параметров. Имена вроде leftPwm и rightPwm снижают риск перепутать стороны.
Возвращаемое значение
Если функция вычисляет результат, вместо void указывают его тип и используют return:
int readSensorAverage(int pin, int sampleCount) {
long sum = 0;
for (int i = 0; i < sampleCount; i++) {
sum += analogRead(pin);
}
return sum / sampleCount;
}
Вызов можно сохранить в переменную:
void loop() {
const int sensorValue = readSensorAverage(A0, 8);
Serial.println(sensorValue);
delay(100);
}
Функция предполагает, что sampleCount больше нуля. Надёжная версия должна проверить это условие до деления. Тип long для суммы снижает риск переполнения по сравнению с int, но достаточность типа зависит от числа измерений и диапазона АЦП конкретной платы.
returnreturn значение; немедленно завершает функцию и передаёт значение месту вызова. В функции типа void допустим return; без значения для досрочного выхода.
Локальные и глобальные переменные
Переменная, созданная внутри функции, видна только в её блоке. Это локальная переменная. Константа пина, объявленная вне функций, доступна нескольким функциям и относится к глобальной области видимости.
| Размещение | Видимость | Подходящий пример |
|---|---|---|
| внутри функции | только этот блок и вложенные блоки | временная сумма измерений |
| параметр | тело текущего вызова | требуемая скорость |
| вне функций | последующие части файла | номер физического пина |
static внутри функции | локальное имя, значение сохраняется между вызовами | время предыдущего события |
Не стоит делать каждую переменную глобальной. Чем шире область видимости, тем больше мест могут неожиданно изменить состояние.
Разделяем чтение, решение и действие
Полезная архитектура скетча разделяет три роли:
- функция чтения получает данные от оборудования;
- функция решения вычисляет нужное состояние;
- функция действия управляет выходом или драйвером.
const int lightSensorPin = A0;
const int statusLedPin = 9;
int readLight() {
return analogRead(lightSensorPin);
}
bool isDark(int sensorValue, int threshold) {
return sensorValue < threshold;
}
void setStatusLed(bool enabled) {
digitalWrite(statusLedPin, enabled ? HIGH : LOW);
}
void setup() {
pinMode(statusLedPin, OUTPUT);
}
void loop() {
const int light = readLight();
const bool dark = isDark(light, 400);
setStatusLed(dark);
delay(20);
}
Порог 400 — пример, а не универсальная граница освещённости. Значение зависит от датчика, делителя, опорного напряжения, разрешения АЦП и условий. Его следует определить по серии измерений.
Функцию isDark легко проверить без подключённого датчика: передать ей заранее выбранные значения ниже, на границе и выше порога. А функцию setStatusLed можно отдельно проверить простым вызовом из loop().
Передача по значению
В показанных примерах параметры простых типов передаются по значению: функция получает копию. Изменение копии не меняет переменную вызывающего кода.
int clampToByte(int value) {
value = constrain(value, 0, 255);
return value;
}
Для больших объектов и случаев, когда функция должна изменить исходное значение, C++ предлагает ссылки и указатели. На начальном этапе лучше сначала освоить явный результат через return: он делает поток данных заметным.
Прототипы и порядок определений
Arduino-сборка обычно автоматически создаёт прототипы пользовательских функций, но сложные типы, шаблоны и некоторые конструкции могут потребовать явного объявления. Обычный C++ требует, чтобы объявление было известно до вызова:
int choosePower(int error);
void setup() {
Serial.begin(9600);
}
void loop() {
const int power = choosePower(12);
Serial.println(power);
delay(100);
}
int choosePower(int error) {
return constrain(error * 4, -255, 255);
}
Первая строка — прототип: тип результата, имя и типы параметров без тела. Определение ниже содержит реализацию.
Хорошие границы функций
У функции должна быть одна понятная ответственность. Название updateEverything() не объясняет, что происходит. Названия readLineSensors(), calculateSteering() и applyMotorCommand() разделяют этапы.
Признаки, что блок стоит вынести:
- одинаковые строки встречаются в нескольких местах;
- операции образуют самостоятельный шаг алгоритма;
- блоку можно дать короткое точное имя;
- его результат можно проверить отдельно;
- аппаратные детали мешают читать логику поведения.
Не нужно создавать функцию ради каждой единственной строки. Разбиение должно помогать, а не заставлять постоянно перескакивать между десятками крошечных определений.
Типичные ошибки
| Ошибка | Последствие | Проверка |
|---|---|---|
тип результата не совпадает с return | ошибка компиляции или нежелательное преобразование | сопоставить объявление и каждую ветвь |
| не все ветви возвращают значение | неопределённый результат | проверить пути выполнения не-void функции |
| деление на ноль в функции среднего | неверная работа программы | валидировать число измерений |
| функция скрыто меняет много глобальных переменных | трудно найти источник состояния | передавать входы параметрами, возвращать результат |
| мотор управляется из GPIO напрямую | риск повредить выход | использовать драйвер и отдельное питание |
Что запомнить о функциях
- Определение описывает функцию, вызов запускает её.
- Параметры принимают аргументы конкретного вызова.
voidозначает отсутствие возвращаемого значения.returnвозвращает результат и завершает функцию.- Локальные переменные ограничивают влияние кода.
- Хорошее имя описывает действие или вычисляемый результат.
- Отдельные функции чтения, решения и действия упрощают отладку робота.
Практика
Задание 1
Вынеси включение и выключение светодиода в функцию setLed(bool enabled). В loop() оставьте только последовательность вызовов и ожиданий.
Задание 2
Напишите функцию, которая получает два показания датчиков линии и возвращает их разность. Подберите тип результата и проверьте случаи, когда левое значение меньше правого.
Задание 3
Создайте функцию усреднения аналогового входа. Добавьте обработку недопустимого числа измерений без готового ответа в комментарии.
Задание 4
Разделите скетч робота на чтение кнопки, выбор режима и установку индикации. Запишите, какие функции зависят от оборудования, а какие можно проверить только значениями.
Проверьте себя
- Чем определение функции отличается от вызова?
- Что означает тип
void? - Чем параметр отличается от аргумента?
- Для чего нужен
return? - Где видна локальная переменная?
- Почему номера пинов удобно хранить в глобальных константах?
- Что сообщает прототип функции компилятору?
- Зачем разделять чтение, решение и действие?
Сначала ответьте без подсказки. Ответ можно считать полным, если вы:
- формулируете основную мысль своими словами;
- называете важные условия, ограничения или меры безопасности;
- для схемы, кода или расчёта показываете ход решения и ожидаемый результат.
Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.
Словарь статьи
- Функция — именованный блок кода с определённым интерфейсом.
- Вызов — запуск функции с конкретными аргументами.
- Параметр — имя входного значения в определении функции.
- Аргумент — значение, переданное при вызове.
- Возвращаемое значение — результат, переданный оператором
return. void— тип, обозначающий отсутствие результата.- Область видимости — часть программы, где имя доступно.
- Прототип — объявление интерфейса функции без тела.
Связанные темы
- Переменные — типы и область хранения данных.
- Конструкция ветвления — разные пути возврата и выбор действий.
- Циклы — повторные вызовы функций.
- Драйвер моторов — безопасная реализация функций движения.
Источники
- Arduino Language Reference — функции, типы и структура Arduino-языка.
- Arduino CLI repository: Sketch build process — обработка скетча и автоматические прототипы.
- C++ reference: Functions — объявления, определения и вызовы функций C++.
- C++ reference: Scope — области видимости имён.
- Arduino: constrain() — ограничение значения диапазоном.