analogWrite изнутри: таймеры и частота ШИМ
analogWrite не настраивает таймер — это уже сделала функция init() до setup(). Сама analogWrite только подключает вывод к таймеру и пишет число в регистр сравнения. Разберём, что задано в init(), и перенастроим Timer1 на 31 и 20 кГц.
Обновлено Редакция GAW
От Arduino к регистрамурок 3 из 835 мин
Цель урока: Знать, какой таймер и в каком режиме стоит за каждым ШИМ-выводом Uno, рассчитывать частоту и скважность analogWrite и менять частоту ШИМ регистрами, не ломая millis().
analogWrite(9, 128) — это не «аналоговое» напряжение, а ШИМ: вывод переключается между 0 и 5 В с коэффициентом заполнения около 50 %. Как устроены режимы Fast PWM и Phase Correct, разобрано в уроке о ШИМ трека «AVR с нуля». Здесь — что делает ядро ArduinoCore-avr 1.8.8: функции init() в wiring.c и analogWrite() в wiring_analog.c.
Таймеры настраивает init()
До setup() функция main() ядра вызывает init() (строки 241–324 wiring.c), и та запускает все три таймера:
| Таймер | Что делает init() | Режим по даташиту | Делитель |
|---|---|---|---|
| Timer0 | sbi(TCCR0A, WGM01); sbi(TCCR0A, WGM00); sbi(TCCR0B, CS01); sbi(TCCR0B, CS00); sbi(TIMSK0, TOIE0); | Fast PWM, TOP = 0xFF (WGM02:0 = 3) | 64 (табл. 15-9) |
| Timer1 | TCCR1B = 0; sbi(TCCR1B, CS11); sbi(TCCR1B, CS10); sbi(TCCR1A, WGM10); | Phase Correct 8 бит, TOP = 0x00FF (режим 1, табл. 16-4) | 64 (табл. 16-5) |
| Timer2 | sbi(TCCR2B, CS22); sbi(TCCR2A, WGM20); | Phase Correct, TOP = 0xFF | 64 (табл. 18-9) |
У Timer0 есть вторая работа: прерывание по переполнению отсчитывает millis() — это тема урока 5. Комментарий в wiring.c объясняет выбор: Phase Correct на Timer0 переполнялся бы вдвое реже и millis() вёл бы себя иначе, чем на старом ATmega8.
Что делает analogWrite
// ArduinoCore-avr 1.8.8, cores/arduino/wiring_analog.c, lines 96-285 (abridged)
void analogWrite(uint8_t pin, int val)
{
pinMode(pin, OUTPUT);
if (val == 0) {
digitalWrite(pin, LOW);
} else if (val == 255) {
digitalWrite(pin, HIGH);
} else {
switch (digitalPinToTimer(pin)) {
case TIMER0A:
sbi(TCCR0A, COM0A1); // connect OC0A to the pin
OCR0A = val; // duty
break;
case TIMER1A:
sbi(TCCR1A, COM1A1);
OCR1A = val;
break;
/* ... TIMER0B, TIMER1B, TIMER2A, TIMER2B ... */
case NOT_ON_TIMER:
default:
if (val < 128) digitalWrite(pin, LOW);
else digitalWrite(pin, HIGH);
}
}
}
Функция делает три вещи: переводит вывод в выход (выход сравнения появится на выводе только при DDR = 1, раздел 15.6), ставит бит COMnx1 — неинвертирующий ШИМ, — и пишет значение в регистр сравнения OCRnx. На выводе без таймера ШИМа нет: analogWrite(7, 100) даст постоянный низкий уровень, analogWrite(7, 200) — высокий.
Карта выводов и частоты
| Вывод Uno | Порт | Выход таймера | Таймер и режим | Частота при 16 МГц |
|---|---|---|---|---|
| 3 | PD3 | OC2B | Timer2, Phase Correct | 490,2 Гц |
| 5 | PD5 | OC0B | Timer0, Fast PWM | 976,6 Гц |
| 6 | PD6 | OC0A | Timer0, Fast PWM | 976,6 Гц |
| 9 | PB1 | OC1A | Timer1, Phase Correct 8 бит | 490,2 Гц |
| 10 | PB2 | OC1B | Timer1, Phase Correct 8 бит | 490,2 Гц |
| 11 | PB3 | OC2A | Timer2, Phase Correct | 490,2 Гц |
Частоты — по формулам даташита. Fast PWM (раздел 15.7.3):
Phase Correct (раздел 15.7.4; для Timer1 — 16.9.4 с TOP = 255):
Соответствие выводов и выходов таймеров — из разделов 14.3.1 и 14.3.3 даташита, номера выводов — из pins_arduino.h. Ту же таблицу для любой платы показывают калькулятор ШИМ Arduino и карта выводов.
Скважность на разных таймерах
Одно и то же число в analogWrite даёт чуть разную скважность. В Fast PWM выход ставится в единицу в начале периода и сбрасывается при совпадении, а при OCR0x = 0 остаётся импульс длиной в один такт таймера (раздел 15.7.3). Значит, высокий уровень длится OCR0x + 1 тактов из 256. В Phase Correct выход меняется при совпадении на подъёме и на спуске, и доля высокого уровня — OCRnx / 255 (оба вывода — расчёт наш по описанию режимов):
| analogWrite | Выводы 5, 6 (Fast PWM) | Выводы 3, 9, 10, 11 (Phase Correct) |
|---|---|---|
| 1 | 2 / 256 = 0,78 % | 1 / 255 = 0,39 % |
| 128 | 129 / 256 = 50,4 % | 128 / 255 = 50,2 % |
| 254 | 255 / 256 = 99,6 % | 254 / 255 = 99,6 % |
Отсюда и особый случай для нуля: в Fast PWM OCR0A = 0 не выключил бы светодиод полностью — остался бы импульс в 1/256 периода. В Phase Correct OCRnx = 0 и так даёт постоянный ноль (разделы 15.7.4 и 16.9.4), но ядро не различает таймеры и для 0 и 255 на любом выводе вызывает digitalWrite, которая заодно выключает ШИМ (её turnOffPWM мы видели в уроке 1).
Кто ещё занимает таймеры
- Timer0 —
millis(),micros()иdelay(). Его делитель и режим лучше не менять. - Timer2 — функция
tone(): вTone.cppдля ATmega328P выбранUSE_TIMER2, иtone()переводит Timer2 в режим CTC (строки 183–186). Пока звучитtone(), ШИМ на выводах 3 и 11 работать не будет. И послеnoTone()он сам не вернётся:disableTimer(2)(строки 426–457) восстанавливает Phase Correct и делитель 64, но пишетTCCR2A = (1 << WGM20)— биты COM сброшены, — и обнуляет OCR2A. ШИМ на выводах 3 и 11 появится только после следующегоanalogWrite. - Timer1 — свободен от ядра, его часто занимают библиотеки. Проверяйте документацию библиотеки, прежде чем перенастраивать.
Своя частота регистрами
31,4 кГц на выводах 9 и 10 одной строкой
Двигатель на 490 Гц пищит. Если оставить у Timer1 режим ядра и убрать делитель, частота станет 16 000 000 / 510 ≈ 31 372 Гц — выше слышимого диапазона (расчёт наш):
void setup() {
// Timer1 keeps the core's 8-bit phase correct mode; only the prescaler changes.
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11) | _BV(CS10))) | _BV(CS10); // clk/1
analogWrite(9, 128); // still works: 50 % at about 31.4 kHz
}
void loop() {}
analogWrite продолжит работать на выводах 9 и 10: она пишет только биты COM и OCR1x, а делитель не трогает.
20 кГц с разрешением 800 шагов
Режим 14 Timer1 (табл. 16-4) — Fast PWM с вершиной в регистре ICR1. Частота по разделу 16.9.3 — fclk / (N · (1 + TOP)): для 20 кГц без делителя TOP = 16 000 000 / 20 000 − 1 = 799 (расчёт наш).
void setup() {
TCCR1B = 0; // stop Timer1: init() left it running
DDRB |= _BV(DDB1); // pin 9 (OC1A) as output
TCCR1A = _BV(COM1A1) | _BV(WGM11); // non-inverting OC1A; WGM11 of mode 14
ICR1 = 799; // TOP: 16 MHz / (1 + 799) = 20 kHz
OCR1A = 199; // (199 + 1) / 800 = 25 %
TCNT1 = 0;
TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // WGM13:12 of mode 14, clk/1: start
}
void loop() {}
ICR1 и OCR1A записываются, пока таймер остановлен, и только последняя строка запускает счёт: таймер сразу стартует с нужными вершиной и скважностью. После такой настройки не вызывайте analogWrite(9, …): функция запишет в OCR1A число от 1 до 254, которое теперь означает долю от 800, а значения 0 и 255 выключат ШИМ через digitalWrite. Делитель и вершину под любую частоту подберёт калькулятор таймера.
Сколько стоит одна смена скважности
analogWrite при каждом вызове выполняет pinMode и digitalPinToTimer — те же поиски по таблицам, что в уроке 1, — и только потом пишет регистр. Регистровая версия — одна запись: OCR0A лежит в области ввода-вывода по адресу 0x27, и для него достаточно команды OUT (1 такт), а 16-битный OCR1A — в расширенной области (0x88–0x89), куда пишут две команды STS по 2 такта (разделы 8.5 и 37). Для плавной регулировки в цикле это разница между десятками тактов и одним-четырьмя.
Нагрузка на ШИМ-выводе
ШИМ-вывод — обычный выход с теми же пределами: 40 мА абсолютного максимума (раздел 29.1). Двигатель, реле, мощный светодиод подключают только через транзисторный ключ, а у индуктивной нагрузки параллельно ставят обратный диод, иначе выброс напряжения при выключении пробьёт транзистор.
Задание
- В скетче выполнено
TCCR2B = (TCCR2B & 0xF8) | _BV(CS21);— делитель Timer2 стал 8. Какая теперь частота ШИМ на выводах 3 и 11? Изменится лиmillis()? - Посчитайте скважность для
analogWrite(6, 64)иanalogWrite(10, 64). - После
analogWrite(5, 100)скетч пишетPORTD &= ~_BV(PORTD5);. Пропадёт ли ШИМ на выводе 5? Почему?
Ответ и решение
-
Timer2 остаётся в Phase Correct: 16 000 000 / (8 · 510) ≈ 3921,6 Гц (расчёт наш).
millis()не изменится — время считает Timer0. -
Вывод 6 — Timer0, Fast PWM: (64 + 1) / 256 ≈ 25,4 %. Вывод 10 — Timer1, Phase Correct: 64 / 255 ≈ 25,1 %.
-
Нет. Пока установлен бит COM0B1, выход сравнения OC0B подменяет обычную функцию порта, и бит PORTD5 на выводе не виден (раздел 15.6). Остановить ШИМ можно, сбросив COM0B1 в TCCR0A — так делает
digitalWrite.
Что дальше
В уроке 4 разберём analogRead(): делитель 128, опорное напряжение и почему analogReference() ничего не пишет в регистры. Все регистры таймеров ATmega328P с битами — на страницах Timer0, Timer1 и Timer2. Чем Phase Correct отличается от Fast PWM, коротко объясняет термин «Phase Correct PWM», а про делители — термин «Предделитель таймера».
Частые вопросы
Почему на выводах 5 и 6 частота ШИМ почти в два раза выше?
Выводы 5 и 6 работают от Timer0, а его ядро переводит в режим Fast PWM: таймер считает только вверх, и период равен 256 тактам таймера. Timer1 и Timer2 ядро ставит в Phase Correct PWM, где счёт идёт вверх и вниз и период равен 510 тактам. При делителе 64 и 16 МГц это 976,6 и 490,2 Гц.
Почему analogWrite(5, 0) и analogWrite(9, 0) обрабатываются особо?
Причины для двух таймеров разные. В Fast PWM на Timer0 (выводы 5 и 6) значение OCR0x = 0 не даёт постоянного нуля: по даташиту на выходе остаётся узкий импульс длиной в один такт таймера каждый период. У Timer1 в Phase Correct (выводы 9 и 10) OCR1x = 0 и так даёт постоянный низкий уровень (разд. 16.9.4), так что для analogWrite(9, 0) особый случай не обязателен. Ядро всё равно обрабатывает 0 и 255 одинаково для всех выводов: не включает ШИМ, а вызывает digitalWrite с LOW или HIGH, которая заодно отключает таймер от вывода.
Можно ли изменить частоту analogWrite, не потеряв millis()?
Да, если не трогать Timer0. Делитель Timer1 (выводы 9 и 10) и Timer2 (выводы 3 и 11) можно поменять одной записью в TCCR1B или TCCR2B, и analogWrite продолжит работать — он пишет только биты COM и регистр сравнения. Timer0 отвечает за millis(), micros() и delay(): сменив его делитель, вы измените и ход времени.
Источники
- Arduino. ArduinoCore-avr 1.8.8: cores/arduino/wiring_analog.c, wiring.c, variants/standard/pins_arduino.h, Tone.cpp — тег 1.8.8 — коммит 86df345 от 21 мая 2026 (GitHub API, tags и commits); wiring_analog.c, стр. 96–285: analogWrite — pinMode(OUTPUT), значения 0 и 255 через digitalWrite, иначе бит COMnx1 и запись в OCRnx, на выводе без таймера — LOW ниже 128 и HIGH от 128; wiring.c, стр. 241–324: init() — Timer0 в Fast PWM (WGM01, WGM00) с делителем 64 и прерыванием TOIE0, Timer1 — TCCR1B = 0, делитель 64 (CS11, CS10), 8-битный Phase Correct (WGM10), Timer2 — делитель 64 (CS22) и Phase Correct (WGM20); pins_arduino.h, стр. 196–232: выводы 3 — TIMER2B, 5 — TIMER0B, 6 — TIMER0A, 9 — TIMER1A, 10 — TIMER1B, 11 — TIMER2A; Tone.cpp, стр. 115–124: tone() на ATmega328P использует Timer2; стр. 183–186: tone() ставит Timer2 в CTC; стр. 426–457 и 480–495: noTone вызывает disableTimer(2) — TCCR2A = (1 << WGM20) без битов COM, делитель 64, OCR2A = 0
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — разд. 14.3.1 и 14.3.3: OC0A — PD6, OC0B — PD5, OC1A — PB1, OC1B — PB2, OC2A — PB3, OC2B — PD3; разд. 15.6: при установленных битах COM0x1:0 выход сравнения подменяет функцию порта, но направление задаёт DDR; разд. 15.7.3: Fast PWM, частота fclk/(N·256), OCR0x = 0 — узкий импульс в каждом периоде, OCR0x = MAX — постоянный уровень; разд. 15.7.4: Phase Correct, частота fclk/(N·510); табл. 15-9: CS02:0 = 011 — делитель 64; разд. 16.9.3: Fast PWM Timer1, частота fclk/(N·(1 + TOP)); разд. 16.9.4: Phase Correct Timer1, fclk/(2·N·TOP), OCR1x = BOTTOM — постоянный низкий уровень, OCR1x = TOP — постоянный высокий; табл. 16-4: режим 1 — Phase Correct 8 бит, TOP = 0x00FF, режим 14 — Fast PWM, TOP = ICR1; табл. 16-5: CS12:0 = 001 — без делителя, 011 — делитель 64; табл. 18-9: CS22:0 = 100 — делитель 64; разд. 29.1: 40 мА на вывод
- AVR-LibC 2.3.2: avr/io.h, iom328p.h (github.com/avrdudes/avr-libc) — имена регистров и битов TCCR1A, TCCR1B, ICR1, OCR1A, COM1A1, WGM11, WGM12, WGM13, CS10; 16-битные регистры доступны как одна переменная
Нашли ошибку?
Смотрите также
- ШИМ на таймерахFast PWM и Phase Correct, WGM, COM, OCR0A, частота и скважность
- Частота ШИМ ArduinoЧастоты analogWrite по таймерам, выводы, смена делителя, millis() и tone()
- Таймер AVR и STM32Предделитель, OCRnA, PSC и ARR под частоту, мёртвое время, энкодер
- Phase Correct PWMШИМ со счётом вверх и вниз и симметричным импульсом
- Таймеры AVRTimer0/1/2, режимы WGM, предделители, формулы ШИМ, захват, примеры
- Почему двигатель пищит при ШИМ от Arduino?analogWrite даёт 490 и 980 Гц — это слышно; Timer1 с ICR1 даёт 25 кГц
- Почему после Servo или tone() перестал работать analogWrite?Servo забирает Timer1 (выводы 9, 10), tone() — Timer2 (выводы 3, 11)