lundi 1 juin 2020

Programmer Arduino avec un langage bas niveau (manipulation de registres)



Des fois lors du développement d'un projets sur Arduino, on peut être amenée à acquérir des mesures avec une fréquence assez élevée ou tout simplement optimiser l'espace que peut prendre notre programme dans la mémoire du microcontrôleur alors qu'avec les instructions tels digitalRead() ou analogRead() qui sont constitués d'une dizaine de lignes de code vont demander un certain temps pour tout exécuter. Pour palier à ça, sachant que la majorité des opérations comme la lecture ou l'écriture sur les ports se font à partir de registres, on peut justement directement accéder à ces derniers afin d'exécuter des tâches à Arduino c'est ce qu'on appel le langage bas niveau .

Pourquoi compliquer les choses alors qu'on a un langage Arduino assez simple à utiliser?

L'intérêt de la manipulation des registres :

Un résultat concret vaut mieux que mille explications sur l'intérêt de la manipulation des registres. 😇
On va comparaître le temps d'exécution de la lecture de la broche D2 de l'Arduino Uno avec 2 manières différentes, l'utilisation de l'instruction digitalRead() et la lecture du registre du port D où est inclut la broche D3 (on y verra plus clair sur les différents registres dans quelques instants).

Dans le code suivant, on va mesurer le temps que prends digitalRead() puis celui de la lecture avec des registres à l'aide la fonction millis().
On va effectuer cette mesure plusieurs fois pour avoir la moyenne.

Résultat sur le moniteur série :


Comme on peut le voir parmi les 10 essais réalisés, en moyenne la mesure par digitalRead() est plus lente que par la méthode de mesure avec les registres. 
Donc à partir de là, on peut déjà voir que coder en langage bas niveau (manipulation des registres) augmente significativement la vitesse d'exécution du programme développé. Ajouté à ça un programme plus petit implique un programme qui consomme moins d'espace mémoire.


Il faut savoir que chaque information de l'Arduino est contenue dans des cases mémoires, chacune avec des adresses définies et fixes. Faire appel à un registre (en lecture ou en écriture) revient à aller directement lire ou écrire dans cette case mémoire ! 

Sur la partie suivante, on va voir les différents registres liés aux ports de l'Arduino.

Registres de ports de l'ATMega328 :


Comme tout microcontrôleur, l'ATMega328 monté sur l'Arduino Uno est équipé d'un nombre de ports GPIO (broches entrées sorties), il est composé de 3 ports : port B, port C et port D.


Chaque port possède 3 registres : un registre DDRx qui a pour rôle de configurer une broche en entrée/sortie (direction), un registre PORTx qui a pour rôle de mémoriser l'information à écrire sur une pin (écriture) et enfin un registre PINx qui a pour rôle de la lecture d'une pin (lecture).
Ce qui fait qu'il y a en tout 9 registres, donc 3 chaque pour fonction (lecture, écriture et direction) :

PORTD registre de Port D des I/Os digital de D0 à D7 (PD0 à PD7)

  • DDRD : Registre de direction ("1" output et "0" input)

  • PORTD : Registre de données - écriture 

  • PIND : Registre de donnée - lecture uniquement 


PORTB registre de Port B des I/Os de D8 à D13 (PB0 à PB5, les PB6 et PB7 sont pas utilisables car elles sont réservé au quartz)

  • DDRB : Registre de direction ("1" output et "0" input)

  • PORTB : Registre de données - écriture 

  • PINB : Registre de donnée - lecture uniquement 


PORTC registre de Port C des I/Os de A0 à A5 (PC0 à PC5, les PC6 et PC7 sont pas utilisables)

  • DDRC : Registre de direction ("1" output et "0" input)

  • PORTC : Registre de données - écriture

  • PINC : Registre de donnée - lecture uniquement


NB : Évidement pour pouvoir mettre un état haut, il faut envoyer un "1" et pour un état bas ça va être un "0".

Attribution des broches Arduino UNO aux pin de l'ATMega328 ainsi que le numéro du bit de chaque des 3 registres de ports :

                                             Port B (broches numériques Arduino UNO D8 à D13)

