Sempre s’ha dit que cal evitar l’ús de variables en punt flotant (float o double) en sistemes encastats. Això prové dels temps en que els microcontroladors disponibles no tenien cap unitat hardware de punt flotant i aquestes operacions s’havien de fer per software i això penalitzava moltíssim el rendiment.
Això encara és aplicable per la majoria de casos, tot i que els nous microcontroladors basats en Cortex-M4 o Cortex-M7 poden portar unitats de punt flotant. Anem a veure amb detall una mica com treballar amb les eines i el codi perquè tot funcioni correctament.
Treballarem amb dos exemples molt senzills, que son les funcions mulf i muld, que multipliquen dos valors de tipus float i double respectivament. Cal recordar que el tipus float correspon a un tipus de punt flotant de 32 bits conegut com single precision seguint l’standard IEEE 754. Double correspon a un tipus de 64 bits conegut com double precision del mateix standard.
/* mulf.c */
float mulf(float a, float b) {
return a*b;
}
/* muld.c */
double muld(double a, double b) {
return a*b;
}
Les biblioteques estàndard de C incorporen funcions per operar amb aquests tipus, i son les que es fan servir per defecte pel compilador si no li donem ordres especials.
Així doncs, si compilem el fitxer mulf.c amb la següent comanda
arm-none-eabi-gcc mulf.c -o- -S -mthumb -mcpu=cortex-m4
ens mostrarà per pantalla el codi en assemblador que genera el compilador. Cal fixar-se que els flags que li passem al compilador son només que el microcontrolador és un Cortex-M4.
push {r7, lr}
sub sp, sp, #8
add r7, sp, #0
str r0, [r7, #4] @ float
str r1, [r7] @ float
ldr r1, [r7] @ float
ldr r0, [r7, #4] @ float
bl __aeabi_fmul
mov r3, r0
mov r0, r3
adds r7, r7, #8
mov sp, r7
@ sp needed
pop {r7, pc}
Aquí el que es veu és que es preparen uns registres i es crida una funció anomenada __eabi_fmul que és la funció de la biblioteca encarregada de fer les multiplicacions per software.
Si ara especifiquem que el cortex-M4 te el mòdul d’operacions en punt flotant amb la segúent comanda
arm-none-eabi-gcc muld.c -o- -S -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
El resultat serà el següent, on ja es veu que es fan servir instruccions de punt flotant (vstr, vldr, vmul, vmov, etc.)
push {r7}
sub sp, sp, #12
add r7, sp, #0
vstr.32 s0, [r7, #4]
vstr.32 s1, [r7]
vldr.32 s14, [r7, #4]
vldr.32 s15, [r7]
vmul.f32 s15, s14, s15
vmov.f32 s0, s15
adds r7, r7, #12
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
En aquest cas els flags del compilador indiquen quin mòdul FPU te el nostre microcontrolador (fpv4-sp-d16): fp versió 4, single precision i 16 registres).
Això seria pel cas de la funció que treballa amb precisió simple, si ara fem el mateix per la funció que treballa amb dobles, fent servir els mateixos flags
arm-none-eabi-gcc muld.c -o- -S -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16
push {r7, lr}
sub sp, sp, #16
add r7, sp, #0
vstr.64 d0, [r7, #8]
vstr.64 d1, [r7]
ldrd r2, [r7]
ldrd r0, [r7, #8]
bl __aeabi_dmul
mov r2, r0
mov r3, r1
vmov d7, r2, r3
vmov.f32 s0, s14
vmov.f32 s1, s15
adds r7, r7, #16
mov sp, r7
@ sp needed
pop {r7, pc}
Veiem que altre cop es fa l’operació per software enlloc de fer-la via les instruccions de punt flotant. Per què passa això? Doncs perquè l’arquitectura Cortex-M4 només permet FPUs de precisió simple i no pot treballar amb precisió doble i així li hem especificat al compilador. Per tant el compilador fa les crides a la biblioteca software pertinent (__eabi_dmul).
Per tant, compte amb treballar amb doubles i arquitectures Cortex-M4 o inferiors! Cal tenir en compte que algunes operacions en C que fan servir constants poden acabar en un tipus double si no vigilem.
Si ara fem la prova amb la mateixa funció però usant els flags per un Cortex-M7
arm-none-eabi-gcc muld.c -o- -S -mthumb -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-d16
En aquest cas, els flags indiquen que el processador és un Cortex-M7 i la unitat de punt flotant és la versió 5 (Tal com indica el ARM Cortex-M7 Processor Technical Reference Manual (pàg 8-2)).
Amb aquests flags, el codi assemblador que es genera és el que es veu al llistat següent
push {r7}
sub sp, sp, #20
add r7, sp, #0
vstr.64 d0, [r7, #8]
vstr.64 d1, [r7]
vldr.64 d6, [r7, #8]
vldr.64 d7, [r7]
vmul.f64 d7, d6, d7
vmov.f64 d0, d7
adds r7, r7, #20
mov sp, r7
@ sp needed
ldr r7, [sp], #4
bx lr
Aquí es veu que es torna a fer servir registres i instruccions pròpies del punt flotant (vstr, vldr, vmul, vmov) amb el suffix .f64 que es correspon a la mida del tipus double (64 bits) i que, per tant, les operacions no es fan per una rutina software si no que les executa el mòdul de punt flotant del microcontrolador.
Per tant, podem veure clar que l’ús del tipus double només és recomanat per arquitectures Cortex-M7 i posteriors si no volem tenir una pèrdua de rendiment considerable.
Per últim, no cal espantar ningú, ja que aquest flags els maneguen les eines de cada fabricant segons les característiques dels seus microcontroladors i normalment no ens n’hem de preocupar.