Lezione 26 di 43

Lezione 25

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

UART — Comunicazione seriale RS-232 📡

Primo modulo con il PIC16F628A. USART hardware: TX polling, RX con interrupt,
calcolo baudrate, gestione errori e invio di numeri decimali via seriale.

🎯 Cosa impari in questa lezione

  • Struttura del frame UART: start bit, 8 bit dati (LSB first), stop bit — 104µs a 9600 baud
  • Registri USART del PIC16F628A: TXSTA, RCSTA, SPBRG, TXREG, RCREG
  • Formula SPBRG con BRGH=1: (Fosc ÷ (16 × baud)) − 1
  • TX con polling su bit TRMT (shift register vuoto)
  • RX con interrupt su RCIF (bit5 di PIR1) + flag PEIE obbligatorio
  • Gestione dell’Overrun Error (OERR): reset CREN
  • Conversione binario → stringa decimale ASCII con divisione ripetuta

📋 Scheda lezione

Lezione: 25 / 42
Modulo: 5 — Periferiche avanzate
Device: PIC16F628A (nuovo!)
Prerequisiti: Lez. 18, 21
Difficoltà: ★★★★☆
Periferica: USART hardware
Differenza F84A→F628A: più periferiche, più RAM


1 · Il protocollo UART e il frame seriale

UART (Universal Asynchronous Receiver Transmitter) è il protocollo seriale più diffuso nell’embedded. Asincrono significa che non c’è un clock condiviso: mittente e ricevitore si accordano sul baudrate in anticipo e si sincronizzano sul fronte di discesa del start bit. Ogni byte trasmesso è preceduto da 1 start bit (0) e seguito da 1 o 2 stop bit (1). I bit dati vengono trasmessi LSB first — il bit meno significativo per primo.

Frame UART asincrono — formato bit per bit a 9600 baud IDLE START ‘0’ b0=0 b1=0 b2=0 b3=0 b4=1 … STOP ‘1’ 104µs @ 9600 baud ‘P’ = 0x50 = 0b01010000 — 10 bit totali: 1 start + 8 dati + 1 stop

2 · Registri USART e calcolo baudrate

Registri USART PIC16F628A — configurazione e flusso dati TXSTA (0x98) — Bank 1 CSRC TX9 TXEN SYNC — BRGH TX9D TRMT x 0 1 0 1 0 ? TXEN=1: abilita TX BRGH=1: alta velocità TRMT=1: shift reg. vuoto RCSTA (0x18) — Bank 0 SPEN RX9 SREN CREN ADDEN FERR OERR RX9D 1 0 0 1 0 0 0 0 SPEN=1: abilita UART CREN=1: ricezione continua OERR: overflow SPBRG (0x99) Bank 1 = Fosc/(16×baud) – 1 = 25 per 9600 @ 4MHz PIE1 / PIR1 RCIE (bit5 PIE1): abilita INT RX RCIF (bit5 PIR1): flag RX TXREG / RCREG TXREG (0x19): scrivere per TX RCREG (0x1A): leggere per RX ⚠ Per interrupt UART: INTCON deve avere GIE=1 e PEIE=1 (bit6 = peripheral interrupt enable) Calcolo SPBRG — tabella baudrate comuni @ 4MHz con BRGH=1 Baudrate SPBRG (teorico) SPBRG (intero) Errore % 1200 207.33 207 0.16% 9600 25.04 25 0.16% 19200 12.02 12 0.16% 38400 5.51 5 o 6 2.34% / −7.8% 115200 1.17 1 −7.8% ⚠

Regola pratica sull’errore baudrate: la UART tollera fino a circa ±3–4% di errore sul baudrate prima che i frame inizino a corrompersi. Con errori attorno al 2% su entrambi i lati, l’errore combinato sale a ~4% — al limite della tolleranza. Per baudrate alti come 115200 con cristallo a 4MHz, l’errore del 7.8% è inaccettabile: usare un cristallo da 7.3728 MHz o 11.0592 MHz che sono multipli esatti di 9600.


3 · Codice — TX con polling (trasmissione stringa)

; ============================================================
; Lezione 25 — UART: trasmissione seriale (TX)
; PIC16F628A @ 4 MHz  UART hardware (USART)
; Baudrate: 9600 bps  BRGH=1, SPBRG=25
; Formula: SPBRG = (Fosc / (16 × baud)) - 1
;          = (4.000.000 / (16 × 9600)) - 1 = 25.04 ≈ 25
; ============================================================
    LIST    P=16F628A
    #INCLUDE <p16f628a.inc>
    __CONFIG _INTOSC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _BOREN_OFF

