1) Cel lekcji
Do tej pory korzystałeś z _delay_ms(), które jest proste, ale blokuje program. W embedded to problem, bo gdy CPU „śpi” w opóźnieniu, nie reaguje szybko na przyciski, czujniki i zdarzenia.
- poznasz zasadę działania timerów (sprzętowe liczniki),
- nauczysz się ustawiać preskaler i tryb CTC,
- zrobisz dokładne opóźnienia bez blokowania pętli głównej,
- zrobisz „system tick” (np. 1 ms), który jest podstawą większości projektów.
2) Co to jest timer w AVR?
Timer to sprzętowy licznik, który zlicza impulsy zegara. Gdy osiągnie wartość graniczną, może:
- ustawić flagę (TOV/OCF),
- wywołać przerwanie (ISR),
- wyzerować się i liczyć od nowa.
- Timer0 — 8-bit (0..255),
- Timer1 — 16-bit (0..65535),
- Timer2 — 8-bit (często do PWM/audio).
- odmierzanie czasu (tick),
- PWM (silniki, LED),
- pomiar czasu (input capture),
- generowanie częstotliwości.
3) Preskaler — po co i jak działa?
Timer liczy „takt” z zegara systemowego, ale możesz go spowolnić preskalerem. Przykładowo preskaler 64 oznacza, że timer dostaje 1 impuls na 64 impulsy CPU.
f_timer = f_cpu / prescaler
czas_1_tiku = 1 / f_timer
Jeśli masz F_CPU = 8 MHz i preskaler 64:
- f_timer = 8 000 000 / 64 = 125 000 Hz
- 1 tik = 1 / 125 000 = 8 µs
4) Tryb CTC — najwygodniejszy do „odmierzania”
CTC (Clear Timer on Compare Match) działa tak:
- timer liczy od 0 do wartości w rejestrze OCR,
- gdy osiągnie OCR → ustawia flagę porównania, może wywołać przerwanie,
- automatycznie wraca do 0 i liczy dalej.
5) Timer0 w CTC: zdarzenie co 1 ms (bez przerwań — polling)
Najpierw wersja najprostsza: nie używamy ISR, tylko sprawdzamy flagę porównania w pętli. To już usuwa _delay_ms() jako blokadę.
Założenia do obliczeń
- F_CPU = 8 MHz (jeśli masz inaczej — podam też wariant dla 16 MHz)
- Preskaler = 64
- 1 tik = 8 µs
- 1 ms = 1000 µs → potrzebujesz 125 tików
- OCR0A = 124 (bo liczenie od 0)
#include <avr/io.h>
#define LED_DDR DDRB
#define LED_PORT PORTB
#define LED_PIN PB0
static void timer0_ctc_1ms_init(void)
{
// CTC: WGM01=1, WGM00=0
TCCR0A = (1 << WGM01);
// preskaler 64: CS01=1 i CS00=1
TCCR0B = (1 << CS01) | (1 << CS00);
// Compare Match co 1ms dla F_CPU=8MHz i preskaler=64
OCR0A = 124;
// wyczyść flagę porównania
TIFR0 = (1 << OCF0A);
}
static inline uint8_t timer0_1ms_elapsed(void)
{
if (TIFR0 & (1 << OCF0A))
{
TIFR0 = (1 << OCF0A); // skasuj flagę (piszesz 1!)
return 1;
}
return 0;
}
int main(void)
{
LED_DDR |= (1 << LED_PIN);
timer0_ctc_1ms_init();
uint16_t ms = 0;
while (1)
{
if (timer0_1ms_elapsed())
{
ms++;
// co 500 ms przełącz LED
if (ms % 500 == 0)
{
LED_PORT ^= (1 << LED_PIN);
}
}
// TU CPU jest wolny — możesz robić inne rzeczy (przycisk, UART, itp.)
}
}
6) Wariant dla F_CPU = 16 MHz (jeśli masz kwarc 16 MHz)
Dla 16 MHz i preskalera 64:
- f_timer = 16 000 000 / 64 = 250 000 Hz
- 1 tik = 4 µs
- 1 ms = 250 tików
- OCR0A = 249
7) System tick i harmonogram „co X ms”
Mając tick 1 ms możesz tworzyć proste „zadania” w pętli:
// przykładowe interwały
// LED1: co 200 ms
// LED2: co 500 ms
// BUZZ: co 1000 ms (pik)
uint16_t t200 = 0, t500 = 0, t1000 = 0;
if (timer0_1ms_elapsed())
{
t200++;
t500++;
t1000++;
if (t200 >= 200) { t200 = 0; /* akcja 200ms */ }
if (t500 >= 500) { t500 = 0; /* akcja 500ms */ }
if (t1000 >= 1000) { t1000 = 0; /* akcja 1000ms */ }
}
Tak działa mnóstwo prostych urządzeń: nie RTOS, tylko tick + liczniki.
8) Timer1: kiedy go używać?
Timer0 jest 8-bit i często bywa zajęty (np. pod PWM). Timer1 jest 16-bit i daje większy zakres, dokładność i wygodę w dłuższych czasach.
Timer0 — tick/scheduler, Timer1 — dokładniejsze pomiary i dłuższe okresy, Timer2 — PWM/audio.
9) Zadania obowiązkowe
- Uruchom Timer0 CTC 1ms i zrób miganie LED co 250 ms.
- Dodaj drugi licznik i zrób „heartbeat”: krótkie mignięcie co 1000 ms.
- Połącz z lekcją 10: odczyt przycisku ma działać „od razu” nawet podczas migania.
10) Zadania dodatkowe (dla lepszych)
- Zrób 3 tryby migania LED (200/500/1000 ms) przełączane przyciskiem — bez _delay_ms().
- Zrób „debounce timerowy”: przycisk uznaj za stabilny, jeśli jest wciśnięty przez 20 ms (z ticka).
- Zrób wzorzec z lekcji 9 (4 LED) sterowany schedulerem.
11) Zadania PRO (prawie firmware)
- Zrób strukturę „tasków”: funkcje task_1ms(), task_10ms(), task_100ms().
- Zrób licznik czasu uptime_ms i wypisz go przez UART (jeśli masz w kursie) albo sygnalizuj LED.
- Dodaj timeout: jeśli przez 5 s nie było kliknięcia przycisku, LED przechodzi w tryb oszczędny (np. wolne miganie).
Następny krok to przerwania (ISR) i tick w ISR — wtedy pętla główna jest jeszcze czystsza.