Какие таймеры есть в ATmega328P
В ATmega328P три таймера/счётчика:
- Timer0 — 8-битный, два канала ШИМ (OC0A = PD6, OC0B = PD5). В Arduino занят под
millis(). - Timer1 — 16-битный, каналы OC1A (PB1) и OC1B (PB2), есть вход захвата ICP1. Самый гибкий.
- Timer2 — 8-битный, OC2A (PB3) и OC2B (PD3), умеет работать от часового кварца 32768 Гц в асинхронном режиме — основа часов реального времени.
Таймер — это просто счётчик TCNTn, который инкрементируется на каждый такт (или каждые N тактов через предделитель). При совпадении с регистром сравнения OCRn или при переполнении возникает событие: прерывание или автоматическое переключение вывода OCnx.
Предделители: первый шаг
Частота счёта = F_CPU / N, где N — предделитель: 1, 8, 64, 256 или 1024. Задаётся битами CS в регистре TCCRnB. Пока CS=0 — таймер стоит.
Для 8-битного таймера при 16 МГц максимальный период: 256 тиков × 1024 / 16 МГц = 16,38 мс. Для 16-битного: 65536 × 1024 / 16 МГц ≈ 4,19 с. Нужны длиннее интервалы — считайте переполнения в прерывании.
Режим CTC: точный системный тик на 1 мс
CTC (Clear Timer on Compare) — счётчик обнуляется при совпадении с OCRnA, генерируя прерывание с точной частотой. Для 1 мс на Timer0: 16000000 / 64 / 1000 - 1 = 249.
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint32_t g_ms = 0;
ISR(TIMER0_COMPA_vect)
{
g_ms++;
}
static void timer0_init(void)
{
TCCR0A = (1 << WGM01); // режим CTC
TCCR0B = (1 << CS01) | (1 << CS00); // предделитель 64
OCR0A = 249; // 16000000/64/1000 - 1
TIMSK0 = (1 << OCIE0A); // прерывание по совпадению A
}
uint32_t millis(void)
{
uint32_t ms;
cli();
ms = g_ms;
sei();
return ms;
}
int main(void)
{
DDRB |= (1 << PB5);
timer0_init();
sei();
uint32_t last = 0;
while (1) {
if (millis() - last >= 500) {
last = millis();
PINB = (1 << PB5); // toggle LED
}
// процессор свободен для других задач
}
}
Получили свой millis() — базу для неблокирующей многозадачности. Переменная g_ms обязательно volatile, а чтение 32-битного значения оборачиваем в cli()/sei() — иначе прерывание может обновить его «наполовину».
Формулы для любой частоты
Частота прерывания CTC: F_int = F_CPU / (N × (OCR + 1)). Отсюда:
// искомый OCR под нужную частоту:
OCR = F_CPU / (N * F_int) - 1;
Примеры для 16 МГц:
- 1 кГц, N=64 → OCR = 249
- 10 кГц, N=8 → OCR = 199
- 100 Гц, N=1024 → OCR = 155 (8-бит) или 15624 при N=1 для Timer1
Всегда проверяйте, что OCR влезает в разрядность таймера, и подбирайте минимальный предделитель — так точность выше.
Fast PWM: яркость светодиода
ШИМ аппаратно дёргает ножку OCnx: чем больше OCRn, тем больше скважность. Никакого кода в цикле — таймер работает сам.
// Fast PWM 8-bit на Timer2, выход OC2A = PB3 (Arduino D11)
static void pwm_init(void)
{
DDRB |= (1 << PB3);
TCCR2A = (1 << COM2A1) // неинверсный ШИМ на OC2A
| (1 << WGM21) | (1 << WGM20); // Fast PWM, TOP=255
TCCR2B = (1 << CS22); // предделитель 64 → ~976 Гц
}
static inline void pwm_set(uint8_t duty) { OCR2A = duty; }
Плавное «дыхание» светодиода:
int main(void)
{
pwm_init();
uint8_t d = 0;
int8_t dir = 1;
while (1) {
pwm_set(d);
d += dir;
if (d == 0 || d == 255) dir = -dir;
_delay_ms(4);
}
}
Частота Fast PWM 8-бит: F_pwm = F_CPU / (N × 256). При N=64 и 16 МГц — 976 Гц: для светодиодов достаточно, мерцания не видно.
Phase Correct PWM и 16-битный Timer1
Phase Correct считает вверх-вниз — частота вдвое ниже, зато симметричные фронты: лучше для моторов и звука. Timer1 позволяет задать TOP через ICR1 и получить любую частоту с разрешением до 16 бит.
Сервопривод: 50 Гц и импульс 1–2 мс
Серву нужен период 20 мс (50 Гц) и импульс 0,5–2,5 мс для крайних положений. Идеально ложится на Timer1: режим Fast PWM с TOP в ICR1.
// ICR1 задаёт период 20 мс: 16000000/8/50 = 40000 тиков
// OCR1A задаёт импульс: 1 мс = 2000 тиков, 2 мс = 4000 тиков
static void servo_init(void)
{
DDRB |= (1 << PB1); // OC1A = PB1 (Arduino D9)
TCCR1A = (1 << COM1A1) | (1 << WGM11);
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Fast PWM, TOP=ICR1, N=8
ICR1 = 39999; // 20 мс
OCR1A = 3000; // 1.5 мс — центр
}
// угол 0..180 -> импульс 2000..4000 тиков (1.0..2.0 мс)
static void servo_angle(uint8_t deg)
{
OCR1A = 2000 + (uint16_t)deg * 2000 / 180;
}
Точность — 0,5 мкс на тик, джиттер нулевой: серва стоит мёртво, чего не добиться программным ШИМом. Второй канал OCR1B (PB2) управляет ещё одной сервой независимо.
Генерация звука: toggle-режим
Если ШИМ не нужен, а нужна частота — режим CTC с автоматическим toggle ножки OCnx (COM0A0=1). Пьезопищалка на OC0A (PD6):
// частота = F_CPU / (2 * N * (OCR0A + 1))
static void beep(uint8_t ocr)
{
DDRD |= (1 << PD6);
TCCR0A = (1 << COM0A0) | (1 << WGM01); // toggle OC0A, CTC
TCCR0B = (1 << CS01) | (1 << CS00); // N=64
OCR0A = ocr; // 124 -> ~1 кГц
}
static void silence(void) { TCCR0B = 0; PORTD &= ~(1 << PD6); }
Нота Ля (440 Гц): OCR0A = 16000000/(2×64×440) - 1 ≈ 283.
Input Capture: измерение периода сигнала
У Timer1 есть аппаратный вход захвата ICP1 (PB0). По фронту/спаду на нём текущее значение TCNT1 мгновенно фиксируется в ICR1 и вызывает прерывание — точность измерения периода до такта, не зависящая от длительности ISR.
volatile uint16_t g_period_ticks = 0;
ISR(TIMER1_CAPT_vect)
{
static uint16_t prev = 0;
uint16_t now = ICR1;
g_period_ticks = now - prev; // разность корректна и при переполнении
prev = now;
}
static void icp_init(void)
{
DDRB &= ~(1 << PB0); // ICP1 — вход
TCCR1B = (1 << ICNC1) // noise canceler (4 выборки)
| (1 << ICES1) // захват по фронту
| (1 << CS11); // предделитель 8 -> 0.5 мкс/тик
TIMSK1 = (1 << ICIE1);
}
Частота сигнала: F = F_CPU / (N * g_period_ticks). При 0,5 мкс на тик и периоде 2000 тиков получаем ровно 1 кГц с точностью кварца — так делают частотомеры и измерение оборотов по датчику Холла.
Типовые ошибки
- Забыли
DDRx |=на выход OCnx — ШИМ «есть», а на ножке тишина. - Неправильный режим WGM — у каждого таймера своя раскладка битов WGMx0..3 между TCCRxA и TCCRxB, сверяйтесь с таблицей даташита.
- OCR больше TOP — в 8-битном Fast PWM с TOP=ICRn запись OCR выше TOP даст 100% скважность.
- Делить регистры между Arduino и своим кодом —
analogWrite()переинициализирует таймер, ваши настройки слетят. - Писать 16-битные регистры как два байта вручную —
OCR1A = xв avr-libc атомарна только в main; в прерываниях сохраняйте порядок: старший байт, потом младший.
Итог
Таймеры — сердце AVR: системный тик, ШИМ, сервы, звук, точные интервалы — всё это работает аппаратно, пока процессор занят логикой. Следующий шаг — прерывания: именно через них таймеры и периферия сообщают программе о событиях.