Lezione 40 di 43

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

Principio contatore di frequenza — Timer1 conta, Timer0 misura 1 secondo T1CKI RC0 ogni fronte ↑ = TMR1++ Timer1 — contatore eventi TMR1CS=1 (ext), T1SYNC=1 16-bit: conta 0→65535 Timer0 — base tempi 1s prescaler 1:256, overflow 65.5ms 15×full + 1×parziale (TMR0=115) = 1s ISR — 16° overflow leggi TMR1H:L → FREQ_H:L reset TMR1, FLAGS.0=1 Risultato FREQ_H:L = Hz aggiornato /1s parziale 15 overflow completi (×65.5ms) + 1 parziale = 1.000s esatto · ogni blocco = un overflow TMR0

⏱️ 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: &quot;F=XXXXXHZ&quot;)
LCD_LINE2       ; buffer linea 2 (8 char: &quot;T=XXXXus &quot;)

; 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 &gt; 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

LCD HD44780 via PCF8574 I²C — protocollo 4-bit e mappa pin PCF8574 I²C→GPIO P7 → D7 P6 → D6 P5 → D5 P4 → D4 P3 → BL P2 → EN P1 → RW P0 → RS I²C 0x4E LCD HD44780 16×2 caratteri D7-D4: nibble dati BL: backlight (1=on) EN: 0→1→0 latch RW: 0=write RS: 0=cmd 1=data F= 1234Hz PS=1 RNG=0 Invio byte 0x28 (cmd: 4-bit, 2 righe) 1. PCF8574 ← 0x20|BL (nibble alto, EN=0) 2. PCF8574 ← 0x24|BL (nibble alto, EN=1) ↑ 3. PCF8574 ← 0x20|BL (nibble alto, EN=0) ↓ 4. PCF8574 ← 0x80|BL (nibble basso, EN=0) 5. PCF8574 ← 0x84|BL (nibble basso, EN=1) ↑ 6. PCF8574 ← 0x80|BL (nibble basso, EN=0) ↓ EN pulse: 0→1→0 fa acquisire il nibble al LCD attesa 37µs tra nibble per esecuzione LCD 6 transazioni I²C per inviare 1 byte al LCD

🔌 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

Autorange e Double-Dabble — adattamento scala e conversione BCD Logica Autorange misura = FREQ×PS overflow? (FREQ=0xFFFF) sì PS×2 no ok FREQ_L < 100 e PS > 1? sì PS÷2 prescaler: 1, 2, 4, 8 → range ×8 max freq reale = conteggio × prescaler Double-Dabble BIN16→BCD5 Esempio: 0x04D2 = 1234 D4 D3 D2 D1 D0 bin_H bin_L 0000 0000 0000 0000 0000 00000100 11010010 start 0000 0000 0000 0001 0010 00110100 xxxxxxxx step 8 0000 0001 0010 0011 0100 xxxxxxxx finale 0 1 2 3 4 → “1234” Regola: se cifra BCD ≥ 5 aggiungi 3 prima dello shift left 16 iterazioni per 16 bit → 5 cifre BCD (max 65535) cifre poi convertite in ASCII con +0x30 per il LCD
; ============================================================
; 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 &lt; 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 &gt; 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

  1. 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?
  2. 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”.
  3. 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.
  4. 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