Comprendre la mobilité du futur avec les voitures robots, partie 2
Bienvenue dans le deuxième épisode de notre nouvelle série de blogs sur les voitures robotisées. Dans la première partie, nous avons découvert les bases du véhicule de base. Cette fois-ci, nous voulons aborder les possibilités de commande à distance, en développant un système de code qui nous permet de déterminer les étapes de conduite de la voiture robot et, enfin, de réaliser une commande à distance simple avec une télécommande infrarouge.
Pourquoi ai-je besoin de niveaux de vitesse pour une voiture RC pour microcontrôleurs Arduino ?
1. Pour contrôler les vitesses du moteur à partir d'un autre appareil, il existe en principe la possibilité de déterminer et de transmettre des valeurs en continu à l'aide d'un potentiomètre (poti), ou d'augmenter ou de diminuer la valeur de l'étape de conduite en appuyant sur un bouton. Sur les images suivantes, quelques exemples choisis, sur la première image des commandes par joystick :
Sur la deuxième image, nous voyons des commandes à distance infrarouges, une application pour smartphone avec récepteur Bluetooth ainsi que le panneau de commande LCD1602 qui peut être utilisé, entre autres, avec un émetteur-récepteur 433 MHz :
Pour obtenir un schéma uniforme, il est judicieux de réduire les valeurs analogiques du potentiomètre avec la fonction map() à des valeurs qui peuvent être transmises par radio. Le convertisseur analogique-numérique fournit des nombres de 10 bits, c'est-à-dire des valeurs comprises entre 0 et 1023. La position centrale des mini-joysticks est d'environ 511. En divisant cette valeur par 100, tu peux obtenir deux valeurs (direction x et y) avec un joystick, qui se situent entre 0 et 10 ou, en utilisant la fonction map(), entre 1 et 9. Cela suffit largement pour entrer la vitesse et pour les virages. Que nous déterminions 11 ou 9 valeurs, l'immobilité signifie une valeur de 5 dans la direction y et une valeur de 5 pour aller en ligne droite.
En ce qui concerne le contrôleur avec les deux joysticks (partie du kit 4DOF Mini bras robotique avec joysticks et servocommande), j'opte pour un code facile à transmettre entre 1111 et 9999, avec le premier chiffre pour la direction y du joystick gauche, le deuxième chiffre pour la direction x du joystick gauche, le troisième et le quatrième chiffre pour un deuxième joystick optionnel ou certaines touches. Dans le premier exemple avec la télécommande IR, nous n'avons besoin que des deux premiers chiffres du code à quatre chiffres.
Le contrôle de la vitesse de la voiture RC pour Arduino
La régulation de la vitesse des moteurs se fait par modulation de largeur d'impulsion (PWM). La valeur de ce que l'on appelle le duty cycle est un nombre de 8 bits, c'est-à-dire une valeur comprise entre 0 et 255 (=2 puissance 8 -1). Si tu appliques des tensions entre 0 et 6V au moteur avec un bloc d'alimentation réglable, tu constateras que jusqu'à environ 1,5 V, tu ne remarqueras rien. Ensuite, le moteur commence à bourdonner, mais ne bouge pas. Si tu mesures en même temps l'intensité du courant, tu constateras une valeur relativement élevée. L'énergie est transformée en chaleur et en ronflement - pas vraiment bon. A partir de 2,4 - 3 volts, le moteur commence à tourner, l'intensité du courant diminue un peu lorsque le moteur n'est pas chargé. Ensuite, la vitesse augmente en fonction de la tension appliquée. En baissant la tension, le moteur tourne jusqu'à moins de 2 volts, mais s'il s'arrête, par exemple à cause d'une résistance de frottement accrue ou d'un obstacle, il ne redémarre pas. Conclusion : les valeurs inférieures à environ 2,4 volts devraient être évitées, l'entrée devrait être fixée à 0 (zéro) afin d'éviter une usure et une consommation d'énergie inutiles. En dehors de l'arrêt (code=5), nous avons besoin de Duty Cycle entre environ 40% et 100% pour une tension d'alimentation de 6V pour les moteurs.
L'alimentation électrique de la voiture RC pour Arduino
Nous avons déjà abordé plusieurs possibilités d'alimentation électrique dans la première partie. Quatre piles AA de 1,5V chacune donnent 6V, la tension maximale des petits moteurs jaunes. Quatre piles AA de 1,2 V chacune ne suffisent pas pour l'alimentation. Il faut alors un support de piles pour 6 piles, ce qui donne 7,2 volts. Et deux batteries lithium-ion (tension nominale de 3,7V) fournissent plus de 8 volts lorsqu'elles sont complètement chargées. Il est donc logique de définir dans le programme un facteur qui limite la tension du niveau de conduite le plus élevé à 6V.
Le contrôle de la RC Car pour Arduino
Si les instructions de conduite sont données avec des boutons, qui peuvent même se trouver dans une application pour smartphone, la valeur de sortie 55xx (arrêt) est augmentée ou diminuée de la valeur correspondante, sans que le maximum ne puisse être dépassé.
Lire le code correctement
L'attribution du premier chiffre du code aux valeurs nécessaires des étapes de conduite se fait par l'index d'une liste avec les valeurs numériques correspondantes.
Conclusion : avec un code à quatre chiffres, nous pouvons contrôler la vitesse et les virages avec les deux premiers chiffres, les deux derniers chiffres servent à d'autres fonctions (pas nécessaires au début). Donc, par exemple, code95xx pour aller le plus vite en ligne droite, 55xx pour s'arrêter, 77xx pour avancer vers la droite.
y ↓ 0 x→ |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
9 |
← |
|
|
↑ |
|
|
→ |
||
8 |
← |
|
|
↑ |
|
|
→ |
||
7 |
← |
|
|
↑ |
|
|
→ |
||
6 |
← |
|
|
↑ |
|
|
→ |
||
5 |
← |
|
|
0 |
|
|
→ |
||
4 |
← |
|
↙ |
|
↓ |
|
↘ |
|
→ |
3 |
← |
|
↙ |
|
↓ |
|
↘ |
|
→ |
2 |
← |
|
↙ |
|
↓ |
|
↘ |
|
→ |
1 |
← |
|
↙ |
|
↓ |
|
↘ |
|
→ |
Maintenant, nous voulons construire notre première Smart Robot Car avec le kit, un microcontrôleur de type ATmega328 (forme de construction de l'UNO R3), un MotorShield V2 et des émetteurs et récepteurs IR.
Le Motor Shield V2 peut contrôler jusqu'à quatre moteurs, en utilisant le bus I2C avec les connecteurs SDA (= données sérielles) sur l'entrée analogique A4 et SCL (= horloge sérielle) sur A5 pour connecter les lignes de contrôle. Pour cela aussi, Adafruit a développé et mis à disposition une bibliothèque de programmes adaptée. Attention : les bibliothèques pour les Motor Shield V1 et V2 ne sont pas compatibles.
Image Motor Shield V2 avec modification :
Connecteurs femelles soudés (connecteurs à ressort) pour la connexion d'équipements supplémentaires
Quel que soit le Motor Shield que tu souhaites utiliser - V1 ou V2 -, il est judicieux de souder des connecteurs femelles supplémentaires sur les deux Motor Shield pour pouvoir y connecter plus tard des émetteurs/récepteurs Bluetooth ou 433 MHz (en anglais transceiver=émetteur + récepteur) ou des capteurs. Plus de détails dans les articles de blog suivants. Le récepteur IR n'a besoin que de l'alimentation électrique des broches 3 et 4 ainsi que de la broche 2 pour le récepteur IR. Le fer à souder peut encore rester froid.
Comme commande, nous utiliserons d'abord la petite télécommande infrarouge de Funduino et un récepteur IR. Bien que le capteur IR "nu" suffise, je recommande la petite carte de breakout, car une LED scintille lorsque des signaux IR sont reçus ; une aide précieuse lorsqu'une erreur est recherchée.
Le sketch est composé de deux parties qui ont fait leurs preuves : Le sketch d'exemple d'Armin Joachimsmeyer, qu'il a ajouté à sa superbe bibliothèque de programmes IRremote, et un sketch de voiture robot de l'auteur, basé sur la bibliothèque de programmes d'Adafruit.
Le code de programme pour la RC Car pour Arduino
/* Sample Code for Robot Car with Motor Shield V2 and IR receiver, as of 20220515
* based on Adafruit Motor shield V2 library, copyright Adafruit Industries LLC, 2009
* et SimpleReceiver.cpp, partie de Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote
* MIT License Copyright (c) 2020-2022 Armin Joachimsmeyer
* modifié pour Funduino
* Motor Shield V2 utilise I2C avec SDA=A4 et SCL=A5
* IRreceiver utilise la broche 2
************************************************************************************
* Permission est accordée par la présente, gratuitement, à toute personne obtenant une copie
* de ce logiciel et des fichiers de documentation associés (le "logiciel"), à traiter
* dans le logiciel sans restriction, y compris, sans limitation, les droits de
* d'utiliser, de copier, de modifier, de fusionner, de publier, de distribuer, de sous-licencier, et/ou de vendre
* copies du logiciel, et à autoriser les personnes à qui le logiciel est fourni à le faire
* à le faire, sous réserve des conditions suivantes :
*
* La notice de copyright ci-dessus et cette notice d'autorisation doivent être incluses dans toutes les copies
* copies ou parties substantielles du logiciel.
*
* LE LOGICIEL EST FOURNI "EN L'ÉTAT", SANS GARANTIE D'AUCUNE SORTE, EXPRESSE OU IMPLICITE,
* Y COMPRIS, MAIS SANS S'Y LIMITER, LES GARANTIES DE QUALITÉ MARCHANDE, D'APTITUDE À UN USAGE PARTICULIER
* UN BUT PARTICULIER ET L'ABSENCE D'ERREURS. EN AUCUN CAS LES AUTEURS OU LES COPYRIGHTS NE PEUVENT ÊTRE TENUS POUR RESPONSABLES
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRAT, TORT OU AUTRE, DÉCOULANT DE, OU EN RELATION AVEC LE LOGICIEL
* OU L'UTILISATION OU D'AUTRES TRANSACTIONS DANS LE LOGICIEL.
*************************************************************************************/
#include <Adafruit_MotorShield .h>
// Créer l'objet motor shield avec l'adresse I2C par défaut 0x60
Adafruit_MotorShield AFMS = Adafruit_MotorShield() ;
// Choisir quel 'port' M1, M2, M3 ou M4.
Adafruit_DCMotor *motor1 = AFMS.getMotor(2) ;
Adafruit_DCMotor *motor2 = AFMS.getMotor(3) ;
// définir le protocole pour le contrôle à distance, pour en savoir plus see samples code SimpleReceiver.cpp
#define DECODE_NEC // Inclut Apple et Onkyo, aussi pour la petite télécommande Funduino
//#define INFO // Pour voir des informations précieuses du décodeur universel pour les protocoles de largeur d'impulsion ou de distance d'impulsion
#define IR_RECEIVE_PIN 2 // à la place de #include "PinDefinitionsAndMore.h"
#include <Arduino .h>
#include <IRremote .hpp>
// Signal du récepteur IR connecté à Pin2, VCC à Pin3, GND à Pin4
int IR_GND = 4;
int IR_VCC = 3;
int x = 0;
int y = 0;
int gauche = 0;
int droite = 0;
int code = 5555;
int speedL = 0;
float factor = 1.8; // Correction pour speedLevel 255/100 * 6V/VBatt
void setup() {
Serial.begin(9600) ;
Serial.println("Test moteur !") ;
Serial.println("Motorshield v2 - test moteur DC !") ;
if (!AFMS.begin()) { // create with the default frequency 1.6KHz
Serial.println("Could not find Motor Shield. Check wiring.");
while (1);
}
Serial.println("Bouclier moteur trouvé.") ;
// Juste pour savoir quel programme est en cours d'exécution sur mon Arduino
Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE)) ;
// Démarrer le récepteur
IrReceiver.begin(IR_RECEIVE_PIN) ; //, ENABLE_LED_FEEDBACK) ;
Serial.print(F("Prêt à recevoir les signaux IR des protocoles : ")) ;
printActiveIRProtocols(&Serial) ;
Serial.print(F("à la broche ")) ;
Serial.println(IR_RECEIVE_PIN) ;
// initialiser les broches numériques comme sortie pour l'alimentation électrique
pinMode(IR_GND,OUTPUT) ;
pinMode(IR_VCC,OUTPUT) ;
digitalWrite(IR_GND,LOW) ;
digitalWrite(IR_VCC,HIGH) ;
} // end setup
void loop() {
if (IrReceiver.decode()) {
// Imprime un bref résumé des données reçues
IrReceiver.printIRResultShort(&Serial) ;
if (IrReceiver.decodedIRData.protocol == UNKNOWN) {
// We have an unknown protocol here, print more info
IrReceiver.printIRResultRawFormatted(&Serial, true);
}
Serial.println() ;
/*
* !!Important ! ! Activer la réception de la valeur suivante,
* car la réception s'est arrêtée après la fin du paquet de données actuellement reçu.
*/
delay(100) ; // désensibilisation, pas de répétition rapide
IrReceiver.resume() ; // Activer la réception de la valeur suivante
/*
* Enfin, vérifier les données reçues et effectuer des actions en fonction de la commande reçue
*/
if (IrReceiver.decodedIRData.command == 0x46) {
if (code<9000) code = code + 1000;
Serial.print("Code = ");
Serial.println(code);
}
else if (IrReceiver.decodedIRData.command == 0x15) {
if (code>2000) code = code - 1000;
Serial.print("Code = ");
Serial.println(code);
}
else if (IrReceiver.decodedIRData.command == 0x43) {
if ((code-1000*int(code/1000))<900) code = code + 100;
Serial.print("Code = ");
Serial.println(code);
}
else if (IrReceiver.decodedIRData.command == 0x44) {
if (code-1000*int(code/1000) > 200) code = code - 100;
Serial.print("Code = ");
Serial.println(code);
}
else if (IrReceiver.decodedIRData.command == 0x40) {
code = 5555;
Serial.print("Code = ");
Serial.println(code);
}
else {
Serial.print("invalid code");
}
motor() ;
}
} // fin de la boucle
void motor(){
int speedLevel[9]={-100,-80,-60,-40,0,40,60,80,100};
y = int(code / 1000) ;
x = int((code - 1000*y) / 100) ;
speedL = speedLevel[y-1] ;
Serial.print("code = ") ;
Serial.print(code) ;
Serial.print(" y = ") ;
Serial.print(y) ;
Serial.print(" x = ") ;
Serial.print(x) ;
Serial.print(" speedL = ") ;
Serial.println(speedL) ;
//Correction des niveaux de vitesse pour les virages
if (x==1){
right = speedL+16;
left = speedL-16;
}
else if (x==2){
right = speedL+13;
left = speedL-13;
}
else if (x==3) {
right = speedL+10;
left = speedL-10;
}
else if (x==4) {
right = speedL+7;
left = speedL-7;
}
else if (x==6) {
right = speedL -7;
left = speedL+7;
}
else if (x==7) {
right = speedL-10;
left = speedL+10;
}
else if (x==8) {
right = speedL-13;
left = speedL+13;
}
else if (x==9) {
right = speedL-16;
left = speedL+16;
}
else {
right = speedL;
left = speedL;
}
//Entrer les niveaux de déplacement pour "left" et "right"
Serial.print("left = ") ;
Serial.print(left) ;
Serial.print(" right = ") ;
Serial.println(right) ;
if (left < 40 & left > -40) {
motor1->run(RELEASE);
}
if (right < 40 & right > -40) {
motor2->run(RELEASE);
}
if (left>=40) {
if (left>100) left=100;
motor1->run(FORWARD);
motor1->setSpeed(left * factor);
}
if (right>=40) {
if (right>100) right=100;
motor2->run(FORWARD);
motor2->setSpeed(right * factor);
}
if (left<= -40) {
if (left<-100) left=-100;
motor1->run(BACKWARD);
motor1->setSpeed(-left * factor);
}
if (right<= -40) {
if (right<-100) right=-100;
motor2->run(BACKWARD);
motor2->setSpeed(-right * factor);
}
} // end motor
Explications du code de programmation pour la voiture RC
Après avoir inclus les bibliothèques pour le Motor Shield V2 et IRremote, deux moteurs sont instanciés avec le numéro sur le bloc terminal du contrôleur et quelques variables globales sont déclarées (type de données) et initialisées (valeur initiale).
Dans la fonction setup(), l'interface série et le récepteur IR sont initialisés et les broches pour l'alimentation du récepteur IR sont configurées comme sorties avec l'état HIGH ou LOW.
Dans la fonction loop(), le signal de la télécommande IR est d'abord reçu, puis en fonction de la touche appuyée, le code pour la commande du moteur est modifié. Les touches de curseur up et down modifient le premier chiffre, left et right le deuxième chiffre du code dans la plage de valeurs 1 à 9. La touche X entraîne l'arrêt avec le code 5555. Les troisième et quatrième chiffres du code n'ont pas encore d'importance pour le moment. A la fin, la fonction autodéfinie motor() est appelée sans arguments, car nous avions défini les variables globalement, c'est-à-dire valables dans toutes les fonctions.
Dans la fonction autodéfinie motor(), que nous utiliserons également dans d'autres configurations avec d'autres télécommandes, les niveaux de conduite pour les moteurs gauche et droit sont déterminés à partir du code. Le pourcentage du niveau de vitesse est finalement converti en valeur PWM pour setSpeed à l'aide du facteur défini au début.
La voiture robot est alors prête à fonctionner. Cela fonctionne très bien tant que tu peux maintenir la connexion optique entre la télécommande et le récepteur IR. Cependant, en roulant sur la route, j'ai constaté qu'un fort rayonnement solaire entrave la réception. C'est pourquoi je passe à la télécommande radio. A bientôt.