Перейти к основному содержимому

Функции

Отдельные функции для датчика, светодиода и двигателя

Когда скетч растёт, в 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, но достаточность типа зависит от числа измерений и диапазона АЦП конкретной платы.

return

return значение; немедленно завершает функцию и передаёт значение месту вызова. В функции типа void допустим return; без значения для досрочного выхода.

Локальные и глобальные переменные

Переменная, созданная внутри функции, видна только в её блоке. Это локальная переменная. Константа пина, объявленная вне функций, доступна нескольким функциям и относится к глобальной области видимости.

РазмещениеВидимостьПодходящий пример
внутри функциитолько этот блок и вложенные блокивременная сумма измерений
параметртело текущего вызоватребуемая скорость
вне функцийпоследующие части файланомер физического пина
static внутри функциилокальное имя, значение сохраняется между вызовамивремя предыдущего события

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

Разделяем чтение, решение и действие

Полезная архитектура скетча разделяет три роли:

  1. функция чтения получает данные от оборудования;
  2. функция решения вычисляет нужное состояние;
  3. функция действия управляет выходом или драйвером.
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

Разделите скетч робота на чтение кнопки, выбор режима и установку индикации. Запишите, какие функции зависят от оборудования, а какие можно проверить только значениями.

Проверьте себя

  1. Чем определение функции отличается от вызова?
  2. Что означает тип void?
  3. Чем параметр отличается от аргумента?
  4. Для чего нужен return?
  5. Где видна локальная переменная?
  6. Почему номера пинов удобно хранить в глобальных константах?
  7. Что сообщает прототип функции компилятору?
  8. Зачем разделять чтение, решение и действие?
Ориентиры для самопроверки

Сначала ответьте без подсказки. Ответ можно считать полным, если вы:

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

Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.

Словарь статьи

  • Функция — именованный блок кода с определённым интерфейсом.
  • Вызов — запуск функции с конкретными аргументами.
  • Параметр — имя входного значения в определении функции.
  • Аргумент — значение, переданное при вызове.
  • Возвращаемое значение — результат, переданный оператором return.
  • void — тип, обозначающий отсутствие результата.
  • Область видимости — часть программы, где имя доступно.
  • Прототип — объявление интерфейса функции без тела.

Связанные темы

Источники