Перейти к содержанию

Ключевое слово volatile

volatile — квалификатор типа в языке C, который сообщает компилятору, что значение переменной может измениться без его ведома. В программах для микроконтроллеров без него не работают регистры периферии и флаги, которые выставляют прерывания.

Обновлено Редакция GAW

volatile — квалификатор типа в языке C. Он говорит компилятору, что объект может изменяться способами, которые компилятору неизвестны, поэтому каждое обращение к нему должно выполняться так, как написано в программе (C11, 6.7.3p7). Компилятор не имеет права выбросить «лишнее» чтение, объединить две записи в одну или вынести чтение из цикла.

Где он нужен

  • Регистры периферии. Значение регистра меняет аппаратура, а запись в регистр — это действие, а не просто сохранение значения. Поэтому заголовки avr-libc обращаются к регистрам через указатель на volatile (макросы _SFR_IO8, _MMIO_BYTE), а в CMSIS поля структур регистров объявлены с квалификаторами __I, __O и __IO — это тот же volatile.
  • Переменные, общие с прерываниями. Флаг, который выставляет обработчик прерывания, основная программа должна каждый раз читать из памяти.
  • Память, которую меняет DMA или другое процессорное ядро.

Чего volatile не делает

volatile не делает обращение неделимым. 8-битный AVR читает uint16_t или uint32_t за несколько команд, и если между ними сработает прерывание и изменит переменную, программа получит половину старого и половину нового значения. На 32-битном Cortex-M чтение выровненного uint32_t — одна команда, но counter++ и там состоит из чтения, сложения и записи. Такие участки выполняют при запрещённых прерываниях — в avr-libc для этого есть макрос ATOMIC_BLOCK. Подробно о volatile, атомарности, размерах типов и доступе к регистрам — в статье «Язык C для микроконтроллеров», об атомарной установке битов — в статье о битовых операциях.

Частые вопросы

Когда нужен volatile?

В трёх случаях: для регистров периферии, для переменных, которые меняет обработчик прерывания и читает основная программа (или наоборот), и для переменных, которые меняет другое ядро или DMA. Ставить volatile везде «на всякий случай» не стоит: он запрещает оптимизации и раздувает код.

Делает ли volatile доступ атомарным?

Нет. volatile только запрещает компилятору выкидывать, объединять и переносить обращения. 16-битная переменная на 8-битном AVR читается двумя командами, и прерывание между ними может изменить её наполовину. Такие обращения выполняют при запрещённых прерываниях — в avr-libc для этого есть ATOMIC_BLOCK(ATOMIC_RESTORESTATE).

Почему цикл ожидания флага зависает без volatile?

Компилятор видит, что внутри цикла переменная не меняется, и вправе прочитать её один раз — до цикла. Если флаг выставляет обработчик прерывания, основная программа об этом не узнает и будет крутиться бесконечно. С volatile каждое обращение к флагу становится настоящим чтением памяти.

Источники

  1. ISO/IEC 9899:201x (C11), Committee Draft N1570, April 2011 — 6.7.3p7 — объект volatile может изменяться способами, неизвестными реализации; обращения выполняются по правилам абстрактной машины
  2. AVR-LibC: <avr/sfr_defs.h>, <util/atomic.h> (github.com/avrdudes/avr-libc) — _SFR_IO8 и _MMIO_BYTE — доступ к регистрам через указатель на volatile; ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
  3. Arm. CMSIS 5: core_cm3.h и cmsis_gcc.h (github.com/ARM-software/CMSIS_5) — квалификаторы __I, __O, __IO — это volatile для полей структур регистров

Нашли ошибку?