L'intérêt de la manipulation des registres :
Registres de ports de l'ATMega328 :
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
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 :
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.
Opérations logiques utilisé pour le langage bas niveau:
1. Opération de masquage :
- Isolation d'une donnée :
- Forçage à 1 (set) ou à 0 (reset) :
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 :
|