Salta al contenuto
Note per Studenti Realizzare un filtro o un regolatore a virgola fissa

Realizzare un filtro o un regolatore a virgola fissa

In questa pagina 5
**; per sommare due prodotti con scale diverse si fa lo shift **prima** di sommare. Con a+b=216a+b=2^{16} il filtro y=(a yk−1+b (u≪4))≫16y=\big(a\,y_{k-1}+b\,(u\ll4)\big)\gg16 non va in overflow in un `uint32_t`. Nel codice il formato di ogni variabile va dichiarato in un commento; la funzione sin⁡x≈x−x36+x5120\sin x\approx x-\frac{x^3}6+\frac{x^5}{120} mostra come si approssima una funzione con polinomi a coefficienti in virgola fissa. -->

Le note precedenti danno gli ingredienti: formati (Virgola fissa - formati n.m e normalizzazioneIn virgola fissa il processore fa aritmetica sugli interi (con segno) e il fattore di scalacostante per cui si moltiplica un valore reale per ottenere l'intero memorizzato $2^{m}$ resta sottinteso: il formato n.m dice che dei bit disponibili $n$ sono la parte intera (compreso il segno se il numero è con segno, S; nessun segno se U) e $m$ la parte frazionaria. Il valore è $\text{codice}/2^m$. Passare dal formato n.m al decimale, o viceversa, è il calcolo più frequente dell'esame: su 16 bit $\text{valore}=\text{codice}/2^m$, l'intervallo è $[0,2^n)$ (U) oppure $[-2^{n-1},2^{n-1})$ (S), la risoluzione è $2^{-m}$.Virgola fissa - formati n.m e normalizzazione →), scelta e errore (Rappresentare un valore reale in virgola fissa - formato ottimo ed errorePer rappresentare un reale $x$ su $W$ bit a virgola fissa si sceglie il formato che minimizza l'erroresceglie la rappresentazione per cui la differenza tra il valore rappresentato e quello vero è la più piccola possibile: il maggior numero di bit frazionari $m$ compatibile con l'intervallo ($n=W-m$ bit interi, con U se $x\ge0$, con S se $x<0$). Poi $C=\mathrm{round}(x\cdot2^m)$, scritto in esadecimale (per un negativo, in complemento a 2), e l'errore $\varepsilon=\frac{C}{2^m}-x$, positivo se in eccesso, ha modulo $\le2^{-(m+1)}$. Se invece è dato l'errore massimo, i bit minimi si ricavano da $2^{-(m+1)}\le\varepsilon_{max}$.Rappresentare un valore reale in virgola fissa - formato ottimo ed errore →), prodotto e riallineamento (Operazioni in virgola fissa - somma, prodotto e riallineamentoIn virgola fissa il processore opera sugli interi e non sa dov'è la virgola: tocca al programmatore. Regola 1: si sommano solo dati con lo stesso formato (stesso $m$). Regola 2: il prodotto di $n_1.m_1$ per $n_2.m_2$ ha $m_1+m_2$ bit frazionari e il doppio dei bit: va riallineato con uno shift a destra di $m_2$ posizioni (per riportarlo a $m_1$) o preso dalla parte alta. Con la normalizzazione frazionaria $1.15$ il prodotto è $2.30$ e basta uno shift a sinistra di 1 prima di prendere la parte alta. I fattori di scala si scelgono per evitare overflow e perdita di risoluzione.Operazioni in virgola fissa - somma, prodotto e riallineamento →), discretizzazione (Discretizzazione di Eulero e TustinPer realizzare numericamente un filtro o regolatore $F(s)$ si approssima $\frac1s$ con un integratore a tempo discreto. Eulero: $\boxed{s=\dfrac{1-z^{-1}}{T_c}}$ (da $\mathrm{Int}(nT)=\mathrm{Int}((n-1)T)+T,x(n)$). Così $\dfrac{1+s\tau_z}{1+s\tau_p}$ diventa $y(k)=\dfrac{\tau_p}{T_c+\tau_p}y(k-1)+\dfrac{T_c+\tau_z}{T_c+\tau_p}x(k)-\dfrac{\tau_z}{T_c+\tau_p}x(k-1)$. Il passaggio inverso (da algoritmo a $F(s)$) usa $z^{-1}\approx1-sT_c$. L'approssimazione è affidabile solo fino a circa $f_c/10$ (distorsione, warping); Tustin $s=\frac2{T_c}\frac{1-z^{-1}}{1+z^{-1}}$ è più fedele.Discretizzazione di Eulero e Tustin →), quantizzazione (Quantizzazione, fattori di scala e cicli limiteUn algoritmo di filtro o regolatore pensato per un processore ideale va adattato alla realtà: temporizzazionedistribuzione nel tempo delle operazioni di un algoritmo (l'algoritmo nella ISR dell'ADC, durata minore di $T_c$), acquisizione (condizionamento del segnale per sfruttare tutto il fondo scala dell'ADC), fattori di scala (normalizzazione intera o 1.15; evitare overflow), quantizzazione di ingresso, uscita, coefficienti e prodotti (sposta i poli, genera errori a regime e cicli limite). Una frequenza di campionamento troppo alta rende più critica la quantizzazione dei coefficienti: va scelta coerente con la lunghezza di parola.Quantizzazione, fattori di scala e cicli limite →). Qui si mettono insieme su un caso concreto, quello del laboratorio, e su altri due tipici da esame.

