INITIATION AUX MICROCONTROLEURS

Le texte équivalent pour Energia/MSP430G se trouve sous http://www.pyr.ch/coursera/LC7-msp.pdf

7. Moteurs, servos, capteurs, multitâche

Les sources des exemples sont à disposition sous www.didel.com/coursera/LC7ino.zip

7.1 Commande de moteurs

Les moteurs à courant continu existent à partir de 4 mm de diamètre et tournent à 100-200 tours par seconde. On les utilise avec des réducteurs. Ils fonctionnent dans une large gamme de tension, entre un minimum qui permet au moteur de lutter contre les frottements et la limite floue d’un échauffement excessif qui réduit la durée de vie. Entre ces extrêmes, il y a des lois simples entre courant, tension, couple, vitesse de rotation. La résistance de bobine et le facteur de réduction caractérise au mieux les moteur 3-12 V utilisés dans les applications robotiques. L’à-coup de courant au démarrage dépend de cette résistance. Les moteurs-jouets ont souvent une tension de démarrage élevée, qui empêche de bons asservissements avec du PWM. Le PFM permet des vitesses très lentes si chaque impulsion PFM est suffisante pour “décoller” le moteur.

On a vu le PWM et le PFM. On va s’intéresser aux robots, et le LearnCbot va mériter la dernière partie de son nom. Pour penser robot, on va définir des noms appropriés, par exemple #define MousGOn Pous1On si la moustache gauche est activée. Pour les moteurs, on aura par exemple #define AvG Led2On; Led1Off (une macro #define peut lister plusieurs instructions). La led verte du LCbot montre alors que le moteur gauche fait avancer le robot.

Pour une vitesse proportionnelle sur le moteur droite #define PwmD(vv) analogWrite(6,vv) permettra d’écrire PwmD (128); pour une vitesse moitié. Ceci évite de traîner des analogWrite non portables dans le programme principal (et c’est plus lisible, non ?). Pour le PFM, on écrira une fonctions qui permet d’utiliser des vitesses positives et négatives DoPfm (vitD,vitG).

Ces nouvelles définitions se mettront ultérieurement dans un fichier Robot.h

Exemple 7.11

Le robot démarre si on touche une moustache. Ensuite il avance et recule à l’infini

  //A711 avance et recule
  #include "LcDef.h"
  #define AvG Led2On; Led1Off
  #define RecG Led2Off; Led1On
  #define StopG Led2Off; Led1Off
  #define AvD Led3On; Led4Off
  #define RecD Led3Off; Led4On
  #define StopD Led3Off; Led4Off
  #define ObsG Pous1On
  #define ObsD Pous2On
  void setup () {
    LcSetup ();
  }
  void loop () {
    while (!(ObsG | ObsD)) {}
    while (1) {
      AvD; AvG; delay (1000);
      RecD; RecG; delay (1000);
    }
  }

Avancer-reculer en PWM à 25 % de vitesse n’est pas évident, puisque les leds rouges (recule) sur les pins 4 et 7 n’ont pas de PWM.

La solution est d’inverser les signaux sur les deux pins, comme le montre la figure.

Image

Exemple 7.12

Le robot avance et recule lentement.

Image

  //A712.ino Avance et recule
  #include "LcDef.h"
  #define AvG Led2On; Led1Off
  #define RecG Led2Off; Led1On
  #define StopG Led2Off; Led1Off
  #define AvD Led3On; Led4Off
  #define RecD Led3Off; Led4On
  #define StopD Led3Off; Led4Off
  #define PwmD(vv) analogWrite(6,vv)
  #define PwmG(vv) analogWrite(5,vv)

  LcSetup ();
  }
  #define Vit 80
  void loop () {
    AvD; PwmD(Vit); // on avance
    AvB; PwmG(Vit);
    delay (2000);
    StopD; StopG;
    RecD; PwmD(256-Vit); // on recule
    RecG; PwmG(256-Vit);
    delay (2000);
    StopD; StopG;
  }