Pins ATMega328

PB7 (MSB)

PB6

PB5

PB4

PB3

PB2

PB1

PB0 (LSB)

Pin Arduino

-

-

D13

D12

D11

D10

D9

D8

N° du bit

7

6

5

4

3

2

1

0

PINB

PINB7

PINB6

PINB5

PINB4

PINB3

PINB2

PINB1

PINB0

DDRB

DDRB7

DDRB6

DDRB5

DDRB4

DDRB3

DDRB2

DDRB1

DDRB0

PORTD

PORTD7

PORTD6

PORTD5

PORTD4

PORTD3

PORTD2

PORTD1

PORTD0

En gris c’est les bits de registres non utilisables du fait que les ports du même bit ne sont pas utilisés comme broche entrée/sortie.

Port C (broches analogique Arduino UNO A0 à A5)

Pins ATMega328

-

PC6 (MSB)

PC5

PC4

PC3

PC2

PC1

PC0 (LSB)

Pin Arduino

-

-

A5

A4

A3

A2

A1

A0

N° du bit

7

6

5

4

3

2

1

0

PINB

PINC7

PINC6

PINC5

PINC4

PINC3

PINC2

PINC1

PINC0

DDRB

DDRC7

DDRC6

DDRC5

DDRC4

DDRC3

DDRC2

DDRC1

DDRC0

PORTD

PORTC7

PORTC6

PORTC5

PORTC4

PORTC3

PORTC2

PORTC1

PORTC0

 

Port D (broches numériques Arduino UNO D0 à D7)

Pins ATMega328

PD7 (MSB)

PD6

PD5

PD4

PD3

PD2

PD1

PD0 (LSB)

Pin Arduino

D7

D6

D5

D4

D3

D2

D1

D0

N° du bit

7

6

5

4

3

2

1

0

PINB

PIND7

PIND6

PIND5

PIND4

PIND3

PIND2

PIND1

PIND0

DDRB

DDRD7

DDRD6

DDRD5

DDRD4

DDRD3

DDRD2

DDRD1

DDRD0

PORTD

PORTD7

PORTD6

PORTD5

PORTD4

PORTD3

PORTD2

PORTD1

PORTD0


Fonctionnement du circuit d'interface entre la partie numérique (code) et les pins I/O :


Dans cette partie on va voir la manipulation physique sur les registres des ports. 

Circuit interne représentatif du PORTD en mode écriture

La lecture et écriture sur les pins entrés/sorties se fait à travers le circuit présent dans chaque port qui comprend les 3 registres présenté dans la figure précédente. La communication entre le programme Arduino et ce circuit se fait via le bus de donnée.
Les registres sont en fait un ensemble de bascules qui font office de mémoire pour stocker la configuration de la PIN (1/0 pour la configuration sortie/entrée).

         1. Écriture sur une pin :

Par exemple pour pouvoir allumer une LED sur la pin 4, donc mettre un état Haut sur cette broche, il faut d'abord commencer par mettre un “1” sur la bascule DDRD4 (1 correspond à sortie) puis mettre un “1” sur le registre PORTD4 c'est ce qu'on fait sur un programme Arduino habituel. Si on ne met pas un 1 d'abord sur DDRD4 avant d'en mettre sur PORTD4, le buffer lié à la sortie de PORTD sera pas activé donc restera ouvert, ce qui veut dire que l'état haut qu'on souhaite envoyer ne sera jamais acheminé jusqu'à la pin où est branché la LED qui veut dire qu'elle restera toujours éteinte.

La lecture sur PIND4 doit relire un "1". Si on relit un "0" ça veut dire qu'il y a court-circuit !

 
        2. Lecture d'une pin :

Par exemple pour la lecture de la broche D2 ça va être la même chose, il faut d'abord mettre un “0” (0 correspond à une entrée) au registre DDRD, ce qui va mettre en haute impédance le buffer et “débrancher” le registre PORTD de la pin. Reste plus qu’à lire le registre PIND.