Esempio 1: passa-basso su STM32 (laboratorio)

Specifiche: passa-basso del primo ordine con banda 500 Hz, fc=50f_c=50 kHz, guadagno 1. Quindi Tc=20 μT_c=20\ \mus: il dato va convertito e il calcolo concluso in meno di 20 µs. Con il clock da circa 14 ns ci sono circa 1400 cicli: molti di più di quelli richiesti dal filtro. Si sceglie un IIR del primo ordine (un FIR costerebbe molto di più: Filtri numerici IIR e FIRUn filtro numerico è una equazione alle differenzerelazione che dà il campione di uscita in funzione di campioni di ingresso e di uscita passati $y(k)=\sum_ib_ix(k-i)+\sum_ia_iy(k-i)$. IIR (ricorsivo: qualche $a_i\ne0$) ha risposta impulsiva infinita e bastano pochi termini; FIR (tutti $a_i=0$) è sempre stabile ma richiede molti termini. Il passa-basso IIR del primo ordine $y(k)=b,x(k)+a,y(k-1)$ con $a+b=1$ ha guadagno 1 ed è stabile se $|a|<1$; la risposta al gradino è $y(k)=1-a^{k+1}$. Il filtro analogico equivalente (Eulero) ha $\tau=\dfrac{a,T_c}{1-a}$ e $f_T=\dfrac1{2\pi\tau}$. La media mobile di $N$ campioni è un FIR passa-basso che si assesta in $N$ passi.Filtri numerici IIR e FIR →).

Coefficienti (Eulero). τ=12π500=3,1831⋅10−4\tau=\frac1{2\pi500}=3{,}1831\cdot10^{-4} s; a=ττ+Tc=0,940883a=\frac{\tau}{\tau+T_c}=0{,}940883 (coefficiente di y(k−1)y(k-1)) e b=Tcτ+Tc=0,059117b=\frac{T_c}{\tau+T_c}=0{,}059117 (coefficiente di x(k)x(k)), a+b=1a+b=1.

