Dans un premier temps, l'ensemble fonctionne sur un Raspberry Pi 4B / 8Gb avec comme système d'exploitation 64-bit Raspberry Pi OS Debian GNU/linux 11(bullseye) et comme logiciel de programmation Python 3. Dans un second temps, le système d'exploitation Raspberry Pi OS Debian GNU/linux 12 (bookworm) permet d'augmenter les fonctionalités (commande InfraRouge).
L'ajout d'un bras manipulateur fera intervenir de façon complètement indépendante, une carte à microcontrôleur de type Raspberry Pi Pico et MicroPython comme logiciel de programmation. Comme alternative, une carte à microcontrôleur de type Raspberry Pi Pico2W est aussi envisagée pour augmenter les fonctionalités à l'aide du bluetooth et du WiFi.
Comme base de la plateforme, on a choisi un kit de châssis de voiture comprenant 4 roues, 4 moteurs CC avec disques à fentes qui permettront plus tard de déterminer les distances parcourues et les vitesses.
![]() |
![]() ![]() |
Liens
Comme le véhicule que l'on veut dévelpper doit être autonome,
Dans un premier temps, on peut relier le Raspberry Pi 4B embarqué à l'écran
extérieur par le câble HDMI et à l'alimentation extérieure par le câble UBSC.
En supportant le chassis à ses extrémités, les roues pourront tourner sans que le chassis ne se déplace.
Liens internes principaux
Les spécifications des moteurs sont données dans la référence
https://www.techniscience.com/nl/cmsdata/artikelen/files/600_106597/106597%20com-motor01-specifications.pdf.
.
La partie métallique contient le moteur électrique proprement dit. L'axe est longitudinal. Le moteur est solidaire du carter jaune grâce à une sangle en plastique transparent. La partie jaune cache le réducteur. La partie blanche est l'axe de rotation (transversal) disponible des deux côtés du carter jaune. Les méplats permettent de fixer les roues du véhicule et les disques à fentes sans ambiguïté.
Deux cosses métalliques sont disponibles du côté invisible de la partie métallique sur le dessin. Pour éviter qu'elle ne fonde, il faut faire ATTENTION de bien retirer la sangle en plastique transparent avant de souder les fils rouge et noir de l'alimentation électrique du moteur.
Le tableau suivant donne quelques spécifications.
|
![]() |
Les moteurs se montent sur une des 2 plaques perforées en plastique suivant la référence
https://www.gotronic.fr/pj2-35326-robot03-montage-1601.pdf , dont la dernière image est reprise ci-contre. Ensuite, contrairement à la proposition de la notice, le chassis est retourné. Les moteurs sont ainsi tournés vers le bas. Le côté libre de la plaque est donc tourné vers le haut. Ceci a 2 avantages :
|
![]() |
Notons que les fils d'alimentation rouges et noirs ont été soudés à chacun des 4 moteurs de façon cohérente pour la commande des moteurs.
Liens internes principaux
Pour l'alimentation et la commande, c'est le module "L298N Dual H-Bridge Motor Driver" qui a été utilisé. Il est fixé à l'avant de la plaque perforée en plastique supportant les moteurs. Le radiateur est tourné vers l'extérieur.
Rappelons que ce pilote de moteur bidirectionnel double est basé sur le circuit intégré de pilote de moteur
à double pont en H L298.
Le circuit permet de contrôler facilement et indépendamment deux moteurs jusqu'à 2A chacun dans les deux sens.
Cette carte est équipée d'indicateurs LED de puissance, d'un régulateur + 5V intégré et de diodes de
protection. |
![]() |
L'image ci-contre montre les connexions du module. |
![]() |
La seconde plaque de base a été montée au dessus de la première en utilisant les entretoises du kit.
|
![]() |
Une troisième plaque plastique et des entretoises ont été découpées. Elle a été montée au dessus des deux autres. Le Raspberry Pi 4B a été simplement posé sur cette nouvelle plaque.
Les 3 programmes python3 car1.py, car2.py et car3.py, dont la tâche est identique, permettent de vérifier le sytème.
Les connexions de base ont permis de tester les moteurs à pleine puissance. Ici, la commande de la puissance par (PWM = Pulse Width Modulation) est introduite.
![]() |
![]() |
Le programme python3 car4.py permet de tester le système. Comme car3.py, il met en oeuvre la sous-classe gpiozero.Motor mais aussi la sous-classe gpiozero.PWMOutputDevice.
Il est à noter que la commande des moteurs nécessite finalement 6 broches GPIO sur le Raspberry Pi 4B.
Liens internes principaux
Le programme python3 car5.py permet ce
pilotage à distance.
La figure ci-contre montre le tableau de commande généré par le programme python sur le smartphone.
|
![]() |
![]() | A |
L'écran embarqué a été conçu pour le Raspberry Pi 4B. Il est semblable à celui d'un smartphone. Il est donc tactile et on peut lancer et contrôler les processus en touchant l'écran. Cependant, ce n'est pas bien pratique et on utilisara de préférence une souris et un clavier sans fil. Contenu de la livraison
Fonctionnalités
Commentaires
|
![]() | B | |
![]() | C | |
![]() | D | |
![]() | E | |
https://yadom.fr/ecran-lcd-tactile-5-dsi-raspberry-pi-800x480-ips.html |
Liens internes principaux
L'estimation de la distance parcourue par le véhicule ainsi que sa vitesse se fonde sur la mise en oeuvre de capteurs optiques à rainure associés à des disques à fentes. Cette section présente de façon progressive quelques algorithmes de calcul de la vitesse et des distances parcourues par le véhicule "Robot Car Kit 4WD" développé dans cette section. |
![]() ![]() |
Le programme distance1.py a été développé à partir de l’exemple donné par la vidéo Youtube Tracking Raspberry Pi Robot's Distance with Encoders. Un disque à fentes solidaire d’une roue du véhicule contient 20 fentes entourées par des rayons de l'épaisseur approximative d'une fente. On se propose de calculer
IMPORTANT : Seules les vitesses absolues et les distances cumulées sont calculables. Le sens du mouvement ne peut pas être déterminé avec cette configuration matérielle.
Il s’agit donc du calcul de la distance parcourue par le véhicule et de sa vitesse sur la base des informations de rotation fournies par les 2 capteurs, l’un à gauche, l’autre à droite.
Contrairement au programme initial sur Youtube, on ne donne pas ici d’ordre aux moteurs. On considère le capteur permettant de calculer la distance comme indépendant et ne tient donc pas compte des ordres donnés aux moteurs.
Le programme montre qu’il n’y a pas de détection du sens de rotation et donc par conséquent la distance calculée ne pourra qu’augmenter et la vitesse sera toujours positive. C’est un problème qu’il faudra examiner mais d’ors et déjà il semble qu’un capteur complémentaire soit nécessaire.
Pour vérifier que le programme distance1.py fonctionne sans erreurs, il suffit de mettre le véhicule sur cales et de tourner les roues avec capteurs optiques à la main.
Les résultats du programme distance1.py ne sont pas observables confortablement. C’est pourquoi, une fenêtre graphique créée à partir de la classe python3 tkinter remplace les simples impressions de distance1.py.
Pour des raisons de facilité, la sortie graphique est développée séparément en mettant au point le programme test_tk.py qui prend en compte l’affichage des distances et des vitesses en temps réel. L’affichage graphique est donc fondé sur la classe python3 tkinter et est aussi simple que possible.
Voici le résultat graphique, les nombres ne signifient rien.
![]() |
La procédure développée ci-dessus est simplement appliquée au programme distance1.py pour devenir distance2.py.
Il s’agit maintenant d’afficher la distance parcourue et vitesse instantanée pour le véhicule en mouvement.
D’une part, nous disposons du programme car5.py où le pilotage intégral par smartphone
permet d’actionner les moteurs et donc de faire tourner les roues,
ce qui permet au véhicule d’avancer, de reculer, de tourner à gauche ou à droite, d’accélérer, de décélérer.
D’autre part, les programmes élaborant les distances et les vitesses sont indépendant de
car5.py.
Il faudrait donc exécuter simultanément car5.py et par exemple
distance1.py.
Voici quelques façons d'exécuter simultanément plusieurs scripts python.
Par soucis de faciliter la présentation en organisant une progression, on met en oeuvre les programmes distance1.py et car5.py qui impriment des informations à l’écran. On lance un terminal à partir du répertoire commun aux 2 programmes puis on entre l'instruction d'exécution simultanée des programmes et car5.py :
python3 distance1.py & python3 car5.py
Ceci fonctionne bien : les écritures apparaissent à l’écran au fur et à mesure de leur génération.
Par contre, à l’arrêt forcé par Crtl + c seul le dernier programme s’arrête (ici car5.py). Il suffit de tourner les roues à la main pour s’apercevoir que distance1.py ne s’est pas arrêté. Pour arrêter le premier programme, il faut ouvrir un nouveau terminal, y demander les processus actifs, identifier le n° du processus distance1.py et tuer le processus :
ps x | Imprime les processus actifs et on repère l’identificateur du processus à tuer par exemple 1550 |
kill 1550 | Tue le processus |
Si maintenant, on met en oeuvre les programmes distance2.py et car5.py où distance2.py transmet ses informations à une fenêtre graphique gérée par tkinter, le départ est semblable au précédent :
python3 distance2.py & python3 car5.py
Les écritures issues de car5.py apparaissent à l’écran au fur et à mesure
de leur génération et celles de distance2.py apparaissent dans la
fenêtre graphique.
Ici aussi, à l’arrêt forcé par Crtl + c seul le dernier programme s’arrête
(car5.py).
Par contre, il suffit de fermer la fenêtre graphique par un clic sur le X
pour arrêter le programme distance2.py.
Les commandes Linux sont conservées dans un programme python appelé ici scripts.py :
# Programme scripts.py
from subprocess import run
run("python3 distance1.py & python3 car5.py", shell=True)
Dans un terminal ouvert à partir du répertoire où se trouvent les programmes, on exécute le programme scripts.py :
python3 scripts.py
Ceci fonctionne de façon identique à la méthode 1 avec les mêmes difficultés à l’arrêt et la même solution.
L’intérêt par rapport à la méthode précédente est de pouvoir préparer les programmes qui doivent être exécutés ensemble et ainsi de ne pas devoir entrer à chaque fois une instruction complexe.
Dans le premier terminal, on lance distance1.py | python3 distance1.py |
Dans le second terminal, on lance car5.py | python3 car5.py |
Pour arrêter les programmes, il suffit de cliquer sur le terminal correspondant et appuyer sur Crtl + c.
Cette méthode fonctionne bien mais devient laborieuse lorsque le nombre de programmes à exécuter simultanément augmente.
Il y a moyen d'automatiser le processus d'ouverture de terminaux et de lancement des programmes
python.
L’utilitaire de terminal Linux
terminator est à
cet égard très pratique.
Note : Il est écrit en python.
Sous Raspberry Pi OS, on installe terminator en choisissant dans le menu principal l’onglet préférences et Add / Remove Software. On recherche ensuite terminator qui s’installe dans le menu principal sous l’onglet Outils système.
Les principales fonctionnalités de terminator sont les suivantes :
Pour préparer l’exécution simultanée du programme de calcul des vitesses et distances et du programme de pilotage du véhicule, l’utilitaire terminator a été configuré en une fenêtre divisée en 2 terminaux.
Voici la procédure utilisée :
Cette configuration a été appelée test et vient s’ajouter à la configuration par défaut qui est composée d’une simple fenêtre contenant un seul terminal.
![]() |
À partir de cette configuration par défaut, en cliquant droit sur la fenêtre, on peut choisir Préférences, puis Dispositions, cliquer sur la disposition test puis successivement sur terminal3 et terminal2. On obtient les images suivantes :
![]() ![]() |
Le cadre "Type Nom" a été généré automatiquement par terminator sur la base du découpage de la fenêtre et montre la structure interne des éléments générés.
En choisissant terminal3 dans ce cadre, on découvre la partie droite qui peut être en partie remplie par l’utilisateur.
En choisissant terminal2, la commande personnalisée à droite est ls && sleep 30. Elle est simplement là pour donner quelque chose à faire à ce terminal. Si la commande personnalisée était simplement ls, le terminal se serait fermé dès l’exécution de la commande, c’est-à-dire très vite. L’ajout de sleep 30 signifie qu’il faut attendre 30 secondes avant la fermeture du terminal dédié. La fenêtre continue à être ouverte et l’autre processus à être exécuté dans l’autre terminal q ui prendrait maintenant toute la fenêtre puisque ce serait le seul terminal actif.
Expérimentation numérique | |
ou | |
L’utilitaire terminator est simplement ouvert (Menu principal en haut à gauche, Outils système puis clic sur terminator) et la fenêtre par défaut spécifique de terminator apparaît à l’écran. | On peut aussi lancer un terminal gnome traditionnel. L'instruction terminator -s fait apparaître un menu "Terminator Layout". |
Un clic gauche dans cette fenêtre puis ALT + L fait apparaître un menu "Terminator Layout". | |
En sélectionnant test puis clic en bas à droite sur Launch, la configuration donnée par la capture d’écran en une fenêtre divisée en 2 terminaux apparait et chaque terminal exécute ce qui lui a été assigné et indique le déroulement du processus. | |
![]() Le terminal du dessus affiche le contenu du répertoire indiqué dans la configuration puis attend 30 secondes avant de se refermer. Le terminal du dessous ouvre la fenêtre graphique où défilent le résultat des calculs de vitesse et de distance. ![]() Lorsque le terminal du dessus se referme, le terminal du dessous prend toute la place de la fenêtre et le processus continue à être exécuté jusqu’à ce qu’on clique sur la croix de la fenêtre graphique. Le seul terminal restant se ferme alors ainsi que la fenêtre hôte. |
On revient maintenant au véhicule autonome où le système de détermination des vitesses et des distances est
géré par le programme distance2.py et les moteurs du véhicules sont
gérés par le programme car5.py lui-même relié par bluetooth au smartphone.
L’utilitaire
terminator
est ici mis en œuvre avec le couple de programmes
python3 distance2.py et
car5.py.
On peut utiliser la même découpage d'écran que pour la disposition test et
remplacer les propriétés du terminal2 et du terminal3.
Cette disposition est appelée car1 :
![]() ![]() |
Les résultats correspondent à l’attente.
Comme le montre la figure ci-contre (par exemple en "Marche avant") prise par mise en œuvre d'un oscilloscope TWF102, les signaux des deux capteurs vont avoir la même forme mais décalée. C’est ce déphasage qui va permettre de déterminer le sens de rotation des roues. En effet, les figures à droite montrent le déphasage en marche avant et en marche arrière. La pulsation du second capteur a un grand déphasage en marche avant et un petit déphasage en marche arrière. C’est justement ce que l’on cherche. Bien sûr, il faut caler correctement le second capteur par rapport au premier et le cas échéant ajuster le support du second capteur. Remarques
|
![]() Marche avant ![]() Marche arrière |
La figure ci-après permet d’analyser les différentes situations et possibilités. Ces analyses servent à orienter la programmation.
![]() |
Origine du temps et des distances
Une situation bien précise doit permettre de définir sans ambiguïté l’origine du temps et des distances. C’est le premier front descendant du capteur principal qui a été choisi. C’est-à-dire que le capteur principal vient juste de passer du niveau HAUT au niveau BAS. À ce moment, le capteur de signe est forcément au niveau HAUT, d’après les calages et les mesures.
En langage python, la méthode wait_for_edge() de RPi.GPIO aurait dû constituer un chemin rapide et élégant à l’objectif. Mais cela ne fonctionne pas car vraisemblablement à cause du trigger de Schmitt du capteur optique, le front est trop raide pour cette méthode, mais une autre voie a été trouvée en observant directement les niveaux HAUT et les niveaux BAS du signal du capteur principal.
Le programme test1.py fondé lui-aussi sur RPi.GPIO permet de fixer l’algorithme.
Détermination des vitesses et des distances avec leur signe Par convention, une vitesse positive signifie un mouvement en marche avant, une vitesse négative signifie une marche arrière. Ici aussi par convention, les distances en marche arrière se déduisent des distances en marche avant mais on aurait pu choisir de cumuler les distances car c’est la distance totale qui contribue à l’usure du véhicule. Pour le reste, il faut suivre le programme distance3g.py en s’aidant de la figure ci-dessus. Le raisonnement effectué pour le côté gauche peut être transposé au côté droit. Le programme distance3d.py en résulte. Bien entendu, comme l'illustre la figure ci-contre, un autre capteur optique à rainure est nécessaire sur le disque à fentes de la roue arrière droite. Le dispositif de gauche fonctionne bien. Il n'en est pas de même de celui de droite. Le déphasage en marche avant est équivalent à celui en marche arrière. Il va falloir ajuster le support du capteur de signe. Nous y reviendrons plus loin. |
![]() |
Voici la situation : nous disposons des programmes python
D'un autre côté, que ce soit dans distance3d.py ou dans distance3g.py, l'afficheur graphique tkinter est géré en même temps que les calculs de vitesse et de distance.
Faisons un petit calcul.
Supposons que le véhicule avance avec une vitesse maximale de 1m/s, la circonférence de roue étant approximativement de 0.2m, ça signifie 5 tours/s pour la roue. Comme il y a 20 fentes sur le disque à fentes, il faut compter sur le signal de l'ordre de 100 impulsions/s. Un cycle de capteur dure donc approximativement 0.010s. Or la gestion par tkinter exige une pause de 1ms entre itérations. cela signifie donc 10 échantillons par cycle. La précision n'est donc pas très grande sur l'estimation des vitesses et des distances.
En conclusion, il faudrait que l'échantillonnage pour le calcul des vitesses et distances soit indépendant de l'affichage.
L'idée est de faire usage d'une mémoire partagée par plusieurs programmes en principe indépendants.
Un programme calculerait la vitesse et la distance à partir des signaux associés à la roue droite.
Il écrirait ses résultats dans la mémoire partagée.
Parallèlement, un autre programme calculerait la vitesse et la distance à partir des signaux associés
à la roue gauche.
Il écrirait aussi ses résultats dans la mémoire partagée.
Un troisième programme exécuté parallèlement, lirait les vitesses et distances présentes dans la
mémoire partagée et les publierait, à son propre rythme, dans la fenêtre graphique produite par
tkinter.
Pour mémoire, un quatrième programme, car5.py, serait aussi
exécuté parallèlement mais sans besoin d'avoir accès à la mémoire partagée.
La technique de "mémoire partagée" peut être mise en oeuvre avec python.
Test de la technique
Pour tester cette technique, 2 programmes python ont été mis au point :
Les programmes sont indépendants et échangent des informations de façon asynchrone par
l'intermédiaire de la mémoire partagée.
Pour les lancer, le plus simple est d'ouvrir 2 terminaux à partir du répertoire hébergeant les
deux programmes.
Dans le premier terminal, on lance MP_tk.py
qui crée la mémoire partagée et ouvre l'afficheur tkinter.
Dans le second terminal, on lance MP_DV.py qui se lie
à la mémoire partagée maintenant existante et produit les valeurs de vitesses et distances
Les valeurs produites apparaissent alors dans l'afficheur tkinter.
Pour terminer,
Remarque :
Chaque programme travaille à son rythme. Le calcul des valeurs de vitesse et de distance peut s'effectuer plus rapidement que l'affichage qui reste cependant correct et ne ralentit pas le rythme de calcul des distances et vitesses.
Application au véhicule autonome
D'abord, les programmes distance3d.py et distance3g.py ont été délestés de la partie concernant la représentation graphique des résultats. Ces parties ont été remplacées par la déclaration de mémoire virtuelle existante et l'utilisation de cette mémoire pour conserver les valeurs des vitesses et distances calculées. il en résulte les programmes distance3dMP.py et distance3gMP.py.
Avant de se servir de terminator, 4 terminaux ont été ouvert à partir du répertoire contenant les programmes. Les 4 terminaux servent aux programmes indépendants suivants :
Les écritures permettent de voir que la fréquence de lecture pour l'acquisition est de l'ordre
de 45kHz (0.000022s).
Un résultat de calcul est fourni toutes les 0.03s soit 1500 points pour échantillonner un pas
de disque à fente qui en compte 20 !
Une centaine se points devraient suffire.
Évidemment, ce résultat dépend exclusivement de la vitesse imposée au véhicule.
Cette vitesse est ici maximale à 5V sur les moteurs.
En plus le véhicule est ici sur cales.
Il y a donc encore une bonne marge de manoeuvre si nous voulons ajouter d'autres programmes.
Pour simplifier la programmation, distance3gMP.py et distance3dMP.py ont été repris pour donner distance3gMP1.py et distance3dMP1.py. La logique est légèrement différente. Elle peut être comprise par l'intermédiaire de la figure ci-contre et des programmes.
|
![]() |
Tous les programmes d'acquisition pour le calcul de distance et de vitesse ont utilisé la classe python RPi.GPIO. Or la classe python gpiozero plus récente et plus dans l'esprit python peut aussi présenter un intérêt.
Les programmes distance3gMP1.py et distance3dMP1.py ont ainsi été "traduits" pour donner distance3dMP2.py et distance3gMP2.py.
NB : Pour faciliter l'exécution, on peut ouvrir 4 fenêtres pour exécuter simultanément 4 programmes car5.py, MP_tk.py, distance3dMP2.py et distance3gMP2.py. Mais il est plus simple de définir une disposition de "terminator". Cette disposition a été appelée CarV. Ceci appelle cependant les remarques suivantes :
|
Pour restreindre le nombre de programmes à exécuter simultanément, distance3dMP1.py et distance3gMP1.py peuvent être facilement agrégés pour donner distance3MP1.py.
Il en est de même pour
distance3dMP2.py et
distance3gMP2.py qui agrégés donnent
distance3MP2.py.
En ce qui concerne ces programmes qui, rappelons le, utilise la classe
gpiozero, une difficulté est apparue sur la broche GPIO04 qui a
dû être remplacée par la broche GPIO20.
Liens internes principaux
L'idée est d'installer sur le véhicule autonome, un radar de recul avec avertisseur sonore. Le son doit être d'autant plus fort et aigu que le véhicule s'approche de l'obstacle. À cet effet deux composants sont utilisés :
Un capteur de distance à ultrasons HC-SR04
Les composants, y compris le capteur ultrasonique, sont tous soudés sur une plaquette perforée qui sert aussi de borne 3.3V et de borne Masse pour d'autres composants à proximité (Moteurs, ...). Cela évite d'alimenter beaucoup de composants à partir de la plaque de prototypage.
|
![]() |
Un buzzer actif TMB12A05 Comme le montre la photo tout à droite, les composants sont fixés sur la plaque de prototypage montée à l'étage supérieur du véhicule autonome. |
![]() |
![]() |
Le capteur de distance est pris en charge par la classe gpiozero.DistanceSensor de python3.
Vue éclatée du véhicule autonome |
![]() |
Autres vues du véhicule autonome | |
![]() |
![]() |