PIC: OTTIMIZZARE LA VELOCITÀ CON IL COMPILATORE HI-TECH C
Ecco alcuni consigli su come ottimizzare la velocità di esecuzione del codice nella programmazione dei microcontrollori PIC:
- La fase di inizializzazione del PIC è piuttosto lunga in termini di tempo di esecuzione; per aumentare al massimo la velocità è consigliato inizializzare per primi i registri nel bank 0 quindi quelli nel bank 1 e così via;
- Ricordarsi che non tutte le variabili necessitano di essere inizializzare;
- Ove possibile riordinare gli operatori aritmetici in modo da minimizzare l’uso da parte del compilatore del registro W o di locazioni temporanee;
- Per evitare inutili commutazioni di banco, utilizzare in una stessa operazione aritmetica variabili appartenenti allo stesso banco di memoria;
- Se possibile preferire l’uso di variabili byte anziché word;
- Se possibile utilizzare utilizzare puntatori piuttosto che array indicizzati;
- Una serie di comandi IF/ELSE IF spesso genera meno codice dell’equivalente costrutto case;
- In un costrutto switch/case, preferire numeri sequenziali consecutivi;
- A seconda dei banchi di memoria coinvolti, i comandi
var=valore1; if (!flag) var=valore2;
- generano un codice più ottimizzato rispetto alla soluzione
if (!flag) val=valore1 else val=valore2;
- La chiamata a funzioni comporta un impiego di risorse piuttosto pesante per cui è preferibile sostituire con macro le piccole funzioni;
- Nei cicli ripetitivi è preferibile utilizzare un decremento della variabile di conteggio in quanto è più semplice verificare se una variabile in RAM ha raggiunto il valore nullo piuttosto che verificare che la stessa variabile abbia raggiunto un valore specifico. Si consideri infatti i due cicli FOR seguenti:
unsigned char i; for(i=0;i<250;i++) azione(); for(i=250;i!=0;i—) azione();
I due cicli eseguono la funzione azione() per 250 volte ma mentre il primo lo fa in 3,25ms (supponendo un quarzo a 4MHz), il secondo lo fa in 2,5ms. I due cicli vengono infatti tradotti in assembler nel seguente modo:
for(i=0;i<250;i++) azione(); //esecuzione in 3251 cicli 1617 01B8 clrf 0x38 1618 260F call 0x60F 1619 0AB8 incf 0x38 161A 3008 movlw 0xFA 161B 0238 subwf 0x38,W 161C 1C03 btfss 0x3,0x0 161D 2E18 goto 0x618 for(i=250;i!=0;i—) azione(); //esecuzione in 2502 cicli 1621 3008 movlw 0xFA 1622 00B8 movwf 0x38 1623 260F call 0x60F 1624 0BB8 decfsz 0x38 1625 2E23 goto 0x623
- Nel caso di gestioni di timeout piuttosto lunghi un primo approccio potrebbe essere quello di utilizzare un ciclo FOR nel seguente modo:
unsigned int timeout; for(timeout=0;timeout<20000 timeout++) azione(); //380011 cicli for(timeout=20000;timeout!=0;timeout—) azione();//320011 cicli
è possibile ottimizzare l’operazione nel seguente modo:
unsigned int timeout; #define hibyte(x) ((unsigned char)(x>>8)) #define lobyte(x) ((unsigned char)(x&0xff)) //keeps lobyte(timeout)==0 timeout=(20000/0x100)*0x100; for(;hibyte(timeout)!=0;timeout—) azione(); //295704 cicli
questo accorgimento esegue il controllo solo sulla parte alta della variabile timeout controllando quando tale byte diviene nullo. Per la gestione del timeout il modo più efficiente è quello di utilizzare i timer interni del micro ed i relativi flag di interrupt.
- Per effettuare divisioni e moltiplicazioni per fattori che sono potenze di due, è consigliato utilizzare operazioni di shift a destra o sinistra anziché l’operazione di divisione o moltiplicazione vera e propria. Questo, oltre a velocizzare il calcolo, permette di risparmiare da 13 a 23 byte di RAM.

Ottime considerazioni che valgono anche nel linguaggio C generale, soprattutto le prime.