Circuit interne représentatif du PORTD en mode lecture
Avant de pouvoir développer un programme complet en langage bas niveau pour la lecture et l'écriture sur les pins, on va d'abord commencé par voir les différentes opérations logiques combinatoires nécessaires tels le masquage et le décalage en plus des opérations classiques OR, AND et NOT car contrairement aux fonctions Arduino utilisés sur les ports comme : 
Sur le langage bas niveau, la lecture "directe" du registre de la lecture PIND qui s'occupe des pins D0 à D7 va retourner l'état de toutes les pins (D0 à D7) et pas seulement celle qu'on souhaite lire. C'est là qu'on va utiliser le masquage sur le registre pour lire par exemple la broche uniquement D2, ce qui donne : 
le masque 0x03 permet d'isoler le bit N°2 du registre PIND pour pouvoir lire uniquement la pin D4 de l'Arduino et pas toutes.

Opérations logiques utilisé pour le langage bas niveau:

En plus des opérations logiques basiques comme l'opération, OR, AND et NOT, il existe d'autres opérations tels le masquage et le décalage qui permettent d'isoler un ou un groupe de bit parmi le reste dans un mot.

        1. Opération de masquage :

Le masquage est une opération logique combinatoire qui sert comme son nom l'indique à masquer une donnée pour pouvoir extraire un bit ou plusieurs qu'on souhaite traiter.
L'opération consiste à appliquer un ensemble de bits (le masque) sur un autre ensemble de bits (donnée) de même taille.

On peut exploiter le masquage avec les opération OR ( | ) et AND (&) pour :
  •  Isolation d'une donnée :
- Isoler un bit ou ensemble de bit sous le masque AND avec la valeur 1

- Isoler un bit ou ensemble de bit sous le masque OR avec la valeur 0

  • Forçage  à 1 (set) ou à 0 (reset) :
- Forcer un bit ou un ensemble de bit à 0 (reset) sous le masque AND avec la valeur 0
- Forcer un bit ou un ensemble de bit à 1 (set) sous le masque OR avec la valeur 1





        2. Opération de décalage :

Le décalage comme son nom l’indique est une opération qui consiste à décaler tous les chiffres d'un ou plusieurs crans vers la gauche (<<) ou la droite (>>). Les vides qui apparaissent après le décalage sont remplacés par des zéros.

Exemple :

1001100 >> 2 = 0010011 // on a décalé vers la droite de 2 crans

1001100 << 3 = 1100000 // on a décalé vers la gauche de 3 crans

        3. Inversion de la valeur d'un bit :

On peut inverser la valeur d’un bit à l’aide de l’opération logique XOR (le symbole de XOR en C est ‘^’) 

X ^ 1 = ! X

Rappe de la table de vérité de l’opération XOR :

 

X

opération

! X

0

0 ^ 1

1

1

1 ^ 1

0

Exemple : 

On souhaite inverser la valeur du 3 ième bit d’un mot de 8 bits : XXXXXXXX

Solution :

Mot=Mot  ^ 0x08; // on inverse le 3eme bit avec 0x08=1000


Comment coder le langage bas niveau ?

Maintenant qu’on a terminé sur la théorie indispensable pour la manipulation directe sur les registres, on peut commencer à voir des exemples avec des petits bouts de codes sur la façon de faire pour lire, écrire et configurer les broches de l’Arduino en entrée ou sortie avant de passer sur un code complet ou va faire ces 3 taches.

        1.Configurer les ports de l'Arduino en entrée ou sortie :

Avant d’aller plus loin dans l’écriture sur les ports, il faut faire attention sur les bits 0 et 1 des registres PORTx (PORTB, PORTC et PORTD) de ne pas les modifier de leurs valeurs d’origine : 0BXXXXXX10.

Le bit ‘1’ correspond à Tx et le bit ‘0’ correspond à Rx, ces 2 broches servent de port à la communication série. Si par exemple, on modifie le bit 0 à ‘1’ alors la broche Rx deviens une sortie et donc elle ne peut plus recevoir de donnée alors que c’est sa tâche.

