volatile
Описание
volatile
— это ключевое слово, известное как квалификатор переменной; обычно оно ставится перед типом данных переменной, чтобы изменить способ, которым компилятор и последующая программа обращаются с этой переменной.
Объявление переменной как
volatile
является директивой компилятору. Компилятор — это программа, которая транслирует ваш код на C/C++ в машинный код, то есть в реальные инструкции для чипа Atmega на плате Arduino.
В частности, эта директива указывает компилятору загружать переменную из RAM, а не из регистра — временного места в памяти, где хранятся и обрабатываются переменные программы. При определённых условиях значение переменной, хранящейся в регистрах, может оказаться неточным.
Переменную следует объявлять как
volatile
всякий раз, когда её значение может быть изменено чем-то, что находится вне контроля той секции кода, в которой она используется, например, параллельно выполняющимся потоком. На плате Arduino единственное место, где это вероятно произойдёт, — это участки кода, связанные с прерываниями, называемые обработчиком прерывания (interrupt service routine).
int или long volatiles
Если
volatile
переменная больше одного байта (например, 16-битный int или 32-битный long), то микроконтроллер не сможет прочитать её за один шаг, потому что он 8-битный. Это означает, что пока ваша основная секция кода (например, loop) читает первые 8 битов переменной, прерывание уже может изменить вторые 8 битов. Это приведёт к получению случайных значений переменной.
Решение: во время чтения переменной прерывания должны быть отключены, чтобы они не могли вмешаться в биты во время чтения. Это можно сделать несколькими способами:
Использовать макрос ATOMIC_BLOCK. Атомарные операции — это одиночные операции MCU, наименьшая возможная единица.
Пример кода
Модификатор
volatile
гарантирует, что изменения переменной
changed
немедленно видны в
loop()
. Без модификатора
volatile
переменная changed может быть загружена в регистр при входе в функцию и больше не обновлялась бы до завершения функции.
// Мигает светодиодом 1 с, если за предыдущую секунду
// вход изменился.
volatile byte changed = 0;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(2), toggle, CHANGE);
}
void loop() {
if (changed == 1) {
// toggle() была вызвана из прерываний!
// Сбросить changed в 0
changed = 0;
// Мигнуть светодиодом 200 мс
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
}
}
void toggle() {
changed = 1;
}
Чтобы получить доступ к переменной, размер которой больше 8-битной шины данных микроконтроллера, используйте макрос
ATOMIC_BLOCK
. Этот макрос гарантирует, что переменная читается в атомарной операции, то есть её содержимое не может быть изменено во время чтения.
#include <util/atomic.h> // эта библиотека содержит макрос ATOMIC_BLOCK.
volatile int input_from_interrupt;
// Где-то в коде, например внутри loop()
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// код с заблокированными прерываниями (последовательные атомарные операции не будут прерваны)
int result = input_from_interrupt;
}