Les ordinateurs embarqués sous le système d’exploitation Linux sont massivement présents dans les technologies modernes (transports, multimédia, téléphonie mobile, appareils photos …).
L’ordinateur Raspberry PI constitue un support d’apprentissage performant, très bon marché et disposant d’une forte communauté sur le net. Il possède des entrées/sorties puissantes permettant une connexion avec le monde physique par l’intermédiaire de capteurs et d’actionneurs.
L’objectif de ce TP est de réaliser une rapide prise en main d’un système embarqué au travers d’un ordinateur Raspberry PI qui sera largement utilisée en projet et d’effectuer un tour d’horizon des pratiques de mise en œuvre et de développement.
Présentation de l’activité
Objectifs
Se remémorer :
-
les bases de la programmation objet en C++ :
-
Déclaration/Implémentation d’une classe
-
Instanciation d’une classe (→ constructeur)
-
Associations entre classes
-
-
les bases du formalisme UML
-
les principes de la cross-compilation
Apprendre :
-
les principes de la cross-compilation
-
la manipulation des broches GPIO du Raspberry pi
Mettre en oeuvre :
-
des dispositifs d’entrée/sortie basic (LED/SWITCH)
-
un capteur de distance à ultrason
Durée
-
Entre 8h et 10h
Ressources
Matériel(s) :
-
1 PC avec système d’exploitation Linux
-
1 Raspberry Pi avec OS Débian
-
1 platine d’essai + fil de câblages
-
1 LED + résistance
-
1 bouton poussoir + résistance
-
1 capteur ultrasonic Ping Parallax
Logiciel(s) :
-
cross-compilateur
arm-linux-gnueabihf-g++
Documentation :
-
Cours de Mr Antoine sur la programmation objet de 1ière année (disponible sur Chamilo).
-
Sites de référence sur C/C++ :
-
cplusplus.com
-
cppreference.com
-
…
-
Pré-requis
-
Notions sur le formalisme UML (représentation d’une classe et des relations)
-
Maîtrise du langage C (séquences, tests, boucles, variables, fonctions, paramètres, compilation multi-fichiers)
-
Maîtrise de la gestion des fichiers sur un système déporté (ssh, scp, Dolphin, …)
-
Maîtrise de la cross-compilation pour processeurs à architecture ARM
Compte rendu
Il sera constitué :
-
des réponses aux questions en veillant à soigner la rédaction
-
des programmes source commentés
1. Présentation
Le Raspberry Pi offre quelques possibilités d’entrées-sorties directes en utilisant les broches GPIO présentes sur son connecteur J8. Elles ne sont pas très nombreuses (une dizaine) mais cela peut suffire pour des petits projets interactifs nécessitant d’interroger des capteurs tout-ou-rien ou de valider des actionneurs.
Nous pouvons utiliser ces GPIO de différentes façons, depuis l’espace utilisateur ou depuis le noyau, comme de simples fichiers ou au travers de bibliothèques spécialisées.
2 L’interface sysfs
Sysfs
est un système de fichiers virtuel introduit par le noyau Linux 2.6. Sysfs
permet d’exporter depuis l’espace noyau vers l’espace utilisateur des informations sur les périphériques du système et leurs pilotes, et est également utilisé pour configurer certaines fonctionnalités du noyau.
Pour chaque objet ajouté dans l’arbre des modèles de pilote (pilotes, périphériques, classes de périphériques), un répertoire est créé dans sysfs
.
-
La relation parent/enfant est représentée sous la forme de sous-répertoires dans
/sys/devices/
(représentant la couche physique). -
Le sous-répertoire
/sys/bus/
est peuplé de liens symboliques, représentant la manière dont chaque périphérique appartient aux différents bus. -
/sys/class/
montre les périphériques regroupés en classes, comme les périphériques réseau par exemple.
Pour les périphériques et leurs pilotes, des attributs peuvent être créés. Ce sont de simples fichiers, la seule contrainte est qu’ils ne peuvent contenir chacun qu’une seule valeur et/ou n’autoriser le renseignement que d’une valeur. Ces fichiers sont placés dans le sous-répertoire du pilote correspondant au périphérique.
2.1 Ecrire sur une broche GPIO
Exercice 1
-
Accédez depuis le shell au dossier contenant les GPIO
$ cd /sys/class/gpio
$ ls
export gpiochip0 unexport
-
Accédez au GPIO 24 :
$ echo 24 > export
$ ls
export gpio24 gpiochip0 unexport
$ cd gpio24/
$ ls
active_low direction edge subsystem uevent value
-
Sur quelle broche du connecteur J8 se trouve le GPIO24 ?
-
Connectez une led sur cette broche :
-
Configurez le GPIO24 en sortie pour écrire :
$ echo out > direction
$ echo 1 > value
$ echo 0 > value
-
Codez un programme en C++ pour faire clignoter la led toutes les 500ms. Vous pouvez vous inspirer du programme suivant :
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream GPIO17("/sys/class/gpio/gpio17/value",ios::out);
if(GPIO17)
{
GPIO17 << "1";
GPIO17.close();
}
else cerr << "Impossible d'ouvrir /sys/class/gpio/gpio17/value" << endl;
}
-
Cross-compilez, transférez l’exécutable obtenu et exécutez le.
Le cross-compilateur doit être péalablement installé sur votre système. Reportez vous au précédent TP : Prise en main de la carte Raspberry Pi. |
2.2 Lire une broche GPIO
Exercice 2
-
Configurez le GPIO 21 en entrée et connectez sur sa broche un bouton poussoir comme sur la figure ci-dessous :
-
D’après le schéma électrique ci-dessous, quel doit être l’état du bouton (pressé ou relaché) pour obtenir respectivement un état haut (1), puis un état bas (0) sur le GPIO 21 ?
-
Lisez l’état du GPIO 21 lorsque le bouton est pressé puis relaché :
$ cd /sys/class/gpio/gpio21
$ cat value
-
Codez un programme en C++ pour faire clignoter la led toutes les 500ms lorsque le bouton poussoir est pressé. Vous pouvez vous inspirer du programme ci-dessous :
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
ifstream gpio23("/sys/class/gpio/gpio23/value", ios::in);
if(gpio23)
{
string value;
gpio23 >> value ;
gpio23.close();
cout << value << endl;
}
else cerr << "Impossible d'ouvrir /sys/class/gpio/gpio23/value!" << endl;
return 0;
}
-
Cross-compilez, transférez l’exécutable obtenu et exécutez le.
Le cross-compilateur doit être péalablement installé sur votre système. Reportez vous au précédent TP : Prise en main de la carte Raspberry Pi. |
2.3 Ecriture d’une classe
Pour faciliter l’utilisation des GPIO dans un programme C++, nous allons écrire une classe qui devra répondre aux spécifications du diagramme de classe ci-dessous :
Exercice 3
-
Codez la classe
CgpioSysfs
répondant à la spécification du diagramme de classe ci-dessus. -
Codez un programme
mainGpioSyfs.cpp
dans lequel vous utiliserez deux objets de la classeCgpioSysfs
pour obtenir le même résultat que l’exercice précédent :-
LED
sur le GPIO 24 -
INTER
sur le GPIO 21
-
3. La bibliothèque BCM2835
Cette bibliothèque C pour Raspberry Pi fournit un accès aux GPIO et à d’autres fonctions I/O sur la puce Broadcom BCM 2835, pour permettre l’accès aux broches du connecteur J8 de la carte afin de pouvoir contrôler et interfacer différents périphériques externes.
Elle fournit des fonctions pour lire les entrées numériques et écrire sur les sorties numériques, pour utiliser les bus SPI et I2C, et pour accéder aux timers du système. La détection d’événement sur une broche est prise en charge par scrutation (les interruptions ne sont pas prises en charge).
Elle est compatible C ++ et s’installe en tant que fichier d’en-tête et bibliothèque partagée ou non sur n’importe quelle distribution Linux.
Elle ne sert à rien sauf sur Raspberry Pi ou une autre carte équipée d’un SOC BCM 2835 |
3.1 Installation
Nous savons maintenant que la meilleur façon de travailler est cross-compiler nos programmes sur une "station puissante" et de transférer l’exécutable sur le Raspberry Pi. Nous allons donc cross-compiler la bibliothèque pour pouvoir générer des exécutables qui l’utilisent à destination du Raspberry pi.
Exercice 4
-
Téléchargez l’archive de la dernière version de la bibliothèque bcm2835 ici : http://www.airspayce.com/mikem/bcm2835/index.html
-
Décompactez l’archive dans votre dossier Documents de votre hôte linux openSuse.
$ tar zxvf bcm2835-1.52.tar.gz
-
Dans le dossier décompacté, procédez à la cross-compilation :
Le cross-compilateur doit être péalablement installé sur votre système. Reportez vous au précédent TP : Prise en main de la carte Raspberry Pi. |
$ cd bcm2835-1.52
$ ./configure -host=arm CC=arm-linux-gnueabihf-gcc ar=arm-linux-gnueabihf-ar -includedir=~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/arm-linux-gnueabihf/include -libdir=~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/arm-linux-gnueabihf/lib
$ make
$ sudo make install
-
Donnez les signification des options de configuration :
-
-host
-
CC
-
ar
-
-includedir
-
-libdir
-
-
Créez la librairie partagée à partir du fichier objet :
$ cd src
$ arm-linux-gnueabihf-gcc -shared bcm2835.o -o libbcm2835.so
$ cp libbcm2835.so /tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/arm-linux-gnueabihf/lib
-
Testez la compilation du programme d’exemple
blink.c
$ cd ../examples/blink/
$ arm-linux-gnueabihf-gcc blink.c -lbcm2835 -o blink
-
Transférez l’exécutable obtenu sur le Raspberry Pi et exécutez le.
Ne pas oublier de transférer le fichier libbcm2835.so sur le Raspberry pi dans le dossier /usr/lib
|
3.2 Utilisation
Exercice 5
-
Observez le programme
blink.c
du dossierExamples
et donnez la description des fonctions suivantes :-
bcm2835_init()
-
bcm2835_gpio_fsel()
-
bcm2835_gpio_write()
-
bcm2835_delay()
-
-
Donnez la signification de la définition
#define PIN RPI_GPIO_P1_11
-
A quelle broche du connecteur J8 se rapporte-elle ?
-
A quelle version de Raspberry Pi appartient-elle ?
-
Quelle définition serait-il plus judicieux d’utiliser ici sachant que votre Raspberry pi est une version 2 ?
Aidez vous de la documentation en ligne de la bibliothèque http://www.airspayce.com/mikem/bcm2835/group__gpio.html |
#include <bcm2835.h>
#include <stdio.h>
// Blinks on RPi Plug P1 pin 11 (which is GPIO pin 17)
#define PIN RPI_GPIO_P1_11
int main(int argc, char **argv)
{
if (!bcm2835_init())
return 1;
// Set the pin to be an output
bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);
// Blink
while (1)
{
// Turn it on
bcm2835_gpio_write(PIN, HIGH);
// wait a bit
bcm2835_delay(500);
// turn it off
bcm2835_gpio_write(PIN, LOW);
// wait a bit
bcm2835_delay(500);
}
bcm2835_close();
return 0;
}
-
Observez le programme
input.c
du dossierExamples
et donnez la description des fonctions suivantes :-
bcm2835_gpio_set_pud()
-
bcm2835_gpio_lev()
-
#include <bcm2835.h>
#include <stdio.h>
// Input on RPi pin GPIO 15
#define PIN RPI_GPIO_P1_15
int main(int argc, char **argv)
{
if (!bcm2835_init())
return 1;
// Set RPI pin P1-15 to be an input
bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_INPT);
// with a pullup
bcm2835_gpio_set_pud(PIN, BCM2835_GPIO_PUD_UP);
// Blink
while (1)
{
// Read some data
uint8_t value = bcm2835_gpio_lev(PIN);
printf("read from pin 15: %d\n", value);
// wait a bit
delay(500);
}
bcm2835_close();
return 0;
}
-
Codez un programme C/C++ qui fait clignoter la led toutes les 500ms lorsque l’on appuie sur le bouton poussoir. Vous pouvez pour cela vous aider de la documentation en ligne de la bibliothèque http://www.airspayce.com/mikem/bcm2835/group__gpio.html et vous inspirer des programmes d’exemples
blink.c
etinput.c
étudiés précédemment.
3.3 Ecriture d’une classe
Pour faciliter l’utilisation des GPIO dans un programme C++, nous allons écrire une classe qui devra répondre aux spécifications du diagramme de classe ci-dessous :
Exercice 6
-
Codez la classe
CgpioBcm2835
répondant à la spécification du diagramme de classe ci-dessus.
int lire(); // Retourne l'état de l'attribut gpio configuré en entrée
int lire(std::string pull); // Retourne l'état de l'attribut gpio configuré en entrée pullup ou pulldown
int ecrire(int value); // Ecrit value sur l'attribut gpio configuré en sortie
int setOutput(); // Configure l'attribut gpio en sortie
int setInput(); // Configure l'attribut gpio en entrée
int setInput(std::string pull); // Configure l'attribut gpio en entrée pullup ou pulldown
int getGpio(); // Retourne la valeur de l'attribut gpio
-
Codez un programme
mainGpioBcm2835V1.cpp
dans lequel vous utiliserez deux objets de la classeCgpioBcm2835
pour obtenir le même résultat que l’exercice précédent :-
LED
sur la broche 18 de J8 -
INTER
sur la broche 40 de J8
-
-
Modifiez la classe
CgpioBcm2835
pour lui ajouter une méthodeattendreFront()
qui doit permettre de détecter un évènement sur une entrée comme un front descendant ou un front montant :
attendreFront(front : Chaine) : Entier
Début
si (front:="montant") bcm2835_gpio_ren(gpio);
sinon si(front:="descendant") bcm2835_gpio_fen(gpio);
sinon retourner -1;
bcm2835_gpio_set_eds(gpio);
tant que (NON bcm2835_gpio_eds(gpio));
attendre(200ms);
returner 1;
Fin
-
Codez un programme
mainGpioBcm2835V2.cpp
dans lequel la led sera allumée par une première pression sur le bouton poussoir et éteinte par une seconde pression sur le bouton poussoir.
4. Application
4.1 Mesure de distance
On souhaite réaliser un télémètre qui affichera la distance à un objet à partir d’un capteur ultrasonic Ping de Paralax.
La documentation du capteur est disponible ici : Ping Ultrasonic Distance Sensor
4.2 Classe Cping
Pour faciliter l’accès à la mesure de distance, nous allons écrire une classe `Cping`qui devra répondre aux spécifications du diagramme de classe ci-dessous :
Exercice 7
-
Codez la classe Cping.
Pour mesurer le temps, vous pouvez utiliser la fonction
|
4.3 Utilisation
Exercice 8
-
Codez un programme qui permet de mesurer la distance avec un capteur ultrasonic Ping.
5. Conclusion
Les ports GPIO (General Purpose Input/Output) sont des ports qui sont très utilisés dans le monde des microcontrôleurs, en particulier dans le domaine de l’électronique embarquée. Selon la configuration, ces ports peuvent fonctionner aussi bien en entrée qu’en sortie.
Ils permettent une extension des fonctionnalités du Raspberry-Pi, notamment l’accès au « monde extérieur » pour piloter des LED, des afficheurs LCD, des moteurs, … ou lire l’état d’un capteur TOR ou ses changements d’état.
Le numéro de GPIO n’est pas sa position sur le connecteur, mais son numéro dans les registres de la puce ARM BCM2835. |
Les niveaux de tension des GPIO sont de 3,3V et ne tolèrent pas le 5V. Il n’y a pas de protection contre les surtensions sur la carte. L’utilisation de level shifter (3.3V <→ 5V) ou d’une isolation galvanique (optocoupleur, …) est fortement recommandé pour un interfaçage sérieux. |