A noter que l’on pourrait écrire –Vit car 256–Vit = 0-Vit = -Vit, mais cela induirait en erreur. Le paramètre est toujours un nombre positif 8 bits, il n’y a pas de bit de signe.

7.2 Commande bidirectionnelle en PFM

Choisissons 32 niveaux de vitesse. Les vitesses négatives sont complémentaires et on va saturer les vitesses à +31 et –31, c’est à dire que toute valeur plus grande sera ramenée à +31 ou –31, selon son signe. Le type pour la vitesse est `byte`. Il n’a pas toutes les propriétés d’un nombre négatif et le compilateur ne doit pas le considérer comme négatif.

if (vit&(1<<7)) { // négatif
  if (vit > Nniv) { vit = Nniv; }
}
else {
  if (vit < -Nniv) { vit = -Nniv; }
}

Pour des vitesses positives, le cœur de la fonction DoPfm, dérivée du programme vu en 6.71 est :

if ((pfmCnt += pfm) > Nniv) {
  pfmCnt -= Nniv;
  AvD;
}
else { StopD; }

Pour les vitesse négative, il suffit de permuter les signaux.

La fonction DoPfm(); pour un moteur est donnée ci-contre. Elle doit être appelés toutes les 2 ms pour un moteur de 10 à 20 mm.

Image

// Fonction DoPfm();
byte pwmD; // paramètre global
void DoPfmD () {
  if (!(pwmD &(1<<7))) { // positif
    if (pwmD > Nniv) { pwmD = Nniv; }
    if ((pfmCnt += pwmD) > Nniv) {
      pfmCnt -= Nniv;
      AvD;
    }
    else { StopD; }
  }
  else { // négatif
    if (pwmD < -Nniv) { pwmD = -Nniv; }
    if ((pfmCnt -= pwmD) > Nniv) {
      pfmCnt -= Nniv;
      RecD;
    }
    else { StopD; }
  }
}

Exemple 7.21

Avant d’aller plus loin, il faut tester cette fonction avec le programme le plus simple possible et bien comprendre comment elle réagit.

DoPfm est appelé toutes les 2 ms. La vitesse augmente toutes les 0.1 s. Au bout de 3.2 s, elle sature jusqu’à ce qu’elle devienne brusquement négative saturée. On voit la led rouge diminuer à partir de -32. Un temps d’arrêt est programmé en zéro.

Créez le programme A721b qui commande l’autre moteur.

//A721.ino Test DoPfmD()
. . . définitions, setup
. . . fonction DoPfmD
byte vitD=0;
void loop () {
  for (int i; i<100; i++) {
    delay (2);
    DoPfmD ();
  }
  vitD++;
  if (vitD++==0) {
    Led1On; delay (500); Led1Off;
  }
  }

Cette partie PFM étant bien comprise, il faut la cacher dans un #include et de nouveau faire quelques programmes simple pour tester la fonction DoPfmDG() qui agit sur les 2 moteurs.

Exemple 7.22

Le fichier Robot.h sera notre référence. Il contient la fonction DoPfmDG() et les variables globales pfmD pfmD associées.

Les valeurs pfmD pfmG vont de –31 à +31 et sont saturées à ces valeurs.

La période PFM dépend du moteur, avec 2 ms, un petit moteur démarre et ne s’emballe pas.

Les à-coups que l’on voit sur les Leds sont absorbés par le réducteur et l’inertie du moteur.

Essayez une valeur PFM de 1, 16 et 31.

Secouez le LearnCbot et interprétez.

//A722.ino test simple
#include "LcDef.h"
#include "Robot.h"
void setup () {
  LcSetup ();
}
void loop () {
  delay (2);
  pfmD=10; pfmG=20;
  DoPfmDG ();
}

