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.

rasp1
Figure 1. Ecosystème Raspberry Pi

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.

rasp4
Figure 2. Affectation des broches

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 :

rasp7
Figure 3. Led sur GPIO24
  • 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 :

led17.cpp
#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 :

rasp8
Figure 4. Poussoir sur GPIO21
  • 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 ?

rasp9
Figure 5. Poussoir sur GPIO21
  • 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 :

poussoir23.cpp
#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 :

CgpioSysfs
Figure 6. Diagramme de classe CgpioSysfs

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 classe CgpioSysfs 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

$ 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 dossier Examples 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
blink.c
#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 dossier Examples et donnez la description des fonctions suivantes :

    • bcm2835_gpio_set_pud()

    • bcm2835_gpio_lev()

input.c
#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 et input.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 :

CgpioBcm2835 1
Figure 7. Diagramme de classe CgpioBcm2835

Exercice 6

  • Codez la classe CgpioBcm2835 répondant à la spécification du diagramme de classe ci-dessus.

Détail des méthodes
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 classe CgpioBcm2835 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éthode attendreFront() qui doit permettre de détecter un évènement sur une entrée comme un front descendant ou un front montant :

CgpioBcm2835 2
Figure 8. Diagramme de classe CgpioBcm2835
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.

Principe fonctionnement Sonar Ping Parallax
Figure 9. Principe de la mesure de distance

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 :

Cping
Figure 10. Diagramme de classe Cping

Exercice 7

  • Codez la classe Cping.

Pour mesurer le temps, vous pouvez utiliser la fonction clock() de la librairie <time.h> :

#include <time.h>
...
clock_t t0,t1;
...
t0=clock();
...
t1=clock();
double duration = ((double)t1-(double)t0)/CLOCKS_PER_SEC;

4.3 Utilisation

Exercice 8

  • Codez un programme qui permet de mesurer la distance avec un capteur ultrasonic Ping.

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.
UrZO0
Figure 11. Isolation galvanique d’une entrée RPI