Переменные

Роботу мало получить сигнал — значение нужно сохранить, сравнить и передать следующей части программы. Переменная даёт данным имя и место хранения. Благодаря ей код говорит не «возьмите число из неизвестного места», а «проверьте расстояние», «увеличь счётчик» или «запомни, разрешено ли движение».
Переменная в C++ имеет тип, имя и текущее значение. Тип определяет допустимые значения и операции, имя объясняет назначение, а присваивание обновляет состояние. Для неизменяемых настроек используют const, а для значений времени на Arduino часто выбирают unsigned long.
Зачем это нужно
В одном проходе loop() контроллер может прочитать левый и правый датчики, вычислить ошибку положения, выбрать скорость двух моторов и запомнить время предыдущего действия. Без переменных эти данные невозможно удобно связать.
На hero два абстрактных датчика передают значения в программу, четыре цветных круга обозначают переменные состояния, а две стрелки — рассчитанные команды для левого и правого приводов. Это функциональная схема потока данных, не монтаж: точные типы и значения существуют в коде, а драйвер и силовое питание моторов намеренно не показаны.
Хорошие переменные помогают:
- отличать измерение от порога;
- видеть единицу прямо в имени;
- хранить состояние между проходами
loop(); - изменять настройку в одном месте;
- печатать внутренние данные в Serial;
- находить несоответствие типов до испытания устройства.
Главная идея
Объявление сообщает компилятору: «создайте объект этого типа с таким именем». Инициализация сразу задаёт начальное значение:
int distanceCm = 42;
bool obstacleDetected = false;
Позже присваивание меняет уже существующий объект:
distanceCm = 18;
obstacleDetected = true;
Левая часть = — куда записать, правая — какое значение вычислить. Оператор = не означает математическое равенство. Для сравнения на равенство применяется ==.
| Часть | Пример | Что сообщает |
|---|---|---|
| тип | int | какие значения и операции ожидаются |
| имя | distanceCm | какую величину хранит объект |
| инициализатор | 42 | начальное значение |
| присваивание | distanceCm = 18; | новое значение существующей переменной |
Основные типы для первого скетча
C++ предоставляет целые, вещественные, логические и символьные типы. Их точный диапазон зависит от типа и платформы. На классической Arduino Uno тип int обычно 16-битный, а на многих современных Arduino-платформах — 32-битный. Если границы критичны для протокола или расчёта, не переносите предположение между платами: сверяйте документацию платформы и используйте целочисленные типы фиксированной ширины из <stdint.h>.
| Тип | Пример | Диапазон на Arduino UNO | Подходящее применение | Ограничение |
|---|---|---|---|---|
bool | bool enabled = true; | false или true | состояние «да/нет» | не хранит несколько режимов |
byte | byte pwm = 120; | от 0 до 255 | байт данных, короткое неотрицательное значение | не хранит отрицательные числа |
char | char command = 's'; | от -128 до 127 | один символ команды | не равен строке "s" |
int | int distanceCm = 25; | от -32768 до 32767 | небольшое целое со знаком | на UNO уже, чем на многих новых платах |
unsigned int | unsigned int samples = 0; | от 0 до 65535 | неотрицательный счётчик в подходящем диапазоне | переполнение циклическое |
long | long encoderPosition = -1200; | от -2147483648 до 2147483647 | более широкий счётчик со знаком | занимает больше памяти, чем int |
unsigned long | unsigned long nowMs = millis(); | от 0 до 4294967295 | время millis() и интервалы | переполняется, но разность можно обработать корректно |
float | float voltage = 4.82f; | примерно от -3.4E38 до 3.4E38 | приближённая вещественная величина | около 6-7 значащих цифр |
double | double k = 1.25; | как float на Arduino UNO | совместимость с кодом, где ожидают double | на UNO не даёт большей точности |
Не выбирайте float только потому, что величина физическая. Расстояние в миллиметрах, напряжение в милливольтах и коэффициент в тысячных нередко удобно хранить целыми числами: единица явно задана, а поведение вычислений легче проверить.
bool motorEnabled говорит читателю, что ожидаются два состояния. unsigned long lastUpdateMs показывает неотрицательное значение времени и единицу. Компилятор проверяет часть договора, а осмысленное имя — человек.
Имена и единицы измерения
Имена x, a1 и value допустимы синтаксически, но плохо объясняют устройство. Сравните:
int value = 20;
int stopDistanceCm = 20;
Вторая строка сообщает назначение и единицу. Это предотвращает смешивание сантиметров с миллиметрами и сырого значения АЦП с рассчитанным напряжением.
Полезные формы:
leftSensorRaw— сырое значение левого датчика;batteryVoltageMv— напряжение в милливольтах;lastCommandMs— время команды поmillis();motorsEnabled— логическое состояние;targetSpeedPercent— заданная относительная скорость.
Имена C++ чувствительны к регистру: distanceCm и distanceCM — разные идентификаторы. Не используйте зарезервированные слова языка и имена, конфликтующие с библиотеками.
const: настройка, которую не меняют
Если значение после инициализации должно оставаться прежним, выразите это в коде:
const int sensorPin = A0;
const int stopDistanceCm = 20;
const unsigned long reportIntervalMs = 500;
Попытка позже присвоить новое значение такому объекту приведёт к ошибке компиляции. Это полезнее комментария «не изменять»: ограничение проверяется инструментом.
const не означает, что величина физически неизменна. Порог — настройка программы, а показание датчика меняется:
const int sensorPin = A0;
int sensorRaw = 0;
void loop() {
sensorRaw = analogRead(sensorPin);
}
| Данные | Меняются во время работы? | Подходящая форма |
|---|---|---|
| номер контакта | обычно нет | const int sensorPin |
| порог реакции | нет, если скетч его не перенастраивает | const int threshold |
| текущее измерение | да | обычная переменная |
| счётчик событий | да | обычная переменная подходящего диапазона |
| факт разрешения движения | да | bool |
Область видимости и время жизни
Переменная, объявленная внутри функции или блока, доступна в ограниченной области:
void loop() {
const int sensorRaw = analogRead(A0);
if (sensorRaw > 700) {
const bool alarm = true;
Serial.println(alarm);
}
}
sensorRaw доступна до конца loop(), а alarm — только внутри блока if. Это снижает риск случайно использовать временное значение в другой части программы.
Глобальную переменную объявляют вне функций. Она существует всё время работы скетча и доступна последующим функциям файла:
unsigned long eventCount = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
eventCount = eventCount + 1;
Serial.println(eventCount);
delay(1000);
}
Глобальное состояние полезно, когда значение должно сохраниться между вызовами loop(), но избыток глобальных переменных усложняет понимание: любая функция может изменить их. Сначала выбирайте минимальную область видимости, достаточную задаче.
Счётчик и обновление значения