On peut imaginer maintenant un programme qui, dans une boucle de 2 ms, active le PFM, teste des capteurs, lit des poussoirs, balaye un affichage, chaque tâche non bloquante communiquant par des variables et des flags.

Exemple 7.23

On peut aussi décider d’appeler la fonction DoPfm par interruption. On voit que c’est très simple. Le programme principal peut maintenant avoir des instructions bloquantes.

Le programme A723 fait une inversion brusque de vitesse. Modifier pour que la vitesse diminue et s’inverse progressivement. Le robot fera un aller-retour régulier.

Image

//A723.ino PFM par interruption
#include "LcDef.h"
#include "Robot.h"
void setup () {
  LcSetup ();
  TCCR2A = 0; //default
  TCCR2B = 0b00000111; // clk/1024 16kKz div 32->500Hz
  TIMSK2 = 0b00000001; // TOIE2
  sei();
}
ISR (TIMER2_OVF_vect) {
  TCNT2 = -32 ;
  DoPfmDG();
}
void loop () {
  delay (200);
  pfmD++; pfmG++;
  if (pfmD==31) {
    pfmD=-31; pfmG=-31; }
}

7. Moteur pas à pas

Les moteurs pas-à-pas ont différents câblages et séquences de pas. La séquence d’un moteur donné est mise dans une table, balayée à la vitesse voulue en sens direct ou inverse. Plusieurs librairies existent pour Arduino, et pour les moteurs Lavet utilisés dans les horloges et indicateurs de tableau de bord, voir www.didel.com/kidules/CKiClock.pdf

La programmation génère des séquences à partir d’une table. Modifier une vitesse, un sens de rotation, commander deux moteurs, ne pose pas de problème particulier, et est expliqué dans www.didel.com/kidules/CKiPas2Aig.pdf et www.didel.com/kidules/CKiDelta.pdf.

7.8 Servo de télécommande

Un servo est formé d’un moteur fortement réducté, avec un potentiomètre sur l’axe final. La valeur analogique lue est comparée avec la longueur du signal de 1 à 2 ms reçu toutes les 20 ms. L’angle de rotation varie selon le modèle, et certains servos travaillent de 0.7 à 2.5ms.

S’il y a plusieurs servos, il sont décalés dans le temps pour minimiser les pointes de courant et utiliser un seul timer.

Image

Image

Exemple 7.81

Ce programme teste un servo sur la pin 4 (L1) en passant d’une position extrême à l’autre. On voit sur la Led3 un léger changement d’intensité (la durée on est de 1/20 à 2/20 du temps).

//A781.ino Teste un servo
#include "LcDef.h"
#define ServoOn Led1On
#define ServoOff Led1Off
void setup () {
  LcSetup ();
}
#define MinServo 1000
#define MaxServo 2000
int durServo=MinServo;
void loop () {
  Led13On;
  while (durServo<MaxServo) {
    ServoOn;
    delayMicroseconds (durServo);
    ServoOff;
    durServo +=2; // selon vitesse angulaire
    delayMicroseconds (20000-durServo);
  }
  Led13Off;
  . . . très similaire
}

Exemple 7.82

Arduino offre une librairie Servo gérée par interruption qui permet de câbler 12 servos vus comme des objets en C++, auxquels on peut donner un nom explicite.

Evidemment cela prend un peu plus de place en mémoire (>2000 bytes).

//A782.ino Teste la librairie Servo
#include <Servo.h>
Servo brasRobot;
void setup () {
  brasRobot.attach (4); // sur Pin 4 = L1
}
void loop () {
  brasRobot.writeMicroseconds(1000);
  delay (1000);
  brasRobot.writeMicroseconds(2000);
  delay (1000);
}

7.9 Capteur ultrasons

Le capteur de distance à ultrason est facile à comprendre et utiliser.

Les circuits SR05/SR04 ont 4 broches actives:

Gnd Vcc