; Registri USART (Bank 1 per config, Bank 0 per dati)
; TXSTA (0x98): CSRC TX9 TXEN SYNC - BRGH TX9D TRMT
; RCSTA (0x18): SPEN RX9 SREN CREN ADDEN FERR OERR RX9D
; TXREG (0x19): registro trasmissione
; RCREG (0x1A): registro ricezione
; SPBRG (0x99): baudrate generator

    CBLOCK  0x20
TX_CHAR         ; carattere da trasmettere
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    BSF     STATUS,RP0         ; Bank 1

    ; Configurazione pin UART
    ; RB1 = TX (output), RB2 = RX (input)
    MOVLW   b'00000110'        ; RB1,RB2 come da USART
    MOVWF   TRISB

    ; SPBRG = 25 → 9600 baud @ 4MHz con BRGH=1
    MOVLW   D'25'
    MOVWF   SPBRG

    ; TXSTA: TXEN=1 (abilita TX), BRGH=1 (alta velocità), SYNC=0 (asincrono)
    MOVLW   b'00100100'        ; TXEN=1, BRGH=1
    MOVWF   TXSTA

    BCF     STATUS,RP0         ; Bank 0

    ; RCSTA: SPEN=1 (abilita porta seriale)
    MOVLW   b'10000000'        ; SPEN=1
    MOVWF   RCSTA

    ; Trasmetti la stringa "PIC16\r\n"
    MOVLW   'P'
    CALL    UART_TX
    MOVLW   'I'
    CALL    UART_TX
    MOVLW   'C'
    CALL    UART_TX
    MOVLW   '1'
    CALL    UART_TX
    MOVLW   '6'
    CALL    UART_TX
    MOVLW   0x0D               ; CR
    CALL    UART_TX
    MOVLW   0x0A               ; LF
    CALL    UART_TX

MAIN
    GOTO    MAIN

; ============================================================
; UART_TX — trasmette il carattere in W
; Attende che TXREG sia libero (TRMT=1 in TXSTA Bank1)
; ============================================================
UART_TX
    MOVWF   TX_CHAR            ; salva carattere
    BSF     STATUS,RP0         ; Bank 1
UART_TX_WAIT
    BTFSS   TXSTA,TRMT         ; TRMT=1 → shift register vuoto
    GOTO    UART_TX_WAIT
    BCF     STATUS,RP0         ; Bank 0
    MOVF    TX_CHAR,W
    MOVWF   TXREG              ; carica carattere → avvia trasmissione
    RETURN

    END

4 · Codice — RX con interrupt + echo

La ricezione con interrupt usa RCIF (bit5 di PIR1) come flag e richiede che sia GIE che PEIE (peripheral interrupt enable, bit6 di INTCON) siano a 1 — senza PEIE, gli interrupt dalle periferiche (UART, Timer1, SPI…) non raggiungono la CPU anche con GIE=1. Leggere RCREG azzera automaticamente RCIF.

; ============================================================
; Lezione 25 — UART: ricezione con interrupt (RX)
; PIC16F628A @ 4 MHz, 9600 baud
; ISR si attiva ad ogni byte ricevuto
; Echo: ritrasmette ogni byte ricevuto (loopback)
; ============================================================
    LIST    P=16F628A
    #INCLUDE <p16f628a.inc>
    __CONFIG _INTOSC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _BOREN_OFF

    CBLOCK  0x20
W_SAVE
STATUS_SAVE
RX_BUF          ; ultimo byte ricevuto
RX_READY        ; flag: nuovo byte disponibile
TX_CHAR
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0004
ISR
    MOVWF   W_SAVE
    SWAPF   STATUS,W
    MOVWF   STATUS_SAVE

    ; Interrupt da UART RX? (RCIF in PIR1, bit5)
    BTFSS   PIR1,RCIF          ; RCIF=1 → byte ricevuto
    GOTO    ISR_EXIT

    ; Leggi byte (operazione che azzera RCIF automaticamente)
    MOVF    RCREG,W
    MOVWF   RX_BUF

    ; Controlla errori (OERR e FERR in RCSTA)
    BTFSC   RCSTA,OERR         ; Overrun Error?
    CALL    CLEAR_OERR

    BSF     RX_READY,0         ; segnala byte pronto al main

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

; ============================================================
; Gestione Overrun Error: disabilita e riabilita CREN
; ============================================================
CLEAR_OERR
    BCF     RCSTA,CREN         ; disabilita ricevitore
    BSF     RCSTA,CREN         ; riabilita → azzera OERR
    RETURN

    ORG     0x0030
