Lezione 35 di 43

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.

Tabella seno in Flash — 256 RETLW, mappatura e output su DAC R-2R 255 128 0 0 64 128 192 255 indice tabella (PHASE = 0…255) 0°-90° 90°-180° 180°-270° 270°-360° DAC R-2R 8bit su PORTD → 0-5V

⚠️ 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

Algoritmo MUL8×8 — shift-and-add, esempio 13 × 25 = 325 Iter MUL_B (shift →) SCRATCH (A×2^i) Bit B LSB RES accumulato init 0b00001101 (13) 25 — RES=0 1 0b00000110 25 (×1) 1 → +25 RES=25 2 0b00000011 50 (×2) 0 → skip RES=25 3 0b00000001 100 (×4) 1 → +100 RES=125 4 0b00000000 200 (×8) 1 → +200 RES=325 ✓ 5-8 0b00000000 400,800… 0 → skip RES invariato 325 = 0x0145 → RES_HI=0x01, RES_LO=0x45 8 iterazioni × ~7 istruzioni ≈ 56µs @ 4MHz
; ============================================================
; 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 &lt;&lt;= 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 &gt;= 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

Formati virgola fissa — Q4.4, Q8.8 e confronto con float Q4.4 — 1 byte bit7:4 = parte intera (0-15) bit3:0 = fraz. (0-0.9375) Risoluz: 1/16 = 0.0625 Range: 0.0 … 15.9375 Es: 3.5 = 0b00110000 | 1000 Q8.8 — 2 byte byte alto = parte intera (0-255) byte basso = fraz. (0-0.996) Risoluz: 1/256 ≈ 0.0039 Range: 0.0 … 255.996 Es: 3.25 → INT=3, FRAC=64 float IEEE754 — 4 byte 1 bit segno, 8 esp, 23 mantissa Risoluz: ~7 cifre decimali Range: ±3.4×10^38 NON nativo su PIC16 ~100µs per operazione Prestazioni @ 4MHz PIC16 Q4.4 somma: ~4µs Q8.8 somma: ~6µs Q8.8 moltipl: ~56µs float add: ~200µs

📐 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 &gt;= B_FRAC
    GOTO    T_HIGH
    GOTO    T_LOW
COMPARE_BY_INT
    BTFSC   STATUS,C           ; C=1 → A_INT &gt;= B_INT
    GOTO    T_HIGH
T_LOW
    BCF     PORTB,7            ; nessun allarme (36.7 &lt; 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

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