Sur les différents forums de discussion "Arduino", on lit souvent:
"Je fais clignotter une LED toutes les 5 secondes avec la fonction delay() et je mesure la largeur d'une impulsion provenant d'un récepteur RC avec pulseIn(), puis après traitement, je pilote un servo à l'aide de la librairie <SoftwareServo>. Mon servo saccade. Je constate que je rate beaucoup d'impulsions et que mon servo ne reçoit pas d'impulsion pendant le délai de 5 secondes: il est "mou". Comment puis-je faire ces 3 actions en même temps."
Voilà typiquement, un cas programmation dit "synchrone": la fonction delay() de 5 secondes est une fonction bloquante, le micro-contrôleur ne fait (quasi) rien d'autre que d'attendre que les 5 secondes se soient écoulées.
Pour pulseIn(), c'est la même chose, si aucune impulsion n'arrive, par défaut, cette fonction ne rend pas la main avant une seconde: il s'agit d'une fonction quasi bloquante. A l'échelle des capacités de traitement des micro-contrôleurs, une seconde, c'est une éternité!
L'auteur de ces lignes propose donc une solution: la programmation en mode dit "asynchrone" à l'aide d'une collection de bibliothèques non bloquantes taillées pour les applications orientées "Radio-Commande". Pour les évènements entrants (ex: lecture impulsion) Asynchrone signifie qu'il n'est pas nécessaire d'attendre qu'une action soit finie avant de rendre la main au micro-contrôleur. Le simple test d'un indicateur via une fonction de la librairie suffit à savoir si l'action est terminée ou pas. Pour les évènements sortants (ex: génération d'une impulsion), Asynchrone signifie qu'ils sont exécutés dans la boucle principale, dès que le moment est arrivé, ou bien en interruption timer.
Note:
"Bibliothèque" est la vraie traduction française du mot anglais
"library". "Librairie" est est faux-ami, car la traduction anglaise est
"bookstore": l'endroit où on vend des livres ou des journaux. Rien à
voir avec les bibliothèques de fonctions! Cependant, par abus de
langage, "librairie" ou "lib" est très souvent employé pour désigner un
bibliothèque de fonctions.
Les bibliothèques asynchrones développées par l'auteur:
Bibliothèque \ Cible |
UNO |
MEGA |
ATtiny84 |
ATtiny85 |
Digispark (pro) |
Notes | |
Sans DigiUSB |
Avec
DigiUSB |
||||||
TinyPinChange |
O |
O |
O |
O |
O |
N |
usbdrvasm.S ne partage pas le vecteur
d'interruption "pin change". |
SoftRcPulseIn |
O |
O | O |
O |
O |
N |
S'appuie sur TinyPinChange. Voir ci-dessus. |
SoftRcPulseOut |
O |
O | O |
O |
O |
O |
Rien à signaler. |
RcSeq |
O |
O | O |
O |
O |
O* |
Partiellement: s'appuie sur TinyPinChange. Voir ci-dessus. |
SoftSerial |
O |
O | O |
O |
O |
N |
S'appuie sur TinyPinChange. Voir ci-dessus. |
RcTxSerial |
O |
O |
O |
O |
O |
O |
S'utilise avec SoftRcPulseOut ou TinyCppmGen. |
RcRxSerial |
O |
O | O |
O |
O |
N |
S'utilise avec SoftRcPulseIn ou TinyCppmReader. |
TinyCppmGen |
O |
O | O |
O |
O |
O |
Utilise un timer de seulement 8 bits. |
TinyCppmReader | O | O | O | O | O | N | S'appuie sur TinyPinChange. Voir ci-dessus. |
TinyOscCal |
N/A |
N/A |
O |
O |
O |
N |
DigiUSB impose l'horloge à 16.5MHz. |
TinySoftPwm |
O |
? |
? |
O |
O |
0 |
Non testé sur MEGA et ATtiny84. |
Code
couleur |
|
O
|
Support à 100%
sans condition particulière |
O* |
Support partiel |
N
|
Pas supporté
(support à 0%) |
? |
Non testé |
N/A |
Non Applicable |
#include <Rcul.h> // inclusion de la librairie Rcul (utilisee par SoftRcPulseIn et SoftRcPulseOut)
#include <TinyPinChange.h> // inclusion de la librairie TinyPinChange (utilisee par SoftRcPulseIn)
#include <SoftRcPulseIn.h> // inclusion de la librairie SoftRcPulseIn (utilisee par ce sketch)
#include <SoftRcPulseOut.h> // inclusion de la librairie SoftRcPulseOut (utilisee par ce skecth)
#define BROCHE_SIGNAL_RECEPTEUR 2
#define BROCHE_SERVO 3
#define BROCHE_LED 13
#define NEUTRE_US 1500
#define MAINTENANT 1
#define IMPULSION_DELAI_MAX_MS 30 /* Pour rafraichir le servo si disparition des impulsions */
#define DEMI_PERIODE_LED_MS 250 /* Ajustement de la frequence de clignottement de la LED */
#define MOY_SUR_1_VALEURS 0
#define MOY_SUR_2_VALEURS 1
#define MOY_SUR_4_VALEURS 2
#define MOY_SUR_8_VALEURS 3
#define TAUX_DE_MOYENNAGE MOY_SUR_4_VALEURS /* Choisir ici le taux de moyennage parmi les valeurs precedentes possibles listees ci-dessus */
/* Plus le taux est élevé, plus le système est stable (diminution de la gigue), mais moins il est réactif */
#define MOYENNE(Valeur_A_Moyenner,DerniereValeurRecue,TauxDeMoyEnPuissanceDeDeux) Valeur_A_Moyenner=(((Valeur_A_Moyenner)*((1<<(TauxDeMoyEnPuissanceDeDeux))-1)+(DerniereValeurRecue))/(1<<(TauxDeMoyEnPuissanceDeDeux)))
SoftRcPulseIn ImpulsionRecepteur;
SoftRcPulseOut MonServo;
uint32_t DebutChronoLedMs=millis();
uint32_t DebutChronoServoMs=millis();
boolean EtatLed=LOW;
uint16_t Largeur_us_Moyennee; /* Variable pour contenir la largeur d'impulsion moyennee (pour plus de stabilité du servo) */
void setup()
{
ImpulsionRecepteur.attach(BROCHE_SIGNAL_RECEPTEUR);
pinMode(BROCHE_LED, OUTPUT);
MonServo.attach(BROCHE_SERVO);
MonServo.write_us(NEUTRE_US);
Serial.begin(9600);
}
void loop()
{
uint16_t Largeur_us;
static uint32_t DebutChrono=0;
static uint32_t Compteur=0;
if(!DebutChrono) DebutChrono=millis();
/* Acquisition du signal du Recepteur, traitement (inversion), puis mise a jour du Servo */
if(ImpulsionRecepteur.disponible())
{
Largeur_us=(NEUTRE_US*2)-ImpulsionRecepteur.largeur_us(); /* Traitement pour inverser le sens de rotation du servo */
MOYENNE(Largeur_us_Moyennee,Largeur_us,TAUX_DE_MOYENNAGE); /* Moyenne glissante pour filtrer le signal -> Stabilite */
MonServo.write_us(Largeur_us_Moyennee);
SoftRcPulseOut::refresh(MAINTENANT); /* Permet de synchroniser les impulsions sortantes avec les impulsions entrantes */
DebutChronoServoMs=millis(); /* Relance Chrono */
}
else
{
/* Test de disparition des impulsions */
if(millis()-DebutChronoServoMs>=IMPULSION_DELAI_MAX_MS)
{
/* Raffraichit le servo avec la derniere position connue pour eviter qu'il ne soit "mou" */
SoftRcPulseOut::refresh(MAINTENANT); /* Permet la mise a jour immediate des impulsions sortantes */
DebutChronoServoMs=millis(); /* Relance Chrono */
}
}
/* Mise a jour de l'etat de la LED */
if(millis()-DebutChronoLedMs>=DEMI_PERIODE_LED_MS)
{
digitalWrite(BROCHE_LED, EtatLed);
EtatLed=!EtatLed; /* Au prochain passage, l'etat de la LED sera inverse */
DebutChronoLedMs=millis();
}
Compteur++;
if(Compteur>=1000000)
{
Serial.print("Tps boucle = ");Serial.print((millis()-DebutChrono)/1000);Serial.println(" us");
Compteur=0;
DebutChrono=0;
}
}
Résultats:
Avec ce sketch, sur un Arduino UNO à 16 MHz, les mesures montrent que 1000000 tours de boucle principale s'effectuent en environ 12000 ms, soit un temps de boucle moyen de 12 µs! Le micro-contrôleur respire et peut faire encore beaucoup d'autres tâches "en parallèle" à la seule condition que ces taĉhes additionnelles ne fassent appel à aucune fonction bloquante! La programmation doit rester de type "asynchrone" jusqu'au bout!
Remarque:
Les mesures de largeur d'impulsion sont assez sensibles aux masquages d'interruption de quelques micro-secondes. Afin d'avoir une mesure stable, celle-ci est "lissée" à l'aide de la macro MOYENNE() qui effectue une moyenne glissante. cette méthode est d'une efficacité redoutable.Les servomoteurs ont 3 fils de connexion :
Dans cas de l'utilisation d'un à 2 servomoteur(s) standard(s) :
Dans le cas de l'utilisation de plusieurs servomoteurs ou de servomoteurs nécessitant une tension supérieure à 5V et/ou une intensité importante :