Lezione 32 di 43

Lezione 31

🎓 PIC16 — From Zero to Hero · Lezione 31 · Modulo 5

Timer1 e CCP avanzato — Misura e Generazione ⏱️

Timer1 a 16 bit come contatore esterno. Modulo CCP in Capture mode
per misurare frequenza e periodo. Compare mode per generare eventi con precisione al microsecondo.

🎯 Cosa impari in questa lezione

  • Timer1 a 16 bit: range 0–65535, overflow ogni ~65ms a 1MHz
  • Tre modalità Timer1: timer interno (Fosc/4), contatore esterno (RC0/T1CKI), oscillatore LP (cristallo 32kHz)
  • Registro T1CON: TMR1CS, T1SYNC, T1OSCEN, T1CKPS, TMR1ON
  • CCP in Capture mode: salva TMR1 ad ogni fronte su RC2 → misura periodi con risoluzione 1µs
  • CCP in Compare mode: genera interrupt + toggle RC2 quando TMR1 raggiunge CCPR1 → eventi precisi
  • Lettura atomica di TMR1H:TMR1L: problema del rollover durante la lettura dei due byte
  • Generatore di onda 50Hz su RC2 senza occupare la CPU (compare mode + ISR leggera)

📋 Scheda lezione

Lezione: 31 / 42
Modulo: 5 — Periferiche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 16 (Timer0), Lez. 27 (CCP/PWM)
Difficoltà: ★★★★☆
Periferiche: Timer1 + CCP Capture + Compare
Applicazioni: misura frequenza, eventi precisi


1 · Timer1 — tre modalità e T1CON

Timer1 è il timer a 16 bit del PIC16F877A: conta da 0 a 65535 prima dell’overflow, ovvero 256 volte più a lungo di Timer0 (8 bit). La versatilità di Timer1 sta nella sorgente di clock: può usare il clock interno (Fosc/4), un segnale esterno sul pin RC0/T1CKI, oppure un cristallo a bassa potenza da 32.768kHz collegato ai pin T1OSI/T1OSO — utile per applicazioni RTC (orologio in tempo reale) con consumo minimo. Il modulo CCP usa sempre Timer1 come base tempi nelle modalità Capture e Compare.

Timer1 — tre modalità operative a confronto Modalità Configurazione T1CON Uso tipico Timer interno conta cicli Fosc/4 TMR1CS=0, TMR1ON=1 prescaler libero base CCP capture/compare, misura tempi precisi Contatore esterno conta fronti su RC0/T1CKI TMR1CS=1, T1SYNC=0 TMR1ON=1 conta giri encoder, impulsi sensore Hall, misura portata Oscillatore LP esterno cristallo 32.768kHz su T1OSI/T1OSO T1OSCEN=1, TMR1CS=1 T1SYNC=1 orologio RTC, calendario, tick 30.5µs autonomo T1CON bit layout bit7:6=— bit5:4=T1CKPS1:0 bit3=T1OSCEN bit2=T1SYNC bit1=TMR1CS bit0=TMR1ON

2 · Codice — Timer1 come contatore esterno

⚠️ Lettura atomica di TMR1H:TMR1L: TMR1 è un registro a 16 bit diviso in due byte da 8 bit. Se durante la lettura TMR1L overflow da 0xFF a 0x00, TMR1H viene incrementato di 1 tra le due istruzioni MOVF. Il modo sicuro è leggere prima TMR1L, poi TMR1H: se TMR1L è piccolo (vicino a 0) e TMR1H è appena cambiato, il valore combinato è comunque coerente. Alternativa: disabilitare Timer1 (TMR1ON=0) durante la lettura, poi riabilitarlo.

; ============================================================
; Lezione 31 — Timer1: contatore esterno a 16 bit
; PIC16F877A @ 4 MHz
; T1CON (0x10) Bank0:
;   T1CKPS1:T1CKPS0 = prescaler (00=1:1 01=1:2 10=1:4 11=1:8)
;   T1OSCEN = oscillatore Timer1 abilitato
;   T1SYNC  = sincronizzazione clock esterno
;   TMR1CS  = sorgente clock: 0=Fosc/4, 1=pin RC0/T1CKI
;   TMR1ON  = avvia Timer1
; Modalità contatore esterno: TMR1CS=1, T1SYNC=0
; Impulsi su RC0 → TMR1H:TMR1L incrementa
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

    CBLOCK  0x20
