jeudi 13 décembre 2012

premiers pas avec MCU Atmel AVR

Ça faisait déjà plus d'un mois que j'avais acheter un programmeur Polulu AVR. Ajourd'hui j'ai décidé qu'il était temps de m'y mettre en commencant par le plus simple.

La première étape est de télécharger et installer Atmel studio 6.0.
Il faut ensuite installer le logiciel de Polulu pour le programmeur.

Je dois dire que j'avais préalablement acheter et lu le datasheet du AtTiny13A que j'ai utiliser pour programmer une variante de la LED qui clignote.

Atmel studio 6.0

En suivant les instructions du manuel de démarrage du programmeur Polulu. Ça a été simple de me retrouver dans Atmel studio et d'écrire la première version du programme en C. En effet le studio installe le compilateur avr-gcc ce qui permet de programmer en 'C' dès le départ. J'ai donc branché une LED RBG au AtTiny13A comme suit:


et j'ai relié le programmeur au MCU comme ceci:


Le programme que j'ai écris cycle entre les 3 couleurs du LED RGB et le code source est le suivant:

#define F_CPU 9600000
#define DLY 200
#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
CLKPR= (1<<7);
CLKPR = 0 ; // désactivation du diviseur
PORTB |= (1<<PORTB5); // pullup sur RESET
DDRB |= (0x11<<DDB3|1); // met PORTB broche 0, 3 et 4 en sortie
PORTB &= ~(0x11<<PORTB3|1) ; // eteint tous les LED
while (1){ //boucle infinie
PORTB |= (1<<PORTB3); // allume le LED sur PB3
_delay_ms(DLY);
PORTB &= ~(1<<PORTB3); // éteint le LED sur PB3
PORTB |= (1<<PORTB4); // allume le led sur PB4
_delay_ms(DLY);
PORTB &= ~(1<<PORTB4); // éteint LED sur PB4
PORTB |= 1; // allume LED sur PB0
_delay_ms(DLY);
PORTB &=0xFE ; // éteint LED sur PB0
}

}


Ce programme compilé occupe 164 octets sur les 1K de mémoire flash du ATTiny13A.
Je voulais comparé ça avec le code écris en assembleur. Comme je n'ai jamais programmé en assembleur pour les AVR j'ai fait une recherche rapide dans google pour trouver des exemples et me suis inspiré de ce que j'ai trouvé.

/*
* BlinkRGB.asm
*
* Created: 2012-12-13 13:10:32
* Author: Jacques
* REF: http://jaxcoder.com/Projects.aspx?id=912541054
* http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=94232
* DESCRIPTION: une LED RGB est branché sur PB0,PB3 et PB4 le programme cycle les 3 couleurs
*/


.NOLIST
.include "tn13adef.inc"
.LIST

.equ DLY = 500 ; 500 msec
.equ F_OSC = 9600000 ; 9.6Mhz
.equ LED1 = PORTB0
.equ LED2 = PORTB3
.equ LED3 = PORTB4


.cseg
.org 0
rjmp init
.org 0x20 ; les 32 première addresses sont réservée aux vecteurs d'interruption.
init:
ldi r16, LOW(RAMEND)
out SPL, r16 ; initialisation du pointeur de pile.
ldi r16, 1<<7
ldi r17, 0
out CLKPR, r16
out CLKPR, r17 ; pas de diviseur sur horloge cpu
ldi r16, 1<<PORTB5
out PORTB, r16 ; pullup sur PORTB5 (reset)
ldi r16, (1<<LED2|1<<LED3|LED1)
out DDRB, r16 ; broche LED1,LED2 et LED3 en sortie
ldi r16, ~(1<<LED2|1<<LED3|LED1)
out PORTB, r16 ; éteint les 3 LEDs

main:
sbi PORTB, LED1 ; allume LED1
rcall delay_ms
cbi PORTB, LED1 ; éteint LED1
sbi PORTB, LED2 ; allume LED2
rcall delay_ms
cbi PORTB, LED2 ; éteint LED2
sbi PORTB, LED3 ; allume LED3
rcall delay_ms
cbi PORTB, LED3
rjmp main

delay_ms:
/* sous-routine de délais
count=(dly/Tboucle)=~(dly/Tcy*nbreCycles)
count=(dly/(nbreCycles/F_OSC))
count=(dly*F_OSC/nbreClycles)
*/
ldi r16, 0xFF & (DLY*F_OSC/4000) ; initialise le compteur délais
ldi r17, 0xFF & (DLY*F_OSC/4000)>>8
ldi r18, 0xFF & (DLY*F_OSC/4000)>>16
dly_loop:
dec r16 ; 1tcy
nop ; 1tcy
brne dly_loop ; 2tcy
dec r17
brne dly_loop
dec r18
brne dly_loop
ret

Le code assembleur occupe 68 octets ce qui représente 41% de la version 'C', mais avec un programme plus gros cet 'overhead' paraîtrait moins coûteux. J'ai examiné le code assembleur résultant du 'C' pour voir la différence. Ça tient principalement au code d'initialisation généré par le compilateur et au fait que le délais est inséré comme une macro plutôt que défini comme une sous-routine comme c'est le cas dans ma version en assembleur. J'observe aussi une différence dans la routine de délais. La macro du programme 'C' est la suivante pour _delay_ms

; r24,r25 et r26 servent de registre 24 bits pour le compteur de délais.
.macro _delay_ms ; voir note 1
ldi r24, LOW(@0) ; octet faible du premier argument, aussi: BYTE1(@0)
ldi r25, HIGH(@0) ; 2ième octet du premier argument, aussi: BYTE2(@0)
ldi r26, BYTE3(@0) ; 3ième octet du premier argument
dly_loop:
subi r24, 0x01 ; soustrait 1 à (r24-r25-r26)
sbci r25, 0x00 ; sustraction en tenant compte du carry bit.
sbci r26, 0x00
brne dly_loop
.endmacro

