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) + flagPEIEobbligatorio - 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.
2 · Registri USART e calcolo baudrate
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 >= 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
- 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).
- 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 trasmetta0x41in risposta. - Invia
0x0D(CR) al programma echo: verifica che risponda con CR + LF + “> ” (tre byte: 0x0D, 0x0A, 0x3E, 0x20). - 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