Trig Impulsion de 10 µs. Déclenche l’envoi

Echo Impulsion positive pendant le temps de vol.

ImageImage

Une impulsions sur Trig déclenche l’envoi de 8 impulsions à 40 kHz et active le signal Echo, qui est désactivé quand l’écho sonore revient. Le min de distance est 2 cm (~100 µs), le max ~3 m. La durée d’un aller-retour pour une distance de 25 cm est environ 1.3 ms. Si la distance est trop grande, le signal reste à 1 pendant 0.2 s, ce qui est fort gênant.

La directivité est très mauvaise. Il faut s’écarter de 20 degrés d’un petit obstacle pour que l’echo vienne de plus loin.

Image

// Fonction GetSonar
int GetSonar ()
{
  int dist;
  digitalWrite(Trig,HIGH
  delayMicroseconds(10);
  digitalWrite(Trig,LOW
  dist=pulseIn(Echo,HIGH);
  return dist;
}

La fonction Arduino pulseIn(pin,value) est généralement utilisée. Il faut lui dire que l’on attend une impulsion positive, et pulsIn () rend la durée de l’impulsion en microsecondes.

Cette fonction est bloquante, et si le capteur ne répond pas, on attend éternellement.

Il y a donc une 2e fonction pulseIn avec comme 3e paramètre la durée d’un timout. S’il y a timout, la valeur rendue est 0.

7.10 Capteur de lumière

Les photorésistances ou LDR (Light Dependant Resistors) sont faciles à mettre en œuvre. On câble un diviseur de tension et on est heureux de voir sur le terminal une valeur qui change. Utiliser cette valeur dans une application oblige de travailler dans un domaine étroit de luminosité que l’on peut adapter avec un potentiomètre.

ImageImage

Deux LDR en diviseur de tension donnent la différence d’intensité, indépendamment de cette intensité dans une large gamme.

Un capteur intéressant mais relativement cher est le TSL237, qui est un convertisseur lumière-fréquence qui couvre 5 décades.

Dans l’obscurité, la fréquence est de quelques Hz et on mesure la période. En plein soleil, on mesure une fréquence quelques centaines de kHz.

Image

Image

7.11 Capteur infrarouge

En éclairant avec une diode infrarouge et en mesurant la lumière réfléchie, on s’affranchit partiellement de la lumière ambiante.

La diode émettrice est pulsée pour augmenter la puissance. Le phototransistor récepteur est soit associé à une résistance à ajuster, soit à un condensateur qui permet une gamme de mesure beaucoup plus large, voir www.didel.com/xbot/DistIr.pdf et www.pyr.ch/coursera/CaptDistIrDoc.pdf

Image

La décharge d’une capacité s’applique à d’autre capteurs qui ont une résistance variable vers le Gnd.

7.12 Autres capteurs

Les capteurs performant; accéléromètre, boussole ont de plus en plus des interfaces I2C, avec parfois une option SPI selon une soudure à faire ou non sur le module.

7.13 Afficheurs

Il y aurait trop à dire sur les différents types d’afficheurs, 7 segments, LCD, Oleds. Ils sont utiles pour l’interaction et la mise au point s’ils ne perturbent pas le processus à analyser.

7.12 Multitâche

Gérer plusieurs tâches “simultanément se fait par interruption. L’appel d’une interruption dure 1-2 microsecondes et la routine exécutée doit être très courte.

Le programme communique avec les interruptions par des variables globales. S’il faut commander un actuateur, c’est facile, il suffit se mettre à jour ses paramètres. Pour un capteur, une interruption régulière fait la mesure par interruption et met à jour la variable que le programme peut lire. Un flag peut signaler qu’une nouvelle mesure est prête.

Si on lit des événement irréguliers (poussoirs, alarmes), leur gestion par interruption active des flags et la gestion de ces flags par le programme principal reste délicate si certaines parties du programme sont bloquantes.

Une solution élégante pour gérer plusieurs tâches et de les confier à un arbre de décision qui est parcouru toutes les 100 microsecondes par exemple.

Le timer 2 appelle la routine d’interruption et on y traite d’abord les événements rapides. Des compteurs post-diviseurs s’occupent des tâches plus lentes.

Chaque tâche se fait en une dizaine d’instructions au plus. C’est le plus souvent une machine d’état.

Pour la mise au point, on teste une routine à la fois avant de les mettre progressivement ensemble. Les interruptions utilisant d’autres timers vont parfois retarder le début des 100 µs et fausser les timings. Un oscilloscope ou analyseur logique rapide est essentiel pour vérifier qu’il n’y a pas engorgement suite à une mauvaise programmation.

Image

Ce programme montre une étape dans le développement d’une application qui veut asservir la vitesse du robot (GetEncoDG() et DoPfmDG()) gérer par interruption l’évitement d’obstacle (EvitObst()), utiliser deux servos et qui prévoit d’utiliser un timer à la seconde.

Une cascade de programmes de test vérifie chaque fonctionnalité et leur mise ensemble progressive.

On peut passer beaucoup de temps sur un détail. Par exemple ci-contre, il faut tester le timer avec Led13 et pas Led1 à 4 qui sont sous contrôle du PFM.

Relire l’ensemble du code, vérifier les initialisations, afficher les variables concernées (si cela ne perturbe pas), etc, plutôt que de se bloquer sur ce qui n’a pas de raison d’être faux et accuser le matériel !

//A7121.ino Arbre d’interruption – test timer0
#include "LcDef.h"
#include "Robot.h" // avec DoPfmDG()
//include "GetEnco.h"
//include "GetDistance.h"
//include "EvitObstacle.h"
include <Servo>
Servo Leve;
Servo Pince;

void setup () {
  LcSetup ();
  TCCR2A = 0;
  TCCR2B = 0b00000010; // clk/8 2MHz 100 us div200
  TIMSK2 = 0b00000001;
  // ajouter tout ce qu’il faut avant de lancer les interruptions !
  sei();
}
volatile int div20, div2c, div10m; timer0;
ISR (TIMER2_OVF_vect) {
  TCNT2 = -198 ; //100 us
// GetEncoDG();
// GetDistance();
  if (div20--==0) { div20=20; DoPfmDG(); }
// if (div2c--==0) { div2c=200; EvitObst(); }
  if (div10m--==0) { div10m=10000;
  if (timer0 != 0) { timer0--; }
}
void loop () {
  timer0 = 2 ;
  while (timer0) {}
  Led13Toggle;
}

Chaque application est différente et la structure choisie dépend beaucoup de l’expérience du programmeur. Comprendre les programmes des autres est souvent difficile !

Le projet de la dernière vidéo de ce MOOC, décrit sous www.didel.com/coursera/ProjetRobot.pdf n’utilise pas d’interruptions. Le programme se décompose en une successions de tâches simples. Par exemple le robot doit avancer ou tourner par petit bouts. Une solution était de gérer le PFM par interruption et calibrer des délais pour que les distances et angles soient corrects. Des fonctions Tourne() et Avance() ont été préférées, le paramètre étant un nombre d’impulsions pour les moteurs.

Derniers rappels et compléments

Attribut volatile : A mettre pour une variable qui peut être changée par une interruption ou que le compilateur pourrait supprimer car elle n’a pas d’effet.

Attribut static : A mettre pour une variable locale qui ne doit pas être perdue d’un appel à l’autre. Elle reste une variable locale, non visible par le programme principal et les autres fonctions.

Un #define Debug permet de laisser dans le programme les parties utilisées pour tester, et les remettre en service facilement.

#define FRENCH
. . .
#ifdef FRENCH
  Serial.print "Bonjour";
#else
  Serial.print "Hello";
#endif

jdn 140530