INIT
    BSF     STATUS,RP0         ; Bank 1

    MOVLW   b'00000110'
    MOVWF   TRISB              ; RB1=TX out, RB2=RX in

    MOVLW   D'25'
    MOVWF   SPBRG              ; 9600 baud @ 4MHz

    MOVLW   b'00100100'        ; TXEN=1, BRGH=1
    MOVWF   TXSTA

    ; PIE1: abilita interrupt RCIE (bit5)
    MOVLW   b'00100000'        ; RCIE=1
    MOVWF   PIE1

    BCF     STATUS,RP0         ; Bank 0

    ; RCSTA: SPEN=1, CREN=1 (ricezione continua)
    MOVLW   b'10010000'        ; SPEN=1, CREN=1
    MOVWF   RCSTA

    CLRF    RX_READY

    ; GIE=1, PEIE=1 (peripheral interrupt enable)
    MOVLW   b'11000000'        ; GIE=1, PEIE=1
    MOVWF   INTCON

    ; Invia messaggio di benvenuto
    MOVLW   '>'
    CALL    UART_TX
    MOVLW   ' '
    CALL    UART_TX

MAIN
    ; Byte ricevuto?
    BTFSS   RX_READY,0
    GOTO    MAIN

    BCF     RX_READY,0

    ; Echo del byte ricevuto
    MOVF    RX_BUF,W
    CALL    UART_TX

    ; Se era Enter (0x0D), aggiungi LF e prompt
    MOVF    RX_BUF,W
    XORLW   0x0D
    BTFSC   STATUS,Z
    CALL    SEND_NEWLINE

    GOTO    MAIN

SEND_NEWLINE
    MOVLW   0x0A
    CALL    UART_TX
    MOVLW   '>'
    CALL    UART_TX
    MOVLW   ' '
    CALL    UART_TX
    RETURN

UART_TX
    MOVWF   TX_CHAR
    BSF     STATUS,RP0
UART_TX_WAIT
    BTFSS   TXSTA,TRMT
    GOTO    UART_TX_WAIT
    BCF     STATUS,RP0
    MOVF    TX_CHAR,W
    MOVWF   TXREG
    RETURN

    END

5 · Codice — Invio numero decimale

Trasmettere un numero non è trasmettere il byte direttamente — bisogna convertirlo in caratteri ASCII. Il valore 163 non si invia come 0xA3 ma come tre byte: '1' (0x31), '6' (0x36), '3' (0x33). L’algoritmo estrae le cifre decimali con divisioni ripetute e converte ogni cifra in ASCII con ADDLW '0' (somma 0x30).

; ============================================================
; Lezione 25 — UART: invio numero decimale
; Converte un valore binario (0-255) in stringa ASCII
; e lo trasmette via UART seguto da CR+LF
; Es: 0xA3 (163) → "163\r\n"
; PIC16F628A @ 4 MHz, 9600 baud
; ============================================================
    LIST    P=16F628A
    #INCLUDE <p16f628a.inc>
    __CONFIG _INTOSC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _BOREN_OFF

    CBLOCK  0x20
TX_CHAR
BIN_VAL         ; valore binario da convertire
DEC_100         ; cifra delle centinaia
DEC_10          ; cifra delle decine
DEC_1           ; cifra delle unità
TEMP
    ENDC

    ORG     0x0000
    GOTO    INIT

    ORG     0x0005
INIT
    BSF     STATUS,RP0
    MOVLW   b'00000110'
    MOVWF   TRISB
    MOVLW   D'25'
    MOVWF   SPBRG
    MOVLW   b'00100100'
    MOVWF   TXSTA
    BCF     STATUS,RP0
    MOVLW   b'10000000'
    MOVWF   RCSTA

    ; Trasmetti alcuni valori di test
    MOVLW   D'0'
    MOVWF   BIN_VAL
    CALL    SEND_DECIMAL
    MOVLW   D'42'
    MOVWF   BIN_VAL
    CALL    SEND_DECIMAL
    MOVLW   D'163'
    MOVWF   BIN_VAL
    CALL    SEND_DECIMAL
    MOVLW   D'255'
    MOVWF   BIN_VAL
    CALL    SEND_DECIMAL

MAIN
    GOTO    MAIN

; ============================================================
; SEND_DECIMAL — trasmette BIN_VAL come stringa decimale
; Algoritmo: divisione ripetuta per 100, poi per 10
; ============================================================
SEND_DECIMAL
    ; --- Centinaia ---
    MOVLW   D'100'
    MOVWF   TEMP
    CLRF    DEC_100
SD_100
    MOVF    TEMP,W
    SUBWF   BIN_VAL,W          ; BIN_VAL - 100
    BTFSS   STATUS,C           ; C=1 → BIN_VAL &gt;= 100
    GOTO    SD_10_START
    MOVWF   BIN_VAL            ; aggiorna residuo
    INCF    DEC_100,F
    GOTO    SD_100

    ; --- Decine ---
SD_10_START
    MOVLW   D'10'
    MOVWF   TEMP
    CLRF    DEC_10
SD_10
    MOVF    TEMP,W
    SUBWF   BIN_VAL,W
    BTFSS   STATUS,C
    GOTO    SD_1_START
    MOVWF   BIN_VAL
    INCF    DEC_10,F
    GOTO    SD_10

    ; --- Unità = residuo ---
