Je programme occasionnellement sur avr (comprendre autre qu’arduino quoi), mais comme c’est très espacé dans le temps, à chaque fois j’ai l’impression de reprendre depuis le début. Du coups, je me suis dit que ça pourrais être bien de faire un petit papier là-dessus, en expliquant un peu les points qui m’ont posé problème à un moment ou un autre. Je ne suis pas un très bon programmeur, donc il y aura peut être des erreurs ou imprécisions, n’hésitez pas à m’en faire part dans les commentaires si c’était le cas ![]()
On va commencer par un truc simple : un ATtiny85, une LED, une résistance, et on veut faire clignoter notre led
. Déjà, premier truc à savoir, c’est qu’il y a plein de façons différentes de le faire, alors on va commencer par du « haut niveau », et on ira voir un peu plus loin ensuite.
Le schéma nous indique les différents ports E/S. On constate que plusieurs fonctionnalités leur sont attribués, le choix des pins utilisés se fera en fonction de ces fonctionnalités. Dans notre exemple, on peut utiliser n’importe quelle E/S, mais si par exemple on devais gérer une interruption matérielle, elle se ferait obligatoirement sur les broches PB0, PB1 ou PB2 (PCINT0, PCINT1 et PCIN2T).
Pour notre exemple, on va choisir d’utiliser la pin 5 de l’AtTiny85, nommée PB0. Voici la structure de notre programme
#include <avr/io.h>
int main(void)
{
//initialisation des entrées/sorties
for(;;) //la boucle principale de notre programme
{
//on allume la led
//on attend quelques millisecondes
//on eteind la led
//on attend quelques millisecondes
}
}
Voilà, la structure du programme ne devrait pas poser de problème particulier à quelqu’un qui a déjà vu au moins une fois dans sa vie un programme en C. La suite en revanche peut en dérouter plus d’un, si vous n’êtes pas habitués à ce genre de syntaxes. Petites explications :
Afin de configurer une broche en sortie, il faut affecter la bonne valeur dans le bon registre
Le registre configurant la « direction » des données s’appelle DDRB, pour Port B Data Direction Register.
Par défaut, la valeur des bits de ce registre est à 0, ce qui signifie qu’il est accessible en lecture. Pour le passer en écriture, il suffit de mettre à 1 le bit correspondant. Vous pouvez le faire simplement en tapant :
DDRB = 0x01;//configure le port PB0 en sortie
Mais vous pouvez également tomber sur une syntaxe différente qui peut paraître bizarre au premier abord:
DDRB = 1<<PB0;
Celà veut simplement dire que l’on décale le 1 de x vers la gauche, x étant la valeur de PB0 dans mon exemple.
Imaginons que l’on veuille configurer le port PB5 en sortie, il faudrait selon la datasheet que DDRB = 0×00100000. Nous partons donc d’un DDRB qui vaut 0×00000000.On lui fait subir un premier décalage du bit 1 vers la gauche, on a maintenant DDRB = 0×00000001 (équivalent à 1<<0)
On recommence le décalage et on obtiens DDRB=0×00000010 (équivalent à 1<<1)
…
On recommence le décalage et on obtiens DDRB=0×00100000 (équivalent à 1<<5), on a donc bien réglé notre port PB5 en tant que sortie.
On peut donc maintenant mettre notre programme à jour, on obtiens donc :
#include <avr/io.h>
int main(void)
{
//initialisation des entrées/sorties
DDRB |= 1<<PB0;
for(;;) //la boucle principale de notre programme
{
//on allume la led
//on attend quelques millisecondes
//on eteind la led
//on attend quelques millisecondes
}
}
Tant qu’on est dans les calculs binaires, il y a d’autres syntaxes que vous serez amené à croiser, par exemple :
GIMSK |= (1 << PCIE);
Il s’agit là de l’utilisation d’un masque, permettant de ne modifier que le bit qui nous intéresse (via l’utilisation d’un OU binaire). Les opérateurs binaires permettent d’améliorer grandement les performances d’un code, vous trouverez de plus amples explications sur ce sujet ici
Une fois que notre pin à été correctement configurée, il suffit de passer le bit correspondant à 1 pour la faire basculer à l’état 1, et inversement. Pour se faire, il faut écrire le bit correspondant à notre pin dans le registre PORTB. Là encore, c’est la datasheet qui nous aide, en nous fournissant le tableau de correspondances :
![]()
On peut maintenant compléter notre code :
#include <avr/io.h>
int main(void)
{
//initialisation des entrées/sorties
DDRB |= 1<<PB0;
for(;;) //la boucle principale de notre programme
{
PORTB |= (1<<PB0);//on allume la led
//on attend quelques millisecondes
PORTB &= ~(1 << PB0);//on eteind la led
//on attend quelques millisecondes
}
}
Voilà, le plus gros est fait, il ne reste plus qu’à gérer les délais. Pour ce premier tutoriel, on ne va pas s’embêter, on utilisera la fonction prévue pour, fournie avec util/delay.h : _delay_ms()
Comme son nom l’indique elle fait attendre le programme pendant un nombre donné de millisecondes. Dans une prochaine version, nous verrons comment utiliser un timer à la place. On dispose maintenant de tout le nécessaire pour terminer notre programme :
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
//initialisation des entrées/sorties
DDRB |= 1<<PB0;
for(;;) //la boucle principale de notre programme
{
PORTB |= (1<<PB0);//on allume la led
_delay_ms(500);//on attend quelques millisecondes
PORTB &= ~(1 << PB0);//on eteind la led
_delay_ms(500);//on attend quelques millisecondes
}
}
Voilà, pour utiliser notre programme, il reste encore à le compiler et à l’uploader sur le microcontrolleur. Je ne vais pas détailler cette section, elle est déjà largement documentée sur le net (et facilement compréhensible à mon avis). En revanche, je vais vous laisser un fichier Makefile bien pratique, qui permet de gérer la compilation ET l’upload, ainsi que tout un tas d’autres choses, bref, un vrai couteau suisse. (Le Makefile n’est pas de moi, mais je n’arrive pas à en retrouver la source, désolé)












































