«

»

Opérations arithmétiques des PIC16

Les opérations arithmétiques s’appuient principalement sur les 2 instructions add et sub déclinées dans leur mode litéral ou registre. Si add ne pose pas de problème particulier, sub quant a elle peut en dérouter plus d’un car elle opère sur l’opposé de W comme indiqué ci-après :

sublw 0xa              sublw   10            ; W = 10 – W
subwf 0x20, f          subwf   R_2_MAX_TX    ; R_2_MAX_TX = R_2_MAX_TX – W

De plus, l’unité arithmétique et logique des PIC16 ne propose pas d’instruction de comparaison qui peut être considéré comme un manque notoire dans leur jeu d’instructions mais nous allons voir au travers de deux exemples de traitements classiques que l’on peut très bien s’en passer en faisant un peu  d’arithmétique modulo 256 :

  1. La conversion d’un quartet compris entre 0 et 15 en un caractère hexadécimal codé en ASCII [0-9A-F]
  2. La détermination de la plage à laquelle appartient une valeur donnée comprise entre 0 et 255

Pour chaque exemple, il sera proposée la version « optimisée » dans laquelle aucun registre intermédiaire ou temporaire n’est utilisé comme si l’instruction de comparaison était disponible.

  • Conversion d’un quartet  en un caractère hexadécimal codé en ASCII

Tout d’abord ci-après une implémentation possible sans opération arithmétique permettant de bien comprendre l’expression du besoin :

                     . . .
movf  0x7f, w        movf    R_NIBBLE_TO_CONVERT,w ; W = Quartet à convertir
call  0x100          call    nibble_to_ascii
                     . . .   ; W = Valeur HEXA codée en ASCII [0-9A-F]
                     org     0x100
                nibble_to_ascii ; Peut s’exécuter depuis n’importe quel BANK
clrf  0xa            clrf    SFR_PCLATH      ; Init. PCLATH sans altérer W
bsf   0xa, 0         bsf     SFR_PCLATH,0    ; Définitions dans la page 0x01
andlw 0xf            andlw   0x0F            ; Abandon du nibble haut
addwf 0x2, f         addwf   SFR_PCL
retlw 0x30           retlw   ‘0’             ; Liste des 16 codes ASCII
retlw 0x31           retlw   ‘1’
retlw 0x32           retlw   ‘2’
retlw 0x33           retlw   ‘3’
retlw 0x34           retlw   ‘4’
retlw 0x35           retlw   ‘5’
retlw 0x36           retlw   ‘6’
retlw 0x37           retlw   ‘7’
retlw 0x38           retlw   ‘8’
retlw 0x39           retlw   ‘9’
retlw 0x41           retlw   ‘A’
retlw 0x42           retlw   ‘B’
retlw 0x43           retlw   ‘C’
retlw 0x44           retlw   ‘D’
retlw 0x45           retlw   ‘E’
retlw 0x46           retlw   ‘F’

Important!

Suivant l’emplacement dans les pages programme des définitions retlw, l’initialisation du registre PCLATH devra être complétée (i.e. ajout d’un bsf SFR_PCLATH,1 si implantée dans la page 0x03).

Notice

Cette implémentation est relativement coûteuse en code (20 instructions), à contrario elle s’exécute en temps constant (6 cycles hors appel). L’implémentation qui suit est beaucoup plus optimisée mais ne s’exécutera pas en temps constant.

                     org 0x100
                nibble_to_ascii              ; VAL = W à convertir
andlw 0xf            andlw   0x0F            ; Abandon du nibble haut
sublw 0x9            sublw   (0x0A – 1)      ; W = (0x09 – VAL)
btfsc 0x3, 0         skpnc                   ; Test autour du pivot 0x09
goto  0x106          goto    nibble_to_ascii_0_9
                nibble_to_ascii_A_F
                     ; VAL >= 0x0A : return VAL + 0x37 (0x37 = ‘A’ – 0xA)
sublw 0x40           sublw   0x40            ; W = 0x40 – (0x09 – VAL)
return               return                  ;   = VAL + 0x37
                nibble_to_ascii_0_9
                     ; VAL <  0x0A : return VAL + ‘0’
sublw 0x39           sublw   ‘9’             ; W = ‘9’ – (0x09 – VAL)
return               return                  ;   = VAL + 0x30 = VAL + ‘0’

Important!

Comme on peut le constater, l’utilisation de l’instruction sub (W = XXX – W)  deux fois de suite  associée à un calcul modulo 256, permet de s’affranchir de l’emploi d’un registre temporaire qui, sans les opérations données en commentaire, aurait été nécessaire après une sauvegarde de W en entrée de la sous-routine.

Notice

Cette implémentation est beaucoup moins coûteuse en code (8 instructions au lieu de 20 quelle que soit la page d’où est appelé celui-ci) et s’exécute en 7 ou 8 cycles (hors appel). Il faut convenir que le code est moins lisible au profit d’un gain en taille appréciable.

Maintenant, pourquoi ne pas pousser le raisonnement plus en avant en supprimant le ‘goto nibble_to_ascii_0_9‘ et le premier ‘return ; = VAL + 0x37‘. Pour cela, il suffit de les remplacer par une opération arithmétique qui « annule » le traitement fait en « trop » puis regrouper les opérations successives. Nous arrivons alors à l’implémentation suivante qui est encore moins coûteuse que la précédente :

                     org 0x100
                nibble_to_ascii         ; VAL = W à convertir
andlw 0xf            andlw   0x0F       ; Abandon du nibble haut
sublw 0x9            sublw   (0x0A – 1) ; W = (0x09 – VAL)
btfsc 0x3, 0         skpnc              ; Test autour du pivot 0x09
addlw 0x7            addlw   7          ; VAL < 0x0A uniquement
sublw 0x40           sublw   (0x37 + 9) ; Si VAL < 0x0A => return VAL + ‘0’
return               return             ; Sinon => return VAL + (‘A’ – 0x0A)

Notice

Cette implémentation fait gagner 2 instructions (6 instructions au lieu de 8) et s’exécute en temps constant de 7 cycles (hors appel).