Informatique Industrielle · Semestre 2

Chapitre 6 — Timers AVR
en Assembleur et en C

Pr. Baghouri Mostafa · ENSA Tétouan · ATmega328 (Arduino)

Page 1 Page de couverture

Ce cours fait partie du cursus Génie en Cybersécurité et Systèmes Embarqués (Semestre 2) dispensé à l'Université Abdelmalek Essaadi – ENSA Tétouan, Département Intelligence Artificielle et Digitalisation.

L'enseignant responsable est Pr. Baghouri Mostafa (Mostafa.baghouri@ensam-casa.ma).

Le module "Informatique Industrielle" couvre la programmation bas-niveau de microcontrôleurs AVR, notamment la gestion des timers/compteurs, indispensable dans tout système embarqué temps réel.
Page 2 Titre du chapitre 6

Cette diapositive annonce le Chapitre 6 : Timers AVR en Assembleur et en C. Il s'agit du chapitre central consacré aux mécanismes de temporisation et de comptage sur la famille AVR.

Pourquoi ce chapitre est crucial ?
Dans tout système embarqué, la gestion précise du temps est fondamentale : délais, signaux PWM, mesure de durée, comptage d'événements extérieurs. Les timers matériels permettent d'accomplir tout cela sans bloquer le processeur.

Le microcontrôleur ciblé est l'ATmega328 (base de l'Arduino Uno). Les programmes seront écrits en assembleur AVR et en C (avr-gcc).

Page X Timer vs Compteur — Interne et Externe

Un même module matériel peut fonctionner en mode Timer ou en mode Compteur :

  • Timer (chronomètre interne) : le registre s’incrémente automatiquement à chaque coup d’horloge interne du microcontrôleur. On mesure ainsi le temps qui passe, on génère des délais ou des interruptions périodiques.
  • Compteur (compteur externe) : le registre s’incrémente uniquement lorsqu’une impulsion arrive sur une broche dédiée (T0 pour Timer0, T1 pour Timer1). On compte donc des événements physiques extérieurs (capteur, moteur, etc.).
Horloge interne (clkIO)
Timer
Mesure du temps
Impulsions externes (broche Tn)
Compteur
Comptage d’événements
Résumé : Le mot Timer insiste sur la mesure du temps via l’horloge interne, tandis que Compteur insiste sur le comptage d’événements externes. Matériellement, c’est le même registre configuré différemment.
Page 3 Objectifs et contenu du cours

À l'issue de ce chapitre, l'étudiant doit être capable de :

  • Énumérer les timers de l'ATmega328 et leurs registres associés (Timer0, Timer1, Timer2).
  • Décrire les modes Normal et CTC (Clear Timer on Compare Match).
  • Programmer les timers en assembleur et en C pour générer des délais temporels précis.
  • Programmer les compteurs pour compter des événements extérieurs arrivant sur une broche.
Ces quatre compétences couvrent les usages les plus courants des timers dans les systèmes embarqués industriels : horloges, PWM, communication synchrone, mesure de fréquence.
Page 4 Registre d'un compteur — Modèle de base

Un compteur est basé sur un registre compteur (Counter Register). Ce registre peut être :

  • Incrémenté (UP) à chaque front actif de l'horloge.
  • Chargé (Load) avec une valeur initiale pour définir le point de départ du comptage.
  • Lu (COUT) pour connaître la valeur courante du compteur.
▶ UP
Counter Register
Load
Analogie : Imaginez un compteur kilométrique de voiture. Il s'incrémente à chaque km parcouru (UP), peut être remis à zéro ou chargé à une valeur initiale (Load), et vous pouvez lire la valeur affichée (COUT).

Ce modèle simplifié est la brique fondamentale de tout timer/compteur dans un microcontrôleur.

Page 5 Application : Comptage de personnes

Premier exemple concret : un compteur de personnes à l'entrée d'un bâtiment. Un capteur (sensor) envoie une impulsion à chaque passage.

  • Chaque impulsion du capteur → signal UP → le registre s'incrémente.
  • Un comparateur compare la valeur du registre avec une constante (ici 4).
  • Quand le compteur atteint 4 → sortie du comparateur activée → action déclenchée (alarme, fermeture de porte, etc.).
Capteur → UP
Counter Register
Comparateur (= 4)
Ce modèle illustre deux opérations clés : compter des événements externes (mode compteur) et comparer avec une valeur cible (mode CTC). Ces deux mécanismes se retrouvent directement dans les registres OCRn des timers AVR.
Page 6 Génération d'un délai — Principe de base

Pour générer un délai temporel avec un compteur, on exploite la fréquence régulière de l'horloge système :

  • L'horloge CLK alimente le signal UP du compteur.
  • On charge une valeur initiale (ici $FC = 252) dans le registre.
  • Le compteur s'incrémente à chaque coup d'horloge jusqu'au débordement (overflow à 256 pour un compteur 8 bits).
  • Nombre de cycles attendus = 256 − 252 = 4 cycles.
Délai = (256 − TCNT0_initial) × Thorloge
Exemple : XTAL = 1 MHz → T = 1 µs. En chargeant $FC (252), le timer effectue 4 cycles → délai = 4 µs. En chargeant $00, il effectue 256 cycles → délai maximal = 256 µs.
Page 7 Timer/Compteur — Fonctionnement général