На иллюстрации импульсы энкодера поступают в контроллер, круги условно показывают накопление состояния, а цветные полосы — абстрактные записи вывода. Это не буквальная раскладка памяти и не точная временная диаграмма.
Увеличить счётчик можно несколькими эквивалентными для обычной переменной способами:
pulseCount = pulseCount + 1;
pulseCount += 1;
++pulseCount;
В простом отдельном операторе последние две формы удобны. В составных выражениях различие между префиксным и постфиксным инкрементом влияет на получаемое значение, поэтому начинающему лучше не соединять изменение счётчика с другими вычислениями.
Для сигнала энкодера в реальном проекте могут понадобиться прерывания, фильтрация и защита совместно используемых данных. Здесь счётчик обновляется в обычном коде, чтобы сосредоточиться на переменной.
unsigned long pulseCount = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
const bool simulatedPulse = true;
if (simulatedPulse) {
++pulseCount;
}
Serial.println(pulseCount);
delay(500);
}
Переполнение и время
У целого типа конечный диапазон. Беззнаковое значение после максимума переходит к нулю по модульной арифметике. Для времени millis() это ожидаемое поведение, поэтому интервалы проверяют вычитанием:
unsigned long previousMs = 0;
const unsigned long intervalMs = 1000;
void loop() {
const unsigned long nowMs = millis();
if (nowMs - previousMs >= intervalMs) {
previousMs = nowMs;
Serial.println("tick");
}
}
Сравнение nowMs >= previousMs + intervalMs хуже переносит переход счётчика через максимум. Разность беззнаковых значений сохраняет смысл прошедшего интервала, если интервал выбран в корректной половине диапазона и код вызывается достаточно регулярно.
Для знаковых целых переполнение в C++ не следует использовать как таймерный механизм. Выбирайте тип по диапазону, а не по привычке.
Пример: состояние робота по двум датчикам
const int leftSensorPin = A0;
const int rightSensorPin = A1;
const int darkThreshold = 600;
int leftSensorRaw = 0;
int rightSensorRaw = 0;
bool lineDetected = false;
void setup() {
Serial.begin(9600);
}
void loop() {
leftSensorRaw = analogRead(leftSensorPin);
rightSensorRaw = analogRead(rightSensorPin);
lineDetected =
leftSensorRaw > darkThreshold || rightSensorRaw > darkThreshold;
Serial.print("line_detected=");
Serial.println(lineDetected);
}
Названия разделяют контакты, сырые показания, настройку порога и итоговое логическое состояние. Следующий урок добавит ветви, которые выберут действие по lineDetected.
Что запомнить о переменных
- Переменная объединяет тип, имя и изменяемое значение.
- Инициализация задаёт начальное состояние; присваивание обновляет его.
- Тип выбирают по смыслу, диапазону и платформе.
- Единица в имени делает физические значения проверяемыми.
constзащищает настройку от случайного присваивания.- Область видимости стоит делать как можно уже.
- Время
millis()хранят вunsigned longи сравнивают разностью.
Практика
- Объявите переменные для сырого показания датчика, расстояния в миллиметрах, состояния препятствия и времени последнего отчёта. Обоснуйте тип каждого объекта.
- Перепишите набор безымянных чисел как именованные
const-настройки с единицами. Попробуйте изменить одну константу присваиванием и изучите сообщение компилятора. - Создайте счётчик проходов
loop()и печатайте его с ограниченной частотой. Наблюдайте рост и оцените, когда выбранный тип исчерпает диапазон. - Составьте функциональную модель двух датчиков и двух команд моторам. Запишите переменные входов, состояния и выходов, не подключая моторы напрямую к контроллеру.
Проверьте себя
- Чем объявление отличается от присваивания?
- Почему
=и==нельзя менять местами? - Когда подходит тип
bool? - Почему диапазон
intнельзя считать одинаковым на всех Arduino-платформах? - Зачем добавлять единицу измерения в имя?
- Что запрещает
const? - Чем локальная переменная отличается от глобальной?
- Почему интервал
millis()проверяют выражениемnow - previous?
Сначала ответьте без подсказки. Ответ можно считать полным, если вы:
- формулируете основную мысль своими словами;
- называете важные условия, ограничения или меры безопасности;
- для схемы, кода или расчёта показываете ход решения и ожидаемый результат.
Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.
Словарь статьи
| Термин | Значение |
|---|---|
| переменная | именованный объект, хранящий значение определённого типа |
| тип | набор допустимых значений и операций над ними |
| объявление | введение имени и типа объекта |
| инициализация | задание начального значения при создании объекта |
| присваивание | запись нового значения в существующий изменяемый объект |
| идентификатор | имя переменной, функции или другого элемента программы |
const | ограничение, запрещающее менять объект после инициализации |
| область видимости | часть программы, где имя доступно |
| переполнение | выход вычисления за диапазон представимого типа |
| счётчик | переменная, накапливающая число событий или шагов |
Связанные темы
- Первая программа — место объявлений в скетче.
- Монитор порта Serial — наблюдение значений.
- Конструкция ветвления — выбор действия по состоянию.
- Циклы — повторное обновление переменных.
- Функции — параметры, результаты и локальные объекты.
- analogRead: обработка датчика — получение сырых измерений.