Cette macro utilise une soustraction sur 24 bits au lieu de décrémenter successivement les compteurs. J'ai modifié mon code assembleur pour utiliser cette méthode et je sauve 6 octets:
; constantes ajoutées au début du fichier
; maintenant on divise par 5000 au lieu de 4000
; car il y a 5 cycles dans la boucle au lieu de 4.
; le 1000 c'est pour les millisecondes DLY=500/1000 sec.
.equ DLY1 = 0xFF & (DLY*F_OSC/5000)
.equ DLY2 = 0xFF & (DLY*F_OSC/5000)>>8
.equ DLY3 = 0xFF & (DLY*F_OSC/5000)>>16

delay_ms:
/*
sous-routine de délais
version améliorée
*/
ldi r16, DLY1 ; initialise le compteur délais
ldi r17, DLY2
ldi r18, DLY3
dly_loop:
subi r16, 1 ; 1tcy
sbci r17, 0 ; 1tcy
sbci r18, 0 ; 1tcy
brne dly_loop ; 2tcy
ret

Comparaison avec MCU PIC

Prenons la sous-routine delay_ms et comparons là avec ce qu'on ferais avec un PIC baseline ou mid-range:

ms_delay
movlw DLY1
movwf dly_cntr
movlw DLY2
movwf dly_cntr+1
movlw DLY3
movwf dly_cntr+2 ; 6 instructions pour initialiser le compteur au lieu de 3.
dly_loop
movlw 1
subwf dly_cntr,F
clrw
skpc
subwf dly_cntr+1, F
skpc
subwf dly_cntr+2, F
skpnc
goto dly_loop ; 9 instructions pour la boucle au lieu de 4
return

Ça prend 2 fois plus d'instructions sur un PIC!!. Essayons avec un enhanced midrange:

ms_delay
movlw DLY1
movwf dly_cntr
movlw DLY2
movwf dly_cntr+1
movlw DLY3
movwf dly_cntr+2 ; aucun gain par rapport aux baseline/midrange
dly_loop
movlw 1
subwf dly_cntr, F
clrw
subwfb dly_cntr+1, F ; instruction disponible seulement
subwfb dly_cntr+2, F ; sur les enhanced et extended.
skpnc
goto dly_loop ; gain de 2 instructions grâce à subwfb
return

Grâce à l'instruction subwfb disponible sur les cores enhanced, on sauve 2 instructions mais on est encore à 14/8 en comparaison avec le AVR. De plus grâce aux 32 registres interne au core AVR a n'a utilisé aucune mémoire RAM alors que les PIC en utilisent 3 pour le compteur.

Beaucoup de points positifs pour le core AVR mais tout n'est pas positif. En effet une instruction AVR occupe 2 octets et certaines en prennent 4. Donc pour le AtTiny13a qui a 1K de flash ça veut dire un maximum de 512 instructions. Il faut en tenir compte lorsqu'on choisi un mcu AVR pour un projet. L'autre point négatif est que l'accès à la RAM prend 2 cycles au lieu de 1 pour les PIC. Par contre un cycle AVR c'est un cycle d'horloge alors qu'un cycle PIC c'est 4 cycles d'horloges. Donc un PIC qui a une horloge interne de 16Mhz comme le PIC10F322 a un cycle d'instruction de 250nsec alors qu'un AVR AtTiny13A avec son horloge à 9.6Mhz a un cycle d'instruction de 104nsec. Donc ce qui semblait-être un point négatif au départ est en fait positif. A fréquence d'horloge égales l'AVR est 4 fois plus rapide pour les opérations arithmétiques et logiques qui se font sur ses registres internes et 2 fois plus rapide pour les accès à la RAM. Par contre s'il doit faire une opération sur une variable dans la RAM il doit d'abord la ramené dans un registre internet faire l'opération et la réécrire dans la RAM. Avec 1 PIC si on veut par exemple incrémenter une variable eu peut le faire en 1 seule instruction et 1 seul cycle. Mais avec ses 32 registres internes au core les AVR tiny peuvent faire pas mal de chemin sans utiliser la RAM.

Conclusion

L'expérience a été intéressante et ce n'est surement la dernière fois que je travaille avec des AVR. Au niveau de l'assembleur la richesse du jeux d'instructions à comparer à la pauvreté du jeux PIC baseline et mid-range rends l'expérience plus agréable. Le fait de pouvoir programmer en 'C' sans avoir à acheter un compilateur coûteux est aussi un point intéressant.



NOTES:
1) contrairement à mpasm avrasm n'utilise pas de noms pour les arguments de macros.
le nombre maximum d'arguments est de 10 et ils sont nommé @0-@9.
Une définition de macro peut se terminée par .endm ou .endmacro
Toutes les directives de l'asembleur commence par un '.'

1 commentaire:

  1. Très intéressant, pour quelqu'un comme moi qui travaille souvent avec des PIC et hésite à passer à l'AVR.

    Personnellement je développe sur PIC en C, et cela gratuitement grâce au compilateur SDCC. Bon je dois avouer que le code est un peu lourd, mais c'est bien pratique.

    Dans le cas de l'atmel, AVRGCC marche très bien il parait.

    En tout cas vous me motivez à passer à l'avr. Le fait qu'il utilise plus de registres et qu'il lui faille un coup d'horloge par cycle est positif.

    RépondreEffacer