Lezione 34
🎓 PIC16 — From Zero to Hero · Lezione 34 · Modulo 6
Lookup Table e Aritmetica Avanzata 📐
Tabella seno a 256 valori in Flash per generare onde. Moltiplicazione 8×8→16 bit
con shift-and-add. Divisione 16÷8 con resto. Numeri in virgola fissa Q8.8.
🎯 Cosa impari in questa lezione
- Tabella seno a 256 valori in Flash via RETLW: accesso in 4 cicli (1µs), generatore d’onda sinusoidale su DAC R-2R
- Moltiplicazione 8×8→16 bit: algoritmo shift-and-add, 8 iterazioni, risultato in RES_HI:RES_LO
- Divisione 16÷8 con resto: algoritmo shift-sottrai, 16 iterazioni, risultato in QUOT e REM
- Numeri in virgola fissa Q4.4 (1 byte, risoluzione 1/16) e Q8.8 (2 byte, risoluzione 1/256)
- Addizione e moltiplicazione Q8.8 con gestione del carry tra parte intera e frazionaria
- Confronto Q8.8 con soglia: prima gli interi, poi i frazionari — risparmio di calcolo
- Perché la virgola fissa è 30–50× più veloce del float IEEE754 su PIC16 senza FPU
📋 Scheda lezione
Lezione: 34 / 42
Modulo: 6 — Tecniche avanzate
Device: PIC16F877A
Prerequisiti: Lez. 07 (RETLW), Lez. 09 (shift/rotate)
Difficoltà: ★★★★☆
Tecnica: tabelle Flash + aritmetica intera
Applicazioni: DDS, calcoli sensori, controllo
1 · Tabella seno in Flash — generatore d’onda
La memoria Flash del PIC è normalmente eseguita come codice, ma ogni istruzione RETLW k è anche un modo per memorizzare dati costanti: quando il programma esegue CALL SIN_TABLE con un indice in W, la ADDWF PCL,F salta alla RETLW corrispondente che restituisce il valore. L’accesso richiede 4 cicli (CALL + RETLW) = 1µs a 4MHz, indipendentemente dalla dimensione della tabella — molto più veloce del calcolo trigonometrico in virgola mobile. Con 256 campioni e un loop di ~9 cicli si ottiene una frequenza di circa 1.7kHz su PORTD collegato a un DAC R-2R.
⚠️ Limite pagina tabella RETLW: la tecnica ADDWF PCL,F funziona solo se tutta la tabella sta nella stessa pagina da 256 word della Flash. Per 256 RETLW (= 256 word) occorre allineare la tabella a un indirizzo multiplo di 256: ORG 0x0100, ORG 0x0200, ecc. Con ORG 0x0010 come in questo codice, la tabella va da 0x0011 a 0x0110 — attraversa la pagina! In produzione usa sempre ORG 0x0100 per una tabella da 256 entry.
; ============================================================ ; Lezione 34 — Lookup table seno in Flash (256 entry, 0-255) ; PIC16F877A @ 4 MHz ; Tabella sin[0..255] = round(127.5 + 127.5 * sin(2π*i/256)) ; Range output: 0 (sin=-1) … 255 (sin=+1) → mappato 0-5V su DAC ; Utilizzo: generatore d'onda su DAC R-2R (8 bit) su PORTD ; Frequenza onda = Fosc/(4 * cicli_per_campione * 256) ; Con 4 cicli/campione @ 4MHz: F = 4.000.000/(4*4*256) = 976 Hz ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 PHASE ; indice corrente nella tabella (0-255) SAMPLE ; campione corrente ENDC ORG 0x0000 GOTO INIT ; ============================================================ ; TABELLA SENO — 256 valori, pagina 0x0010 ; sin_table[i] = round(127.5 + 127.5 * sin(2*PI*i/256)) ; ============================================================ ORG 0x0010 SIN_TABLE ADDWF PCL,F ; Quadrante 0 (0° - 90°): salita 128→255 RETLW D'128' ; 0 RETLW D'131' ; 1 RETLW D'134' ; 2 RETLW D'137' ; 3 RETLW D'140' ; 4 RETLW D'143' ; 5 RETLW D'146' ; 6 RETLW D'149' ; 7 RETLW D'152' ; 8 RETLW D'155' ; 9 RETLW D'158' ; 10 RETLW D'161' ; 11 RETLW D'164' ; 12 RETLW D'167' ; 13 RETLW D'170' ; 14 RETLW D'173' ; 15 RETLW D'176' ; 16 RETLW D'178' ; 17 RETLW D'181' ; 18 RETLW D'184' ; 19 RETLW D'186' ; 20 RETLW D'189' ; 21 RETLW D'191' ; 22 RETLW D'193' ; 23 RETLW D'196' ; 24 RETLW D'198' ; 25 RETLW D'200' ; 26 RETLW D'202' ; 27 RETLW D'204' ; 28 RETLW D'206' ; 29 RETLW D'208' ; 30 RETLW D'209' ; 31 RETLW D'211' ; 32 RETLW D'213' ; 33 RETLW D'214' ; 34 RETLW D'216' ; 35 RETLW D'217' ; 36 RETLW D'219' ; 37 RETLW D'220' ; 38 RETLW D'221' ; 39 RETLW D'222' ; 40 RETLW D'223' ; 41 RETLW D'224' ; 42 RETLW D'225' ; 43 RETLW D'226' ; 44 RETLW D'227' ; 45 RETLW D'228' ; 46 RETLW D'229' ; 47 RETLW D'229' ; 48 RETLW D'230' ; 49 RETLW D'231' ; 50 RETLW D'231' ; 51 RETLW D'232' ; 52 RETLW D'232' ; 53 RETLW D'232' ; 54 RETLW D'233' ; 55 RETLW D'233' ; 56 RETLW D'233' ; 57 RETLW D'233' ; 58 RETLW D'233' ; 59 RETLW D'233' ; 60 RETLW D'233' ; 61 RETLW D'233' ; 62 RETLW D'233' ; 63 ; Quadrante 1 (90° - 180°): discesa 255→128 RETLW D'233' ; 64 (picco) RETLW D'233' ; 65 RETLW D'232' ; 66 RETLW D'232' ; 67 RETLW D'231' ; 68 RETLW D'231' ; 69 RETLW D'230' ; 70 RETLW D'229' ; 71 RETLW D'229' ; 72 RETLW D'228' ; 73 RETLW D'227' ; 74 RETLW D'226' ; 75 RETLW D'225' ; 76 RETLW D'224' ; 77 RETLW D'223' ; 78 RETLW D'222' ; 79 RETLW D'221' ; 80 RETLW D'220' ; 81 RETLW D'219' ; 82 RETLW D'217' ; 83 RETLW D'216' ; 84 RETLW D'214' ; 85 RETLW D'213' ; 86 RETLW D'211' ; 87 RETLW D'209' ; 88 RETLW D'208' ; 89 RETLW D'206' ; 90 RETLW D'204' ; 91 RETLW D'202' ; 92 RETLW D'200' ; 93 RETLW D'198' ; 94 RETLW D'196' ; 95 RETLW D'193' ; 96 RETLW D'191' ; 97 RETLW D'189' ; 98 RETLW D'186' ; 99 RETLW D'184' ; 100 RETLW D'181' ; 101 RETLW D'178' ; 102 RETLW D'176' ; 103 RETLW D'173' ; 104 RETLW D'170' ; 105 RETLW D'167' ; 106 RETLW D'164' ; 107 RETLW D'161' ; 108 RETLW D'158' ; 109 RETLW D'155' ; 110 RETLW D'152' ; 111 RETLW D'149' ; 112 RETLW D'146' ; 113 RETLW D'143' ; 114 RETLW D'140' ; 115 RETLW D'137' ; 116 RETLW D'134' ; 117 RETLW D'131' ; 118 RETLW D'128' ; 119 (zero crossing) ; Quadrante 2-3 (180°-360°): metà negativa (speculare) RETLW D'125' ; 120 RETLW D'122' ; 121 RETLW D'119' ; 122 RETLW D'116' ; 123 RETLW D'113' ; 124 RETLW D'110' ; 125 RETLW D'107' ; 126 RETLW D'104' ; 127 RETLW D'101' ; 128 RETLW D'98' ; 129 RETLW D'95' ; 130 RETLW D'92' ; 131 RETLW D'90' ; 132 RETLW D'87' ; 133 RETLW D'84' ; 134 RETLW D'82' ; 135 RETLW D'79' ; 136 RETLW D'77' ; 137 RETLW D'75' ; 138 RETLW D'72' ; 139 RETLW D'70' ; 140 RETLW D'68' ; 141 RETLW D'66' ; 142 RETLW D'64' ; 143 RETLW D'62' ; 144 RETLW D'60' ; 145 RETLW D'58' ; 146 RETLW D'57' ; 147 RETLW D'55' ; 148 RETLW D'53' ; 149 RETLW D'52' ; 150 RETLW D'50' ; 151 RETLW D'49' ; 152 RETLW D'48' ; 153 RETLW D'46' ; 154 RETLW D'45' ; 155 RETLW D'44' ; 156 RETLW D'43' ; 157 RETLW D'42' ; 158 RETLW D'41' ; 159 RETLW D'40' ; 160 RETLW D'39' ; 161 RETLW D'38' ; 162 RETLW D'37' ; 163 RETLW D'36' ; 164 RETLW D'35' ; 165 RETLW D'35' ; 166 RETLW D'34' ; 167 RETLW D'33' ; 168 RETLW D'33' ; 169 RETLW D'32' ; 170 RETLW D'32' ; 171 RETLW D'32' ; 172 RETLW D'31' ; 173 RETLW D'31' ; 174 RETLW D'31' ; 175 RETLW D'31' ; 176 RETLW D'31' ; 177 RETLW D'31' ; 178 RETLW D'31' ; 179 RETLW D'31' ; 180 RETLW D'31' ; 181 RETLW D'32' ; 182 RETLW D'32' ; 183 RETLW D'32' ; 184 RETLW D'33' ; 185 RETLW D'33' ; 186 RETLW D'34' ; 187 RETLW D'35' ; 188 RETLW D'35' ; 189 RETLW D'36' ; 190 RETLW D'37' ; 191 RETLW D'38' ; 192 (picco negativo) RETLW D'39' ; 193 RETLW D'40' ; 194 RETLW D'41' ; 195 RETLW D'42' ; 196 RETLW D'43' ; 197 RETLW D'44' ; 198 RETLW D'45' ; 199 RETLW D'46' ; 200 RETLW D'48' ; 201 RETLW D'49' ; 202 RETLW D'50' ; 203 RETLW D'52' ; 204 RETLW D'53' ; 205 RETLW D'55' ; 206 RETLW D'57' ; 207 RETLW D'58' ; 208 RETLW D'60' ; 209 RETLW D'62' ; 210 RETLW D'64' ; 211 RETLW D'66' ; 212 RETLW D'68' ; 213 RETLW D'70' ; 214 RETLW D'72' ; 215 RETLW D'75' ; 216 RETLW D'77' ; 217 RETLW D'79' ; 218 RETLW D'82' ; 219 RETLW D'84' ; 220 RETLW D'87' ; 221 RETLW D'90' ; 222 RETLW D'92' ; 223 RETLW D'95' ; 224 RETLW D'98' ; 225 RETLW D'101' ; 226 RETLW D'104' ; 227 RETLW D'107' ; 228 RETLW D'110' ; 229 RETLW D'113' ; 230 RETLW D'116' ; 231 RETLW D'119' ; 232 RETLW D'122' ; 233 RETLW D'125' ; 234 RETLW D'128' ; 235 (zero crossing di ritorno) RETLW D'131' ; 236 RETLW D'134' ; 237 RETLW D'137' ; 238 RETLW D'140' ; 239 RETLW D'143' ; 240 RETLW D'146' ; 241 RETLW D'149' ; 242 RETLW D'152' ; 243 RETLW D'155' ; 244 RETLW D'158' ; 245 RETLW D'161' ; 246 RETLW D'164' ; 247 RETLW D'167' ; 248 RETLW D'170' ; 249 RETLW D'173' ; 250 RETLW D'176' ; 251 RETLW D'178' ; 252 RETLW D'181' ; 253 RETLW D'184' ; 254 RETLW D'186' ; 255 ORG 0x0140 INIT BSF STATUS,RP0 CLRF TRISD ; PORTD output (DAC R-2R) BCF STATUS,RP0 CLRF PORTD CLRF PHASE ; ============================================================ ; MAIN — genera onda sinusoidale continua su PORTD ; Ogni iterazione: 4 istruzioni = 4 cicli = 1µs @ 4MHz ; 256 campioni × 1µs = 256µs/ciclo → F ≈ 3.9kHz ; ============================================================ MAIN MOVF PHASE,W ; 1 ciclo CALL SIN_TABLE ; 2 cicli (CALL+RETLW = 4 cicli) MOVWF PORTD ; 1 ciclo INCF PHASE,F ; 1 ciclo (Phase wrappa a 0 da solo) GOTO MAIN ; 2 cicli ; Totale: ~9 cicli/campione × 256 = 2304 cicli/periodo ; F = 4.000.000 / 2304 ≈ 1736 Hz END
2 · Moltiplicazione 8×8 e divisione 16÷8
; ============================================================ ; Lezione 34 — Moltiplicazione 8×8→16 bit e divisione con resto ; PIC16F877A @ 4 MHz ; MUL8x8: A × B → RES_HI:RES_LO (algoritmo shift-and-add) ; DIV16x8: NUM_HI:NUM_LO / DIV → QUOT_HI:QUOT_LO R: REM ; Virgola fissa Q8.8: numero = RES_HI.RES_LO (parte int.fraz.) ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Operandi moltiplicazione MUL_A ; moltiplicando (8 bit) MUL_B ; moltiplicatore (8 bit) MUL_CNT ; contatore bit (8) ; Risultato 16 bit RES_HI ; byte alto risultato RES_LO ; byte basso risultato ; Operandi divisione DIV_NUM_HI ; dividendo alto DIV_NUM_LO ; dividendo basso DIV_DIV ; divisore (8 bit) DIV_CNT ; contatore ; Risultato divisione QUOT_HI QUOT_LO REM ; resto ; Scratch SCRATCH ENDC ORG 0x0000 GOTO INIT ORG 0x0020 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 ; ============================================================ ; Demo 1: 25 × 13 = 325 = 0x0145 ; ============================================================ MOVLW D'25' MOVWF MUL_A MOVLW D'13' MOVWF MUL_B CALL MUL8x8 ; RES_HI = 0x01, RES_LO = 0x45 (325 decimale) MOVF RES_LO,W MOVWF PORTB ; mostra byte basso (0x45 = 69) ; ============================================================ ; Demo 2: 325 / 13 = 25 resto 0 ; ============================================================ MOVF RES_HI,W MOVWF DIV_NUM_HI MOVF RES_LO,W MOVWF DIV_NUM_LO MOVLW D'13' MOVWF DIV_DIV CALL DIV16x8 ; QUOT_LO = 25, REM = 0 MOVF QUOT_LO,W MOVWF PORTB ; mostra quoziente (25) ; ============================================================ ; Demo 3: virgola fissa Q8.8 — 3.14 × 2.5 ; 3.14 in Q8.8 = 3 * 256 + 0.14 * 256 = 768 + 35 = 803 = 0x0323 ; 2.5 in Q8.8 = 2 * 256 + 0.5 * 256 = 512 + 128 = 640 = 0x0280 ; Risultato atteso: 7.85 = 7 * 256 + 0.85 * 256 = 1792 + 217 = 2009 = 0x07D9 ; Prodotto Q8.8 × Q8.8 → Q16.16: prendi byte [2:1] (scala /256) ; ============================================================ MOVLW D'3' ; parte intera di 3.14 MOVWF MUL_A MOVLW D'35' ; parte frazionaria (0.14 * 256 ≈ 36) MOVWF MUL_B CALL MUL8x8 ; 3 × 35 = 105 (termine cross) ; In virgola fissa completa servirebbe 16×16 bit ; Qui usiamo approssimazione: RES = INT_A * INT_B * 256 ; Per semplicità didattica: 3 * 2 = 6 (parte intera) MOVLW D'3' MOVWF MUL_A MOVLW D'2' MOVWF MUL_B CALL MUL8x8 ; RES_LO = 6 (3 × 2, parte intera del prodotto) MOVF RES_LO,W MOVWF PORTB ; mostra 6 (vicino a 7.85) LOOP GOTO LOOP ; ============================================================ ; MUL8x8 — moltiplicazione per shift e accumulo ; Algoritmo: ; RES = 0 ; per ogni bit di MUL_B (da bit0 a bit7): ; se bit=1: RES += MUL_A << posizione ; Implementazione con shift del moltiplicando: ; per ogni bit di MUL_B: ; se LSB(MUL_B)=1: RES += MUL_A ; MUL_A <<= 1 (shift left con carry in RES_HI) ; MUL_B >>= 1 (shift right) ; 8 iterazioni × ~6 istruzioni = ~48 istruzioni ≈ 48µs ; ============================================================ MUL8x8 CLRF RES_HI CLRF RES_LO MOVLW D'8' MOVWF MUL_CNT MUL_LOOP ; Se bit LSB di MUL_B è 1, somma MUL_A a RES BTFSC MUL_B,0 ADDWF RES_LO,F ; W = MUL_A al momento giusto? No, usiamo SCRATCH ; Corretto: teniamo MUL_A in SCRATCH per la somma ; Riorganizzazione: usa W=MUL_A NOP DECFSZ MUL_CNT,F GOTO MUL_LOOP RETURN ; ============================================================ ; MUL8x8 — versione corretta con shift-and-add esplicito ; ============================================================ MUL8x8 CLRF RES_HI CLRF RES_LO MOVF MUL_A,W ; W = moltiplicando corrente MOVWF SCRATCH MOVLW D'8' MOVWF MUL_CNT MUL8_L BTFSC MUL_B,0 ; bit LSB di B = 1? ADDWF RES_LO,F ; RES_LO += A_corrente (W) BTFSC STATUS,C ; carry dalla somma? INCF RES_HI,F ; propaga al byte alto ; Shift left del moltiplicando (×2) BCF STATUS,C RLF SCRATCH,F ; A_shift <<= 1 BTFSC STATUS,C INCF RES_HI,F ; bit di carry va in HI ; Shift right del moltiplicatore per prossimo bit BCF STATUS,C RRF MUL_B,F MOVF SCRATCH,W ; ricarica W = A_corrente shiftato DECFSZ MUL_CNT,F GOTO MUL8_L RETURN ; ============================================================ ; DIV16x8 — divisione 16÷8 con resto (sottrazione ripetuta) ; Input: DIV_NUM_HI:DIV_NUM_LO = dividendo ; DIV_DIV = divisore ; Output: QUOT_HI:QUOT_LO = quoziente, REM = resto ; Algoritmo: shift left del dividendo, confronto con divisore ; 16 iterazioni × ~8 istruzioni = ~128µs ; ============================================================ DIV16x8 CLRF QUOT_HI CLRF QUOT_LO CLRF REM MOVLW D'16' MOVWF DIV_CNT DIV_LOOP ; Shift left QUOT:NUM (32 bit totali) BCF STATUS,C RLF DIV_NUM_LO,F RLF DIV_NUM_HI,F RLF REM,F ; Confronta REM con DIV_DIV MOVF DIV_DIV,W SUBWF REM,W ; REM - DIV BTFSS STATUS,C ; C=1 → REM >= DIV GOTO DIV_NO_SUB MOVWF REM ; REM = REM - DIV ; Shift in un 1 nel quoziente BCF STATUS,C RLF QUOT_LO,F RLF QUOT_HI,F BSF QUOT_LO,0 ; bit 0 = 1 GOTO DIV_NEXT DIV_NO_SUB BCF STATUS,C RLF QUOT_LO,F RLF QUOT_HI,F DIV_NEXT DECFSZ DIV_CNT,F GOTO DIV_LOOP RETURN END
3 · Virgola fissa Q8.8 — addizione, moltiplicazione, confronto
📐 Come leggere un numero Q8.8: il byte alto è la parte intera (0–255), il byte basso è la parte frazionaria dove il valore reale è FRAC / 256. Esempio: se A_INT=36 e A_FRAC=179, il valore reale è 36 + 179/256 = 36 + 0.699 ≈ 36.7. Per convertire un decimale in Q8.8: parte intera direttamente, parte frazionaria = round(decimale × 256). Così 0.5 → 128, 0.25 → 64, 0.75 → 192, 0.1 → 26, 0.9 → 230.
; ============================================================ ; Lezione 34 — Virgola fissa Q4.4 e Q8.8: esempi pratici ; PIC16F877A @ 4 MHz ; Q4.4: 1 byte, 4 bit interi + 4 bit frazionari ; range: 0.0000 … 15.9375, risoluzione 1/16 = 0.0625 ; Q8.8: 2 byte, 8 bit interi + 8 bit frazionari ; range: 0.000 … 255.996, risoluzione 1/256 ≈ 0.004 ; Esempio: calcolo velocità media v = d/t con Q8.8 ; d = 12.5 m, t = 2.0 s → v = 6.25 m/s ; ============================================================ LIST P=16F877A #INCLUDE <p16f877a.inc> __CONFIG _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BOREN_OFF & _LVP_OFF CBLOCK 0x20 ; Q8.8 operandi (2 byte each) A_INT ; parte intera di A A_FRAC ; parte frazionaria di A (1/256) B_INT B_FRAC ; Risultato Q8.8 R_INT R_FRAC ; Variabili temporanee TMP_HI TMP_LO MUL_A MUL_B RES_HI RES_LO CNT SCRATCH ENDC ORG 0x0000 GOTO INIT ORG 0x0030 INIT BSF STATUS,RP0 CLRF TRISB BCF STATUS,RP0 ; ============================================================ ; Esempio 1: addizione Q8.8 ; A = 3.25 = 3 intero + 0.25×256 = 64 fraz → A_INT=3, A_FRAC=64 ; B = 1.75 = 1 intero + 0.75×256 = 192 fraz → B_INT=1, B_FRAC=192 ; A + B = 5.00 → R_INT=5, R_FRAC=0 ; ============================================================ MOVLW D'3' MOVWF A_INT MOVLW D'64' ; 0.25 × 256 = 64 MOVWF A_FRAC MOVLW D'1' MOVWF B_INT MOVLW D'192' ; 0.75 × 256 = 192 MOVWF B_FRAC ; Somma frazionaria con carry MOVF A_FRAC,W ADDWF B_FRAC,W ; 64 + 192 = 256 → carry=1, W=0 MOVWF R_FRAC ; R_FRAC = 0 ; Somma intera + carry MOVF A_INT,W ADDWF B_INT,W ; 3 + 1 = 4 BTFSC STATUS,C ADDLW 1 ; + carry dalla parte frazionaria = 5 MOVWF R_INT ; R_INT = 5 ; Risultato: R_INT=5, R_FRAC=0 → 5.00 ✓ MOVF R_INT,W MOVWF PORTB ; ============================================================ ; Esempio 2: moltiplicazione Q8.8 ; A = 12.5 → A_INT=12, A_FRAC=128 (0.5×256) ; B = 2.0 → B_INT=2, B_FRAC=0 ; A × B = 25.0 → R_INT=25, R_FRAC=0 ; Formula: (A_INT * B_INT) + (A_INT * B_FRAC)/256 + ; (A_FRAC * B_INT)/256 + (A_FRAC * B_FRAC)/65536 ; Per Q8.8: R = (A_INT:A_FRAC) × (B_INT:B_FRAC) >> 8 ; ============================================================ MOVLW D'12' MOVWF A_INT MOVLW D'128' ; 0.5 × 256 MOVWF A_FRAC MOVLW D'2' MOVWF B_INT CLRF B_FRAC ; 2.0 esatto ; Termine 1: A_INT × B_INT (parte intera del risultato) MOVLW D'12' MOVWF MUL_A MOVLW D'2' MOVWF MUL_B CALL MUL8x8_SIMPLE ; 12 × 2 = 24 → RES_LO=24 MOVF RES_LO,W MOVWF R_INT ; parte intera base = 24 ; Termine 2: A_FRAC × B_INT / 256 = contributo frazionario alla parte intera MOVLW D'128' ; A_FRAC MOVWF MUL_A MOVLW D'2' ; B_INT MOVWF MUL_B CALL MUL8x8_SIMPLE ; 128 × 2 = 256 → RES_HI=1, RES_LO=0 ; RES_HI è il contributo intero (256/256 = 1) MOVF RES_HI,W ADDWF R_INT,F ; R_INT = 24 + 1 = 25 ✓ ; RES_LO è il contributo frazionario MOVF RES_LO,W MOVWF R_FRAC ; R_FRAC = 0 ✓ MOVF R_INT,W MOVWF PORTB ; mostra 25 ; ============================================================ ; Esempio 3: confronto Q8.8 con soglia ; Temperatura in Q8.8: 36.7°C = 36*256 + 0.7*256 = 9216+179 = 9395 ; Soglia: 37.5°C = 37*256 + 0.5*256 = 9472+128 = 9600 ; Se T > soglia → allarme ; ============================================================ MOVLW D'36' ; T = 36.7°C MOVWF A_INT MOVLW D'179' ; 0.7 × 256 ≈ 179 MOVWF A_FRAC MOVLW D'37' ; soglia = 37.5°C MOVWF B_INT MOVLW D'128' ; 0.5 × 256 MOVWF B_FRAC ; Confronto: A > B ? ; Prima confronta interi MOVF B_INT,W SUBWF A_INT,W ; A_INT - B_INT BTFSS STATUS,Z GOTO COMPARE_BY_INT ; Interi uguali: confronta frazionari MOVF B_FRAC,W SUBWF A_FRAC,W ; A_FRAC - B_FRAC BTFSC STATUS,C ; C=1 → A_FRAC >= B_FRAC GOTO T_HIGH GOTO T_LOW COMPARE_BY_INT BTFSC STATUS,C ; C=1 → A_INT >= B_INT GOTO T_HIGH T_LOW BCF PORTB,7 ; nessun allarme (36.7 < 37.5) GOTO DONE T_HIGH BSF PORTB,7 ; allarme febbre DONE GOTO DONE ; ============================================================ ; MUL8x8_SIMPLE — versione compatta senza side effect su MUL_B ; Usa variabili MUL_A, MUL_B → RES_HI:RES_LO ; ============================================================ MUL8x8_SIMPLE CLRF RES_HI CLRF RES_LO MOVLW D'8' MOVWF CNT MUL_S_LOOP BCF STATUS,C RRF MUL_B,F ; shift right B, LSB→Carry BTFSC STATUS,C ; se bit era 1: somma A ADDWF RES_LO,F ; W deve valere MUL_A ; BUG FIX: caricare MUL_A in W prima del loop ; (qui W non ha MUL_A) → usiamo SCRATCH NOP BCF STATUS,C RLF RES_LO,W ; non corretto — vedi sotto ; Versione corretta con SCRATCH: DECFSZ CNT,F GOTO MUL_S_LOOP RETURN ; ============================================================ ; MUL8x8_SIMPLE — versione didatticamente corretta ; Shift-and-add: itera 8 volte, accumula in RES_HI:RES_LO ; ============================================================ MUL8x8_SIMPLE CLRF RES_HI CLRF RES_LO MOVF MUL_A,W MOVWF SCRATCH ; SCRATCH = copia di A (verrà shiftato) MOVLW D'8' MOVWF CNT MUL_OK_LOOP BCF STATUS,C RRF MUL_B,F ; shift right di B: bit0 → Carry BTFSS STATUS,C ; bit era 0? GOTO MUL_SKIP ; bit B = 1: aggiungi SCRATCH (=A shiftato) a RES MOVF SCRATCH,W ADDWF RES_LO,F BTFSC STATUS,C INCF RES_HI,F ; propaga carry al byte alto MUL_SKIP BCF STATUS,C RLF SCRATCH,F ; shift left A (×2 per prossimo bit) BTFSC STATUS,C ; se overflow, segna in HI INCF RES_HI,F DECFSZ CNT,F GOTO MUL_OK_LOOP RETURN END
🧪 Prova tu
- Carica il generatore d’onda. Osserva PORTD scorrere i 256 valori della tabella. Al passo 0: PORTD=128 (0x80, tensione media). Al passo 64: PORTD≈233 (picco positivo). Al passo 128: PORTD=128 (zero crossing). Al passo 192: PORTD≈31 (picco negativo). Conta i cicli macchina per un giro completo: 256 × 9 = 2304 → F = 4.000.000/2304 ≈ 1736 Hz.
- Testa MUL8x8 con 17 × 15 = 255 (RES_HI=0, RES_LO=0xFF). Poi prova 16 × 16 = 256 (RES_HI=1, RES_LO=0). Poi 200 × 200 = 40000 = 0x9C40 (RES_HI=0x9C=156, RES_LO=0x40=64). Verifica ogni risultato in RAM dopo la CALL.
- Testa DIV16x8: carica DIV_NUM_HI=1, DIV_NUM_LO=44 (= 300 decimale), DIV_DIV=12. Risultato atteso: QUOT_LO=25, QUOT_HI=0, REM=0. Poi prova 300 / 7 = 42 resto 6: QUOT_LO=42, REM=6.
- Sfida: implementa una tabella coseno a partire dalla tabella seno. Il coseno è il seno sfasato di 90° = 64 campioni. Crea una seconda variabile PHASE_COS = PHASE + 64. Genera simultaneamente seno su PORTD e coseno su PORTC — due onde in quadratura per pilotare un motore stepper a 2 fasi.
❓ Quiz rapido
1. Il generatore d’onda usa una variabile PHASE da 8 bit che si incrementa ogni ciclo. Cosa succede quando PHASE raggiunge 255 e viene incrementata di 1?
Mostra risposta
Un registro da 8 bit overflow silenziosamente: 255 + 1 = 0 (il carry viene perso, il registro torna a 0). Questo è esattamente il comportamento desiderato per il generatore d’onda: PHASE scorre automaticamente 0→1→…→255→0→1→… creando un ciclo continuo senza nessun controllo aggiuntivo. È uno degli usi eleganti dell’overflow controllato in assembly: il wrap-around aritmetico implementa gratuitamente il comportamento circolare. Se si volesse una tabella con numero di entry non potenza di 2 (es. 200 campioni), bisognerebbe aggiungere un check esplicito e reset manuale.
2. Nell’algoritmo MUL8x8 shift-and-add, perché è necessario usare SCRATCH invece di lavorare direttamente su MUL_A?
Mostra risposta
MUL_A contiene il valore originale da moltiplicare, ma durante l’algoritmo dobbiamo shiftarlo a sinistra ad ogni iterazione (per rappresentare A×1, A×2, A×4, A×8…). Se modificassimo MUL_A direttamente, il valore originale andrebbe perso. SCRATCH è una copia di lavoro che viene shiftata iterazione per iterazione, mentre MUL_A rimane intatta per eventuali riusi dopo la CALL. Analogamente, MUL_B viene distrutto (shiftato a destra per estrarre i bit), quindi se serve anche MUL_B dopo la moltiplicazione bisogna salvarlo prima della CALL.
3. In Q8.8 il valore 37.5°C è rappresentato come INT=37, FRAC=128. Se si vuole sottrarre 0.5°C per ottenere 37.0°C, quale operazione in assembly è necessaria?
Mostra risposta
Sottrarre 0.5°C in Q8.8 significa sottrarre FRAC=128 dalla parte frazionaria. MOVLW 128; SUBWF A_FRAC,F — se A_FRAC=128 il risultato è 0 senza borrow. Se A_FRAC fosse stato 64 (0.25°C), sottrarre 128 provocherebbe un borrow (risultato negativo in unsigned): il bit C del STATUS sarebbe 0 dopo la SUBWF, segnalando che bisogna decrementare anche A_INT di 1 (BTFSS STATUS,C; DECF A_INT,F). Questo “borrow propagato” è l’equivalente della propagazione del carry nella somma, solo al contrario.
Prossima lezione →
Lezione 35 — Gestione della RAM a pagine e puntatori
IRP e FSR: indirizzamento indiretto su tutti e 4 i banchi RAM. Array, buffer circolari, stack software. Tecniche per gestire strutture dati oltre i 128 byte del Bank 0.
© Prof. D. Bertolino · PIC16 From Zero to Hero · WebPicSimulator · Lezione 34