Lezione 39
🎓 PIC16 — From Zero to Hero · Lezione 39 · Modulo 7
Progetto 2: Contatore di Frequenza 📡
Timer1 come contatore eventi su T1CKI · Timer0 genera finestra di 1 secondo preciso
LCD HD44780 16×2 via PCF8574 I²C · Autorange automatico 1Hz–500kHz · Double-Dabble BCD
🎯 Cosa costruisci in questa lezione
- Timer1 come contatore: TMR1CS=1 fa contare TMR1 su ogni fronte di salita di T1CKI (RC0) — non usa il clock interno ma eventi esterni. In 1 secondo il conteggio finale = frequenza in Hz
- Finestra di misura 1s precisa: Timer0 con prescaler 1:256 genera overflow ogni 65.5ms. ISR conta 15 overflow completi + 1 parziale (TMR0 precaricato a 115) per ottenere esattamente 1.000.000µs
- Divisione 16×8 bit con algoritmo shift-and-subtract per calcolare kHz e resto Hz
- LCD HD44780 in modalità 4-bit via PCF8574: l’expander I²C converte 1 byte I²C in 8 GPIO per il LCD. Ogni byte al LCD richiede 6 transazioni I²C (2 nibble × 3 messaggi ciascuno: EN=0, EN=1, EN=0)
- Inizializzazione LCD: sequenza a 8-bit forzata × 3, poi switch a 4-bit, function set, display on, clear, entry mode
- Double-Dabble BIN→BCD: converte un numero binario a 16 bit in 5 cifre BCD decimali con 16 iterazioni di shift-left + add-3-se-≥5
- Autorange: prescaler TMR1 regolato automaticamente (1:1, 1:2, 1:4, 1:8) per adattare il range di misura. Se overflow → raddoppia prescaler; se conteggio basso → dimezza prescaler
📋 Scheda progetto
Lezione: 39 / 42
Modulo: 7 — Progetti Completi
Device: PIC16F877A
Prerequisiti: Lez. 20 (TMR0/1), 28 (I²C), 35 (ISR)
Difficoltà: ★★★★★
Hardware: LCD 16×2, PCF8574, segnale test
Range: 1Hz – ~500kHz (con autorange)
Modulo 1 · Timer1 contatore + base tempi 1 secondo
⏱️ Calcolo finestra 1s esatta: TMR0 con clock 1MHz (prescaler 1:4 del clock 4MHz) e prescaler 1:256 → tick ogni 256µs, overflow ogni 65536µs = 65.536ms. Per 1s: 1000000 / 65536 = 15.259 overflow. La soluzione è contare 15 overflow completi poi lasciare scorrere 1000000 − 15×65536 = 36160µs aggiuntivi. Questo si ottiene precariando TMR0 con 256 − (36160/256) = 256 − 141 = 115 al 15° overflow, così il 16° scatta esattamente a 1s.
; ============================================================ ; Lezione 39 — Contatore di frequenza: Timer1 + Timer0 ; PIC16F877A @ 4 MHz ; Principio: conta impulsi su T1CKI (RC0) per esattamente 1s ; Timer0 genera base tempi 1s via overflow + conteggio overflow ; Timer1 conta fronte di salita su RC0 (modalità contatore) ; Range: 1Hz – 65535Hz senza overflow TMR1 ; Per frequenze > 65kHz serve prescaler TMR1 ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Risultato misura FREQ_H ; byte alto frequenza misurata FREQ_L ; byte basso (Hz esatti, max 65535) FREQ_KHZ ; frequenza in kHz (per display) FREQ_HZ_R ; resto Hz dopo divisione per kHz ; Timing base tempi TMR0_OVF ; contatore overflow TMR0 GATE_ACTIVE ; 1 = finestra misura aperta ; Display LCD (via I²C, vedi Modulo 2) LCD_LINE1 ; buffer linea 1 (8 char: "F=XXXXXHZ") LCD_LINE2 ; buffer linea 2 (8 char: "T=XXXXus ") ; Calcolo periodo PERIOD_H ; periodo in µs (alto) PERIOD_L ; periodo basso ; Variabili calcolo DIV_H DIV_L DIV_DIV QUOT_H QUOT_L REM CNT TMP FLAGS ; bit0=misura_pronta, bit1=overflow_tmr1 ENDC ORG 0x0000 GOTO INIT ORG 0x0004 ; ============================================================ ; ISR — gestisce overflow TMR0 (base tempi 1s) ; TMR0 @ prescaler 1:256: overflow ogni 256×256=65536µs ≈ 65.5ms ; Per 1s esatto: 1000000µs / 65536µs ≈ 15.26 overflow ; Soluzione: TMR0_OVF conta 15 overflow + correzione residuo ; Correzione: 1s = 15×65536 + 36160µs → 15° overflow a 36160µs ; TMR0 precaricato a 256 - 36160/256 = 256 - 141 = 115 ; In pratica: 15 overflow completi poi 1 parziale ; ============================================================ ISR BTFSS INTCON,T0IF RETFIE BCF INTCON,T0IF INCF TMR0_OVF,F ; Dopo 15 overflow completi: imposta TMR0 per overflow parziale MOVF TMR0_OVF,W SUBLW D'15' BTFSS STATUS,Z GOTO ISR_CHECK_16 ; 15° overflow: precarica TMR0 per residuo (141 tick) MOVLW D'115' ; 256-141=115 MOVWF TMR0 GOTO ISR_EXIT ISR_CHECK_16 ; 16° overflow: un secondo trascorso MOVF TMR0_OVF,W SUBLW D'16' BTFSS STATUS,Z GOTO ISR_EXIT ; Leggi e ferma Timer1 BCF T1CON,TMR1ON ; ferma TMR1 MOVF TMR1L,W MOVWF FREQ_L MOVF TMR1H,W MOVWF FREQ_H ; Reset per prossima misura CLRF TMR1H CLRF TMR1L CLRF TMR0_OVF BSF T1CON,TMR1ON ; riavvia TMR1 BSF FLAGS,0 ; misura pronta ; Controlla overflow TMR1 (FREQ_H:FREQ_L = 0xFFFF?) MOVF FREQ_H,W ANDWF FREQ_L,W ADDLW 1 BTFSC STATUS,Z BSF FLAGS,1 ; overflow: freq > 65kHz ISR_EXIT RETFIE ORG 0x0060 INIT ; Configura Timer1 come contatore esterno su T1CKI (RC0) BSF STATUS,RP0 BSF TRISC,0 ; RC0 = T1CKI input MOVLW b'10111111' ; TMR0 prescaler 1:256 (PS=111, PSA=0) MOVWF OPTION_REG CLRF TRISB CLRF TRISD BCF STATUS,RP0 ; T1CON: TMR1CS=1 (ext clock), T1CKPS=00 (prescaler 1:1), TMR1ON=1 MOVLW b'00000111' ; TMR1CS=1, T1SYNC=1 (no sync), TMR1ON=1 MOVWF T1CON ; Abilita interrupt TMR0 MOVLW b'10100000' ; GIE=1, T0IE=1 MOVWF INTCON CLRF TMR0_OVF CLRF FREQ_H CLRF FREQ_L CLRF FLAGS CLRF TMR1H CLRF TMR1L ; ============================================================ ; MAIN — attende misura pronta, la elabora e mostra ; ============================================================ MAIN BTFSS FLAGS,0 ; misura pronta? GOTO MAIN BCF FLAGS,0 ; Controlla overflow BTFSC FLAGS,1 GOTO SHOW_OVERFLOW BCF FLAGS,1 ; Calcola kHz e Hz residui ; FREQ_H:FREQ_L / 1000 → QUOT=kHz, REM=Hz residui MOVF FREQ_H,W MOVWF DIV_H MOVF FREQ_L,W MOVWF DIV_L MOVLW D'100' ; dividi per 100 → risultato in centinaia MOVWF DIV_DIV CALL DIV16x8 ; QUOT_L = kHz approssimati (se FREQ < 25600) ; Per semplicità: mostra FREQ_H:FREQ_L direttamente ; Output diretto: PORTB = FREQ_L, PORTD = FREQ_H MOVF FREQ_L,W MOVWF PORTB MOVF FREQ_H,W MOVWF PORTD ; Aggiorna LCD (se disponibile) CALL DISPLAY_FREQ GOTO MAIN SHOW_OVERFLOW ; Frequenza > 65kHz: segnala con tutti 1 su PORTB MOVLW 0xFF MOVWF PORTB MOVWF PORTD BCF FLAGS,1 GOTO MAIN ; ============================================================ ; DIV16x8 — divide DIV_H:DIV_L per DIV_DIV ; Risultato: QUOT_H:QUOT_L, resto in REM ; ============================================================ DIV16x8 CLRF QUOT_H CLRF QUOT_L CLRF REM MOVLW D'16' MOVWF CNT DIV_LOOP BCF STATUS,C RLF DIV_L,F RLF DIV_H,F RLF REM,F MOVF DIV_DIV,W SUBWF REM,W BTFSS STATUS,C GOTO DIV_NO MOVWF REM BCF STATUS,C RLF QUOT_L,F RLF QUOT_H,F BSF QUOT_L,0 GOTO DIV_NXT DIV_NO BCF STATUS,C RLF QUOT_L,F RLF QUOT_H,F DIV_NXT DECFSZ CNT,F GOTO DIV_LOOP RETURN ; Stub display DISPLAY_FREQ RETURN END
Modulo 2 · LCD HD44780 via PCF8574 I²C
🔌 PCF8574 come bridge I²C→LCD: il chip PCF8574 ha 8 pin GPIO configurabili via I²C. Collegando P7:P4 ai dati D7:D4 del LCD e P2:P0 ai controlli EN/RW/RS si ottiene il LCD completo con soli 2 fili (SDA, SCL). Il costo è la velocità: ogni nibble richiede 3 scritture I²C (dato con EN=0, dato con EN=1, dato con EN=0) → 6 transazioni I²C per 1 byte LCD. A 100kHz I²C il throughput è ~1700 byte LCD/s — più che sufficiente per aggiornare un display di testo.
; ============================================================ ; Lezione 39 — LCD HD44780 16×2 via I²C (PCF8574 expander) ; PIC16F877A @ 4 MHz ; PCF8574: expander I/O a 8 bit su I²C, indirizzo 0x27 (A2:A0=111) ; Mappa pin PCF8574 → LCD: ; P7=D7, P6=D6, P5=D5, P4=D4 (modo 4-bit) ; P3=BL (backlight), P2=EN, P1=RW, P0=RS ; RS=0: comando, RS=1: dato ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF ; I²C pin (open-drain, pull-up 4.7kΩ) #DEFINE SCL_H BSF TRISC,3 #DEFINE SCL_L BCF TRISC,3 #DEFINE SDA_H BSF TRISC,4 #DEFINE SDA_L BCF TRISC,4 #DEFINE SDA_IN PORTC,4 #DEFINE LCD_ADDR 0x27 ; PCF8574 indirizzo #DEFINE LCD_BL 0x08 ; backlight bit #DEFINE LCD_EN 0x04 ; enable bit #DEFINE LCD_RW 0x02 ; R/W bit (0=write) #DEFINE LCD_RS 0x01 ; RS bit CBLOCK 0x20 ; I²C I2C_DATA I2C_CNT I2C_ACK ; LCD LCD_CMD ; comando o dato da inviare LCD_IS_DATA ; 0=comando, 1=dato LCD_NIBBLE ; nibble corrente ; Display content DISP_FREQ_HH ; decine di kHz DISP_FREQ_H ; kHz unità DISP_FREQ_L ; centinaia Hz DISP_FREQ_U ; decine Hz DISP_FREQ_T ; unità Hz FREQ_H ; frequenza misurata (byte alto) FREQ_L ; frequenza (byte basso) ; Calcolo BCD BCD_VAL_H BCD_VAL_L BCD_D4 ; decine di migliaia BCD_D3 ; migliaia BCD_D2 ; centinaia BCD_D1 ; decine BCD_D0 ; unità TMP DLY ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; I2C primitive (inline per velocità) ; ============================================================ I2C_START BSF STATUS,RP0 SDA_H SCL_H BCF STATUS,RP0 NOP NOP NOP NOP NOP ; setup 5µs BSF STATUS,RP0 SDA_L BCF STATUS,RP0 BCF PORTC,4 NOP NOP NOP NOP NOP BSF STATUS,RP0 SCL_L BCF STATUS,RP0 RETURN I2C_STOP BSF STATUS,RP0 SDA_L SCL_H BCF STATUS,RP0 BCF PORTC,4 NOP NOP NOP NOP NOP BSF STATUS,RP0 SDA_H BCF STATUS,RP0 NOP NOP NOP NOP NOP RETURN I2C_SEND_BYTE ; Invia I2C_DATA, scarta ACK MOVLW D'8' MOVWF I2C_CNT I2C_SB_LOOP BSF STATUS,RP0 BTFSS I2C_DATA,7 SDA_L BTFSC I2C_DATA,7 SDA_H BCF STATUS,RP0 BTFSS I2C_DATA,7 BCF PORTC,4 NOP NOP NOP BSF STATUS,RP0 SCL_H BCF STATUS,RP0 NOP NOP NOP NOP NOP BSF STATUS,RP0 SCL_L BCF STATUS,RP0 RLF I2C_DATA,F DECFSZ I2C_CNT,F GOTO I2C_SB_LOOP ; ACK clock BSF STATUS,RP0 SDA_H SCL_H BCF STATUS,RP0 NOP NOP NOP NOP BSF STATUS,RP0 SCL_L BCF STATUS,RP0 RETURN ; ============================================================ ; PCF8574_WRITE — invia byte al PCF8574 via I²C ; ============================================================ PCF8574_WRITE ; I2C_DATA = byte da inviare al PCF8574 MOVWF TMP ; salva dato CALL I2C_START MOVLW (LCD_ADDR << 1) ; 0x4E (write) MOVWF I2C_DATA CALL I2C_SEND_BYTE MOVF TMP,W MOVWF I2C_DATA CALL I2C_SEND_BYTE CALL I2C_STOP RETURN ; ============================================================ ; LCD_PULSE_EN — invia nibble con pulse EN ; LCD_NIBBLE = nibble (bit7:4) | BL | RW | RS ; ============================================================ LCD_PULSE_EN MOVF LCD_NIBBLE,W IORLW LCD_EN ; EN=1 CALL PCF8574_WRITE NOP NOP NOP ; EN pulse width ~1µs MOVF LCD_NIBBLE,W ANDLW ~LCD_EN ; EN=0 CALL PCF8574_WRITE RETURN ; ============================================================ ; LCD_SEND — invia LCD_CMD come comando o dato (LCD_IS_DATA) ; Modalità 4-bit: invia nibble alto poi nibble basso ; ============================================================ LCD_SEND ; Nibble alto (bit7:4) SWAPF LCD_CMD,W ; nibble alto → bit3:0 ANDLW 0xF0 ; isola IORLW LCD_BL ; backlight on BTFSC LCD_IS_DATA,0 IORLW LCD_RS ; RS=1 se dato MOVWF LCD_NIBBLE CALL LCD_PULSE_EN ; Nibble basso (bit3:0) MOVF LCD_CMD,W ANDLW 0xF0 ; nibble basso shiftato IORLW LCD_BL BTFSC LCD_IS_DATA,0 IORLW LCD_RS MOVWF LCD_NIBBLE CALL LCD_PULSE_EN ; Delay 37µs (tempo esecuzione LCD) MOVLW D'12' MOVWF DLY LCDW DECFSZ DLY,F GOTO LCDW RETURN ; ============================================================ ; LCD_CMD_SEND — invia comando (RS=0) ; ============================================================ LCD_CMD_SEND MOVWF LCD_CMD CLRF LCD_IS_DATA CALL LCD_SEND RETURN ; ============================================================ ; LCD_DATA_SEND — invia carattere ASCII (RS=1) ; ============================================================ LCD_DATA_SEND MOVWF LCD_CMD MOVLW 1 MOVWF LCD_IS_DATA CALL LCD_SEND RETURN ; ============================================================ ; LCD_INIT — sequenza inizializzazione HD44780 4-bit ; ============================================================ LCD_INIT ; Attendi >40ms dopo power-on MOVLW D'50' MOVWF DLY LI1 CALL PCF8574_WRITE ; 0x30 × 3 per forzare modalità 8-bit poi 4-bit DECFSZ DLY,F GOTO LI1 ; Reset a 8-bit (3 volte) MOVLW 0x30 CALL PCF8574_WRITE MOVLW 0x34 ; con EN CALL PCF8574_WRITE MOVLW 0x30 CALL PCF8574_WRITE ; Ripeti ×3 MOVLW 0x30 CALL PCF8574_WRITE MOVLW 0x34 CALL PCF8574_WRITE MOVLW 0x30 CALL PCF8574_WRITE MOVLW 0x30 CALL PCF8574_WRITE MOVLW 0x34 CALL PCF8574_WRITE MOVLW 0x30 CALL PCF8574_WRITE ; Passa a 4-bit MOVLW 0x20 CALL PCF8574_WRITE MOVLW 0x24 CALL PCF8574_WRITE MOVLW 0x20 CALL PCF8574_WRITE ; Function set: 4-bit, 2 righe, 5×8 dots MOVLW 0x28 CALL LCD_CMD_SEND ; Display on, cursor off, blink off MOVLW 0x0C CALL LCD_CMD_SEND ; Clear display MOVLW 0x01 CALL LCD_CMD_SEND ; Ritardo clear (1.52ms) MOVLW D'50' MOVWF DLY LI2 NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP DECFSZ DLY,F GOTO LI2 ; Entry mode: increment, no shift MOVLW 0x06 CALL LCD_CMD_SEND RETURN ; ============================================================ ; BIN16_TO_BCD5 — converte BCD_VAL_H:BCD_VAL_L in 5 cifre BCD ; Algoritmo double-dabble (shift-and-add-3) ; ============================================================ BIN16_TO_BCD5 CLRF BCD_D4 CLRF BCD_D3 CLRF BCD_D2 CLRF BCD_D1 CLRF BCD_D0 MOVLW D'16' MOVWF TMP BCD_LOOP ; Controlla e aggiusta ogni cifra BCD (add 3 se ≥ 5) MOVLW D'5' SUBWF BCD_D0,W BTFSC STATUS,C ADDLW D'3' ; BCD_D0 ≥ 5: aggiungi 3 BTFSC STATUS,C MOVWF BCD_D0 MOVLW D'5' SUBWF BCD_D1,W BTFSC STATUS,C ADDLW D'3' BTFSC STATUS,C MOVWF BCD_D1 MOVLW D'5' SUBWF BCD_D2,W BTFSC STATUS,C ADDLW D'3' BTFSC STATUS,C MOVWF BCD_D2 MOVLW D'5' SUBWF BCD_D3,W BTFSC STATUS,C ADDLW D'3' BTFSC STATUS,C MOVWF BCD_D3 ; Shift left tutto (BCD_D4:D3:D2:D1:D0 << 1, MSB da BCD_VAL_H:L) BCF STATUS,C RLF BCD_VAL_L,F RLF BCD_VAL_H,F RLF BCD_D0,F RLF BCD_D1,F RLF BCD_D2,F RLF BCD_D3,F RLF BCD_D4,F DECFSZ TMP,F GOTO BCD_LOOP RETURN ; ============================================================ ; DISPLAY_FREQUENCY — mostra FREQ_H:FREQ_L su LCD ; Linea 1: "F= XXXXXHZ" Linea 2: "T= XXXXXus" ; ============================================================ DISPLAY_FREQUENCY ; Converti in BCD MOVF FREQ_H,W MOVWF BCD_VAL_H MOVF FREQ_L,W MOVWF BCD_VAL_L CALL BIN16_TO_BCD5 ; Posiziona cursore linea 1 col 0 MOVLW 0x80 ; DDRAM addr 0x00 CALL LCD_CMD_SEND ; "F=" MOVLW 'F' CALL LCD_DATA_SEND MOVLW '=' CALL LCD_DATA_SEND ; 5 cifre (sopprime zeri iniziali) ; BCD_D4 MOVF BCD_D4,W BTFSC STATUS,Z GOTO SKIP4 ADDLW '0' CALL LCD_DATA_SEND GOTO SHOW3 SKIP4 MOVLW ' ' CALL LCD_DATA_SEND SHOW3 MOVF BCD_D3,W ADDLW '0' CALL LCD_DATA_SEND MOVF BCD_D2,W ADDLW '0' CALL LCD_DATA_SEND MOVF BCD_D1,W ADDLW '0' CALL LCD_DATA_SEND MOVF BCD_D0,W ADDLW '0' CALL LCD_DATA_SEND MOVLW 'H' CALL LCD_DATA_SEND MOVLW 'z' CALL LCD_DATA_SEND RETURN ORG 0x01C0 INIT BSF STATUS,RP0 BSF TRISC,3 BSF TRISC,4 CLRF TRISB CLRF TRISD BCF STATUS,RP0 BCF PORTC,3 BCF PORTC,4 CALL LCD_INIT ; Messaggio iniziale MOVLW 0x80 CALL LCD_CMD_SEND MOVLW 'F' CALL LCD_DATA_SEND MOVLW 'r' CALL LCD_DATA_SEND MOVLW 'e' CALL LCD_DATA_SEND MOVLW 'q' CALL LCD_DATA_SEND MOVLW ':' CALL LCD_DATA_SEND ; Demo: mostra 1234 Hz MOVLW 0x04 ; 1234 = 0x04D2 MOVWF FREQ_H MOVLW 0xD2 MOVWF FREQ_L CALL DISPLAY_FREQUENCY MAIN GOTO MAIN END
Modulo 3 · Autorange e progetto completo
; ============================================================ ; Lezione 39 — Autorange e progetto completo ; PIC16F877A @ 4 MHz ; Autorange: seleziona automaticamente la scala di lettura ; Range 1: 1Hz–999Hz → mostra "XXX Hz" ; Range 2: 1kHz–9999Hz → mostra "X.XXX kHz" ; Range 3: 10kHz–65kHz → mostra "XX.XX kHz" ; Overflow (>65kHz): attiva prescaler TMR1 1:8 → range ×8 ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 FREQ_H ; conteggio grezzo (byte alto) FREQ_L ; conteggio grezzo (byte basso) PRESCALE ; prescaler attivo: 1, 8, o 64 RANGE ; 0=Hz, 1=kHz basso, 2=kHz alto, 3=overflow DISP_VAL_H ; valore scalato per display DISP_VAL_L TMR0_OVF FLAGS ; bit0=pronto, bit1=overflow ; BCD BCD_D4 BCD_D3 BCD_D2 BCD_D1 BCD_D0 BCD_IN_H BCD_IN_L ; LCD LCD_CMD LCD_IS_DATA LCD_NIBBLE TMP DLY CNT ENDC ORG 0x0000 GOTO INIT ORG 0x0004 ISR BTFSS INTCON,T0IF RETFIE BCF INTCON,T0IF INCF TMR0_OVF,F MOVF TMR0_OVF,W SUBLW D'15' BTFSS STATUS,Z GOTO ISR_CHK16 MOVLW D'115' MOVWF TMR0 GOTO ISR_EXIT ISR_CHK16 MOVF TMR0_OVF,W SUBLW D'16' BTFSS STATUS,Z GOTO ISR_EXIT BCF T1CON,TMR1ON MOVF TMR1L,W MOVWF FREQ_L MOVF TMR1H,W MOVWF FREQ_H CLRF TMR1H CLRF TMR1L CLRF TMR0_OVF BSF T1CON,TMR1ON BSF FLAGS,0 MOVF FREQ_H,W ANDWF FREQ_L,W ADDLW 1 BTFSC STATUS,Z BSF FLAGS,1 ISR_EXIT RETFIE ; ============================================================ ; AUTORANGE — determina scala e configura TMR1 ; Logica: ; Se ultima misura overflow → aumenta prescaler ; Se FREQ_H:L < 100 e prescaler > 1 → diminuisci prescaler ; RANGE determina come formattare il display ; ============================================================ AUTORANGE BTFSC FLAGS,1 ; overflow? GOTO RANGE_UP ; Controlla se troppo basso (sotto-scala) MOVF FREQ_H,F BTFSS STATUS,Z GOTO AR_DONE ; FREQ_H != 0: siamo nel range giusto MOVF FREQ_L,W SUBLW D'100' BTFSS STATUS,C ; FREQ_L < 100? GOTO AR_DONE ; Frequenza bassa: riduci prescaler se possibile MOVF PRESCALE,W SUBLW D'1' BTFSC STATUS,Z GOTO AR_DONE ; già al minimo ; Dimezza prescaler BCF T1CON,TMR1ON MOVF PRESCALE,W MOVWF TMP BCF STATUS,C RRF TMP,W ; / 2 MOVWF PRESCALE CALL SET_T1_PRESCALE BSF T1CON,TMR1ON GOTO AR_DONE RANGE_UP BCF FLAGS,1 ; Frequenza alta: aumenta prescaler MOVF PRESCALE,W SUBLW D'64' BTFSC STATUS,Z GOTO AR_OVERFLOW ; già al massimo → overflow reale BCF T1CON,TMR1ON MOVF PRESCALE,W MOVWF TMP BCF STATUS,C RLF TMP,W ; × 2 (1→2→4→8→...) MOVWF PRESCALE CALL SET_T1_PRESCALE BSF T1CON,TMR1ON GOTO AR_DONE AR_OVERFLOW ; Vero overflow: > 65kHz × 64 = >4MHz (impossibile leggere) MOVLW 0xFF MOVWF PORTB GOTO AR_DONE AR_DONE ; Determina RANGE per display ; FREQ effettiva = FREQ_H:L × PRESCALE CLRF RANGE MOVF FREQ_H,F BTFSS STATUS,Z BSF RANGE,1 ; FREQ_H != 0 → kHz range MOVF PRESCALE,W SUBLW D'1' BTFSS STATUS,Z BSF RANGE,0 ; prescaler > 1 → scala su RETURN ; ============================================================ ; SET_T1_PRESCALE — imposta prescaler TMR1 in base a PRESCALE ; PRESCALE=1: T1CKPS=00 ; PRESCALE=2: T1CKPS=01 (1:2) ; PRESCALE=4: T1CKPS=10 (1:4) ; PRESCALE=8: T1CKPS=11 (1:8) — max TMR1 ; ============================================================ SET_T1_PRESCALE MOVF PRESCALE,W SUBLW D'1' BTFSC STATUS,Z GOTO PS_1 MOVF PRESCALE,W SUBLW D'2' BTFSC STATUS,Z GOTO PS_2 MOVF PRESCALE,W SUBLW D'4' BTFSC STATUS,Z GOTO PS_4 PS_8 MOVLW b'00110111' ; T1CKPS=11 (1:8), TMR1CS=1, TMR1ON=1 MOVWF T1CON RETURN PS_4 MOVLW b'00100111' ; T1CKPS=10 (1:4) MOVWF T1CON RETURN PS_2 MOVLW b'00010111' ; T1CKPS=01 (1:2) MOVWF T1CON RETURN PS_1 MOVLW b'00000111' ; T1CKPS=00 (1:1) MOVWF T1CON RETURN ; ============================================================ ; CALC_DISPLAY — calcola DISP_VAL = FREQ × PRESCALE ; Per visualizzare la frequenza reale (non il conteggio raw) ; Moltiplicazione 16×8: FREQ_H:L × PRESCALE → DISP_VAL_H:L ; ============================================================ CALC_DISPLAY CLRF DISP_VAL_H MOVF FREQ_L,W MOVWF DISP_VAL_L ; Moltiplica per PRESCALE con shift MOVF PRESCALE,W SUBLW D'1' BTFSC STATUS,Z RETURN ; ×1: già ok ; ×2: shift left BCF STATUS,C RLF DISP_VAL_L,F RLF DISP_VAL_H,F MOVF FREQ_H,W ADDWF DISP_VAL_H,F ; aggiungi FREQ_H shiftato MOVF PRESCALE,W SUBLW D'2' BTFSC STATUS,Z RETURN ; ×4: shift left ancora BCF STATUS,C RLF DISP_VAL_L,F RLF DISP_VAL_H,F MOVF PRESCALE,W SUBLW D'4' BTFSC STATUS,Z RETURN ; ×8: shift left ancora BCF STATUS,C RLF DISP_VAL_L,F RLF DISP_VAL_H,F RETURN ; ============================================================ ; FORMAT_LCD — formatta e invia al LCD in base a RANGE ; ============================================================ FORMAT_LCD ; Manda frequenza calcolata al display ; Linea 1: "F=XXXXXHZ " o "F=XX.XXkHz" ; Linea 2: "PS=X RNG=X" ; Per semplicità: output su PORTB/D per simulazione MOVF DISP_VAL_L,W MOVWF PORTB MOVF DISP_VAL_H,W MOVWF PORTD ; RANGE su parte alta PORTD SWAPF RANGE,W IORWF PORTD,F RETURN ORG 0x01A0 INIT BSF STATUS,RP0 BSF TRISC,0 ; T1CKI input BSF TRISC,3 BSF TRISC,4 MOVLW b'10111111' MOVWF OPTION_REG CLRF TRISB CLRF TRISD BCF STATUS,RP0 MOVLW D'1' MOVWF PRESCALE MOVLW b'00000111' ; TMR1 ext, prescaler 1:1, ON MOVWF T1CON MOVLW b'10100000' MOVWF INTCON CLRF TMR0_OVF CLRF FREQ_H CLRF FREQ_L CLRF FLAGS CLRF RANGE CLRF TMR1H CLRF TMR1L ; ============================================================ ; MAIN INTEGRATO — ciclo completo contatore di frequenza ; ============================================================ MAIN BTFSS FLAGS,0 GOTO MAIN ; attendi misura BCF FLAGS,0 CALL AUTORANGE ; aggiusta scala se necessario CALL CALC_DISPLAY ; frequenza reale = raw × prescaler CALL FORMAT_LCD ; mostra su display GOTO MAIN END
🧪 Prova tu
- Carica Modulo 1 nel simulatore. Simula 50Hz: imposta manualmente TMR1L=50, TMR1H=0 prima che l’ISR legga. Verifica che dopo il 16° overflow dell’ISR, FREQ_L=50 e FLAGS,0=1. Poi simula 1000Hz: TMR1H=0x03, TMR1L=0xE8. Cosa contiene PORTB e PORTD dopo MAIN?
- Carica Modulo 2. Chiama LCD_INIT e poi invia la stringa “TEST” carattere per carattere con LCD_DATA_SEND. Quante transazioni I²C vengono generate per i 4 caratteri? Calcola: 4 char × 2 nibble × 3 msg I²C = ? Poi chiama DISPLAY_FREQUENCY con FREQ_H=0x00, FREQ_L=60 (60Hz). Il display deve mostrare “F= 60Hz”.
- Testa Double-Dabble: imposta BCD_VAL_H=0x27, BCD_VAL_L=0x0F (= 9999 decimale). Chiama BIN16_TO_BCD5. I registri BCD_D4:D0 devono essere 0,9,9,9,9. Poi prova 0xFFFF = 65535: BCD_D4:D0 devono essere 6,5,5,3,5.
- Sfida autorange: imposta FREQ_H=0xFF, FREQ_L=0xFF e FLAGS,1=1 (overflow). Chiama AUTORANGE con PRESCALE=1. Verifica che PRESCALE diventi 2 e T1CON sia aggiornato a prescaler 1:2. Poi chiama di nuovo con FLAGS,1=1: PRESCALE deve diventare 4. Alla terza volta: PRESCALE=8. Alla quarta (overflow con PS=8): deve entrare in AR_OVERFLOW.
❓ Quiz rapido
1. Il contatore TMR1 conta 47832 in 1 secondo con PRESCALE=4. Qual è la frequenza reale del segnale in ingresso?
Mostra risposta
La frequenza reale si calcola moltiplicando il conteggio raw per il prescaler: 47832 × 4 = 191328 Hz ≈ 191.3 kHz. Il prescaler del Timer1 divide la frequenza del segnale prima di farla arrivare al contatore: se il prescaler è 1:4, ogni 4 impulsi del segnale TMR1 incrementa di 1. In 1 secondo TMR1 conta 47832 gruppi di 4 impulsi = 191328 impulsi totali. La frequenza massima misurabile con PRESCALE=8 e TMR1 a 16 bit è 65535 × 8 = 524280 Hz ≈ 524 kHz — oltre non si può senza un pre-divisore hardware esterno.
2. Perché nell’ISR si usa il flag T1SYNC=1 (no synchronization) nella configurazione di TMR1 come contatore esterno?
Mostra risposta
Con TMR1CS=1 (clock esterno) e T1SYNC=0 (sincronizzato), il PIC sincronizza il segnale T1CKI con il clock interno prima di passarlo al contatore. Questo richiede che il segnale esterno abbia un periodo di almeno 2 cicli di clock (≥0.5µs a 4MHz) e introduce una latenza. Con T1SYNC=1 (no sync), il contatore conta il segnale esterno direttamente senza sincronizzazione — può contare segnali più veloci ma il valore di TMR1 non è stabile quando si legge dal software (possibile glitch se si legge durante un incremento). La soluzione è fermare TMR1 prima di leggere (BCF T1CON,TMR1ON) esattamente come fa l’ISR in questo progetto.
3. Nel Double-Dabble, perché si aggiunge 3 quando una cifra BCD è ≥ 5, e non 6 o un altro valore?
Mostra risposta
Il BCD usa solo cifre 0–9. Quando si fa uno shift left (×2 in binario), una cifra BCD che vale 5 diventerebbe 10, che in BCD dovrebbe produrre un riporto nella cifra superiore e il resto 0. Ma in binario 4-bit, 10 si rappresenta come 1010 — un valore illegale in BCD. Aggiungendo 3 prima dello shift: 5+3=8, poi shift → 16 = carry (1) + resto 0, che corrisponde correttamente a 10 in BCD. Aggiungere 6 invece di 3 darebbe 5+6=11, shift → 22 = carry(1) + 6, sbagliato. Il valore 3 (= 16/2 − 5 = 8 − 5) è esattamente la correzione necessaria per far “traboccare” correttamente il nibble BCD nello shift successivo.
Prossima lezione →
Lezione 40 — Progetto 3: Stazione meteo
Sensore BME280 (temperatura + umidità + pressione) via SPI. Media mobile su 8 campioni. Visualizzazione su LCD con scroll automatico tra le misure.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 39