Dans le code suivant, on va travailler sur le port D (D0 à D7) où on va configurer les broches D5 et D2 en entrée et D4 et D7 en sortie.

Avant d’aller plus loin, on va rappeler dans un tableau tous les symboles utilisés dans les opérations logiques qu’on va utiliser dans ce cours :

Opération logiques

Notation sur Arduino et C

OR

|

AND

&

NOT

! (pour un seul bit)

NOT

~ (pour tous les bits du mot)

XOR

^

Décalage à droite

>> 

Décalage à gauche

<< 

Etat logique haut

1

Etat logique bas

0


Pour la construction du programme, on commence par déclarer les broches :

#define pin5 5 //en entrée

#define pin6 6 //en entrée

#define pin4 4 //en sortie

#define pin7 7 //en sortie

Ensuite il faut mettre un ‘1’ sur les pins 4 et 7 pour les configurer en sortie et un ‘0’ sur les pins 5 et 6 pour les configurer en entrée sur le registre DDRD :

N° bit

7

6

5

4

3

2

1

0

DDRD

1

0

0

1

0

0

0

0

Ce qui donne le mot de 8 bits : 0B10010000 à mettre sur le registre DDRD

Ce mot peut se décomposer ainsi : 10010000=10000000 | 10000

On va nommer les ‘1’ pour la sortie en X et les ‘0’ pour l’entrée en Y pour mieux comprendre la construction du mot à inclure dans le code. Ce qui donne la décomposition suivante après avoir utilisé le décalage sur chaque des 4 bits :

X << 7 | Y << 6 | Y << 5 | X << 4 = X0000000 | Y000000 | Y00000 | X0000 = 10010000 =DDRD avec X=1 et Y=0

Cette ligne de code qui a le même rôle que la fonction pinMode() sera déposé dans la fonction setup() comme c’est habituel avec pinmode().

Code complet :

Programme en bas niveau réalisé

Programme équivalent en fonctions Arduino

#define pin5 5 //en entrée

#define pin6 6 //en entrée

#define pin4 4 //en sortie

#define pin7 7 //en sortie

 

Void setup()

{

       //on met les broches D4 et D7 en sortie

       //et les broches D5 et D6 en entrée

       DDRD = 1 << 7 | 0 <<6 | 0 << 5 | 1 << 4 ;

}

#define pin5 5 //en entrée

#define pin6 6 //en entrée

#define pin4 4 //en sortie

#define pin7 7 //en sortie

 

Void setup()

{

       pinMode( pin5, INPUT) ;

       pinMode( pin6, INPUT) ;

       pinMode( pin4, OUTPUT) ;

       pinMode( pin7, OUTPUT) ;

}

 

        2. Écrire sur les ports de l'Arduino :

Dans cette partie on va essayer d’allumer 2 LEDs et de les éteindre après une seconde écoulé, l’une sur la broche D4 et l’autre sur la broche D7, évidemment toujours avec la manipulation des registres.


Pour commencer, on déclare les 2 broches utilisées :

#define led1 4

#define led2 7

Ensuite pour pouvoir allumer les 2 LEDs, on va mettre les broches D4 et D7 en sortie en mettant un ‘1’ sur le bit 4 et le bit 7 du registre de direction DDRD comme on la fait auparavant :

DDRD = 1 << 4 | 1 << 7 ;

Pour pouvoir allumer ces 2 LEDs il faudra envoyer +5v sur les 2 résistances qui sont lié aux 2 LEDs donc envoyer un état haut ‘1’ sur le registre de donnée PORTD du port D sur le bit 7 et le bit 4.

Pour ce faire, on écrit :

PORTD = PORTD | (1 << 4) | (1 << 7) ; //allumer la LED 1 et la LED 2

Explication :

Comme je l’ai cité avant, pour pouvoir forcer un bit à 1 il faut utiliser un masquage avec OR (|). C’est ce qui est fait ici, on applique un ‘1’ sur le bit 4 et le bit 7 sont pour autant changé les autres bits.

