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.
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'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.
; ============================================================ ; 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'è 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 >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.
; ============================================================ ; 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
- 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.
- 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.
- 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.
- 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