Arduino PDF
Arduino PDF
Arduino PDF
Microcontrôleurs 2/29
1 Introduction
Le modèle UNO de la société ARDUINO est une carte électronique dont le coeur est un
microcontrôleur ATMEL de référence ATMega328. L'ATMega328 est un microcontrôleur 8bits de
la famille AVR dont la programmation peut être réalisée en langage C/C++.
L'intérêt principal des cartes ARDUINO (d'autres modèles existent : Mega, Nano...) est leur
facilité de mise en oeuvre. Un environnement de développement (IDE), s'appuyant sur des outils
open-source, est fourni. En outre, charger le programme compilé dans la mémoire du
microcontrôleur se fait très simplement (via par port USB). Enfin, beaucoup de bibliothèques de
fonctions sont également fournies pour l'exploitation des entrées-sorties courantes : E/S TOR,
gestion des convertisseurs ADC, génération de signaux PWM, exploitation de bus TWI/I2C,
exploitation de servomoteurs, d'afficheurs LCD ...
L'objectif du cours Microcontrôleurs n'est pas simplement de savoir utiliser la carte Arduino
UNO. C'est surtout l'occasion d'aborder des problèmes de programmation de bas niveau (la valeur
binaire des variables manipulées importe alors beaucoup) et d'apprendre à utiliser le langage C pour
cette programmation bas niveau, notamment en sachant gérer des registres/variables "au niveau du
bit". Donc quand on se complique la tâche, alors qu'une fonction Arduino existe, dites-vous que
c'est voulu.
L'objectif de ce document est de mettre en évidence certaines informations techniques
concernant l'exploitation des périphériques intégrés, en particulier lorsqu'on n'utilise pas les
fonctions "clé en main" d'ARDUINO, dans l'objectif de comprendre comment ça marche !
Microcontrôleurs 3/29
Vue du dessus, la carte fournit les informations suivantes:
ATTENTION : avec les fonctions Arduino (pinMode, digitalRead, digitalWrite ...), les signaux
sont repérés selon la numérotation des connecteurs (partie gauche). En revanche, lorsque l'on
programme en bas niveau, on utilise le nom des registres/des broches du microcontrôleur (partie
droite).
Microcontrôleurs 4/29
Plusieurs broches multi-fonctions : toutes les broches ont plusieurs fonctions différentes, choisies
par programmation. Elles ont donc plusieurs noms sur le brochage.
Par exemple, les broches PB1, PB2, PB3, PD3, PD5, PD6 peuvent servir de sortie PWM (Pulse
Width Modulation), c'est-à-dire des sorties qui joueront le rôle de sorties analogiques. Elles
correspondent aux broches des connecteurs 3,5,6,9,10 et 11. Cet autre rôle possible est lié aux
timers et ces broches sont alors appelées OCxA ou OcxB dans la documentation. Ce sont les mêmes
broches, mais pour une autre fonction.
Les broches du PORTC peuvent être converties par un convertisseur Analog toDigital.
Gestion bus I2C (TWI Two Wire Interface) = le bus est exploité via les broches
SDA(PC5)/SCL(PC4).
Microcontrôleurs 5/29
4 Le programmation avec l'IDE Arduino
int main(void)
{
init(); // initialisations ARDUINO pour fonctions
// utiles : delays, PWM ...
setup();
for (;;) loop(); // répète indéfiniment loop()
return 0;
}
-----------------------------
Microcontrôleurs 6/29
4.2 Langage C pour ARDUINO UNO
Variables Types/Taille :
void loop()
{
digitalWrite(13,HIGH); // set PB5
delay(200); // délai 200 ms
digitalWrite(13,LOW); // clear PB5
delay(1000); // délai 1 s
}
L'exemple le plus simple, fourni par ARDUINO, consiste à faire clignoter la LED (sur la carte
UNO) connectée à la broche PB5 du microcontrôleur, broche n° 13 sur les connecteurs de la carte.
La fonction setup() configure la broche PB5 (connexion n°13 sur la carte) en sortie, à l'aide de la
fonction Arduino pinMode(). La fonction loop() décrit ensuite ce qui sera répété indéfiniment :
mettre PB5 à 1 pendant 200ms puis mettre PB5 à 0 pendant 1s, et ainsi de suite.
Temporisations/Chronomètres
delay(unsigned long ms) : temporisation de ms milli secondes (ms sur 32 bits)
delayMicroseconds(unsigned int ms) : temporisation de ms microsecondes (ms sur 16 bits)
unsigned long micros() : nombre de micro secondes depuis démarrage. Revient à zéro tous
les 70mn environ.
unsigned long millis() : nombre de milli secondes depuis démarrage. Revient à zéro tous
les 50 jours environ.
Microcontrôleurs 7/29
Liaison Série : ces fonctions permettent de lire/écrire sur la liaison série du microcontrôleur et donc
de communiquer avec le PC auquel il est relié (via cordon USB).
//Exemple :
// indique la durée (millis) où le signal
// sur PC5 vaut 0. Cette durée est envoyée
// au PC via le cordon USB et peut être
// affichée dans le Moniteur Série
void setup()
{
Serial.begin(9600); //UART microC à 9600 bauds
pinMode(A5,INPUT_PULLUP); // PC5 en entrée (avec pull-up)
pinMode(13,OUTPUT); // PB5 (LED jaune) en sortie
digitalWrite(13,LOW); // PB5 à 0 (LED éteinte)
}
void loop()
{
unsigned long t1,t2,duree; // variables locales
while(digitalRead(A5)==1);
// PC5 passe de 1->0 (front descendant)
t1=millis();
digitalWrite(13,HIGH); // allume LED
while(digitalRead(A5)==0);
// PC5 passe de 0->1 (front montant)
t2=millis();
digitalWrite(13,LOW); // éteint LED
duree = t2-t1;
Serial.print("Duree=");
Serial.println(duree,DEC); // envoie la durée
}
Lorsque l'on souhaite contrôler les périphériques Arduino au plus bas niveau, c'est-à-dire sans
utiliser les - pourtant bien sympathiques - fonctions Arduino, on doit lire/écrire dans des registres
internes du microcontrôleur. Ceux-ci sont détaillés, pour certains périphériques, dans la suite de ce
document. A noter que la documentation technique complète de ce microcontrôleur fait plusieurs
centaines de pages. Il s'agit donc ici d'une présentation très partielle.
Par exemple, faire clignoter la LED de la carte Arduino (PB5), sans utiliser les fonctions
ARDUINO, nécessite l'accès aux registres de configuration des ports E/S (voir section 5.2). Pour la
broche PB5, les registres impliqués sont DDRB, PORTB et PINB. Dans le programme C, les
registres sont référencés par leur nom en MAJUSCULE.
Microcontrôleurs 8/29
Exemple de gestion bas-niveau de la broche PB5.
void setup() {
// configurer broche PB5 en sortie (voir section 5.2)
DDRB |= 0x20; // DDRB.5 <- 1
// ou DDRB|=B100000; // parce que B100000 vaut 0x20
// ou DDRB|=32; // parce que 0x20 vaut 32
PORTB &= 0xDF; // PORTB.5 <- 0
// ou DDRB&= ~0x20; // 0xDF est le complément de 0x20
}
void loop() {
PORTB |= 0x20; // PORTB.5 <- 1
delay(200); // 200 ms
PORTB &= 0xDF;; // PORTB.5 <- 0
delay(1000); // 1s (seule fonction Arduino utilisée)
}
Remarques :
Les exemples ATMega328 en langage C trouvés sur internet utilisent des "styles" d'écriture parfois
déroutants. Notamment s'agissant de la gestion des ports I/O, on doit souvent faire des opérations
logiques (&,|,~,^) pour mettre des bits à 0, 1 ou inverser des bits, c'est-à-dire pour modifier ou lire
un ou plusieurs bits d'un registre 8 bits.
Exemple déroutant:
L'opération ET logique laisse inchangés tous les bits de PORTB sauf le bit 5 qui est mis à 0 (en
raison du bit à 0 de la valeur binaire (1101 1111)b = ~(1<<PORTB5)).
Microcontrôleurs 9/29
5 Structure interne de l'ATMega328 (extraits de documentations ATMEL)
L'utilisation des périphériques intégrés (Entrées Sorties TOR, Timers, ...) repose sur l'exploitation
(lecture/écriture) de registres internes. Ces registres, essentiellement 8 bits, sont décrits par un nom
en MAJUSCULE dans les programmes en C. Cette section fournit quelques détails importants sur
les registres internes du microcontrôleur ATMega328 impliqués dans l'exploitation des
périphériques. Certaines parties sont des extraits (en langue anglaise) de la documentation Atmel.
Pour la documentation complète (442p) : chercher avec mots clés datasheet ATMega328
------------------
Notation : par la suite, pour un registre nommé R, la notation R.n désigne le bit de rang n du
registre R. Attention, ce n'est qu'une notation. Le compilateur C ne peut pas exploiter cette
notation.
Ex: PORTB.5 signifie "le bit numéro 5 du registre qui s'appelle PORTB"
------------------
Le registre SREG contient des indicateurs liés aux opérations et le bit d'autorisation générale des
interruptions. Les bits de ce registre sont : Z (Zero), C (Carry), S (Sign) ...
Le bit d'activation général du système d'interruptions est le bit I (SREG.7)
Note : en langage C, ce bit I est modifiable via les appels sei() (set IT) cli() (Clear IT)
Microcontrôleurs 10/29
exploiter des registres internes décrits ci-dessous.
Les entrées-sorties sont réparties dans 3 groupes de broches appelés ports. Le port B regroupe les
broches notées PBx, le port C les broches PCx et le port D les broches PDx (voir brochage). Chaque
port est exploité grâce à 3 registres.
Ex: PORTB, DDRB et PINB les registres pour la gestion des broches PB0 à PB7
Ex: si le bit DDRB.6 vaut 1 alors la broche PB6 est une sortie (TOR)
PORTx pour l'écriture des sorties TOR : si une broche est configurée en sortie (DDRx.n=1) alors
l'écriture du bit PORTx.n permet de définir l'état de la sortie (0 ou 1).
PINx pour la lecture des entrées TOR : si une broche est configurée en entrée (DDRx.n=0) alors
la lecture du bit PINx.n permet de connaître l'état de l'entrée.
Microcontrôleurs 11/29
Dans la documentation, les registres impliqués sont décrits ci-dessous (exemple pour les broches
PB0 à PB7).
En technologie MOS, une entrée "en l'air" a un état indeterminé. Aussi, lorsqu'on veut exploiter des
boutons poussoir/switchs, on les branche de façon à ramener l'entrée à 0 quand on ferme le contact.
A l'inverse, lorsque le contact est ouvert, l'état de l'entrée doit être amené à 1 par des resistances de
tirage à 1 (pull-up en anglais). Ces résistances sont activées ou non par programmation:
Détail : le bit PUD du registre MCUCR permet la désactivation de toutes les resistances de pull-up.
Si PUD=1 ALORS toutes les resistances de pull-up -interne de tous les ports désactivées
Microcontrôleurs 12/29
6 Sources d'interruption exploitables sur ATMega328 (carte Arduino UNO)
Interruption (IT) = suspension du programme pour exécuter un traitement particulier. Mais c'est
différent d'une fonction : ce n'est pas appelé explicitement par le programme. On n'écrit pas l'appel
de ce traitement. C'est le processeur qui, suite à la détection d'une cause particulière, déclenche le
traitement de l'interruption. La fonction liée au traitement d'une interruption est appelée fonction
d'interruption ou routine d'interruption (Interrupt Service Routine ISR).
Les périphériques peuvent conduire à déclencher des interruptions. Ce mécanisme permet d'assurer
des temps de réponse très courts entre la cause de l'interruption et son traitement.
Important : les appels des ISR s'insèrent de façon asychrone (on ne sait pas à l'avance quand elles
vont être appelées) dans l'exécution du programme. Par exemple, un programme Arduino est
interrompu chaque milli seconde par une ISR liée au Timer 0. Cette routine ISR Timer 0 met à jour
les variables de temps utilisées par les fonctions delay() millis() etc. Un programme Arduino est
donc cycliquement suspendu (interrompu) pour l'ISR Timer 0, ce qui ralentit son exécution
d'environ 6%.
Ci-dessous, le vecteur d'interruptions, c-à-d toutes les sources (causes possibles) sur ATMEGA238.
Microcontrôleurs 13/29
6.1 Interruptions Externes (liées aux entrées PD2 et PD3)
Il s'agit d'interruptions où les causes sont liées à des niveaux ou à des changements d'états des
broches PD2 (INT0) ou PD3 (INT1) du microcontrôleur. Notez bien le nom INT0/INT1 en
référence à cette fonction alternative des broches PD2/PD3. Pour ce rôle d'interruption externe, les
broches doivent être configurées en entrée (cf. 5.2 DIGITAL I/O).
Broches INT0 (PD2)/INT1(PD3) : configurables pour déclencher les interruptions (n° 2 et 3 dans
le vecteur ou INT0_vect/INT1_vect). Les causes possibles (choisies par programmation) sont
Microcontrôleurs 14/29
Détail : Flags internes = lorsqu'une cause d'IT est détectée, un flag interne de EIFR est positionné
Microcontrôleurs 15/29
Pour le programme précédent, on branche un bouton poussoir entre PD2(/INT0) et la masse
GND. Quand on enfonce le bouton poussoir, on amène le niveau de l'entrée PD2 à 0. On utilise ici
la résistance de pull-up interne pour retirer le niveau à 1 quand le bouton est relâché.
Principe : chaque fois que le bouton poussoir fait passer le niveau de 1 à 0 sur l'entrée INT0(PD2),
la fonction d''interruption associée à INT0 est exécutée. Cette action a pour effet d'inverser l'état de
la LED et de revenir au programme principal. Il est important de comprendre que l'interruption ne
dure que quelques microsecondes. En dehors de ces interruptions, le programme principal (fonction
loop()) envoie chaque seconde la valeur de la variable cpt sur le port série.
[Point technique] Mettre à 0 flag dans registre EIFR : les bits du registre EIFR indiquent (flags)
qu'une demande d'interruption INT0/INT1 est en attente : un flag à 1 dans ce registre signifie que la
cause d'IT a été détectée mais que la routine ISR n'est pas encore exécutée. Si l'on souhaite annuler
une demande d'IT (avant son exécution), on doit remettre ces flags à 0. Bizarrerie : pour annuler
une demande (clear flag), il faut écrire 1 (et pas 0) dans le registre EIFR pour le flag concerné
Broches PCINT0 à PCINT23 (nom alternatif pour PBx, PCx et PDx) : configurables pour
déclencher des interruptions (n° 4, 5 et 6 ou PCINT0_vect, PCINT1_vect, PCINT2_vect) suite à
des changements d'état ("Pin Change") des broches (configurées en entrée DDRx.n=1). Les broches
sont séparées en 3 sous-groupes, il y a une source d'interruption par sous-groupe, et pour chaque
broche on peut activer ou non le système "Pin Change Interrupt"
Les registres PCMSK0, PCMSK1 et PCMSK2 contrôlent, pour chacun de ces groupes (donc pour
chaque port B, C D), quelle(s) broche(s) peut(vent) conduire (ou non) à une interruption de type
"pin change".
Microcontrôleurs 16/29
Activation des interruptions PCINT0 à PCINT23 si bit SREG.7=1 et mise à 1 de PCIEx
PCICR.0 : activation des IT Pin Change pour les broches du port B (PB0 à PB7)
PCICR.1 : activation des IT Pin Change pour les broches du port C (PC0 à PC6)
PCICR.2 : activation des IT Pin Change pour les broches du port D (PD0 à PD7)
Activation à l'intérieur d'un groupe : le registre PCMSKx détermine quelles broches du groupe
sont prises en compte pour l'interruption "pin change"
Microcontrôleurs 17/29
Flags internes pour les IT "Pin Change"
void setup()
{
Serial.begin(9600);
cli(); // aucune IT possible (SREG.7<-0)
PCICR |= 0x06; // Pin Change actif sur port D et port C
PCMSK2=0x0C; // Pin Change pour broches PD3 et PD2
PCMSK1=0x30; // Pin Change pour broches PC5 et PC4
DDRD&=~0x0C; // PD2 et PD3 en entrée
PORTD|=0x0C; // pull-up sur PD2 PD3
DDRC&=~0x30; // PC4 et PC5 en entrée
PORTC|=0x30; // pull-up sur PC4 et PC5
sei(); // IT possibles SREG.7<-1
}
void loop()
{
delay(2000);
Serial.print("cpt1=");
Serial.println(cpt1,DEC);
Serial.print("cpt2=");
Serial.println(cpt2,DEC);
}
Remarque : sur AVR, par défaut, une fonction d'interruption ne peut pas elle-même être
interrompue. Le CPU empêche l'ISR d'être suspendue.
Les timers intégrés peuvent déclencher des interruptions. On propose une section complète sur la
configuration des timers intégrés et l'exploitation des interruptions associées.
Microcontrôleurs 18/29
7. Timers/Counters de ATMega328 (partie technique)
Le microcontrôleur ATMega328 dispose de plusieurs modules de temporisation/comptage internes
(Timers), fonctionnant pour certains avec des registres de comptage sur 8 bits, et pour d'autres sur
16 bits. Dans tous les cas, chaque événement de comptage conduit à une modification du registre de
comptage (+1). L'événement de comptage peut être un "tick" de l'horloge du microcontrôleur, ce qui
revient à mesurer l'écoulement du temps. L'événement de comptage peut aussi être un front sur une
broche d'entrée du microcontrôleur (les broches T0 et T1 peuvent servir d'entrée de comptage).
Fonction Temporisateur : lorsque l'on compte des "ticks" de l'horloge qui cadence le
microcontrôleur, on mesure l'écoulement du temps. Les modules Timers/Counters fournissent cette
fonctionnalité. On peut aussi compter les ticks d'un signal de fréquence plus faible obtenu en
divisant la fréquence de l'horloge par un prescaler.
Note : sur la carte Arduino UNO, l'horloge est à 16MHz, soit 16 000 000 de cycles horloge par
seconde, ou 16 cycles horloge par micro seconde. Ce sont ces cycles là qui sont comptés en
fonction temporisateur. 16000000 cycles = une seconde.
Fonction Compteur : lorsque l'on compte des fronts sur une entrée de comptage (broches T0 ou
T1), on utilise alors la fonction "compteur" du module (non étudié ici).
Le choix entre fonction de temporisation (avec prescaler ou non) et fonction de comptage se fait par
configuration de registres dédiés à la gestion des modules Timers/Counters. Vous allez voir, c'est
technique.
Remarque : les timers sont des périphériques intégrés complexes (environ 70 pages du datasheet
ATMega). Seule une vision simplifiée est fournie ici.
C'est un module Timer/Counter avec registre de comptage 8 bits. En utilisant l'IDE Arduino,
le timer 0 (et l'interruption associée) est implicitement utilisé par les fonctions de gestion du temps
(delay(),millis() ...). Ce module Timer/Counter n'est donc pas utilisable directement avec la carte
ARDUINO Uno. Sauf si l'on accepte de se passer des fonctions de gestion de temps Arduino.
La structure générale du module Timer/Counter 2 est donnée dans le schéma ci-après. Le registre de
comptage est TCNT2 (registre 8 bits).
Microcontrôleurs 19/29
Points importants (Timer 2) :
– détection et IT sur débordement (TIMER2_OVF_vect)
– entrée de comptage = signal d'horloge avec prescaler ou non
– possibilité de comparer TCNT2 à deux registres de comparaison OCR2A/OCR2B
– l'égalité TCTN2=OCR2A peut déclencher une IT (TIMER2_COMPA_vect)
– l'égalité TCTN2=OCR2B peut déclencher une IT (TIMER2_COMPB_vect)
– Les broches OC2A(PB3) et OC2B (PD3) peuvent être activées par le Timer/Counter
2 pour génération de signaux périodiques (PWM).
Microcontrôleurs 20/29
Modes de fonctionnement (Table 17-8) en mode temporisateur :
Le choix du mode se fait via les bits WGM22:20 (bits TCR2A et TCR2B)
Microcontrôleurs 21/29
Clear Timer on Compare Match (CTC) Mode
En mode CTC (WGM22:0 = 2), le registre OCR2A règle la résolution. Le compteur TCTN2 est
remis à zéro après l'égalité (match) TCTN2=OCR2A. Le registre OCR2A definit la valeur
maximale pour le compteur, et donc sa résolution.
Microcontrôleurs 22/29
7.3 Exemples Timer 2 avec Interruption
L'exemple suivant illustre l'utilisation du timer 2 et de l'interruption associée à son débordement.
SREG.7=1 (bit général d'activation des IT, sans lui aucune IT)
TIMSK0.0 (TOIE0)=1 (interruption sur débordement du timer 0)
Pour le Timer 2, à chaque débordement de TCNT2 (passage de 0xFF à 0x00) une interruption
TIMER2_OVF_vect peut être exploitée. Il faut donc activer ce mécanisme
et configurer le Timer 2.
Microcontrôleurs 23/29
// ARDUINO UNO - IT Timer 2 Overflow
void setup(){
//Configuration PORTB.5
DDRB |= 0x20; // PB5 en sortie
PORTB &= ~0x20; // PORTB.5 <-0
cli();
// Configuration Timer 2
TCCR2A=0; // Mode Normal (pas PWM)
TCCR2B=0x07; // Prescaler 1024 (Clock/1024)
TIMSK2=0x01; // IT Timer2 Over Flow Active
sei(); // activation des IT (SREG.7=1)
}
Paramètres Timer 2
Mode 0 ( Normal) : WGM2=0 WGM1=0 WGM0=0 [TCCR2A=0]
Prescaler = 1024 : CS22=1 CS21=1 CS20=1 [TCCR2B=0x07=(111)b]
Interruptions
Masque d'IT : Interruption sur Overflow = TIMSK2.0 =1
Autorisation générale des IT : SREG.7=1 [activé par sei() ]
Principe : après la fonction setup(), le registre TCNT2 (8bits) est incrémenté à chaque tick du
signal périodique clock/1024. A chaque débordement du registre TCNT2, le débordement déclenche
l'interruption n°10 appelée "Timer 2 Over Flow". Tous les 60 appels de cette fonction, la broche
PB5 (LED) change d'état. La LED clignote donc.
Microcontrôleurs 24/29
Autre exemple : timer2 en mode CTC et interruptions
On utilise ici le mode CTC du Timer 2. A chaque fois que TCNT2=OCR2A, TCNT2 est remis à 0 et
l'égalité déclenche une interruption.
void setup(){
//Configuration PB5
DDRB |= 0x20; // PB5 en sortie
PORTB &= ~0x20; // PORTB.5 <-0
// Configuration Timer 2
TCCR2A=0x02; // Mode CTC (Clear Timer On Compare)
OCR2A=156; // Registre de comparaison A = 156
TCCR2B=0x07; // Prescaler 1024 (Clock/1024)
TIMSK2=0x02; // IT Timer2 Quand TCNT2=OCR2A
sei(); // activation des IT (SREG.7=1)
}
La LED s'allume 1/10 de seconde puis s'éteint 4/10 de seconde. Elle s'allume
brièvement 2 fois par seconde.
Microcontrôleurs 25/29
Fonction de configuration du timer2 en mode CTC
Exemple :
void setup()
{
Serial.begin(9600);
cli();
SetTimer2CTC(6,100); // prescaler /256 periode 100
TIMSK2|=0x02; //IT Timer2 quand TCNT2==OCR2A
sei();
}
void loop()
{
delay(1000);
Serial.println(cpt,DEC);
}
Microcontrôleurs 26/29
7.4 Timer/Counter 1 (comptage 16 bits)
Le registre de comptage TCNT1, ainsi que les registres de comparaison OCR1A et OCR1B, sont
cette fois-ci sur 16 bits.
Note: en langage d'assemblage, il faut deux accès 8 bits pour lire/écrire ces registres 16 bits. En
langage C, on peut manipuler symboliquement des données 16 bits via les symboles TCNT1,
OCR1A et OCR1B sans se soucier de la façon dont le code sera généré.
Microcontrôleurs 27/29
Selon le mode choisi par les bits WGM10:3 on a les options suivantes (mode PWM Phase
correct non décrit)
Microcontrôleurs 28/29
Prescaler Timer 1 (réglage de la vitesse de débordement en fonction de l'horloge)
void setup()
{
Serial.begin(9600);
cli();
SetTimer1CTC(4,10000); // prescaler /256 periode 10000
TIMSK1|=0x02; //IT Timer1 quand TCNT1==OCR1A
sei();
}
void loop()
{
delay(1000);
Serial.println(cpt,DEC);
}
Microcontrôleurs 29/29