SD_1_START
    MOVF    BIN_VAL,W
    MOVWF   DEC_1

    ; --- Trasmetti le 3 cifre + CR LF ---
    ; Sopprime gli zeri iniziali (non stampa "0" se cifra alta e precedenti zero)
    ; Semplificazione: stampa sempre 3 cifre (es. 007, 042, 163)
    MOVF    DEC_100,W
    ADDLW   '0'                ; converti a ASCII
    CALL    UART_TX
    MOVF    DEC_10,W
    ADDLW   '0'
    CALL    UART_TX
    MOVF    DEC_1,W
    ADDLW   '0'
    CALL    UART_TX
    MOVLW   0x0D
    CALL    UART_TX
    MOVLW   0x0A
    CALL    UART_TX
    RETURN

UART_TX
    MOVWF   TX_CHAR
    BSF     STATUS,RP0
UART_WAIT
    BTFSS   TXSTA,TRMT
    GOTO    UART_WAIT
    BCF     STATUS,RP0
    MOVF    TX_CHAR,W
    MOVWF   TXREG
    RETURN

    END

🧪 Prova tu

  1. Carica il programma TX. Nel pannello UART del simulatore, osserva i caratteri “PIC16 ” apparire uno dopo l’altro ad ogni MOVWF TXREG. Conta le istruzioni eseguite tra un carattere e il successivo (dipende dal polling TRMT).
  2. Carica il programma RX+echo. Nel pannello UART, invia il byte 0x41 (‘A’). Verifica che RX_BUF si aggiorni, RX_READY si setti, e poi TXREG trasmetta 0x41 in risposta.
  3. Invia 0x0D (CR) al programma echo: verifica che risponda con CR + LF + “> ” (tre byte: 0x0D, 0x0A, 0x3E, 0x20).
  4. Sfida: modifica SEND_DECIMAL per sopprimere gli zeri iniziali: “007” → “7”, “042” → “42”, “163” → “163”. Suggerimento: usa un flag LEADING_ZERO che parte a 1 e si azzera alla prima cifra non-zero; se il flag è 1 e la cifra è 0, non trasmettere.

❓ Quiz rapido

1. Perché il polling usa TRMT e non TXIF (il flag interrupt TX)?

Mostra risposta

TXIF si setta quando TXREG è vuoto e pronto ad accettare un nuovo byte — ma il byte precedente potrebbe essere ancora nella shift register in trasmissione fisica. Se si scrive subito il byte successivo in TXREG con TXIF=1, va tutto bene perché TXREG è un buffer separato dallo shift register. TRMT invece indica che lo shift register è fisicamente vuoto — usarlo per il polling è più conservativo ma introduce una latenza di un intero frame tra un carattere e il successivo. Per massimizzare il throughput, TXIF è più efficiente; TRMT è più sicuro se si vuole essere certi che la trasmissione sia completata prima di procedere (es. prima di mettere il PIC in SLEEP).


2. Cosa succede se RCREG non viene letto tra un byte ricevuto e il successivo?

Mostra risposta

Il PIC16F628A ha un buffer RX a 2 elementi (RCREG + 1 livello di FIFO interno). Se arrivano 3 byte prima che uno venga letto, il terzo non può essere salvato e si verifica un Overrun Error (OERR=1 in RCSTA). Con OERR=1, il ricevitore si blocca e non riceve più nulla finché OERR non viene azzerato. L’unico modo per azzerare OERR è fare BCF RCSTA,CREN seguito da BSF RCSTA,CREN (reset del ricevitore). Questo spiega perché nel programma RX l’ISR controlla e gestisce OERR immediatamente dopo ogni lettura.


3. Il PIC16F628A ha l’oscillatore interno (INTOSC). Perché per la UART si preferisce comunque un cristallo esterno?

Mostra risposta

L’oscillatore interno RC del PIC16F628A ha una tolleranza di frequenza tipica di ±1–2% ma che può arrivare fino a ±5% con variazioni di temperatura e tensione. Per la UART, l’errore di baudrate deve stare entro ±3–4% totali tra i due dispositivi che comunicano. Se entrambi usano oscillatori interni, gli errori si sommano e il margine si esaurisce. Un cristallo al quarzo ha una tolleranza di ±20–50 ppm (0.002–0.005%) — praticamente zero errore — e mantiene la frequenza stabile con temperatura e tensione. Per la UART è essenziale, specialmente a baudrate alti.

Prossima lezione →

Lezione 26 — ADC: convertitore analogico-digitale (PIC16F877A)
Il modulo ADC a 10 bit del PIC16F877A. Lettura di tensioni analogiche, selezione canale, tempo di acquisizione e conversione in valori fisici.

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