Rappresentazione. L'aritmetica del µC è a 32 bit, ma un prodotto 32×3232\times32 richiederebbe un accumulatore a 64 bitregistro che servirebbe per prodotti di dati a 32 bit che non c'è: dati e coefficientii fattori costanti che moltiplicano i campioni nell'algoritmo devono stare su 16 bit ({{uint16_t|tipo C intero senza segno a 16 bit}}). Poiché i numeri sono tutti positivi e minori di 1 si usa 0.16 senza segnoformato in cui il dato è una frazione tra 0 e 1 con 16 bit frazionari con fattore 216=655362^{16}=65536: a16=round(0,940883⋅65536)=61662=0xF0DE,b16=round(0,059117⋅65536)=3874=0x0F22.a_{16}=\mathrm{round}(0{,}940883\cdot65536)=61662=\texttt{0xF0DE},\qquad b_{16}=\mathrm{round}(0{,}059117\cdot65536)=3874=\texttt{0x0F22}. Il dato dell'ADC a 12 bit (u∈[0,4095]u\in[0,4095]) si allineaporta un dato a 12 bit in una parola a 16 bit con gli MSB o gli LSB coincidenti a sinistra su 16 bit: u≪4u\ll4. La risoluzione numericail passo minimo rappresentabile: 2 elevato a meno m dei coefficienti è 2−16=1,5⋅10−52^{-16}=1{,}5\cdot10^{-5}: il diagramma di Bode del filtro reale e di quello ideale sono quasi sovrapponibili; resta il troncamentoeliminazione dei bit bassi senza arrotondare implicito nelle moltiplicazioni. 4 dei 16 bit di u≪4u\ll4 sono perennemente nulli (l'ADC ha 12 bit, la risoluzione è circa 0,8 mV con FSR 3,3 V).

Prodotto e shift. Il prodotto di due uint16_t (interpretati come interi) è un intero a 32 bit con 16+16=3216+16=32 bit frazionari; per riportarlo a 16 bit frazionari si scartano i 16 bit bassi, cioè shift a destra di 16. Esempio: 0x4000\texttt{0x4000} (0,25) per 0xA000\texttt{0xA000} (0,625) dà 0x28000000\texttt{0x28000000}; ≫16=0x2800=0,15625\gg16=\texttt{0x2800}=0{,}15625 ✓.

Codice (nella ISRroutine di servizio di una interruzione dell'ADC, con y di tipo {{uint32_t|tipo C intero senza segno a 32 bit}}):

c
// u: 12 bit; a16, b16: formato 0.16 U; y e yo: formato 0.16 U (0..65535)
y  = (a16*yo + b16*(u<<4)) >> 16;   // un solo shift: i due prodotti hanno la stessa scala (m = 32)
yo = y;

I due prodotti hanno entrambi 32 bit frazionari: si possono sommare prima di fare lo shift (si perde un solo bit di troncamento, invece di due). Poiché a16+b16=65536a_{16}+b_{16}=65536 e yo,u≪4≤65535y_o,u\ll4\le65535, la somma è al massimo 65536⋅65535<23265536\cdot65535<2^{32}: non va in overflow in 32 bit senza segno. (Nei file del corso i nomi dei coefficienti sono scambiati rispetto a questa nota, ma la struttura è la stessa.)

Esempio 2: coefficienti maggiori di 1 (tema d'esame febbraio 2026)

Algoritmo y(k)=0,8 y(k−1)+12,2 x(k)−12 x(k−1)y(k)=0{,}8\,y(k-1)+12{,}2\,x(k)-12\,x(k-1) con xx in 0.16 U (Q16) e yy in 4.12 U, calcolo su 32 bit. Si scelgono i formati dei coefficienti per minimizzare l'errore:

  • a=0,8a=0{,}8: 0.16 U, a16=round(0,8⋅65536)=52429=0xCCCDa_{16}=\mathrm{round}(0{,}8\cdot65536)=52429=\texttt{0xCCCD} (arrotondato);
  • b0=12,2b_0=12{,}2: positivo e <16<16: 4.12 U, b0=round(12,2⋅4096)=49971=0xC333b_0=\mathrm{round}(12{,}2\cdot4096)=49971=\texttt{0xC333};
  • b1=−12b_1=-12: negativo, ∣b1∣<16|b_1|<16: 5.11 S (segno + 4 bit interi + 11 frazionari), b1=−12⋅2048=−24576=0xA000b_1=-12\cdot2048=-24576=\texttt{0xA000}. Allineamento dei prodotti. a⋅yoa\cdot y_o: 16+12=2816+12=28 bit frazionari, per riportare a 12 (formato di yy) si fa ≫16\gg16. b0⋅xb_0\cdot x: 12+16=2812+16=28; b1⋅xob_1\cdot x_o: 11+16=2711+16=27. Per sommare i due si porta b0b_0 a 11 bit frazionari con b0≫1b_0\gg1 (così (b0≫1)⋅x(b_0\gg1)\cdot x ha 27 frazionari come b1x0b_1x_0) e poi ≫15\gg15 per ottenere 12 bit frazionari:
c
y  = ((a16*y0) >> 16) + ((((b0_16 >> 1)*x) + (b1_16*x0)) >> 15);   // y: 4.12 U
y0 = y;  x0 = x;

(b1_16 va trattato come intero con segno a 32 bit.) Verifica con il programma: per un gradino unitario x=0xFFFFx=0\texttt{xFFFF} (≈1\approx1) si ottiene y(0)=0xC331=12,199y(0)=\texttt{0xC331}=12{,}199 (atteso 12,212{,}2), y(1)=0x9F58=9,959y(1)=\texttt{0x9F58}=9{,}959 (atteso 0,8⋅12,2+0,2=9,960{,}8\cdot12{,}2+0{,}2=9{,}96), ..., y(22)=1,079y(22)=1{,}079 (la risposta teorica è 1+11,2⋅0,8k1+11{,}2\cdot0{,}8^k: 1,0831{,}083): gli scostamenti sono errori di arrotondamento. Valore massimo di xx senza overflow nel calcolo: y(0)=12,2 x<16y(0)=12{,}2\,x<16 richiederebbe x<1,31x<1{,}31, ma x<1x<1 per la scelta di formato, quindi xmax=1x_{max}=1 (fondo scala).

Esempio 3: approssimazione di una funzione (tema d'esame settembre 2026)

Si vuole calcolare y=sin⁡x≈a x−b x3+c x5y=\sin x\approx a\,x-b\,x^3+c\,x^5, con a=1a=1, b=16b=\frac16, c=1120c=\frac1{120}, per x∈[0,π2]x\in[0,\frac\pi2] (sin⁡\sin non esiste in hardware: Architettura del repertorio di istruzioni - RISC, CISC, VLIW e indirizzamentoL'architettura è l'insieme delle risorse visibili al programmatore (istruzioni, modi di indirizzamento). RISC: poche istruzioni semplici, di uguale lunghezza, decodifica cablata, quasi tutte a 1 ciclo; CISC: molte istruzioni complesse, decodifica microprogrammata, più cicli. I DSP sono RISC "potenziati" (MAC, saturazione, barrel shifter, arrotondamento, VLIW, SIMD); i modi di indirizzamento tipici sono immediato, a registro, diretto, indiretto, con auto-incremento, circolare e a bit rovesciati (per la FFT).Architettura del repertorio di istruzioni - RISC, CISC, VLIW e indirizzamento →). Con coefficienti frazionari senza segno 0.16 e arrotondamento: b=16=0,16667→round(10922,67)=10923=0x2AABb=\frac16=0{,}16667\to\mathrm{round}(10922{,}67)=10923=\texttt{0x2AAB} (errore +5,1⋅10−6+5{,}1\cdot10^{-6}); c=1120=0,008333→round(546,13)=546=0x0222c=\frac1{120}=0{,}008333\to\mathrm{round}(546{,}13)=546=\texttt{0x0222} (errore −2,0⋅10−6-2{,}0\cdot10^{-6}). Se xx è in 1.15 Uformato senza segno con un bit intero e 15 frazionari: valori tra 0 e 2 (x∈[0,2)x\in[0,2)), x=60∘=1,0472x=60^\circ=1{,}0472 rad →round(1,0472⋅32768)=34315=0x860B\to\mathrm{round}(1{,}0472\cdot32768)=34315=\texttt{0x860B}: il valore realmente usato è 34315/32768=1,0472134315/32768=1{,}04721. Il polinomio con il xx effettivamente usato dà yvero=0,86630y_{vero}=0{,}86630 (il polinomio di Taylor approssima sin⁡60∘=0,86603\sin60^\circ=0{,}86603 con 2,8⋅10−42{,}8\cdot10^{-4} di errore: è l'errore di troncamento della seriesviluppo di una funzione come somma di potenze di x, non della rappresentazione). Calcolando con x2=x2≫15x_2=x^2\gg15, x3=x2x≫15x_3=x_2x\gg15, x5=x3x2≫15x_5=x_3x_2\gg15 (tutti in 1.15) e y=(x≪1)−(b x3≫15)+(c x5≫15)y=(x\ll1)-(b\,x_3\gg15)+(c\,x_5\gg15) in formato 0.16 si ottiene y16=0xDDC5=0,86629y_{16}=\texttt{0xDDC5}=0{,}86629: errore rispetto a yveroy_{vero} di −1,5⋅10−5-1{,}5\cdot10^{-5} (arrotondamenti dei prodotti). Questo è l'esempio di come, anche per una funzione non lineare, si lavora con poche moltiplicazioni e shift.

Regole pratiche

  1. Scrivere il formato di ogni variabile in un commento (e nei nomi, per esempio a_q16).
  2. Controllare, prima di ogni somma, che i due addendi abbiano lo stesso numero di bit frazionari; se no, shift.
  3. Usare un accumulatore a 32 bit per i prodotti di dati a 16 bit e fare un solo shift dopo aver sommato prodotti con la stessa scala.
  4. Verificare l'assenza di overflow nel caso peggiore (ingresso a fondo scala, somma dei moduli dei coefficienti).
  5. Verificare la risposta con una simulazione (per esempio in Python o con il debugger sul µC) confrontandola con il calcolo in virgola mobile.

Errori comuni

  • Moltiplicare due uint16_t in uint16_t: il prodotto va in overflow; serve convertire in uint32_t prima del prodotto.
  • Shiftare di troppo o troppo poco dopo il prodotto (conta quanti bit frazionari ha il risultato).
  • Dimenticare il segno: un prodotto con un negativo va fatto con interi con segno.
  • Allineare a destra il dato a 12 bit e poi trattarlo come se fosse 0.16 U.

Versione ripasso

Esercizi su questo argomento

Teoria collegata