COUNT_HI        ; copia alta contatore
COUNT_LO        ; copia bassa contatore
PREV_HI         ; valore precedente per delta
PREV_LO
DELTA_HI        ; differenza (impulsi nell&#x27;ultimo periodo)
DELTA_LO
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    BSF     STATUS,RP0
    BSF     TRISC,0            ; RC0 = T1CKI input
    CLRF    TRISB              ; PORTB output (display)
    BCF     STATUS,RP0

    ; T1CON: TMR1CS=1 (clock esterno RC0), T1SYNC=0,
    ; T1OSCEN=0, prescaler=1:1, TMR1ON=1
    MOVLW   b'00000111'        ; TMR1CS=1, T1SYNC=0, TMR1ON=1
    MOVWF   T1CON

    ; Azzera contatore
    CLRF    TMR1H
    CLRF    TMR1L

    CLRF    PREV_HI
    CLRF    PREV_LO

; ============================================================
; MAIN: ogni ~500ms campiona TMR1H:TMR1L, calcola delta
; Delta = numero di impulsi esterni nel periodo 500ms
; Mostra COUNT_LO su PORTB (impulsi basso byte)
; ============================================================
MAIN
    CALL    DELAY_500MS

    ; Leggi contatore a 16 bit in modo atomico
    ; (read HIGH prima per evitare rollover durante lettura)
    MOVF    TMR1L,W
    MOVWF   COUNT_LO
    MOVF    TMR1H,W
    MOVWF   COUNT_HI

    ; Calcola delta rispetto alla lettura precedente
    MOVF    COUNT_LO,W
    SUBWF   PREV_LO,W          ; PREV_LO - COUNT_LO
    MOVWF   DELTA_LO
    MOVF    COUNT_HI,W
    SUBWFB  PREV_HI,W          ; con borrow
    MOVWF   DELTA_HI

    ; NOTA: SUBWFB non è disponibile su tutte le versioni
    ; Alternativa: complemento a 2 manuale
    ; Aggiorna PREV
    MOVF    COUNT_LO,W
    MOVWF   PREV_LO
    MOVF    COUNT_HI,W
    MOVWF   PREV_HI

    ; Mostra byte basso del contatore su PORTB
    MOVF    COUNT_LO,W
    MOVWF   PORTB

    GOTO    MAIN

DELAY_500MS
    MOVLW   D'200'
    MOVWF   0x70
D5A MOVLW   D'250'
    MOVWF   0x71
D5B MOVLW   D'10'
    MOVWF   0x72
D5C DECFSZ  0x72,F
    GOTO    D5C
    DECFSZ  0x71,F
    GOTO    D5B
    DECFSZ  0x70,F
    GOTO    D5A
    RETURN

    END

3 · CCP Capture — misura di periodo e frequenza

In modalità Capture il modulo CCP “fotografa” il valore di TMR1 nel momento esatto in cui arriva un fronte sul pin RC2/CCP1 — senza alcun ritardo software. La differenza tra due catture consecutive è il periodo del segnale in tick di Timer1. Con Timer1 a 1MHz (Fosc/4 senza prescaler) ogni tick vale 1µs, quindi la risoluzione di misura è 1µs su periodi fino a 65ms.

CCP Capture mode — come funziona la misura di periodo TMR1 RC2 CCPR1=C1 CCPR1=C2 CCPR1=C3 T = C2 – C1 (tick) T = C3 – C2 (verifica) Con prescaler=1 e Fosc=4MHz: 1 tick = 1µs → F(Hz) = 1.000.000 / T
; ============================================================
; Lezione 31 — CCP in Capture mode: misura periodo e frequenza
; PIC16F877A @ 4 MHz
; CCP1 capture su RC2: salva TMR1 ad ogni fronte di salita
; Periodo = differenza tra due catture consecutive
; Frequenza = Fosc/4 / (prescaler × periodo_tick)
; Con Timer1 prescaler=1: 1 tick = 1µs → F = 1.000.000 / periodo
; CCP1CON (0x17): CCP1M=0100 → capture ogni fronte salita
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

    CBLOCK  0x20
W_SAVE
STATUS_SAVE
PCLATH_SAVE
CAPT_NOW_HI     ; cattura corrente
CAPT_NOW_LO
CAPT_PREV_HI    ; cattura precedente
CAPT_PREV_LO
PERIOD_HI       ; periodo in tick (= µs con prescaler=1)
PERIOD_LO
FREQ_KHZ        ; frequenza approssimata in kHz (0-255)
NEW_CAPTURE     ; flag: 1 = nuova cattura disponibile
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0004
; ============================================================
; ISR — CCP1 interrupt (CCP1IF in PIR1 bit2)
; ============================================================
ISR
    MOVWF   W_SAVE
    SWAPF   STATUS,W
    MOVWF   STATUS_SAVE

    BTFSS   PIR1,CCP1IF        ; interrupt CCP1?
    GOTO    ISR_EXIT
    BCF     PIR1,CCP1IF

    ; Leggi valore catturato CCPR1H:CCPR1L
    MOVF    CCPR1L,W
    MOVWF   CAPT_NOW_LO
    MOVF    CCPR1H,W
    MOVWF   CAPT_NOW_HI

    ; Calcola periodo: NOW - PREV (16 bit)
    MOVF    CAPT_PREV_LO,W
    SUBWF   CAPT_NOW_LO,W     ; NOW_LO - PREV_LO
    MOVWF   PERIOD_LO
    MOVF    CAPT_PREV_HI,W
    SUBWF   CAPT_NOW_HI,W     ; NOW_HI - PREV_HI (no borrow qui)
    BTFSS   STATUS,C
    ADDLW   0xFF               ; aggiusta se c&#x27;è stato borrow
    MOVWF   PERIOD_HI

    ; Aggiorna PREV
    MOVF    CAPT_NOW_LO,W
    MOVWF   CAPT_PREV_LO
    MOVF    CAPT_NOW_HI,W
    MOVWF   CAPT_PREV_HI

    ; Stima frequenza (approssimazione 8 bit):
    ; F(kHz) ≈ 1000 / PERIOD_LO  (valida per F > ~4kHz)
    ; Approssimazione: F_kHz = 250 / (PERIOD_LO >> 2)
    ; (250 × 4 = 1000, shift right 2 = divide by 4)
    ; Solo se PERIOD_HI = 0 (periodo < 256µs, F > ~4kHz)
    MOVF    PERIOD_HI,F
    BTFSS   STATUS,Z
    GOTO    LOW_FREQ           ; periodo lungo → F bassa
    ; Frequenza alta: approssima
    MOVF    PERIOD_LO,W
    ANDLW   0xFC               ; allinea a multipli di 4
    BTFSC   STATUS,Z
    GOTO    ISR_EXIT           ; evita divisione per 0
    ; F_kHz ≈ 250 / (P/4) = 1000 / P — stima grezza
    ; Usiamo tabella o shift: P=100µs→10kHz, P=50µs→20kHz
    ; Per semplicità: FREQ_KHZ = 0xFF (oltre range misura)
    MOVLW   0x01               ; placeholder &gt;10kHz
    MOVWF   FREQ_KHZ
    GOTO    SET_FLAG
LOW_FREQ
    ; PERIOD_HI > 0 → F < 4kHz: stima 1kHz scale
    ; F(Hz) ≈ 1.000.000 / (256 × PERIOD_HI + PERIOD_LO)
    ; Semplificazione: FREQ_KHZ = 0 (< 1kHz display)
    CLRF    FREQ_KHZ
SET_FLAG
    BSF     NEW_CAPTURE,0      ; segnala main loop

ISR_EXIT
    SWAPF   STATUS_SAVE,W
    MOVWF   STATUS
    SWAPF   W_SAVE,F
    SWAPF   W_SAVE,W
    RETFIE

    ORG     0x0060
INIT
    BSF     STATUS,RP0
    BSF     TRISC,2            ; RC2/CCP1 input
    CLRF    TRISB
    ; Abilita CCP1IE
    MOVLW   b'00000100'        ; CCP1IE=1 (bit2 PIE1)
    MOVWF   PIE1
    BCF     STATUS,RP0

    ; Timer1: Fosc/4, prescaler=1 → 1 tick=1µs
    MOVLW   b'00000001'        ; TMR1ON=1, TMR1CS=0 (interno)
    MOVWF   T1CON
    CLRF    TMR1H
    CLRF    TMR1L

    ; CCP1: capture ogni fronte di salita
    MOVLW   b'00000101'        ; CCP1M=0101 → 1× fronte salita
    MOVWF   CCP1CON

    CLRF    NEW_CAPTURE
    CLRF    CAPT_PREV_HI
    CLRF    CAPT_PREV_LO

    MOVLW   b'11000000'        ; GIE=1, PEIE=1
    MOVWF   INTCON

; ============================================================
; MAIN: quando arriva nuova cattura, mostra PERIOD_LO su PORTB
; ============================================================
MAIN
    BTFSS   NEW_CAPTURE,0
    GOTO    MAIN
    BCF     NEW_CAPTURE,0

    ; Mostra periodo in µs (byte basso) su PORTB
    MOVF    PERIOD_LO,W
    MOVWF   PORTB

    GOTO    MAIN

    END

4 · CCP Compare — generatore di eventi precisi a 50Hz

In modalità Compare il modulo CCP confronta continuamente TMR1 con CCPR1H:CCPR1L. Quando i valori coincidono, genera un evento hardware (toggle del pin RC2) e un interrupt — senza nessuna latenza di polling. Nella ISR si aggiorna CCPR1 sommando il periodo desiderato al valore corrente: in questo modo gli eventi sono distribuiti nel tempo con precisione assoluta, senza deriva accumulata come accade con i delay software.

CCP Compare mode — toggle RC2 ogni 10ms → onda 50Hz TMR1 CCPR1 RC2 match+toggle match+toggle match+toggle match+toggle 10ms → 50Hz 20ms = ciclo completo
; ============================================================
; Lezione 31 — CCP in Compare mode: generatore di eventi precisi
; PIC16F877A @ 4 MHz
; CCP1 compare: quando TMR1 raggiunge CCPR1H:CCPR1L → evento
; Modalità: CCP1M=1010 → toggle RC2 su match + interrupt
; Timer1 a 16 bit, prescaler=1 → 1 tick=1µs
; CCPR1 = 10000 → toggle ogni 10ms → onda quadra 50Hz su RC2
; ============================================================
    LIST    P=16F877A
    #INCLUDE <p16f877a.inc>
    __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF

; CCPR1 per 10ms: 10000 = 0x2710
#DEFINE PERIOD_HI  0x27
#DEFINE PERIOD_LO  0x10

    CBLOCK  0x20
W_SAVE
STATUS_SAVE
NEXT_HI         ; prossimo valore compare (accumulato)
NEXT_LO
EVENT_COUNT     ; contatore eventi
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0004
; ============================================================
; ISR — CCP1 compare match
; ============================================================
ISR
    MOVWF   W_SAVE
    SWAPF   STATUS,W
    MOVWF   STATUS_SAVE

    BTFSS   PIR1,CCP1IF
    GOTO    ISR_EXIT
    BCF     PIR1,CCP1IF

    ; Aggiorna prossimo valore compare: NEXT += PERIOD
    MOVLW   PERIOD_LO
    ADDWF   NEXT_LO,F
    BTFSC   STATUS,C
    INCF    NEXT_HI,F
    MOVLW   PERIOD_HI
    ADDWF   NEXT_HI,F

    ; Scrivi nuovo valore in CCPR1
    MOVF    NEXT_LO,W
    MOVWF   CCPR1L
    MOVF    NEXT_HI,W
    MOVWF   CCPR1H

    INCF    EVENT_COUNT,F      ; conta toggle

ISR_EXIT
    SWAPF   STATUS_SAVE,W
    MOVWF   STATUS
    SWAPF   W_SAVE,F
    SWAPF   W_SAVE,W
    RETFIE

    ORG     0x0050
INIT
    BSF     STATUS,RP0
    BCF     TRISC,2            ; RC2/CCP1 output (compare toggle)
    CLRF    TRISB
    MOVLW   b'00000100'        ; CCP1IE=1
    MOVWF   PIE1
    BCF     STATUS,RP0

    ; Timer1: clock interno Fosc/4, prescaler=1, avvia
    MOVLW   b'00000001'
    MOVWF   T1CON
    CLRF    TMR1H
    CLRF    TMR1L

    ; Primo valore compare: 10ms dopo
    MOVLW   PERIOD_HI
    MOVWF   NEXT_HI
    MOVWF   CCPR1H
    MOVLW   PERIOD_LO
    MOVWF   NEXT_LO
    MOVWF   CCPR1L

    ; CCP1CON: compare mode, toggle RC2 su match
    ; CCP1M=1010 → toggle + interrupt
    MOVLW   b'00001010'
    MOVWF   CCP1CON

    CLRF    EVENT_COUNT
    CLRF    PORTC              ; RC2 parte basso

    MOVLW   b'11000000'        ; GIE=1, PEIE=1
    MOVWF   INTCON

; ============================================================
; MAIN: ogni 100 toggle (= 1 secondo), lampeggia RB0
; EVENT_COUNT = 100 → 100 × 10ms = 1s
; ============================================================
MAIN
    MOVF    EVENT_COUNT,W
    SUBLW   D'100'
    BTFSS   STATUS,Z           ; Z=1 quando EVENT_COUNT=100
    GOTO    MAIN

    CLRF    EVENT_COUNT
    MOVLW   b'00000001'
    XORWF   PORTB,F            ; toggle RB0

    GOTO    MAIN

    END

🧪 Prova tu

  1. Carica il contatore esterno. Nel simulatore, genera 10 impulsi su RC0 (T1CKI). Verifica che TMR1L=10 e TMR1H=0. Genera altri 246 impulsi: TMR1L=0, TMR1H=1 (256 totali). Poi genera 65280 impulsi in totale: TMR1H=0xFF, TMR1L=0x00. Il prossimo impulso causerà overflow a TMR1=0x0000 — osserva.
  2. Carica il Capture. Applica un segnale con periodo 100µs (10kHz) su RC2. Ogni cattura deve dare PERIOD_LO≈100, PERIOD_HI=0. Calcola F: 1.000.000/100 = 10kHz. Poi prova 500µs (2kHz): PERIOD_LO=0xF4(244), PERIOD_HI=0x01 → periodo=500µs.
  3. Carica il Compare. Verifica che RC2 faccia toggle ogni 10ms (controlla EVENT_COUNT++). Dopo 100 toggle (1 secondo) RB0 deve lampeggiare. Modifica PERIOD_HI:LO per ottenere toggle ogni 5ms (CCPR=5000=0x1388) → onda 100Hz su RC2.
  4. Sfida: usa il Compare per generare un segnale PWM software a 1kHz con duty cycle variabile. CCPR_HIGH = T_on (in µs), CCPR_LOW = T_period – T_on. Nella ISR, alterna tra caricare T_on (RC2 alto) e T_off (RC2 basso), usando un flag di stato. Confronta la qualità del segnale con il PWM hardware della lezione 27.

❓ Quiz rapido

1. Con Timer1 in modalità contatore esterno (TMR1CS=1) e prescaler 1:8, quanti impulsi esterni servono per un overflow completo da 0 a 65535?

Mostra risposta

Il prescaler divide gli impulsi prima che raggiungano il contatore TMR1. Con prescaler 1:8, ogni 8 impulsi esterni TMR1 avanza di 1. Per un overflow completo (da 0 a 65535 + 1 = 65536 incrementi di TMR1) servono 65536 × 8 = 524.288 impulsi esterni. Questo è utile per contare eventi rari su lunghi periodi: con prescaler 1:8 si possono contare fino a mezzo milione di impulsi prima di perdere dati per overflow (occorre gestire l’interrupt TMR1IF per contare gli overflow e avere un contatore a 32+ bit).


2. Nel Capture mode, quale problema si verifica se il segnale da misurare ha un periodo maggiore di 65535µs (65.5ms)?

Mostra risposta

Se il periodo supera 65535µs, TMR1 fa overflow (da 0xFFFF a 0x0000) una o più volte tra una cattura e la successiva. La differenza C2-C1 calcolata sarà sbagliata: darà un valore piccolo invece del periodo reale. Soluzione: contare gli overflow di TMR1 tramite l’interrupt TMR1IF. Se durante la misura si verificano N overflow, il periodo reale è C2-C1 + N×65536. Alternativamente si usa un prescaler maggiore (T1CKPS=1:8) che riduce la velocità di conteggio di 8 volte, estendendo il range a ~524ms ma riducendo la risoluzione a 8µs.


3. Nel Compare mode, perché si somma il periodo a NEXT invece di ricaricare CCPR1 con un valore fisso a ogni interrupt?

Mostra risposta

Ricaricare con un valore fisso causerebbe deriva temporale. Supponiamo CCPR1=10000: quando arriva il match, TMR1 potrebbe già essere a 10003 (3µs di latenza ISR). Se si ricarica CCPR1=10000, il prossimo match avverrà a 20000, ma con 3µs di ritardo incorporato che si somma a ogni ciclo → dopo 1000 cicli l’errore accumulato è 3ms. Con l’addizione NEXT += PERIOD, il confronto avviene sempre a 10000, 20000, 30000… — la latenza ISR ritarda solo la risposta software (toggle del pin), ma il prossimo evento hardware è pianificato in modo assoluto senza accumulo di errore.

Prossima lezione →

Lezione 32 — Progetto Modulo 5: stazione meteo
Integrazione di ADC, UART, Timer1 e comparatori in un unico sistema: lettura di temperatura, umidità e pressione, trasmissione dati via seriale e allarmi su soglia.

© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 31