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