Le fait d’écrire directement PORTD = 0B10010000 directement fait pas très propre et risque de changer les autres bits.

Ensuite, pour pouvoir éteindre ces 2 LEDs après 1 seconde, il faut procéder de la même sorte que pour les allumer en envoyant un état bas ‘0’ sur les 2 bits 4 et 7 toujours avec le masquage mais cette fois avec l’opération AND (&) pour forcer les 2 bits à 0 :

delay(1000) ;

PORTD = PORTD & !(1<<4) & !(1<<7) ; //éteindre la LED 1 et la LED 2

Explication :

Cette ligne de code a le même fonctionnement que celle écrite pour allumer les LEDs sauf que cette fois on va forcer les 2 bits à 0 d’où l’utilisation de l’opération AND (&).

On a utilisé ‘ !1’ qui est la même chose que ī = 0. Ca fait plus propre qu’on utilisant des ‘0’ à la place des ‘1’ :

PORTD = PORTD & (0<<4) & (0<<7) ;

Code complet avec son équivalent en Arduino :

Programme en bas niveau réalisé

Programme équivalent en fonction Arduino

#define led1 4

#define led2 7

 

Void setup(){

     

       DDRD = 1 << 4 | 1 << 7 ;

}

 

Void loop(){

      //on allume la led1 et la led2

      PORTD = PORTD | (1 << 4) | (1 << 7) ;

      delay(1000) ;

      //on éteint la led1 et la led2

      PORTD = PORTD & !(1<<4) & !(1<<7) ;

}

 

#define led1 4

#define led2 7

 

Void setup(){

       pinMode( pin4, OUTPUT) ;

       pinMode( pin7, OUTPUT) ;

}

 

Void loop(){

       //on allume la led1 et la led2

       digitalWrite(led1, HIGH) ;

       digitalWrite(led2, HIGH) ;

       delay(1000) ;

       //on éteint la led1 et la led2

       digitalWrite(led1, LOW) ;

       digitalWrite(led2, LOW) ;

}


        3. Lecture sur les ports de l'Arduino :

Dans un premier temps, on va lire les broches D5 et D6 de l’Arduino sur lesquels sont branchés les boutons SW1 et SW2 respectivement. Les 2 boutons renvoient un état haut ‘1’ s'il est relâché (état initial) et un état bas ‘0’ s'il est appuyé. On va tester son état pour savoir s’il est appuyé ou pas.

Par la suite, on va faire un test sur le registre PIND pour savoir lequel des 2 boutons est appuyé. 4 cas sont possibles : aucun d’eux, seulement SW1 appuyé, seulement SW2 appuyé ou les 2 boutons appuyés.


Pour la déclaration des 2 broches en entrée sur la fonction setup(), on écrit :

#define SW1 5 //déclaration de la broche

#define SW2 6

Void setup(){

      DDRD = !(1 << 4);// envoyer un ‘0’ (entrée) sur le bit N°4 su registre DDRD qui correspond à D4

      Serial.begin(9600) ; //démarrage de la lecture série pour affichage du résultat sur le moniteur série

}

Pour le code dans la fonction loop(), pour la lecture et les tests on va travailler avec une variable de 8 bits (byte) qui va accueillir la valeur du registre PIND.

On va commencer par lire le contenue du registre PIND :

byte etat_SW ;

etat_SW=PIND ;

Serial.prinntln(etat_SW) ;

Résultat sur les valeurs possibles qui seront retournés par le registre PIND :

-       Si aucun bouton est appuyé DDRD=0

-       Si seulement le bouton SW2 est appuyé DDRD=1000000=0x40

-       Si seulement le bouton SW1 est appuyé DDRD=100000=0x20

-       Si les 2 boutons SW1 et SW2 sont appuyés DDRD=1100000=0x60

Pour les 4 tests, on va faire simple, on va tester si la variable etat_SW est égal à l’une de ces 4 valeurs vue précédemment pour identifier quel bouton est appuyé.

