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 битов. Это приведёт к получению случайных значений переменной.

Решение: во время чтения переменной прерывания должны быть отключены, чтобы они не могли вмешаться в биты во время чтения. Это можно сделать несколькими способами:

  1. noInterrupts

  2. Использовать макрос 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;
  }

Смотрите также