Lezione 35
🎓 PIC16 — From Zero to Hero · Lezione 35 · Modulo 6
RAM a Pagine, Puntatori e Buffer 🗂️
Indirizzamento indiretto con FSR e INDF. Array, ricerca e buffer circolari.
IRP per accedere ai banchi 2 e 3. Stack software a 8 livelli nell’area shared.
🎯 Cosa impari in questa lezione
- FSR (File Select Register) come puntatore a 8 bit + IRP (bit STATUS) per il 9° bit → 512 locazioni indirizzabili
- INDF: accede alla RAM puntata da FSR — leggere/scrivere INDF equivale a dereferenziare un puntatore C
- Array in RAM: riempimento, somma e ricerca del massimo con loop FSR/INDF senza indici fissi
- Buffer circolare (ring buffer): HEAD per scrittura ISR, TAIL per lettura main, wrap-around via
AND 0x0F - Sezione critica:
BCF GIEprima di aggiornare TAIL/COUNT,BSF GIEdopo — evita race condition con ISR - Banchi 2 e 3 con IRP=1: 256 byte extra per buffer grandi (0x110–0x18F e 0x190–0x1EF)
- Stack software LIFO a 8 livelli nell’area shared (0x70–0x77): PUSH/POP con FSR, overflow e underflow detection
📋 Scheda lezione
Lezione: 35 / 42
Modulo: 6 — Tecniche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 04 (banchi RAM), Lez. 21 (ISR)
Difficoltà: ★★★★☆
Tecnica: FSR/INDF + IRP, buffer circolare
Applicazioni: UART buffering, stack, array
1 · La mappa RAM e l’indirizzamento indiretto
Il PIC16F877A ha 368 byte di RAM organizzati in 4 banchi da 128 locazioni ciascuno. L’accesso diretto tramite istruzioni come MOVF 0x25,W usa un indirizzo a 7 bit embeddato nell’opcode — limitato al banco selezionato da RP0/RP1. L’accesso indiretto tramite FSR e INDF usa invece 9 bit (IRP + FSR a 8 bit) e può raggiungere qualsiasi delle 512 locazioni senza cambiare banco. È lo strumento fondamentale per loop su array, buffer e strutture dati dinamiche.
2 · Codice — array con FSR/INDF
🔑 Analogia C ↔ Assembly: MOVWF FSR è come ptr = &array[0]. MOVWF INDF è come *ptr = valore. INCF FSR,F è come ptr++. Il loop di riempimento in assembly è esattamente equivalente a for(i=0; i<16; i++) array[i] = i; — solo espresso con istruzioni a 8 bit.
; ============================================================ ; Lezione 35 — Indirizzamento indiretto: FSR e INDF ; PIC16F877A @ 4 MHz ; FSR (File Select Register, 0x04) = puntatore a RAM ; INDF (0x00) = accesso al registro puntato da FSR ; IRP (bit7 di STATUS) = seleziona banco 0/1 (IRP=0) ; o banco 2/3 (IRP=1) ; Indirizzo fisico = IRP:FSR (9 bit totali → 512 locazioni) ; Esempio: riempire array di 16 byte con valori 0-15 ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ARRAY_BASE ; 16 byte: 0x20-0x2F ENDC ; (CBLOCK alloca solo la prima) ; Le 15 locazioni successive sono 0x21..0x2F CBLOCK 0x30 LOOP_CNT VALUE PTR_SAVE ; salva FSR se serve ENDC ORG 0x0000 GOTO INIT ORG 0x0020 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 BCF STATUS,IRP ; IRP=0: banchi 0/1 (0x00-0xFF) ; ============================================================ ; Riempimento array: ARRAY[0..15] = 0..15 ; Tecnica: FSR punta alla base, incrementa FSR ad ogni passo ; ============================================================ MOVLW ARRAY_BASE ; FSR = indirizzo base array MOVWF FSR CLRF VALUE ; valore da scrivere MOVLW D'16' MOVWF LOOP_CNT FILL_LOOP MOVF VALUE,W MOVWF INDF ; scrivi in *FSR INCF FSR,F ; puntatore++ INCF VALUE,F ; valore++ DECFSZ LOOP_CNT,F GOTO FILL_LOOP ; Ora 0x20=0, 0x21=1, ... 0x2F=15 ; ============================================================ ; Calcola somma array (dovrebbe essere 0+1+...+15 = 120) ; ============================================================ MOVLW ARRAY_BASE MOVWF FSR CLRF VALUE ; useremo VALUE come accumulatore MOVLW D'16' MOVWF LOOP_CNT SUM_LOOP MOVF INDF,W ; leggi *FSR ADDWF VALUE,F ; accumula INCF FSR,F DECFSZ LOOP_CNT,F GOTO SUM_LOOP ; VALUE = 120 = 0x78 MOVF VALUE,W MOVWF PORTB ; mostra somma (0x78) ; ============================================================ ; Ricerca massimo nell'array ; ============================================================ MOVLW ARRAY_BASE MOVWF FSR MOVF INDF,W ; primo elemento come massimo iniziale MOVWF VALUE INCF FSR,F MOVLW D'15' MOVWF LOOP_CNT MAX_LOOP MOVF INDF,W SUBWF VALUE,W ; VALUE - *FSR BTFSS STATUS,C ; C=0 → *FSR > VALUE MOVF INDF,W ; nuovo massimo BTFSS STATUS,C MOVWF VALUE INCF FSR,F DECFSZ LOOP_CNT,F GOTO MAX_LOOP ; VALUE = 15 (massimo) DONE MOVF VALUE,W MOVWF PORTB GOTO DONE END
3 · Buffer circolare per UART RX
Il buffer circolare è la struttura dati fondamentale per disaccoppiare produttore e consumatore che lavorano a velocità diverse. La ISR UART (produttore) scrive al ritmo dei byte in arrivo — fino a 9600 byte/s — mentre il main loop (consumatore) li elabora quando può. Senza il buffer, ogni byte che arriva mentre il main loop è occupato andrebbe perso. Con 16 slot e il wrap-around via AND 0x0F, la gestione del puntatore non richiede nessun IF: 15+1=16, 16 AND 0x0F=0, il buffer ricomincia automaticamente.
⚠️ Sezione critica — race condition: BUF_TAIL e BUF_COUNT sono scritti sia dalla ISR (su BUF_HEAD e BUF_COUNT) sia dal main loop (su BUF_TAIL e BUF_COUNT). Se il main loop viene interrotto a metà dell’aggiornamento di BUF_COUNT, la ISR potrebbe trovare un valore incoerente. Soluzione: BCF INTCON,GIE prima di modificare la variabile condivisa, BSF INTCON,GIE dopo. La finestra protetta deve essere minima (2–3 istruzioni) per non perdere dati in ingresso.
; ============================================================ ; Lezione 35 — Buffer circolare (ring buffer) per UART RX ; PIC16F877A @ 4 MHz, 9600 baud ; Buffer: 16 byte in RAM, puntatori HEAD (scrittura) e TAIL (lettura) ; ISR UART scrive in HEAD, main loop legge da TAIL ; Full quando (HEAD+1) mod 16 == TAIL ; Empty quando HEAD == TAIL ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF #DEFINE BUF_SIZE D'16' #DEFINE BUF_MASK 0x0F ; maschera per wrap-around (size-1) #DEFINE BUF_BASE 0x40 ; indirizzo base buffer in RAM CBLOCK 0x30 W_SAVE STATUS_SAVE BUF_HEAD ; indice scrittura ISR (0-15) BUF_TAIL ; indice lettura main (0-15) BUF_COUNT ; numero byte presenti RX_CHAR ; carattere letto dal buffer TX_CHAR ECHO_FLAG ; 1 = fai echo del carattere ricevuto ENDC ORG 0x0000 GOTO INIT ORG 0x0004 ; ============================================================ ; ISR — UART RX: salva in buffer circolare ; ============================================================ ISR MOVWF W_SAVE SWAPF STATUS,W MOVWF STATUS_SAVE BCF STATUS,RP0 ; Bank 0 BTFSS PIR1,RCIF ; RX interrupt? GOTO ISR_EXIT ; Controlla se buffer pieno MOVF BUF_COUNT,W SUBLW BUF_SIZE - 1 ; BUF_SIZE-1 - COUNT BTFSS STATUS,C ; C=0 → COUNT >= BUF_SIZE-1 → pieno GOTO BUF_FULL ; Scrivi in BUF[HEAD] BCF STATUS,IRP ; Bank 0/1 MOVLW BUF_BASE ADDWF BUF_HEAD,W ; BUF_BASE + HEAD MOVWF FSR MOVF RCREG,W ; leggi byte ricevuto (azzera RCIF) MOVWF INDF ; salva nel buffer ; Avanza HEAD con wrap INCF BUF_HEAD,F MOVF BUF_HEAD,W ANDLW BUF_MASK ; mod 16 MOVWF BUF_HEAD INCF BUF_COUNT,F GOTO ISR_EXIT BUF_FULL MOVF RCREG,W ; leggi comunque per azzerare RCIF ; carattere scartato (buffer overflow) ISR_EXIT SWAPF STATUS_SAVE,W MOVWF STATUS SWAPF W_SAVE,F SWAPF W_SAVE,W RETFIE ORG 0x0060 INIT BSF STATUS,RP0 MOVLW b'00000100' ; RC2=input, resto output MOVWF TRISC MOVLW D'25' MOVWF SPBRG ; 9600 baud @ 4MHz MOVLW b'00100100' ; TXEN=1, BRGH=1 MOVWF TXSTA MOVLW b'00100000' ; RCIE=1 (bit5 PIE1) MOVWF PIE1 BCF STATUS,RP0 MOVLW b'10010000' ; SPEN=1, CREN=1 MOVWF RCSTA CLRF BUF_HEAD CLRF BUF_TAIL CLRF BUF_COUNT MOVLW 1 MOVWF ECHO_FLAG MOVLW b'11000000' ; GIE=1, PEIE=1 MOVWF INTCON ; ============================================================ ; MAIN — elabora caratteri dal buffer ; ============================================================ MAIN ; Buffer vuoto? MOVF BUF_COUNT,F BTFSC STATUS,Z GOTO MAIN ; empty → attendi ; Leggi da BUF[TAIL] BCF STATUS,IRP MOVLW BUF_BASE ADDWF BUF_TAIL,W MOVWF FSR MOVF INDF,W MOVWF RX_CHAR ; Avanza TAIL con wrap (disabilita interrupt per atomicità) BCF INTCON,GIE INCF BUF_TAIL,F MOVF BUF_TAIL,W ANDLW BUF_MASK MOVWF BUF_TAIL DECF BUF_COUNT,F BSF INTCON,GIE ; Elabora: se ECHO_FLAG, ritrasmettti il carattere BTFSS ECHO_FLAG,0 GOTO PROCESS_CHAR MOVF RX_CHAR,W CALL UART_TX PROCESS_CHAR ; Azione base: se ricevuto 'L', accendi PORTB MOVF RX_CHAR,W SUBLW 'L' BTFSC STATUS,Z BSF PORTB,0 ; Se ricevuto 'O' (off), spegni MOVF RX_CHAR,W SUBLW 'O' BTFSC STATUS,Z BCF PORTB,0 GOTO MAIN UART_TX MOVWF TX_CHAR BSF STATUS,RP0 UTX BTFSS TXSTA,TRMT GOTO UTX BCF STATUS,RP0 MOVF TX_CHAR,W MOVWF TXREG RETURN END
4 · Stack software e IRP per banchi 2/3
; ============================================================ ; Lezione 35 — Stack software e IRP: accesso ai banchi 2/3 ; PIC16F877A @ 4 MHz ; Stack software: 8 livelli in RAM (0x70-0x77, bank0 shared) ; Operazioni: PUSH W, POP → W ; Uso IRP: buffer da 128 byte nel banco 2 (0x110-0x18F) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF ; Stack in area shared (banchi 0-3 tutti vedono 0x70-0x7F) #DEFINE STACK_BASE 0x70 #DEFINE STACK_SIZE D'8' CBLOCK 0x20 SP ; stack pointer (0=vuoto, 8=pieno) STACK_TMP ; temp per push/pop ; Buffer grande banco 2 (dichiarato qui come indirizzo) ; BIG_BUF_BASE = 0x110 (bank 2, IRP=1, FSR=0x10) BIG_IDX ; indice nel buffer grande BIG_LEN ; lunghezza dati nel buffer CALC_A CALC_B ENDC ORG 0x0000 GOTO INIT ORG 0x0030 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 CLRF SP ; stack vuoto ; ============================================================ ; Demo stack: calcola (3 + 4) × 2 con stack ; ============================================================ ; PUSH 3 MOVLW D'3' CALL STACK_PUSH ; PUSH 4 MOVLW D'4' CALL STACK_PUSH ; POP → CALC_B, POP → CALC_A, somma, PUSH risultato CALL STACK_POP MOVWF CALC_B CALL STACK_POP MOVWF CALC_A MOVF CALC_B,W ADDWF CALC_A,W ; 3 + 4 = 7 CALL STACK_PUSH ; PUSH 7 ; PUSH 2 MOVLW D'2' CALL STACK_PUSH ; POP → CALC_B, POP → CALC_A, moltiplica semplice CALL STACK_POP MOVWF CALC_B ; 2 CALL STACK_POP MOVWF CALC_A ; 7 ; Moltiplicazione semplice: loop di somme CLRF STACK_TMP MUL_LOOP MOVF CALC_A,W ADDWF STACK_TMP,F ; STACK_TMP += CALC_A DECFSZ CALC_B,F ; conta CALC_B volte GOTO MUL_LOOP ; STACK_TMP = 7 × 2 = 14 MOVF STACK_TMP,W MOVWF PORTB ; mostra 14 ; ============================================================ ; Demo IRP: riempimento buffer nel banco 2 (0x110-0x18F) ; Indirizzo banco 2: FSR=0x10..0x8F, IRP=1 ; ============================================================ BSF STATUS,IRP ; seleziona banchi 2/3 MOVLW 0x10 ; FSR = 0x10 → indirizzo fisico 0x110 MOVWF FSR MOVLW D'128' MOVWF BIG_LEN CLRF BIG_IDX FILL_BIG MOVF BIG_IDX,W MOVWF INDF ; BIG_BUF[i] = i INCF FSR,F INCF BIG_IDX,F DECFSZ BIG_LEN,F GOTO FILL_BIG ; Torna a banco 0/1 BCF STATUS,IRP ; Leggi back: BIG_BUF[64] deve valere 64 BSF STATUS,IRP MOVLW 0x10 + D'64' ; FSR = 0x50 → 0x150 (indice 64) MOVWF FSR MOVF INDF,W ; leggi valore BCF STATUS,IRP MOVWF PORTB ; deve mostrare 64 MAIN GOTO MAIN ; ============================================================ ; STACK_PUSH — salva W nello stack, incrementa SP ; Se stack pieno, non fa nulla (overflow silenzioso) ; ============================================================ STACK_PUSH MOVWF STACK_TMP ; Controlla overflow MOVF SP,W SUBLW STACK_SIZE - 1 BTFSS STATUS,C ; C=0 → SP >= STACK_SIZE RETURN ; overflow: ignora ; Calcola indirizzo: STACK_BASE + SP MOVLW STACK_BASE ADDWF SP,W MOVWF FSR MOVF STACK_TMP,W MOVWF INDF ; salva valore INCF SP,F ; SP++ RETURN ; ============================================================ ; STACK_POP — carica W dallo stack, decrementa SP ; Se stack vuoto, restituisce 0xFF (underflow marker) ; ============================================================ STACK_POP ; Controlla underflow MOVF SP,F BTFSC STATUS,Z ; Z=1 → SP=0 → vuoto GOTO STACK_UNDERFLOW DECF SP,F ; SP-- MOVLW STACK_BASE ADDWF SP,W MOVWF FSR MOVF INDF,W ; carica valore RETURN STACK_UNDERFLOW MOVLW 0xFF ; marker underflow RETURN END
🧪 Prova tu
- Carica il codice array. Dopo il loop di riempimento, ispeziona la RAM: 0x20=0, 0x21=1, …, 0x2F=15. Verifica che VALUE=120 (0x78) dopo la somma — controlla su PORTB. Poi modifica il loop: scrivi valori decrescenti (ARRAY[0]=15, ARRAY[1]=14, …) e verifica che il massimo rimanga 15.
- Carica il buffer circolare. Simula l’arrivo di 3 byte via UART (imposta RCREG=0x41, 0x42, 0x43 e triggera RCIF manualmente). Verifica BUF_HEAD=3, BUF_COUNT=3, BUF_TAIL=0. Il main loop deve leggere 0x41 dal buffer, avanzare TAIL a 1, inviare 0x41 via UART (echo). Ripeti per gli altri due byte.
- Carica lo stack. Fai PUSH di 5, 10, 20. SP deve valere 3. POP: ottieni 20 (LIFO), SP=2. Poi POP: 10, SP=1. Poi POP: 5, SP=0. Poi ancora POP: deve restituire 0xFF (underflow) senza crash. Testa anche overflow: fai 9 PUSH → l’ottavo viene ignorato silenziosamente.
- Sfida: implementa
memcpyin assembly. Funzione MEMCPY con parametri: SRC_PTR (FSR sorgente), DST_PTR in una seconda locazione, LEN. Copia LEN byte da *SRC a *DST usando FSR per entrambi — ma FSR è uno solo! Strategia: copia un byte alla volta salvando il valore in W tra le due operazioni di puntatore, o usa l’area shared come buffer intermedio di 1 byte.
❓ Quiz rapido
1. Cosa succede se si legge INDF quando FSR=0x00 (INDF stesso)?
Mostra risposta
INDF è un registro speciale che implementa l’indirizzamento indiretto. Se FSR=0x00, punta ad INDF stesso — creando un accesso ricorsivo. Il comportamento è definito dal datasheet: leggere INDF quando FSR=0 restituisce 0x00 (non il contenuto di FSR). Scrivere in INDF quando FSR=0 non fa nulla. Questo è un caso limite da evitare: mai usare 0x00 come indirizzo base di un array. Un’altra trappola: se FSR punta a 0x00 accidentalmente (es. dopo un CLRF FSR sbagliato), tutte le scritture via INDF vengono silenziosamente scartate — un bug difficile da trovare.
2. Nel buffer circolare, perché si usa BUF_SIZE=16 invece di 15 o 17, e perché la condizione “pieno” è COUNT >= BUF_SIZE-1 (non BUF_SIZE)?
Mostra risposta
BUF_SIZE=16 (potenza di 2) permette il wrap-around tramite AND senza divisione: (idx+1) AND 0x0F funziona perfettamente per size=16. Con size=15 o 17 servirebbe una SUBWF con confronto, molto più lenta. La condizione “pieno” a BUF_SIZE-1 (=15) invece di BUF_SIZE (=16) serve per lasciare sempre uno slot libero: quando HEAD e TAIL coincidono il buffer è vuoto (COUNT=0). Se si riempisse fino all’ultimo slot, non si potrebbe distinguere tra “vuoto” e “pieno” usando solo HEAD==TAIL. Sacrificare uno slot elimina questa ambiguità senza introdurre flag aggiuntivi.
3. L’area shared (0x70–0x7F) è visibile da tutti i banchi senza cambiare RP0/RP1. Quali sono i vantaggi e i rischi di usarla per lo stack?
Mostra risposta
Il vantaggio principale è che lo stack è sempre accessibile dalla ISR senza dover salvare e ripristinare RP0/RP1 — il risparmio è 2 istruzioni per push e 2 per pop nell’ISR, cruciale quando la latenza conta. Anche il codice in Bank 1 (con RP0=1) può fare push/pop senza cambiare banco. I rischi: l’area shared è piccola (solo 16 byte), quindi lo stack è limitato a 8 livelli. Se il codice usa anche altre variabili nell’area shared (comuni per W_SAVE e STATUS_SAVE nella ISR), i 16 byte si saturano in fretta. Bisogna pianificare attentamente: tipicamente 2 byte per W_SAVE/STATUS_SAVE, 8 per lo stack, 6 rimasti per altre variabili ISR.
Prossima lezione →
Lezione 36 — Ottimizzazione e profiling del codice
Contare i cicli macchina, ridurre la latenza ISR, ottimizzare loop critici. Bank switching efficiente, uso dei registri shadow, inline vs subroutine.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 35