if(etat_SW ==0) Serial.println(‘’ aucun boutton n’est appuyé’’) ;

else if(etat_SW==0x40) Serial.println(‘’ seulement le bouton SW2 est appuyé’’) ;

else if(etat_SW==0x20) Serial.println(‘’ seulement le bouton SW1 est appuyé’’) ;

else if(etat_SW==0x60) Serial.println(‘’ les 2 boutons SW1 et SW2 sont appuyés’’) ;

 

Code complet :

#define SW1 5 //déclaration de la broche

#define SW2 6

Void setup(){

      DDRD = !(1 << 4);// envoyer un ‘0’ (entrée) sur le bit N°4 su registre DDRD qui correspond à D4

      Serial.begin(9600) ; //démarrage de la lecture série pour affichage du résultat sur le moniteur série

}

 

void loop(){

    byte etat_SW ;

    etat_SW=PIND ;

    //Serial.prinntln(etat_SW) ;

    if(etat_SW ==0)

        Serial.println(‘’ aucun boutton n’est appuyé’’) ;

    else if(etat_SW==0x40)

        Serial.println(‘’ seulement le bouton SW2 est appuyé’’) ;

    else if(etat_SW==0x20)

        Serial.println(‘’ seulement le bouton SW1 est appuyé’’) ;

    else if(etat_SW==0x60)

        Serial.println(‘’ les 2 boutons SW1 et SW2 sont appuyés’’) ;

 Cas spécial :

Si on souhaite lire uniquement le bit N°5, on a le choix entre 2 options :

Option 1 : en utilisant le masquage puis le décalage sur la variable etat_SW.

Le bit à masquer est sur le bit N°5 ce qui donne à la valeur du masque 0B100000(BIN)  = 0x10(HEX) après avoir effectué le masquage on va le décaler vers la droite de 5 bits :

           byte etat_SW ;

etat_SW = (PIND & 0x20) >> 5 ;

Option 2 : déclarer etat_SW en un type de variable à 1 bit (boolean sur Arduino) et utiliser un masquage sans décalage sur PIND puisque la variable n’a qu’un seul bit !

boolean etat_SW ;

etat_SW=DDRD & 0x20 ;

 

        4. Activation de résistances pull-up de l’ATMega   


Aperçu de circuit à base résistance pull-up et pull-down.

Une résistance pull up (en français la résistance de tirage) permet de fixer une entrée numérique à un état haut (+5v) ou bas (0v). Elle permet aussi d'éliminer les broches flottantes et surtout, d'établir deux états électriques distincts:

-       - un état haut (HIGH);

-       - un état bas (LOW).

Il y a 2 sortes de résistances de tirage :

-       - Résistance ‘’pull-up’’, tirage vers le HAUT

-       - Résistance ‘’pull-down’’, tirage vers le BAS

 

Sur l’ATMega328, toutes les broches numériques (Dx) disposent des résistances pull-up internes configurables. On peut les activer ou pas numériquement via les registres de donnée PORTx.


Habituellement on utilise un bouton avec une résistance pull-up externe pour pouvoir l’utiliser. Il est possible de s’en passer en utilisant les résistances pull-up internes avec la fonction Arduino pinMode() comme ceci :

pinMode(Num_broche, INPUT_PULLUP);//activation de la résistance pull-up interne sur Num_broche et configuration de cette dernière en entrée


Afin d'activer les pull-up interne du microcontrôleur en manipulent les registres, il suffit juste d’écrire 2 lignes de code. Cela revient à mettre cette pin en entrée (DDRx=0) et mettre un ‘1’ sur PORTx où on veut activer la résistance pull up :

DDRx=0 et PORTx=1 

Pour désactiver la résistance Pull Up de la énième broches, on fait exactement l'inverse :

DDRx.n=1 et PORTx=0 

Exemple : 

On souhaite mettre la broche D1 en entrée avec pull up interne (sur le port D) :

DDRD=DDRD & ~0x02 //DDRD.1 <- 0

PORTD=PORTD | 0x02 //PORTD.1 <- 1

Détail : le bit PUD du registre MCUCR permet la désactivation de toutes les résistances de pull-up.

Si on met PUD=1 alors toutes les résistances de pull-up interne de tous les ports seront désactivées.

 

Exemples de programmes complets en langage bas niveau :

Maintenant qu’on a vue toute les manipulations sur les registres nécessaires pour la configuration des broches numérique et analogique de l’Arduino (pour les versions Uno et Nano qui sont équipé de l’ATMega328), on va voir quelques exemples de programmes complets sur lesquels on va utiliser toutes les opérations vue précédemment. 

1. Code Blink de l’IDE d’Arduino :

Le fameux simple code que tout le monde à utiliser pour faire clignoter la LED “L” présente sur la broche 13 de l’Arduino UNO :)