Le timer/compteur d'un microcontrôleur est un module matériel autonome qui compte les fronts d'horloge. Il joue plusieurs rôles :

  • Mesurer le temps écoulé (fonctions delay(), millis() en Arduino)
  • Mesurer la durée d'un événement (ex. largeur d'une impulsion)
  • Compter des événements extérieurs sur une broche dédiée
  • Générer des signaux PWM (Modulation à Largeur d'Impulsion)
  • Réaliser des comparaisons de valeurs
  • Générer des interruptions à intervalles réguliers
Méthode de génération d'un délai :
1. Remettre le compteur à zéro (ou charger une valeur initiale).
2. Démarrer le compteur.
3. Attendre qu'il atteigne la valeur cible (ou déborde).
4. Lire le flag d'overflow / compare match pour détecter la fin du délai.
Règle de calcul rapide : Pour un µC à 1 MHz, le registre s'incrémente toutes les 1 µs. Pour un délai de 100 µs, charger TCNT = 256 − 100 = 156.
Page 8 Le Flag d'overflow — Exemple Délai = 4 µs

Chaque timer possède un flag d'overflow (TOVn) qui est automatiquement mis à 1 lorsque le compteur déborde (passe de 0xFF à 0x00 pour 8 bits).

Séquence pour Délai = 4 µs avec valeur initiale $FC

  • On charge TCNT0 = $FC (252 en décimal).
  • Le compteur progresse : FC → FD → FE → FF → (overflow) → 00.
  • 4 coups d'horloge se sont écoulés → TOV0 = 1.
  • Le programme détecte ce flag en boucle (SBIS TIFR0, TOV0), puis l'efface par logiciel en écrivant 1 dans le bit correspondant de TIFR0.
$100 − $FC = $04 = 4 cycles
Important : Contrairement à la plupart des flags, on efface TOVn en écrivant un 1 dans le bit correspondant du registre TIFR (Timer Interrupt Flag Register). Écrire 0 n'a aucun effet.
Page 9 Rôles du Timer/Compteur — Source d'horloge

Le timer/compteur peut recevoir son signal d'horloge depuis deux sources, sélectionnables via les bits CS (Clock Select) du registre TCCRn :

Oscillateur interne (clkIO)
→ MUX
Counter Register
Source externe (broche Tn)
→ MUX
Counter Register
  • Mode Timer : Source = oscillateur interne → mesure de temps précis.
  • Mode Compteur : Source = broche externe Tn → compte des événements.

Le registre TIFR contient le flag TOVn. Quand le compteur déborde, TOVn = 1. Le programme peut soit lire ce flag en polling, soit configurer une interruption.

Page 10 Timers dans l'ATmega328 — Vue d'ensemble

L'ATmega328 (Arduino Uno) possède 3 timers :

TimerRésolutionCaractéristiques
Timer08 bitsUtilisé par Arduino pour millis(), delay(), PWM pins 5 et 6
Timer116 bitsHaute précision, capture d'entrée, PWM pins 9 et 10
Timer28 bitsPrescalers différents de Timer0, PWM pins 3 et 11, mode asynchrone
Comparaison :
• Un timer 8 bits peut compter de 0 à 255 (256 valeurs).
• Un timer 16 bits peut compter de 0 à 65535 (65536 valeurs).
→ Timer1 permet des délais jusqu'à 256 fois plus longs que Timer0 à prescaler égal.
Les microcontrôleurs de la famille AVR plus grands (ATmega32, ATmega2560) peuvent avoir jusqu'à 6 timers. Le principe reste identique.
Page 11 Registres des Timers AVR — TCNTn, TOVn, TCCRn, OCRn, OCFn

Chaque timer n dispose d'un ensemble de registres 8 bits accessibles par adresse :

RegistreNom completRôle
TCNTnTimer/Counter RegisterValeur courante du compteur (lecture/écriture)
TOVnTimer Overflow FlagMis à 1 quand le compteur déborde (dans TIFRn)
TCCRnTimer Counter Control RegisterConfigure le mode (Normal, CTC, PWM) et la source d'horloge
OCRnOutput Compare RegisterValeur de comparaison avec TCNTn
OCFnOutput Compare FlagMis à 1 quand TCNTn = OCRn (dans TIFRn)
Remark importante : Tous ces registres sont adressables par octet (pas au niveau du bit pour la lecture/écriture directe, sauf via les instructions SBI/CBI pour certains).
Les instructions OUT et IN (accès direct au port I/O) ne fonctionnent que pour les adresses ≤ 0x3F. Pour Timer1 (adresses > 0x3F), il faut utiliser STS / LDS.
Section 1 · Timer 0 (8-bit)
Page 12 Section 1 — Timer 0 (timer à 8 bits)

Le Timer 0 est un timer 8 bits, le premier et le plus simple de l'ATmega328. Il compte de 0x00 à 0xFF (0 à 255), puis déborde.

L'Arduino utilise Timer0 en interne pour les fonctions millis(), micros(), delay(). Si vous modifiez ses registres, ces fonctions seront perturbées. Dans ce cours, nous configurons le timer manuellement.
Page 13 Architecture du Timer 0 — Registres et connexions

Le Timer 0 expose les registres suivants :

RegistreAdresseRôle
TCCR0A0x44Contrôle A : mode WGM, sorties OC0A/B
TCCR0B0x45Contrôle B : mode WGM, prescaler CS
TCNT00x46Registre compteur (0–255)
OCR0A0x47Valeur de comparaison A
OCR0B0x48Valeur de comparaison B
TIFR00x35Flags : TOV0, OCF0A, OCF0B

Le registre TIFR0 (Timer Interrupt Flag Register 0) contient 3 bits de flag :

OCF0B
OCF0B
OCF0A
OCF0A
TOV0
TOV0
bit 0 = TOV0 (overflow), bit 1 = OCF0A (compare match A), bit 2 = OCF0B (compare match B)
Page 14 Registres TCCR0A / TCCR0B — Mode et Prescaler

TCCR0A — Bits de mode WGM et sorties COM

COM0A1
COM0A1
COM0A0
COM0A0
COM0B1
COM0B1
COM0B0
COM0B0
WGM01
WGM01
WGM00
WGM00

TCCR0B — Prescaler CS et bit WGM02

FOC0A
FOC0A
FOC0B
FOC0B
WGM02
WGM02
CS02
CS02
CS01
CS01
CS00
CS00

Tableau WGM — Sélection du mode

WGM02WGM01WGM00Mode
000Normal
001Phase correct PWM
010CTC (Clear Timer on Compare Match)
011Fast PWM
101Phase correct PWM (TOP=OCR0A)
111Fast PWM (TOP=OCR0A)

Tableau CS — Sélection du prescaler

CS02CS01CS00Source d'horloge
000Aucune (timer arrêté)
001clk (pas de prescaler)
010clk / 8
011clk / 64
100clk / 256
101clk / 1024
110Horloge ext. (T0, front descendant)
111Horloge ext. (T0, front montant)
Page 15 Mode Normal — Fonctionnement et flag TOV0

En mode Normal (WGM = 000), le timer compte librement de 0x00 à 0xFF, puis repart de 0x00 (overflow). À chaque overflow, le flag TOV0 passe à 1.

TCNT0 : 0x00 → 0x01 → … → 0xFE → 0xFF → 0x00 (TOV0=1) → 0x01 → …
Utilisation typique :
• On charge TCNT0 avec une valeur initiale.
• On attend que TOV0 soit mis à 1 (polling ou interruption).
• On efface TOV0 en écrivant 1 dans le bit 0 de TIFR0.
• Le nombre de cycles écoulés = 256 − valeur_initiale.
Limitation du mode Normal : Si on veut répéter exactement le même délai, il faut recharger TCNT0 à chaque itération (car le timer repart de 0 après l'overflow). Cette opération introduit un léger jitter.
Page 16 Exemple 1 — Énoncé : 14 cycles en mode Normal

Objectif : écrire un programme qui génère un délai de 14 cycles machine en utilisant le Timer0 en mode Normal.

Calcul de la valeur initiale :
14 cycles → on veut que le timer effectue 14 incrémentations avant d'overflow.
Valeur initiale = 256 − 14 = 242 = 0xF2.
Vérification : 0xF2 + 14 = 0x100, ce qui correspond bien à l'overflow.
$100 − $0E = $F2 → TCNT0 = 0xF2

Configuration des registres :

; Mode Normal (WGM02:00 = 000), prescaler = 1 (CS = 001)
TCCR0A = 0x00;  // WGM01=0, WGM00=0
TCCR0B = 0x01;  // CS02=0, CS01=0, CS00=1 → clk/1
Page 17 Exemple 1 — Solution détaillée

Le prescaler à 1 (CS = 001) signifie que le timer s'incrémente à chaque cycle d'horloge. La chaîne de valeurs est :

$F2 → $F3 → … → $FF → $00 (TOV0=1) ← 14 cycles
TCNT0 = 0xF2
→ 14 coups d'horloge →
Overflow → TOV0=1
; Assembleur AVR
TCCR0A = 0x00;   // Mode Normal
TCCR0B = 0x01;   // Prescaler = 1 (clk/1)
Remarque sur le prescaler : Le tableau CS définit par quel facteur on divise l'horloge avant de l'envoyer au timer. Avec CS=001, aucune division → délais courts mais précis. Avec CS=101 (÷1024), délais plus longs mais résolution réduite.
Page 18 Exemple 1 — Code complet Assembleur et C

Programme complet : génère un signal carré sur PB5 en utilisant le Timer0 (mode Normal, TCNT0 = 0xF2).

Code Assembleur

LDI    R16, (1<<5)   ; masque pour PB5
SBI    DDRB, 5      ; PB5 = sortie
LDI    R17, 0
OUT    PORTB, R17  ; PB5 = 0

BEGIN:
LDI    R20, 0xF2
OUT    TCNT0, R20  ; charger timer
LDI    R20, 0x00
OUT    TCCR0A, R20 ; mode normal
LDI    R20, 0x01
OUT    TCCR0B, R20 ; démarrer timer (clk/1)

AGAIN:
SBIS   TIFR0, TOV0  ; sauter si TOV0 = 1
RJMP   AGAIN         ; sinon boucler

LDI    R20, 0x00
OUT    TCCR0B, R20 ; arrêter Timer0
LDI    R20, (1<<TOV0)
OUT    TIFR0, R20  ; effacer TOV0
EOR    R17, R16     ; basculer bit 5
OUT    PORTB, R17  ; appliquer sur PB5
RJMP   BEGIN

Code C équivalent

DDRB = 1<<5;
PORTB &= ~(1<<5);
while (1) {
    TCNT0 = 0xF2;
    TCCR0A = 0x00;
    TCCR0B = 0x01;
    while((TIFR0 & (1<<TOV0)) == 0) {}
    TCCR0B = 0;
    TIFR0 = (1<<TOV0);
    PORTB ^= (1<<5);
}
Comment calculer le délai ?
T = 1/f → durée d'un cycle machine.
Délai = T × nombre de cycles = T × (256 − TCNT0_initial).
Page 19 Exemple 2 — Calcul du délai avec XTAL = 10 MHz

On reprend l'exemple 1 avec XTAL = 10 MHz. Combien de temps dure exactement le délai généré par le timer ?

T = 1 / 10 MHz = 0,1 µs par cycle machine
Solution 1 (approximation — timer seul) :
Nombre de cycles = 256 − 0xF2 = 256 − 242 = 14 cycles
Délai timer = 14 × 0,1 µs = 1,40 µs

Cette valeur ne compte que le temps passé dans le timer. Le programme a aussi des instructions à exécuter, qui prennent du temps supplémentaire (voir page suivante).

Page 20 Exemple 2 — Calcul précis incluant les instructions

Chaque instruction assembleur prend 1 ou 2 cycles machine. Pour un calcul précis du délai total, il faut compter tous les cycles :

InstructionCyclesDescription
LDI R20,0xF21Charger valeur
OUT TCNT0,R201Initialiser timer
LDI R20,0x001
OUT TCCR0A,R201Mode normal
LDI R20,0x011
OUT TCCR0B,R201Démarrer timer
SBIS TIFR0,TOV01/2Test flag (1 si non sauté, 2 si sauté)
RJMP AGAIN2Branchement relatif
LDI/OUT ×44Stop timer + clear flag
EOR R17,R161Toggle bit
OUT PORTB,R171Sortie
RJMP BEGIN2
Total instructions18
Délai total = (14 × 0,1) + (18 × 0,1) = 1,4 + 1,8 = 3,2 µs
En dehors du timer, chaque instruction consomme du temps. Pour un calcul de délai précis, il faut additionner : délai timer + délai instructions.
Page 21 Méthode de détermination des valeurs TCNT

Procédure générale pour calculer la valeur à charger dans TCNT0 :

  • Étape 1 : Calculer la période de l'horloge → T = 1 / f
  • Étape 2 : Calculer le nombre de cycles requis → n = délai_voulu / T
  • Étape 3 : Calculer la valeur de chargement → TCNT0 = 256 − n
TCNT0 = 256 − (délai_voulu / Thorloge)
Exemple avec XTAL = 16 MHz :
T = 1/16 MHz = 0,0625 µs.
Pour 50 µs : n = 50 / 0,0625 = 800 cycles. Mais 800 > 256 → impossible avec Timer0 sans prescaler !
Avec prescaler 8 : T_eff = 0,5 µs → n = 50/0,5 = 100 → TCNT0 = 256 − 100 = 156.
Page 22 Exemple 3 — Signal carré 10 µs sur PORTB.3

Générer un signal carré de période 10 µs sur PORTB.3 avec XTAL = 10 MHz.

Analyse :
• Période = 10 µs → demi-période (délai) = 5 µs.
• T = 0,1 µs → n = 5 / 0,1 = 50 cycles.
• TCNT0 = 256 − 50 = 206.
; Assembleur
BEGIN:
  LDI  R20, 206
  OUT  TCNT0, R20
  LDI  R20, 0x01
  OUT  TCCR0B, R20  ; démarrer, clk/1
AGAIN:
  SBIS TIFR0, TOV0
  RJMP AGAIN
  ; arrêter, clear flag, toggle PB3
  RJMP BEGIN
// C
while (1) {
    TCNT0 = 206;
    TCCR0A = 0x00;
    TCCR0B = 0x01;
    while((TIFR0 & 0x01) == 0) {}
    TCCR0B = 0;
    TIFR0 = 1<<TOV0;
    PORTB = PORTB ^ (1<<3);
}
Pages 23–24 Exemple 4 — Délai maximal sans prescaler

Modifier TCNT0 pour obtenir le plus grand délai possible sans prescaler (XTAL = 10 MHz).

Stratégie : Charger TCNT0 = 0x00. Le timer compte de 0 à 255 (256 états), puis déborde.
Délai max = 256 × 0,1 µs = 25,6 µs.

Au-delà de cette limite, il faut soit utiliser un prescaler, soit utiliser le Timer1 (16 bits).

Délai_max (Timer0, no prescaler) = 256 / f_XTAL
TCNT0 = 0x00;   // Départ à zéro = délai max
TCCR0A = 0x00;
TCCR0B = 0x01;
while((TIFR0 & (1<<TOV0)) == 0) {}
TCCR0B = 0;
TIFR0 = 0x01;
PORTB ^= (1<<3);
Page 25 Génération de grands délais — 3 stratégies

Quand le délai souhaité dépasse la capacité du timer, trois techniques sont disponibles :

  • Boucle logicielle : On exécute plusieurs fois la routine de délai. Ex : pour 1 ms avec Timer0 à 10 MHz, on boucle 40× (40 × 25,6 µs ≈ 1 ms).
  • Prescaler : On divise l'horloge avant le timer (÷8, ÷64, ÷256, ÷1024) → chaque tick dure plus longtemps → délai plus long avec le même compteur.
  • Timer 16 bits (Timer1) : Peut compter jusqu'à 65535 valeurs → délais 256× plus longs que Timer0 à prescaler identique.
Tableau des délais maximaux — XTAL = 16 MHz :
PrescalerT_tickDélai max Timer0 (8 bits)Délai max Timer1 (16 bits)
162,5 ns16 µs4 ms
8500 ns128 µs32 ms
644 µs1,024 ms262 ms
25616 µs4,096 ms1,048 s
102464 µs16,384 ms4,194 s
Page 26 Préscaler — Schéma du diviseur de fréquence

Le préscaler 10 bits de l'ATmega328 est partagé entre Timer0 et Timer1. Il divise clk_IO par un facteur configurable avant de l'envoyer au timer :

clk_IO
Préscaler 10 bits
÷1 ÷8 ÷64 ÷256 ÷1024
Timer/Counter

Les bits CS02:CS00 du registre TCCR0B sélectionnent quelle sortie du prescaler alimente le timer. La broche externe T0 (PD4) peut également être utilisée comme source (CS = 110 ou 111).

PSR10 : En écrivant 1 dans le bit PSR10 du registre GTCCR, on remet le préscaler à zéro — utile pour synchroniser précisément le démarrage du timer.
Page 27 Exemple 5 — Analyse d'un programme avec prescaler ÷64, XTAL = 8 MHz

Programme analysé : TCNT0 = 0x10, prescaler ÷64 (CS = 011), XTAL = 8 MHz.

Solution :
1. T = 1/8 MHz = 0,125 µs par cycle machine.
2. Avec prescaler ÷64 : T_tick = 64 × 0,125 = 8 µs par tick du timer.
3. TCNT0 = 0x10 = 16 en décimal → 256 − 16 = 240 ticks avant overflow.
4. Délai = 240 × 8 µs = 1920 µs.
Délai = (256 − TCNT0) × (prescaler / f_XTAL)

Le code utilise TCCR0B = 0x03 pour sélectionner CS = 011 (÷64), et appelle une fonction DELAY en subroutine (RCALL/RET).

Page 28 Exemple 6 — Prescaler ÷1024, délai maximal

XTAL = 8 MHz, prescaler 1024. Calculer la période d'horloge et le délai maximal.

(a) Période avec prescaler 1024 :
f_timer = 8 MHz / 1024 = 7812,5 Hz
T_tick = 1 / 7812,5 = 128 µs par tick.
(b) Délai maximal (TCNT0 = 0) :
256 ticks × 128 µs = 32 768 µs = 32,768 ms ≈ 0,033 s.
Pour aller au-delà de 32 ms avec Timer0, on doit utiliser une boucle logicielle : exécuter N fois la routine de délai. Ex : 10 × 32,768 ms ≈ 327 ms.
Pages 29–30 Exemple 7 — Signal carré 125 Hz avec prescaler ÷256

XTAL = 8 MHz, prescaler ÷256, signal carré 125 Hz sur PORTB.3.

Calcul :
(a) T = 1/125 Hz = 8 ms → demi-période = 4 ms.
(b) T_tick = 256 / 8 MHz = 32 µs (← erreur dans le cours original ; correct : T_tick = 256/8M = 32 µs).
Ou plus simplement : T_tick = 1/(8M/256) = 256/(8×10⁶) = 0,032 ms.
n = 4 ms / 0,032 ms = 125 ticks.
(c) TCNT0 = 256 − 125 = 131 = 0x83.
; Assembleur — sous-routine DELAY
DELAY:
  LDI  R20, 0x83      ; TCNT0 = 131
  OUT  TCNT0, R20
  LDI  R20, 0x04      ; CS = 100 (÷256)
  OUT  TCCR0, R20
AGAIN:
  IN   R20, TIFR
  SBRS R20, TOV0
  RJMP AGAIN
  LDI  R20, 0
  OUT  TCCR0, R20
  LDI  R20, (1<<TOV0)
  OUT  TIFR, R20
  RET
Page 31 Mode CTC — Clear Timer on Compare Match

En mode CTC (WGM = 010), le timer ne déborde plus à 255 mais se remet à zéro automatiquement lorsque TCNT0 == OCR0A. Le flag OCF0A est alors mis à 1.

TCNT0 : 0 → 1 → … → OCR0A → 0 (OCF0A=1) → 1 → …
Avantages du mode CTC vs Normal :
• Pas besoin de recharger TCNT0 à chaque itération → délai automatiquement répétable.
• La valeur OCR0A définit directement la période → plus intuitif.
• Moins de jitter car le rechargement est matériel.
Formule CTC :
OCR0A = (f_XTAL / (prescaler × f_désirée)) − 1
Ou pour un délai : OCR0A = (délai × f_XTAL / prescaler) − 1
Page 32 Mode CTC — Configuration des registres

Pour activer le mode CTC sur Timer0, configurer les bits WGM dans TCCR0A et TCCR0B :

WGM02 (TCCR0B)WGM01 (TCCR0A)WGM00 (TCCR0A)Mode
010CTC
// Activer mode CTC
TCCR0A = (1<<WGM01);  // WGM01=1, WGM00=0 → CTC
TCCR0B = 0x01;         // prescaler = 1
OCR0A  = 49;           // valeur de comparaison
En CTC, le timer se remet à 0 immédiatement après la comparaison. OCR0A représente la valeur maximale que TCNT0 peut atteindre (TOP). La durée du cycle = (OCR0A + 1) ticks.
Pages 33–34 Exemple 8 — Signal carré 10 µs en mode CTC

Même objectif que l'exemple 3 : signal carré période 10 µs sur PORTB.3, XTAL = 10 MHz. Cette fois en mode CTC.

Calcul OCR0A :
Demi-période = 5 µs, T_tick = 0,1 µs → n = 50 ticks.
OCR0A = 50 − 1 = 49 (car le timer compte de 0 à OCR0A inclus, soit 50 valeurs).
; Assembleur
LDI  R20, 49
OUT  OCR0A, R20         ; TOP = 49
BEGIN:
LDI  R20, (1<<WGM01)
OUT  TCCR0A, R20        ; CTC mode
LDI  R20, 1
OUT  TCCR0B, R20        ; prescaler = 1
AGAIN:
SBIS TIFR0, OCF0A       ; attendre OCF0A
RJMP AGAIN
; stop, clear, toggle PB3
RJMP BEGIN
// C
OCR0A  = 49;
TCCR0A = (1<<WGM01);
TCCR0B = 1;
while((TIFR0 & (1<<OCF0A)) == 0) {}
TCCR0B = 0;
TIFR0 = 1<<OCF0A;
PORTB ^= 1<<3;
CTC vs Normal ici : En mode CTC, pas besoin de recharger TCNT0. Le matériel s'en charge automatiquement. Le code est plus propre et le timing plus précis.
Page 35 Timer2 — Comparaison avec Timer0

Le Timer2 est également un timer 8 bits, structurellement très similaire au Timer0, avec les mêmes registres :

Timer0Timer2Rôle
TCCR0A/BTCCR2A/BContrôle
TCNT0TCNT2Compteur
OCR0A/BOCR2A/BComparaison
TIFR0TIFR2Flags TOV, OCF
Différence clé : les prescalers de Timer2 sont différents. Timer2 offre des facteurs de division supplémentaires : ÷32 et ÷128, absents sur Timer0. De plus, Timer2 peut fonctionner de façon asynchrone (mode RTC avec cristal 32,768 kHz externe).
CS22:CS20Timer0Timer2
011÷64÷32
100÷256÷64
101÷1024÷128
110Ext. falling÷256
111Ext. rising÷1024
Page 36 Différences Timer0 vs Timer2 — Tableau prescalers

La différence principale réside dans les options de prescaler. Timer2 permet des granularités intermédiaires (÷32, ÷128) utiles pour certaines applications audio ou RTC.

Quand choisir Timer2 ?
• Quand Timer0 est réservé pour Arduino (millis()/delay()).
• Pour des applications nécessitant ÷32 ou ÷128.
• Pour la génération de tonalités audio (fréquences autour de quelques kHz).
• Pour un RTC avec cristal 32,768 kHz (mode asynchrone).

En dehors des prescalers, la programmation de Timer2 est identique à Timer0 : mêmes modes, même logique de flag, mêmes formules de calcul.

Page 37 Timer 1 — Architecture 16 bits

Le Timer1 est le seul timer 16 bits de l'ATmega328. Il compte de 0 à 65535 (0xFFFF).

Il possède 3 registres de contrôle (TCCR1A, TCCR1B, TCCR1C) et des registres 16 bits divisés en deux octets H/L :

RegistreComposantesRôle
TCNT1TCNT1H + TCNT1LCompteur 16 bits
OCR1AOCR1AH + OCR1ALComparaison A
OCR1BOCR1BH + OCR1BLComparaison B
ICR1ICR1H + ICR1LCapture d'entrée (ICP1)
TIFR1bit 0=TOV1, bit 1=OCF1A, bit 2=OCF1B, bit 5=ICF1Flags
Adresses > 0x3F : Les registres de Timer1 sont en mémoire I/O étendue. En assembleur, utiliser STS (store) et LDS (load) au lieu de OUT / IN.
Page 38 Timer1 — TCCR1A/B et modes WGM

Timer1 supporte 16 modes de fonctionnement définis par WGM13:WGM10 (4 bits répartis sur TCCR1A et TCCR1B).

ModeWGM13WGM12WGM11WGM10Description
00000Normal (TOP=0xFFFF)
40100CTC (TOP=OCR1A)
81000Phase&Freq correct PWM
121100CTC (TOP=ICR1)
141110Fast PWM (TOP=ICR1)
151111Fast PWM (TOP=OCR1A)
Mode CTC de Timer1 (mode 4) :
WGM13=0, WGM12=1 → TCCR1B |= (1<<WGM12).
WGM11=0, WGM10=0 → TCCR1A = 0.
Donc TCCR1B = 0x09 = WGM12 | CS10 (mode CTC, prescaler 1).
Page 39 Registre TEMP — Accès aux registres 16 bits

L'ATmega328 ne dispose que d'un bus de données 8 bits. Pour lire/écrire un registre 16 bits (TCNT1, OCR1A…), le matériel utilise un registre TEMP interne transparent au programmeur.

Règle d'écriture d'un registre 16 bits

  • Écrire d'abord l'octet fort (H) → stocké dans TEMP.
  • Écrire ensuite l'octet faible (L) → déclenche l'écriture atomique des deux octets.

Règle de lecture d'un registre 16 bits

  • Lire d'abord l'octet faible (L) → stocke automatiquement H dans TEMP.
  • Lire ensuite l'octet fort (H) → provient de TEMP.
// Écriture TCNT1 = 0xF353
TCNT1H = 0xF3;  // → TEMP = 0xF3
TCNT1L = 0x53;  // → écriture atomique

// Lecture TCNT1
uint8_t lo = TCNT1L;  // → TEMP = TCNT1H gelé
uint8_t hi = TCNT1H;  // → lu depuis TEMP
Toujours respecter l'ordre H avant L en écriture, L avant H en lecture ! Un ordre inversé donne une valeur 16 bits incorrecte à cause du registre TEMP.
Page 40 Exemple 5 — Timer1, délai 1 ms, mode Normal, XTAL = 10 MHz

Générer un signal carré de période 2 ms (toggle toutes les 1 ms) sur PB5 avec Timer1 en mode Normal.

Calcul :
T = 0,1 µs → n = 1 ms / 0,1 µs = 10 000 cycles.
TCNT1 = 65 536 − 10 000 = 55 536 = 0xD8F0.
; TCNT1H doit être écrit avant TCNT1L
LDI  R20, 0xD8
STS  TCNT1H, R20   ; TEMP = 0xD8
LDI  R20, 0xF0
STS  TCNT1L, R20   ; écriture atomique
LDI  R20, 0x00
STS  TCCR1A, R20   ; mode normal
LDI  R20, 0x01
STS  TCCR1B, R20   ; CS = 1 (clk/1)

Alternative C plus élégante : TCNT1 = -10000; (le compilateur gère les octets H et L automatiquement).

Page 41 Exemple Timer1 CTC — Délai 1 ms

Même délai de 1 ms, mais avec le mode CTC du Timer1.

Calcul :
OCR1A = 10 000 − 1 = 9 999 = 0x270F.
Mode CTC → TCCR1B = 0x09 (WGM12=1, CS10=1).
// C — sous-routine delay1ms
void delay1ms() {
    TCNT1  = 0;
    OCR1A  = 10000 - 1;
    TCCR1A = 0;
    TCCR1B = 0x09;   // CTC, clk/1
    while((TIFR1 & (1<<OCF1A)) == 0) {}
    TCCR1B = 0;
    TIFR1  = 1<<OCF1A;
}
En mode CTC, le timer se remet à 0 automatiquement à chaque match. Si on laisse le timer tourner sans l'arrêter, il génère des interruptions périodiques précises → base de temps idéale pour un système temps réel.
Pages 42–43 Fin Section 1 + Questions de révision

Questions et Réponses

#QuestionRéponse
1Combien de timers dans l'ATmega32 ?3
2Timer0 est-il 16 bits ?Faux — 8 bits
3Timer1 est-il 16 bits ?Vrai
4TCCR0 adressable au bit ?Faux — adressable par octet
5Mode Normal : débordement de __ à __ ?$FFFF/$FF → $0000/$00
6Mode CTC : débordement quand ?Quand TCNT = OCR1A
7TCNT1 pour 5 ms, XTAL = 8 MHz ?65536 − 40000 = 25536 = 0x63C0
TCNT1H=0x63, TCNT1L=0xC0
8TCNT0 pour 20 µs, XTAL = 1 MHz ?256 − 20 = 236 = 0xEC
Section 2 · Compteur
Page 44 Section 2 — Le Compteur (Counter Mode)

En mode compteur, le timer reçoit son signal d'horloge non plus de l'oscillateur interne mais d'une source externe : la broche T0 (PD4) pour Timer0 ou T1 (PD5) pour Timer1.

On configure CS = 110 (front descendant) ou CS = 111 (front montant).
Le timer s'incrémente à chaque front actif sur la broche Tn au lieu de chaque cycle d'horloge interne.
Page 45 Brochage — T0 sur PD4, T1 sur PD5

Sur l'ATmega328 (28 broches), les entrées d'horloge externe se trouvent sur :

TimerBrocheNuméro portBroche Arduino
Timer0T0 = PD4Pin 6D4
Timer1T1 = PD5Pin 11D5
Ces broches doivent être configurées en entrée (DDRx.bit = 0) avant de les utiliser comme source d'horloge. Le timer accepte des signaux de fréquence inférieure à f_XTAL/2 (critère de Nyquist).

Le prescaler peut également être utilisé en mode compteur : T0 → ÷8 → Timer0. Cela permettrait de ne compter qu'un événement sur 8.

Page 46 Compteur0 — Exemple : affichage sur PORTC (mode Normal)

Programme : compter les impulsions sur T0 (front descendant) et afficher TCNT0 sur PORTC.

Configuration : TCCR0B = 0x06 → CS02=1, CS01=1, CS00=0 → horloge externe, front descendant.
; Assembleur
CBI  DDRD, 4       ; T0 (PD4) = entrée
LDI  R20, 0xFF
OUT  DDRC, R20    ; PORTC = sortie
LDI  R20, 0x00
OUT  TCCR0A, R20  ; mode normal
LDI  R20, 0x06
OUT  TCCR0B, R20  ; ext. clock, falling edge
AGAIN:
IN   R20, TCNT0
OUT  PORTC, R20   ; afficher la valeur
RJMP AGAIN
// C
DDRC   = 0xFF;
TCCR0A = 0;
TCCR0B = 0x06;  // Count on falling edge
while (1) { PORTC = TCNT0; }
À chaque front descendant sur PD4, TCNT0 s'incrémente. En lisant TCNT0 en continu et en l'affichant sur PORTC, on obtient un afficheur de comptage d'impulsions en temps réel (8 LEDs sur PORTC).
Page 47 Compteur1 CTC — Sortie PC0 toutes les 100 impulsions

Programme : compteur1 en mode CTC, broche T1 (PD5), mettre PC0 à 1 toutes les 100 impulsions.

Logique :
OCR1A = 99 (de 0 à 99 = 100 impulsions).
CS = 110 → front descendant sur T1.
Mode CTC → TCCR1B = 0x0E (WGM12=1, CS12=1, CS11=1, CS10=0).
; Assembleur
CBI  DDRD, 5        ; T1 = entrée
SBI  DDRC, 0        ; PC0 = sortie
LDI  R20, 0
STS  OCR1AH, R20   ; OCR1A high = 0
LDI  R20, 99
STS  OCR1AL, R20   ; OCR1A = 99
LDI  R20, 0x00
STS  TCCR1A, R20
LDI  R20, 0x0E
STS  TCCR1B, R20   ; CTC + ext falling
// C
TCCR1A = 0;
TCCR1B = 0x0E;
OCR1A  = 99;
while (1) {
    while((TIFR1 & (1<<OCF1A)) == 0);
    TIFR1 = (1<<OCF1A);
    PORTC |=  (1<<0);
    PORTC &= ~(1<<0);
}
La brève impulsion sur PC0 (SBI puis CBI) peut être détectée par un oscilloscope ou connectée à une LED (mais la durée peut être trop courte pour être visible à l'œil nu — préférer une interruption).
Pages 48–49 Questions de révision — Section Compteur
#QuestionRéponse
1Source d'horloge si CS02:00 = 6 ?Oscillateur externe, front descendant (broche Tn)
2Broche d'entrée pour Compteur0 ?PD4 (T0) — pin 6 du 28-pin
3Pour utiliser PB1 comme horloge de Timer1 ?Configurer DDRB.0 = 0 (entrée) — T1 est sur PD5, pas PB1 !
4Peut-on choisir front montant ou descendant ?Oui — CS=110 (falling) ou CS=111 (rising)
Note : La réponse 3 dans le cours original mentionne DDRB.0=0 pour T1, mais T1 correspond à PD5. La confusion vient peut-être d'une version ATmega différente. Sur l'ATmega328, vérifier toujours le datasheet pour le mapping des broches alternatives.
Synthèse du chapitre
Résumé Récapitulatif complet — Timers AVR ATmega328

Formules essentielles

Mode Normal : TCNT = 256 − n   |   n = délai / T_tick
Mode CTC : OCRnA = n − 1   |   n = délai / T_tick
T_tick = prescaler / f_XTAL
Délai total = Délai_timer + Σ(cycles_instructions) × T

Choisir le bon mode

CritèreMode NormalMode CTC
Délai répétableManuel (recharger TCNT)Automatique
PrécisionLégère dérive possibleTrès précis
ComplexitéSimpleLégèrement plus complexe
PWM possibleNonOui (avec pin OC0A)

Choisir le bon timer

BesoinTimer recommandé
Délai court (< 25 µs à 10 MHz)Timer0 ou Timer2 (8 bits)
Délai moyen (ms)Timer1 (16 bits) ou Timer0 + prescaler
Délai long (dizaines de ms)Timer1 + prescaler 1024
Comptage d'événements (T0)Timer0 en mode compteur
Comptage d'événements (T1)Timer1 en mode compteur
Application RTCTimer2 asynchrone