Cours:TPS 2103 1
Nous avons abordé la programmation des cartes arduino au premier semestre en utilisant des fonctions de haut niveau ce qui, bien que pouvant simplifier certaine tâche, présente un certain nombre de limitation.
L'objectif de ces TPs est de découvrir le fonctionnement du µcontrôleur Atmega328p et d'en explorer les possibilités.
Nous commencerons simplement de façon analogue au premier TP du module M1102 en configurant des e/s (il est sans doute utile de jeter un coup d’œil au lien précédent !).
Sommaire
Ex 1: mise en jambe !
On considère la led f5. La patte correspondante (PB5) sera donc une sortie (commande) pour le µcontrôleur. On configure donc le registre de direction de la façon suivante :
DDRB bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
Valeur | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
On rappelle la connexion des LEDs et couleur avec les PORTs correspondants pour le shield de l'IUT de Troyes :
Numéro | f5 | f4 | f3 | f2 | f1 | f0 | p1 | p0 |
---|---|---|---|---|---|---|---|---|
Couleur | r | o | v | r | o | v | v | r |
Arduino Pin | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 |
Port Arduino UNO | PB5 | PB4 | PB3 | PB2 | PB1 | PB0 | PD7 | PD6 |
Port Arduino LEONARDO | PC7 | PD6 | PB7 | PB6 | PB5 | PB4 | PE6 | PD7 |
Compléter le programme suivant pour que la led s'allume et s'éteigne pendant 500ms
Remarque : On utilisera astucieusement l'opérateur ^
void setup()
{
DDRB = 0x?? ; // configuration des e/s (registre de direction DDRx) sur le port D.
}
void loop()
{
PORTB ???? ; // on modifie en conséquence l'état de la sortie
.... // il faudra utiliser la fonction delay !
}
void setup()
{
DDRB = 0x01 ; // configuration des e/s (registre de direction DDRx) sur le port D.
}
void loop()
{
PORTB = 0x00 ; // mise à zéro de la sortie
delay(500);
PORTB = 0x01 ; // mise à un de la sortie
delay(500);
}
|
Ex 2: sans délais ...
Plusieurs leds simultanées
Modifier le programme précédent pour ajouter une deuxième led (f1) s'allumant et s'éteignant au même rythme
Faire en sorte que les 2 leds soient allumées en alternance
Les limites ...
On souhaite avoir 2 rythmes différents pour le clignotement. L'une des leds aura une période de 1s (2x500ms), et l'autre led de 200ms (2x100ms).
On constate aisément que le rapport entre les périodes est de 5, on choisira un "delay" de boucle de 100ms et il suffira de ne changer l'état de la 2ème led que toutes les 5 itérations, suivant le principe suivant :
unsigned char i;
void setup()
{
...
i=0;
}
void loop()
{
i++;
if (i==Nb) // toutes les Nb itérations
{
...
i=0;
}
delay(???); // un seul delay dans le programme
}
Comme le commentaire l'indique le programme ne doit comporter qu'un seul delay !!! Il faut donc bien le choisir !
Écrire le programme répondant au cahier des charges
Essayer ensuite de modifier le programme pour avoir une période de 225ms pour l'une des leds et 190ms pour l'autre.
Une autre possibilité
Plutôt que d'utiliser des "delay", nous allons utiliser une montre (le temps depuis le démarrage du µcontrôleur en fait).
Le principe est donné dans le programme suivant :
unsigned long dateActuelle, dateModification;
void setup()
{
}
void loop()
{
dateActuelle = millis();
if ( (dateActuelle - dateModification) > dureeSouhaitee ) // la condition est vraie si le dernier changement est trop vieux
{
... // on modifie alors les sorties
dateModification=dateActuelle; // et on mémorise l'heure du dernier changement
}
}
Autre explication : Si on lance la cuisson des pâtes à 20h15 et que celle ci dure 10 minutes il faut les sortir à 20h25 !
Sur cette base, écrire un programme répondant à la question précédente.
Ex 3: bouton et led
Nous considérons pour le moment le bouton A et la led de droite (p0).
Le tableau suivant donne les caractéristiques utiles sur les 4 boutons poussoirs d'entrée pour le shield de l'IUT :
Bouton | Position | Arduino Pin | Port | Interruption | Résistance de tirage | Niveau logique de l'entrée arduino si bouton appuyé |
---|---|---|---|---|---|---|
A | Bas Gauche | 2 | PD2 | int0 | Pull Up + inverseur | 1 |
D | Haut Gauche | 3 | PD3 | int1 | Pull Up + inverseur | 1 |
B | Bas Droite | A0 | PC0 | Pull Down + inverseur | 0 | |
C | Haut Droite | A1 | PC1 | Pull Down + inverseur | 0 |
Complétez le programme suivant pour que la led s'allume si le bouton est appuyé
void setup()
{
DDRD = 0x?? ; // configuration des e/s (registre de direction DDRx) sur le port D.
}
void loop()
{
if ( (PIND & 0x?? ) != 0 ) // on observe l'état de l'entrée
PORTD = 0x??; // on modifie en conséquence l'état de la sortie
else
PORTD = 0x??;
}
Pour le moment nos programmes sont petits et il est donc possible de les lire complètement pour imaginer les actions qu'il réalise. Mais que se passe-t-il lorsque la taille des programmes augmente (pour atteindre 700 lignes par exemple). Il faut alors mettre en place des techniques appropriées. Ces techniques sont appelées debuggage (le verbe francisé associé est déboguer) et nous en présentons une toute simple maintenant.
Il faut apprendre à debugger un programme. Le plus simple est de se servir de la liaison série : void setup()
{
Serial.begin(9600);
DDRD = 0x?? ; // configuration des e/s (registre de direction DDRx) sur le port D.
}
void loop()
{
unsigned char res;
res = PIND & 0x??;
Serial.print("res = ");
Serial.println(res,BIN);
if ( (res) != 0 )
{
Serial.println("le test est vrai.");
PORTD = 0x??; // on modifie en conséquence l'état de la sortie
}
else
{
Serial.println("le test est faux.");
PORTD = 0x??;
}
delay(200); // indispensable pour ne pas avoir un affichage trop rapide !
}
|
On peut simplifier le programme précédent en utilisant l'opérateur décalage : il suffit de "recopier" l'état du bouton poussoir (PD2) sur la led (PD6) en décalant de 4 bits (PD6-PD2) vers la gauche
void setup()
{
DDRD = 0x?? ; // configuration des e/s (registre de direction DDRx) sur le port D.
}
void loop()
{
PORTD = (PIND&0x??)<<4;
}
Ex 4: Changement d'état
Passé et présent
On souhaite maintenant changer l'état de la led (p0) à chaque appui sur le bouton poussoir A (PD2).
On rappelle comme vu en module M1103 que l'une des méthodes repose sur la notion de présent et passé.
Le code suivant (à compléter) répond à ce cahier des charges
char etatPresent=0,etatPasse=0;
unsigned char etatSortie=0;
void setup()
{
DDRD= 0x??; // configuration des e/s
}
void loop()
{
etatPasse=etatPresent;
etatPresent = (PIND & ...)>>2; // 2 valeurs possibles pour etatPresent : 0 ou 1
if ((etatPresent==1)&&(etatPasse==0)) PORTD^=0x??; // modifier la sortie associée à la led p0
}
ou mieux ...
Nous allons utiliser une interruption, dont vous devez comprendre le fonctionnement !
Commencer par comprendre et exécuter le code donné en exemple
Modifier le programme pour que la led(p0) ne change d'état qu'à l'appui sur le bouton poussoir, et non à chaque changement d'état
|
Et pour 2 leds ??
Adapter le programme précédent en ajoutant le bouton D (INT1) et une led au choix avec un fonctionnement identique (chaque bouton commande 1 led)
Autre possibilité
Bien évidemment 2 interruptions peuvent modifier la même variable/registre :
On souhaite que l'appui sur le bouton A allume la led p0 et l'appui sur le bouton D l'éteigne. Écrire le programme.
Ex 5: Drapeaux
Les drapeaux sont très utilisés avec les fonctions d'interruption. Le principe est de positionner une variable à une valeur particulière au sein de l'interruption et de faire un test sur cette valeur dans le programme principal, exemple :
volatile unsigned char chg = 0; // les variables partagées entre interruption et programme principale doivent être de type volatile
ISR(INT0_vect) // programme d'interruption : le programme principal est interrompu,
{ // l'interruption exécutée et ensuite le programme principal continu normalement son exécution
chg = 1; // on positionne le drapeau
}
void setup()
{
DDRB=0x0F; // configuration de PB0 en sortie
cli(); // arrêt des interruptions
EICRA=0x01; // mode de déclenchement de l'interruption
EIMSK=0x01; // choix des interruptions actives
sei(); // autorisation des interruptions
}
void loop()
{
if (chg==1)
{
PORTB^=0x01;
chg=0;
}
}
Premier usage
Modifier le programme précédent pour :
- Ne déclencher l'interruption que sur un appui du bouton A
- Incrémenter le flag à chaque interruption
- Changer l'état de la led tous les 5 appuis
Clignotement
Écrire le programme répondant au cahier des charges suivant :
- Faire clignoter une led
- Chaque appui sur A double la fréquence de clignotement
- Chaque appui sur D divise par 2 la fréquence de clignotement
Ex 6: POUR ALLER PLUS LOIN : lien avec l'automatisme
Les GRAFCETs abordés en automatisme (Module M2102) peuvent être transformés en équations de récurrences puis en programmes.
Équation de récurrences
La méthode simple consiste à écrire pour chacune des étapes les conditions d'activations (notées Échec d'analyse (L’exécutable <code>texvc</code> est introuvable. Lisez math/README pour le configurer.): AC_i ) et les conditions de désactivations (notées Échec d'analyse (L’exécutable <code>texvc</code> est introuvable. Lisez math/README pour le configurer.): D_i ).
Définition
La condition d'activation Échec d'analyse (L’exécutable <code>texvc</code> est introuvable. Lisez math/README pour le configurer.): AC_i s'obtient en se posant la question : comment activer l'étape i si elle n'est pas active ?
La condition de désactivation s'obtient quant à elle en se posant la question : quelles sont les conditions nécessaires pour que le jeton quitte l'étape i s'il est dedans ?
Transformation en équations de récurrences
Avec ces conditions on va pouvoir former les équations de récurrences :
- Pour la ou les étapes initiales : Échec d'analyse (L’exécutable <code>texvc</code> est introuvable. Lisez math/README pour le configurer.): x_i^+=AC_i + \overline{D_i} \cdot x_i + Init
- Pour les étapes non initiales : Échec d'analyse (L’exécutable <code>texvc</code> est introuvable. Lisez math/README pour le configurer.): x_i^+=(AC_i + \overline{D_i} \cdot x_i) \cdot \overline{Init}
L'entrée "Init" sera abandonnée dans toute cette section !
L'indice i parcourant toutes les étapes, il y a autant d'équations de récurrences que d'étapes. En terme matériel, cela signifie que l'on utilisera un bit mémoire par étape. Pour nous on utilisera une variable (de type char) par étape, ce qui est un énorme gaspillage ! mais simplifie la programmation.
Bien sûr, un codage des états (abordé ICI) permet de faire des économies de ce côté là mais rappelez-vous qu'à ce point on a que des étapes et pas encore des états.
Nous allons partir d'un GRAFCET assez général pour réaliser un exemple complet.
Prenez un peu de temps pour relire l'équation de AC1 et celle de D3 qui prennent en compte le parallélisme. C'est le seul type de situation qui diffère du Logique séquentielle : Description par graphe d'état.
Trouvez l'erreur dans l'équation de récurrence x4+.
Transformation en programme
Un programme gérant un GRAFCET devra comporter trois parties :
- une partie pour gérer (et éventuellement calculer) les entrées (temporisées ou pas)
- une partie pour gérer les équations de récurrences
- une partie pour gérer les actions
Le tout est dans une boucle infinie
Exemple correspondant au GRAFCET ci-dessus
Les LEDs du shield IUT Troyes sont couplés à l'arduino MEGA2560. Une lecture de son schéma fait apparaître la correspondance :
Numéro | f5 | f4 | f3 | f2 | f1 | f0 | p1 | p0 |
---|---|---|---|---|---|---|---|---|
Couleur | r | o | v | r | o | v | v | r |
Arduino Pin | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 |
Port UNO | PB5 | PB4 | PB3 | PB2 | PB1 | PB0 | PD7 | PD6 |
Port MEGA2560 | PB7 | PB6 | PB5 | PB4 | PH6 | PH5 | PH4 | PH3 |
Etape de GRAFCET | 1 | 2 | 3 | 4 | 5 | - | - | - |
Le code donné ci-dessous est pour la carte MEGA2560. Il devra être adapté par vos soins pour la carte UNO.
Voici le code correspondant :
//*********** Programme pour MEGA2560 ********************
char x1; // étape 1
void setup(){
//******* sorties
DDRB |= 0xF0;//f5 f4 f2 f1
DDRH |= 0x40;// f0
//******* entrees
DDRE &= 0xCF; // 2 and 3
DDRF &= 0xFC; //A0 et A1
//******* initialisation du GRAFCET
x1 = 1;
}
void loop(){
char e1,e2,e3,e4;
static char x2,x3,x4,x5;
char xf1,xf2,xf3,xf4,xf5; // f pour futur remplace le +
//******* calcul des entrées
if (PINE & 0x20) e1 = 1; else e1 = 0; // 2:Bas gauche
if (PINE & 0x10) e2 = 1; else e2 = 0; // 3:Haut gauche
if (PINF & 0x02) e3 = 0; else e3 = 1; // A0:bas droite
if (PINF & 0x01) e4 = 0; else e4 = 1; // A1:Haut droite
//******* calcul des equations de récurrence
xf1 = x3 & x5 & e4 | x1 & !e1;
xf2 = x1 & e1 | x2 & !e2;
xf3 = x2 & e2 | x3 & !(e4 & x5);
xf4 = x1 & e1 | x4 & ! e3;
xf5 = x4 & e3 | x5 & !(e4 & x3);
// mise à jour du present avec le futur
x1 = xf1;
x2 = xf2;
x3 = xf3;
x4 = xf4;
x5 = xf5;
//******* calcul des sorties (pour MEGA2560)
if (x1) PORTB |= (1<<PB7); else PORTB &= ~(1<<PB7);
if (x2) PORTB |= (1<<PB6); else PORTB &= ~(1<<PB6);
if (x3) PORTB |= (1<<PB5); else PORTB &= ~(1<<PB5);
if (x4) PORTB |= (1<<PB4); else PORTB &= ~(1<<PB4);
if (x5) PORTH |= (1<<PH6); else PORTH &= ~(1<<PH6);
delay(500);
}
La partie calcul des entrées sera mieux comprise avec les informations suivantes :
Bouton | Position | Arduino Pin | Port MEGA2560 | Résistance de tirage | Niveau logique si bouton appuyé |
---|---|---|---|---|---|
A | Bas Gauche | 2 | PE4 | Pull Up + inverseur | 1 |
D | Haut Gauche | 3 | PE5 | Pull Up + inverseur | 1 |
B | Bas Droite | A0 | PF0 | Pull Down + inverseur | 0 |
C | Haut Droite | A1 | PF1 | Pull Down + inverseur | 0 |
La documentation correspondante pour votre carte UNO est disponible ICI.
Travail à faire
Modifier le programme précédent pour :
- l'adapter à votre carte UNO. Pourquoi x1 est une variable globale ?
- que l'arrivée dans l'étape 5 fasse clignoter la LED correspondante
- que l'arrivée dans l'étape 3 fasse clignoter la LED correspondante à une autre fréquence
- que la transition e1 soit sur front montant (sans utiliser d'interruption)
- que la transition e1 soit sur front montant (en utilisant une interruption)