Dans ce qui va suivre, on va réécrire le code blink avec un langage bas niveau c’est à dire sans fonction Arduino :

#define led13 5 // la pin 13 est sur le 5 ième bit du port B

// Version langage bas niveau du programme Blink de Arduino

void setup()

{

 DDRB=1<<led13; //configuration de la pin 13 en sortie

}

 

void loop()

{

   PORTB=PORTB | 0x20;   // on met un 1 sur le 5 ième bit du du registre PORTB pour allumer la LED

   delay(1000);

   PORTB = PORTB & ~0x20; // on met un 0 sur le 5 ième bit du du registre PORTB pour éteindre la LED

   delay(1000);

/*

   //ou tout simplement avec un XOR qui va inverser la valeur du bit à chaque itération de la fonction loop():

   PORTB =PORTB ^ 0x20;

   delay(1000);

*/

}

 

2. Commande de 2 LEDs à partir de 2 boutons :

Dans cet application on va commander 2 LEDs à partir de 2 boutons :

-       - Le bouton SW1 avec la résistance R3 en pull-down

-       - Le bouton SW2 avec la résistance R4 en pull-up


Le circuit de notre application.

Cahier des charges :

Construire un code qui va allumer les LEDs selon le/les boutons appuyés sur les 2 circuits :

  • Allumer la LED1 si le bouton SW1 est appuyé 
  • Allumer la LED2 si le bouton SW2 est appuyé 
  • Allumer les 2 LEDs si les 2 boutons sont appuyés
  • Ne rien faire si aucun des boutons n’est appuyé.

 

On commence par l’étude du circuit :

A l’état initial (avant qu’un bouton soit appuyé),

-       - le bouton SW1 retourne un état bas ‘0’

-       - le bouton SW2 retourne un état haut ‘1’

 

A l’appui,

-       - le bouton SW1 retourne un état haut ‘1’

-       - le bouton SW2 retourne un état bas ‘0’

 

Pour les LEDs, il suffit d’envoyer

-       - un état haut ‘1’ sur les allumer

-       - un état bas pour les éteindre



Avec cette étude, on peut maintenant tracer la table de vérité de ce circuit :

-       - pour les boutons (SW1 et SW2)   1’ : relâché et ‘0’ : appuyé

-       - pour les LEDs (LED1 et LED2)     ‘0’ : éteint    et ‘1’ allumé

 

Pour allumer la LED1, il faut appuyer sur le

SW1

SW2

LED1

LED2

0

relâché

1

relâché

0

0

0

relâché

0

appuyé

0

1

1

appuyé

1

 relâché

1

0

1

appuyé

0

appuyé

1

1

A partir cette table de vérité on peut voir que :

-       - l’état de SW1 et LED1 sont est égal : SW1 = LED1

-       - l’état de SW2 et LED2 sont inverse :  SW2 = ~ LED2

Les broches utilisées dans cette application sont comprises entre D0 et D7 de l’Arduino ce qui veut dire que le port utilisé est le port D donc tout les registres qu’on va manipulé sont aussi de ce port.

 

Les bits N° 5 et 6 seront utilisés pour la lecture des 2 boutons à travers le registre PIND.

Les bits N° 4 et 7 seront utilisés pour l’écriture sur les leds à travers le registre PORTD.

 

