Programmer en C sur l’esieabot
Principes de base
pigpiod
Pour programmer sur l’esieabot, nous utilisons pigpiod. pigpio est une bibliothèque qui permet de contrôler les GPIOs. Contrairement à WiringPi, elle est toujours maintenue à ce jour. pigpiod est une manière d’utiliser pigpio avec un service central permettant de lancer plusieurs programmes à la fois, sans monopoliser les GPIOs.
Cette documentation est un condensé de la documentation officielle de pigpiod.
Compiler un programme avec pigpiod
gcc fichier.c -o executable -lpigpiod_if2
Pour compiler un programme qui utilise la bibliothèque pigpiod, vous devez ajouter l’option -lpigpiod_if2 à votre commande gcc.
#include <pigpiod_if2.h>
Il ne vous reste ensuite qu’à ajouter le header de la bibliothèque de votre code.
Fonctionnement
Lorsque vous exécutez un programme utilisant pigpiod, celui-ci communique avec le service pigpiod afin de transmettre ses commandes. Vous devez donc vous assurez avant de lancer un programme que le service est bien en cours d’exécution avec la commande systemctl status pigpiod. Plusieurs programmes peuvent être exécutés en même temps. Si ils envoient des commandes contradictoires, pigpiod ne prendra en compte que la dernière commande qu’il aura reçu.
Comme sur un Arduino, il existe une multitude de broches GPIOs qui peuvent être utilisées. Plus d’information dans la documentation du Raspberry Pi. Un certain nombre de GPIO sont déjà utilisés sur votre esieabot pour les fonctions de base. Référez vous au manuel d’assemblage de votre esieabot ou à la docummentation de l’add-on board si vous en possédez une.
Comme sur un Arduino, les GPIOs peuvent utilisés comme entrée ou sortie, pour recevoir ou envoyé des signaux logiques. Sur un Raspberry Pi, les signaux logiques doivent être à 3.3V et non 5V. Dans tous les cas, il faut, au début du programme, initialiser la connexion avec le service pigpiod avec cette function :
int pi = pigpio_start(NULL, NULL);
if (pi < 0) {
printf("Can't connect to pigpiod\n");
exit(-1);
}
Par la suite, la variable pi représente l’entité de l’esieabot. Pour interrompre la connexion avec le service pigpiod, il faut utiliser la fonction suivante en fin de programme :
pigpio_stop(pi);
Note
Dans les exemples qui vont suivre, il faut inclure unistd.h pour utiliser la fonction sleep().
Structure de programme « comme sur un Arduino »
Si vous souhaitez retrouver une structure de programme similaire à celle d’un Arduino, vous pouvez utiliser ce code de base avec les fonctions setup() et loop() :
#include "esieabot.h"
#include <pigpiod_if2.h>
// Variables globales
int pi;
void setup() {
// TODO: Initialisation de la bibliothèque pigpiod
}
void loop() {
// TODO: Boucle principale du programme
}
// Fonction main, ne pas toucher
int main(int argc, char *argv[])
{
setup(); // La fonction setup est appelée une seule fois au début
while (1)
{
loop(); // La fonction loop est appelée en boucle infinie
}
return 0;
}
// Fin de la fonction main
Programmer manuellement les GPIOs
Dans cette section, vous allez apprendre à contrôler différents composants électroniques en manipulant les GPIOs de votre esieabot.
« Hello World! » des GPIOs : allumer une LED
Tout d’abord, il faut brancher la LED à un GPIO inutilisé, par exemple sur le GPIO 16. Attention à ne pas oublier de mettre une résistance en série pour limiter le courant.
Ensuite, il faut définir le GPIO 16 comme étant une sortie avec la fonction suivante :
set_mode(pi, 16, PI_OUTPUT); // Met le GPIO 16 en mode sortie
Enfin, il est possible de l’allumer et de l’éteindre avec cette boucle simple :
while(1) {
gpio_write(pi, 16, PI_HIGH); // Envoi un signal "HAUT" sur le GPIO 16 pour l'allumer
sleep(1);
gpio_write(pi, 16, PI_LOW); // Envoi un signal "BAS" sur le GPIO16 pour l'éteindre
sleep(1);
}
Si tout est bien branché, vous devriez avoir une LED qui clignote toutes les secondes !
Utiliser un bouton poussoir
Tout d’abord il faut brancher le bouton poussoir à un GPIO inutilisé, par exemple le GPIO 16.
Avertissement
Attention au sens du bouton poussoir. Vérifiez avec un multimètre dans le doute.
Note
Dans cette situation, quand le bouton poussoir sera pressé un signal de 3.3V sera reçu par le GPIO 16. Sinon, rien ne sera envoyé et le GPIO 16 sera dans un état indéfini. Pour palier à cela, nous allons utiliser le mode « PULL DOWN ». Cela signifie que nous allons dire au Raspberry Pi de « brancher » le GPIO 16 à une résistance interne de rappel reliée à la masse, pour mettre par défaut le GPIO 16 à un 0 logique et non pas dans un état électrique incertain. Ainsi, lorsque le bouton poussoir ne sera pas pressé, on obtiendra un 0 logique lorsque l’on essaiera de lire la valeur du GPIO 16, puisque le GPIO 16 sera reliée à la masse via cette resistance de rappel.
Pour initialiser le bouton poussoir, on procédera donc ainsi :
set_mode(pi, 16, PI_INPUT); // Met le GPIO 16 en mode entrée
set_pull_up_down(pi, 16, PI_PUD_DOWN); // Active la résistance de rappel sur le GPIO 16
Nous avons ensuite deux méthodes possibles pour lire l’état du bouton poussoir. Tout d’abord la méthode dite du « polling », qui va consister à lire en boucle l’état du GPIO pour savoir si il y a eu un changement ou alors la méthode dite de l’interruption, qui va consister à surveiller l’état du GPIO en arrière plan et exécuter une fonction dès que celui-ci change.
Avertissement
Quelque soit la méthode utilisée, les bouton poussoirs peuvent subir un effet « rebond », c’est à dire que le signal peut ne pas être stable quelques instants après avoir appuyé physiquement dessus. Pour contrer cela, on peut rajouter un délai supplémentaire pour ignorer de potentiels changements rapides.
Polling
while (1) {
int state = gpio_read(pi, 16); // Lecture de l'état du bouton poussoir
if (state == PI_HIGH) { // Si le GPIO a reçu un "HAUT"
printf("Le bouton poussoir sur le GPIO 16 est enclenché\n");
} else if (state == PI_LOW) { // Si le GPIO a reçu un "BAS"
printf("Le bouton poussoir sur le GPIO 16 est relaché\n");
} else { // Ne devrait pas exister
printf("Le GPIO 16 est dans un état inconnu\n");
}
sleep(1);
}
Interruption
void ma_fonction() {
printf("J'ai été appelé car l'état du bouton poussoir a changé\n");
}
callback(pi, 16, RISING_EDGE, ma_fonction); // On créé le callback, qui va appelé la fonction
// "ma_fonction" (sans paranthèse) lorsque l'état
// du GPIO 16 sera sur une courbe montante
// (par exemple quand le bouton poussoir vient d'être appuyé)
while(1) {
printf("Il ne se passe strictement rien dans cette boucle.\n");
printf("Mais comme j'ai créé un callback, si le bouton poussoir est appuyé, ma_fonction() sera appelée.\n\n");
sleep(1);
}
Utiliser un pont en H
Les commandes pour utiliser un pont en H sont les mêmes que pour contrôler une LED puisque ce dernier n’a besoin que de recevoir des signaux logiques pour allumer et éteindre un moteur. Référez vous à la documentation du pont en H et au manuel d’assemblage pour connaître les signaux à envoyer. Si vous possédez une add-on board, référez vous également à sa documentation.
Avertissement
Attention, le modèle de pont en H change à partir de l’édition 2023 de l’esieabot. Même si le principe de fonctionnement reste le même, vous devriez vous référez vous à la documentation de l’add-on board pour connaître les signaux à envoyer.
Si vous n’envoyez que des signaux logiques de base (HAUT ou BAS), votre esieabot ira uniquement à pleine vitesse dans un sens ou dans l’autre. Pour contrôler la vitesse plus précisemment, il faut envoyer des signaux PWM.
Utiliser un servomoteur
Pour utiliser un servomoteur, il faut envoyer des signaux de durée très précise afin de communiquer une consigne d’angle. Plus d’information sur la documentation du servomoteur. Avec la bibliothèque pigpiod, on peut directement transmettre une consigne d’angle à un servomoteur, sans devoir s’occuper des signaux précis. Les consignes varient de 500 à 2500. 0 permet de désarmer le servomoteur. N’importe quel GPIO peut être utilisé comme source des signaux. Pour une meilleure précision (afin d’éviter des effets de « jitter ») il est possible d’utiliser les ports de PWM matériel du Raspberry Pi.
Note
Si vous utilisez une add-on board (esieabot 2023 et plus récent), vous devez utiliser le connecteur dédié aux servomoteurs, à côté du bouton intégré.
set_mode(pi, 16, PI_OUTPUT); // Met le GPIO 16 en mode sortie
set_servo_pulsewidth(pi, 16, 500); // Positionne le servo moteur au minimum
sleep(1);
set_servo_pulsewidth(pi, 16, 1500); // Positionne le servo moteur au milieu
sleep(1);
set_servo_pulsewidth(pi, 16, 2500); // Positionne le servo moteur au maximum
sleep(1);
set_servo_pulsewidth(pi, 16, 0); // Désarme le servo moteur
Emettre un signal PWM
Un signal PWM (Pulse Width Modulation) permet de contrôler un composant électronique comme si il était contrôlé par un signal analogique qui varie en tension. Pour cela, on va emettre très rapidemenet des signaux HAUT et BAS à une largeur de bande qui varie en fonction de la tension moyenne souhaitée. Avec un Raspberry Pi, tous les GPIOs peuvent être utilisés pour emettre ce genre de signaux. Cela permet concrètement de pouvoir faire varier la luminosité d’une LED ou la vitesse d’un moteur à courant continu.
Illustration PyroElectro, http://www.pyroelectro.com/tutorials/fading_led_pwm/theory.html
Avec pigpiod, on utilise la fonction set_PWM_dutycycle() pour émettre un signal PWM. La largeur de bande demandée doit être comprise entre 0 et 255. 0 correspond à éteindre, 255 à allumer pleinement.
set_mode(pi, 16, PI_OUTPUT); // Met le GPIO 16 en mode sortie
set_PWM_dutycycle(pi, 16, 0); // Produit une tension moyenne de 0V (comme si on émettait un signal BAS)
sleep(1);
set_PWM_dutycycle(pi, 16, 128); // Produit une tension moyenne de 1.65V (la moitié de 3.3V)
sleep(1);
set_PWM_dutycycle(pi, 16, 255); // Produit une tension moyenne de 3.3V (comme si on émettait un signal HAUT)
Recevoir des signaux d’une manette
Note
Cette partie s’applique tout autant à la manette USB incluse avec votre esieabot qu’à une manette Bluetooth ou manette virtuelle.
Pour recevoir des signaux de la manette en langage C, nous allons utiliser la bibliothèque joystick native de Linux. Pour cela, il faut d’abord inclure les headers suivants en haut de votre programme :
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/joystick.h>
Ensuite, il faut ouvrir le fichier de la manette USB, qui se trouve généralement dans le dossier /dev/input/ et qui s’appelle généralement js0 ou js1 selon le nombre de manettes connectées. Pour cela, on utilise les instructions suivantes :
int fd = open("/dev/input/js0", O_RDONLY);
if (fd < 0) {
printf("Can't open joystick device\n");
exit(-1);
}
Dans cet exemple, la variable fd représente la manette USB. Ensuite, on peut utiliser la fonction read() pour lire les événements de la manette. Les événements sont stockés dans une structure de données appelée js_event. Cette structure est déjà définie dans le header linux/joystick.h. Il faut lire les événements en boucle pour pouvoir traiter les signaux de la manette en temps réel.
struct js_event event; // Structure pour stocker les événements de la manette
while (1) { // Boucle infinie
if (read(fd, &event, sizeof(event)) > 0) { // On attend un événement de la manette et on le stocke dans la variable "event"
// Traitement de l'événement de la manette...
}
}
Pour identifier quel bouton a été pressé ou quel axe a été déplacé, il faut consulter les champs de la structure js_event :
type : indique le type d’événement
Bouton pressé/relâché (
JS_EVENT_BUTTON)Mouvement d’axe (
JS_EVENT_AXIS)
number : indique le numéro du bouton ou de l’axe qui a été actionné
value : indique la valeur de l’événement
1: bouton pressé0: bouton relâchéEntre
-32768et32767: valeur d’un axe
Ainsi, si on veut afficher un message à chaque fois que le bouton numéro 0 est pressé, on peut faire comme ceci :
struct js_event event; // Structure pour stocker les événements de la manette
while (1) { // Boucle infinie
if (read(fd, &event, sizeof(event)) > 0) { // On attend un événement de la manette et on le stocke dans la variable "event"
if (event.type == JS_EVENT_BUTTON && event.number == 0 && event.value == 1) { // Si le bouton numéro 0 a été pressé
printf("Le bouton numéro 0 a été pressé\n");
}
}
}
Programmer avec la bibliothèque libesieabot
Pour faciliter l’utilisation de certaines tâches complexes (comme par exemple la régulation de la vitesse des moteurs via leurs capteurs de vitesse), vous pouvez utiliser la bibliothèque libesieabot qui fournit plusieurs fonctions avancées.
Installation
Pour installer la bibliothèque, exécutez la commande suivante :
sudo apt install libesieabot
Pour compiler un programme qui utilise la bibliothèque libesieabot, vous devez ajouter l’option -lesieabot -lpigpiod_if2 -lm à votre commande gcc habituelle.
Initialisation
Tout d’abord, vous devnez initialiser comme précédemment la connexion avec le service pigpiod :
int pi = pigpio_start(NULL, NULL);
if (pi < 0) {
printf("Can't connect to pigpiod\n");
exit(-1);
}
Contrôleur moteur
Le contrôleur moteur intégré permet de contrôler finement un moteur avec un asservissement de vitesse proportionnel et intégral. Pour l’utiliser, il faut d’abord connaître les GPIOs utilisés pour la marche avant, la marche arrière et le capteur de vitesse, voir la docummentation de l’add-on board si vous en possédez une. Ensuite, il faut initialiser le contrôleur moteur avec ces GPIOs.
Avertissement
Pour que le contrôleur moteur fonctionne, vous devez avoir des capteurs de vitesse fonctionnels. Pour tester leur fonctionnement, vous pouvez utiliser la commande suivante : /esieabot/available/official/add-on-board-test.py
Les vitesses et distances sont exprimées en fentes. Vos moteurs sont équipés de roues codeuses dotées de 20 fentes. On compte ici le nombre de changement de fente (ouvert/fermé). Il y a donc 40 changements de fentes par tour de roue. Si vous souhaitez faire avancer votre robot d’une distance précise, vous devez donc compter le nombre de fentes parcourues par les roues codeuses.
#define GPIO_FORWARD 23 // GPIO pour la commande de marche avant
#define GPIO_BACKWARD 25 // GPIO pour la commande de marche arrière
#define GPIO_SENSOR 24 // GPIO pour le capteur de vitesse
MotorController motor; // Création de lobjet contrôleur moteur
MotorController_init(&motor, pi, GPIO_FORWARD, GPIO_BACKWARD, GPIO_SENSOR); // Initialisation du contrôleur moteur
Ensuite vous devez définir les paramètres suivants permettant de régler le comportement du contrôleur moteur :
MotorController_setStartPower(&motor, 110);
float kp = 4.0f; // Valeurs conseillées pour le contrôleur PI
float ki = 2.0f;
MotorController_setController(&motor, kp, ki);
Le contrôleur moteur est maintenant prêt à être utilisé. Pour le faire avancer, il faut utiliser la fonction suivante :
float speed = 60.f; // Vitesse cible en fentes par seconde
MotorController_setTargetSpeed(&motor, speed);
Pour appliquer la vitesse cible, il faut appeler la fonction suivante dans une boucle infinie. Si vous ne le faites pas, le contrôleur moteur ne pourra pas ajuster la vitesse du moteur en fonction de la vitesse mesurée par le capteur.
while (1) { // Boucle infinie du programme
// ... autres traitements éventuels ...
MotorController_update(&motor);
}
Si vous souhaitez faire reculer le moteur, il suffit d’appeler la fonction suivante :
MotorController_setBackward(&motor, true); // Pour faire reculer le moteur
// MotorController_setBackward(motor, false); // Pour faire avancer le moteur
Si vous souhaitez arrêter le moteur, il suffit d’appeler la fonction suivante :
MotorController_stop(&motor);
Si vous souhaitez faire avancer votre robot sur une distance précise, vous pouvez utiliser la fonction suivante qui nécessite l’Initialisation préalable des contrôleurs de chaque moteur :
drive(&motor_left, &motor_right, slot_count, speed);
Avertissement
Attention, cette fonction est dite bloquante, c’est à dire que le programme ne continuera pas tant que le robot n’aura pas parcouru la distance demandée.
Note
Si vous souhaitez faire reculer votre robot, vous devez appelez la fonction MotorController_setBackward() vue précédemment.
Si vous souhaitez faire tourner votre robot sur place d’un angle précis, vous pouvez utiliser la fonction suivante :
turn(&motor_left, &motor_right, slot_count, speed);
Avertissement
Attention, cette fonction est dite bloquante, c’est à dire que le programme ne continuera pas tant que le robot n’aura pas parcouru la distance demandée.
Note
Si vous souhaitez faire faire tourner votre robot vers la gauche, vous devez définir un nombre de fentes positif. Si vous souhaitez faire faire tourner votre robot vers la droite, vous devez définir un nombre de fentes négatif.