Тактовая частота, фьюзы и F_CPU
Микроконтроллер не знает, на какой частоте работает, а компилятор не знает этого тем более. Частоту задают фьюзы, делитель CLKPR и кварц на плате, а программе её сообщают макросом F_CPU — и если они расходятся, все задержки и скорости UART становятся неправильными.
Обновлено Редакция GAW
AVR с нуляурок 4 из 830 мин
Цель урока: Расшифровать фьюзы ATmega328P, определить по ним тактовую частоту, правильно задать F_CPU и знать, какие фьюзы могут заблокировать кристалл.
До сих пор мы просто писали F_CPU=16000000UL и верили, что плата работает на 16 МГц. На Arduino Uno это так, но только потому, что в кристалл уже записаны нужные фьюзы. Чистый ATmega328P из магазина работает на другой частоте.
Откуда берётся тактовая частота
Источник тактового сигнала выбирают четыре бита фьюзов CKSEL3…0 (табл. 9-1 даташита):
| CKSEL3…0 | Источник |
|---|---|
| 1111–1000 | кварцевый генератор с малым потреблением (Low Power Crystal) |
| 0111–0110 | кварцевый генератор с полным размахом (Full Swing Crystal) |
| 0101–0100 | низкочастотный кварц |
| 0011 | внутренний RC-генератор 128 кГц |
| 0010 | калиброванный внутренний RC-генератор 8 МГц |
| 0000 | внешний тактовый сигнал на XTAL1 |
| 0001 | зарезервировано |
После источника стоит делитель системной частоты — регистр CLKPR (раздел 9.11). Фьюз CKDIV8 задаёт его начальное значение: если фьюз запрограммирован, после сброса частота делится на 8 (раздел 9.12.2).
С завода ATmega328P поставляется с внутренним RC-генератором 8 МГц и запрограммированным CKDIV8, то есть работает на 1 МГц (раздел 9.2.1). Точность заводской калибровки RC-генератора — ±10 % при 3 В и 25 °C (табл. 29-9): для мигания хватит, а для UART уже на грани. Поэтому на Arduino Uno стоит внешний источник 16 МГц, а кристалл настроен фьюзами на него. Наибольшая частота при питании 4,5–5,5 В — 20 МГц.
Фьюзы: три байта
Фьюзы — это биты энергонезависимой конфигурации, которые программа сама изменить не может. У ATmega328P три байта: младший, старший и расширенный (раздел 28.2). Главное правило: 0 — фьюз запрограммирован, 1 — нет. Стирание кристалла (Chip Erase) фьюзы не меняет.
| Байт | Биты 7 → 0 | С завода | На Arduino Uno |
|---|---|---|---|
| Младший (low) | CKDIV8, CKOUT, SUT1, SUT0, CKSEL3, CKSEL2, CKSEL1, CKSEL0 | 0x62 | 0xFF |
| Старший (high) | RSTDISBL, DWEN, SPIEN, WDTON, EESAVE, BOOTSZ1, BOOTSZ0, BOOTRST | 0xD9 | 0xDE |
| Расширенный (extended) | —, —, —, —, —, BODLEVEL2, BODLEVEL1, BODLEVEL0 | 0xFF | 0xFD |
Заводские значения собраны нами из столбцов «Default Value» табл. 28-6, 28-8 и 28-9, значения Uno — из boards.txt ядра Arduino. Расшифруем фьюзы Uno:
- Младший 0xFF = 1111 1111: CKDIV8 не запрограммирован — делителя нет; CKSEL3…1 = 111 — кварцевый генератор на 8–16 МГц (табл. 9-3).
- Старший 0xDE = 1101 1110: SPIEN = 0 — программирование по SPI разрешено; BOOTSZ = 11 — загрузчик 256 слов с адреса 0x3F00 (табл. 27-13); BOOTRST = 0 — после сброса управление получает загрузчик.
- Расширенный 0xFD: BODLEVEL = 101 — детектор пониженного питания сбрасывает кристалл при 2,5–2,9 В, типично 2,7 В (табл. 29-12).
Прочитать фьюзы можно программатором: avrdude -c usbasp -p m328p -U lfuse:r:-:h -U hfuse:r:-:h -U efuse:r:-:h. Загрузчик Optiboot фьюзы не читает и не пишет — для этого нужен ISP-программатор.
Опасные фьюзы
Неверно записанный фьюз может сделать кристалл недоступным для ISP-программатора. Перед записью всегда проверяйте значения в калькуляторе фьюзов AVR.
| Фьюз | Что случится | Почему |
|---|---|---|
| RSTDISBL = 0 | вывод PC6 становится обычным выводом порта, сброс внешним сигналом пропадает | ISP-программирование начинается с удержания RESET на земле (разделы 14.3.2 и 28.8) |
| DWEN = 0 | вывод RESET занят отладчиком debugWIRE | внешний сброс при debugWIRE не работает (раздел 25.5); выключить его может только отладчик |
| SPIEN = 1 | программирование по SPI запрещено | в последовательном режиме бит SPIEN недоступен (примечание к табл. 28-8): ISP-программатором его не выключить, так что эта ловушка грозит только при работе с параллельным программатором — и вернуть бит может только он |
| CKSEL под кварц или внешний генератор, которого нет | кристалл не тактируется | для последовательного программирования нужен рабочий тактовый сигнал (раздел 28.8) |
| CKSEL = 0011 (RC 128 кГц) или CKDIV8 при медленном источнике | кристалл работает, но программатор не связывается | низкий и высокий уровни SCK должны длиться больше 2 тактов кристалла при частоте ниже 12 МГц и больше 3 тактов при 12 МГц и выше, то есть SCK ниже f/4 и f/6 соответственно (раздел 28.8) |
Последний случай лечится просто — снижением скорости программатора ключом -B avrdude. Кристалл без тактов оживляют, подав на XTAL1 сигнал от другого микроконтроллера или генератора. После DWEN выключить debugWIRE может отладчик с его поддержкой, а после RSTDISBL и SPIEN поможет только высоковольтный параллельный программатор (раздел 28.7). Подробности — в разделе о фьюзах на странице ATmega328P.
F_CPU и задержки
Макрос F_CPU ничего не настраивает в кристалле — он только сообщает avr-libc, сколько тактов в секунде. Функция _delay_ms(t) превращается в цикл на F_CPU · t / 1000 тактов. Если F_CPU больше реальной частоты, задержки растягиваются, если меньше — сокращаются. Если F_CPU не задан вовсе, util/delay.h выдаёт предупреждение и считает частоту равной 1 МГц.
Ещё одно свойство задержек из документации avr-libc: на время _delay_ms прерывания не запрещаются, поэтому каждое сработавшее прерывание удлиняет задержку на время своего обработчика.
Делитель частоты можно поменять и из программы, не трогая фьюзы. Запись в CLKPR защищена: сначала пишется байт с единицей только в CLKPCE, а новое значение CLKPS3…0 — не позже чем через четыре такта (раздел 9.12.2):
#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
/* Remove the factory divide-by-8: 8 MHz RC -> 8 MHz system clock. */
static void clock_div1(void)
{
uint8_t sreg = SREG; /* remember the interrupt flag */
cli(); /* no interrupt between the two writes */
CLKPR = _BV(CLKPCE); /* enable the change for 4 cycles */
CLKPR = 0; /* CLKPS3..0 = 0000: divide by 1 */
SREG = sreg; /* restore the interrupt flag */
}
int main(void)
{
clock_div1();
for (;;) {
}
}
С оптимизацией -Os обе записи компилируются в две соседние команды и укладываются в четыре такта; без оптимизации такой код может не сработать. После вызова такой функции на чистом кристалле F_CPU надо задавать равной 8 МГц.
Задание
На плату без кварца поставили чистый ATmega328P, и программатором записали младший байт фьюзов 0xE2.
- На какой частоте теперь работает кристалл?
- Программа собрана с
-DF_CPU=16000000UL. Сколько на самом деле длится_delay_ms(1000)?
Ответ и решение
-
0xE2 = 1110 0010: CKDIV8 = 1 (не запрограммирован, делителя нет), CKOUT = 1, SUT = 10, CKSEL = 0010 — калиброванный RC-генератор 8 МГц. Кристалл работает на 8 МГц с точностью заводской калибровки ±10 %.
-
Задержка отсчитывает 16 000 000 тактов, а в секунду их проходит 8 000 000: 16 000 000 / 8 000 000 = 2 с (плюс-минус 10 % из-за RC-генератора). Нужно собирать программу с
-DF_CPU=8000000UL.
Что дальше
В уроке 5 частота станет основой точного времени: таймер с предделителем и прерыванием отсчитает миллисекунды без пустых циклов. Все биты фьюзов, заводские значения и готовую команду avrdude показывает калькулятор фьюзов AVR, а фактическую длительность _delay_ms при разных частотах — калькулятор задержек AVR. Коротко о фьюзах и битах защиты — термины «Фьюзы AVR» и «Биты защиты»; о кварцевом резонаторе — термин «Кварцевый резонатор»; о сбросе по питанию — «Детектор пониженного питания». Регистры CLKPR и OSCCAL — на странице регистров CPU ATmega328P.
Частые вопросы
Почему _delay_ms(1000) длится 16 секунд?
Потому что F_CPU в программе равна 16 МГц, а кристалл работает на 1 МГц: так он поставляется с завода — внутренний RC-генератор 8 МГц и запрограммированный фьюз CKDIV8, делящий частоту на 8. Задержка считается в тактах по F_CPU, поэтому на в 16 раз меньшей частоте длится в 16 раз дольше.
Можно ли заблокировать ATmega328P фьюзами?
Да. Если запрограммировать RSTDISBL или DWEN, вывод RESET перестаёт работать как сброс, и программатор ISP уже не может войти в режим программирования. Если выбрать в CKSEL внешний генератор или кварц, которого на плате нет, кристалл останется без тактов. После RSTDISBL кристалл возвращают высоковольтным параллельным программатором, после DWEN — отладчиком debugWIRE, а без тактов — подав внешний тактовый сигнал на XTAL1.
Что значит «запрограммированный фьюз»?
У AVR запрограммированный бит фьюза читается как 0, а незапрограммированный — как 1. Поэтому фьюз CKDIV8 включён, когда бит 7 младшего байта равен нулю. Из-за этой инверсии фьюзы удобнее проверять в калькуляторе, а не в уме.
Источники
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — табл. 9-1: источники тактирования по CKSEL3…0; разд. 9.2.1: с завода RC 8 МГц, CKDIV8 запрограммирован — 1 МГц, CKSEL = 0010, SUT = 10; табл. 9-3: кварц 8–16 МГц — CKSEL3…1 = 111; разд. 9.11 и 9.12.2: делитель CLKPR, бит CLKPCE сбрасывается через 4 такта; табл. 28-6, 28-8, 28-9: биты фьюзов, 1 — не запрограммирован; табл. 27-13: размер загрузчика по BOOTSZ; табл. 29-9: точность RC ±10 % с завода; табл. 29-12: BODLEVEL; разд. 28.2: Chip Erase не меняет фьюзы; разд. 28.8: нужен рабочий тактовый сигнал, низкий и высокий уровни SCK — больше 2 тактов кристалла при fck < 12 МГц и больше 3 тактов при fck ≥ 12 МГц; примечание к табл. 28-8: SPIEN недоступен в последовательном режиме; разд. 25.5 и 14.3.2: debugWIRE и RSTDISBL занимают вывод RESET
- AVR-LibC 2.3.2: util/delay.h (github.com/avrdudes/avr-libc) — F_CPU в герцах до подключения заголовка, иначе предупреждение и 1 МГц по умолчанию; задержка — константа, оптимизация включена; прерывания не запрещаются, и задержка удлиняется
- ArduinoCore-avr, boards.txt (github.com/arduino/ArduinoCore-avr) — Uno: low_fuses 0xFF, high_fuses 0xDE, extended_fuses 0xFD, f_cpu 16000000L
Нашли ошибку?
Смотрите также
- Фьюзы AVRБиты фьюзов, заводские значения и avrdude
- Фьюзы AVRБиты конфигурации AVR: тактирование, загрузчик, BOD
- Задержки AVR: _delay_msТакты и фактическое время _delay_ms, _delay_us, циклов; пределы и округление
- ATmega328PРаспиновка, фьюзы, Optiboot, периферия, питание, схема и код на C
- Как проверить, на какой частоте работает ATmega328P?Фьюз CKOUT выводит такт на PB0 (D8), низкий байт 0xFF → 0xBF