Sur le registre PIND, on peut considérer les bits non utilisé étants nuls :

0

D6 (SW2)

D5 (SW1)

0

0

0

0

0

PIND 

Même chose sur le registre PORTD, on envoie des ‘0’ pour les bits non utilisés :

D7 (LED2)

0

0

D4 (LED1)

0

0

0

0

PORTD

Afin d’avoir un code simple mais surtout compacte on va directement placer le bit PIND5 (D5) dans PORTD4 (D4) et l’inverse du bit PIND6 (D6) dans PORTD7 (D7) comme on l’a vu sur la table de vérité.

 

Comment faire ?

D’abord pour isoler les bits PIND5 et PIND6 du registre PIND de 8 bits, on va utiliser le masquage AND (&) :

PIND5=PIND & 0B00100000=PIND & 0x20

PIND6=PIND & 0B01000000=PIND & 0x40



Comme on l’a vu précédemment :

LED1=SW1 et LED2= ~SW2 

PIND5= PIND5  et  PIND6= ~PIND6 (l’opérateur ‘~’ est utilisé pour inverser)


L’inversion faite reste plus qu’à déplacer les bits comme détaillé précédemment à l’aide de l’opérateur de décalage. Il faut déplacer PIND6 sur PORTD7 d'une position (décalage à gauche avec <<1 ) et PIND5 sur PORTD4 d’une position aussi (décalage à droite avec >>1) ce qui donne :

PORTD7=PIND6 << 1 et 

PORTD4=PIND5 >>1


Reste plus qu’à assembler tout ça on sommant (avec | ) les 2 parties, PORTD4 et PORTD7. Ce qui donne : 

PORTD=((~PIND&0x40)<<1)|((PIND&0x20)>>1

NB : Cette relation est celle et la seule qu’on va mettre dans la fonction loop() !

Pour la fonction setup() :

Configuration des ports à travers le registre DDRD :

DDRD= 0<<bouton1 | 0<<bouton2 | 1<<led1 | 1<<led2;

Cette ligne est équivalent à : 0<<5 | 0<<6 | 1<<led4 | 1<<led7;

 

Le code complet :

Programme en bas niveau réalisé

Programme équivalent en fonctions Arduino

#define bouton1 5

#define bouton2 6

#define led1 4

#define led2 7

 

void setup(){

  Serial.begin(9600);

   DDRD= 0<<bouton1 | 0<<bouton2 | 1<<led1 | 1<<led2; //DDRD=0B10010000;     

}

 

void loop(){

    PORTD=((PIND&0x40)<<1)|((~PIND&0x20)>>1);

}

 

#define bouton1 5

#define bouton2 6

#define led1 4

#define led2 7



boolean etat1,etat2;

void setup()

{

  Serial.begin(9600);

  pinMode(bouton1,INPUT);

  pinMode(bouton2,INPUT);

  pinMode(led1,OUTPUT);

  pinMode(led2,OUTPUT);    

}

void loop()

{

  etat1=digitalRead(bouton1);

  etat2=digitalRead(bouton2);

  digitalWrite(led1, bouton1);

  digitalWrite(led2, !bouton2);

}

 

Conclusion :

Dans cet article on a vu différente opération sur les ports numériques et analogiques qu’on peut mener sur le microcontrôleur ATMega328, ce qui nous donne la possibilité de le programmer en langage bas niveau qui rend le code plus compacte et rapide et on peut même programmer Arduino en langage C.

Après pour faire ça, il faut bien faire attention car cette méthode présente quelques inconvénients :

-               - Le code sera moins visible donc plus difficile à vérifier ou modifier.

-               - La portabilité vers d’autres cartes sera moins évidente.

-               - Il pourrait y avoir des dysfonctionnement si on ne manipule pas les bons registres.



Source :

Arduino.cc

https://fr.wikiversity.org/wiki/Micro_contr%C3%B4leurs_AVR/Les_PORTs 

http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf


Source


 

Aucun commentaire:

Enregistrer un commentaire