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 du bus i2c du Raspberry pi

Mettre en oeuvre :

  • un afficheur LCD RGB i2c

  • ? un capteur de température i2c ?

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 afficheur LCD RGB i2c JHD1313M1 de Grove

  • ? 1 capteur de température i2c ?

Logiciel(s) :

  • cross-compilateur arm-linux-gnueabihf-g++

Documentation :

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 dispose de deux interfaces i2c. Le bus numéro 0 qui était accessible à travers le connecteur P5 sur le modèle B a disparu depuis le modèle B+. De plus, ce connecteur étant présent sous forme de simples trous cuivrés, il était donc nécessaire de venir y souder des broches pour pouvoir l’utiliser. Les signaux du bus 0 sont toujours présents – mais pas très accessibles – sur les connecteurs J3 (camera) et J4 (display).

rasp4
Figure 2. Affectation des broches

2. Le bus I2C sur le Raspberry pi

2.1 L’accès au bus I2C

Nous allons nous intéresser au second bus, accessible via le port d’extension P1, sur deux broches (que l’on peut également employer pour des entrées/sorties GPIO) identiques quel que soit le modèle de Raspberry. Il s’agit de la broche 3 (signal SDA) et de la broche 5 (signal SCL).

Exercice 1

Rappelez à quoi servent ses deux signaux.

2.1 Activation du bus I2C sur le Raspberry

Le protocole i2c est supporté par le noyau Linux depuis sa version 2.4. De nombreux périphériques sont reconnus par le kernel, notamment dans le sous-système Hwmon (Hardware Monitor).

L’accès depuis l’espace utilisateur est facilité par le module i2c-dev qui rend les bus i2c visibles dans le répertoire /dev sous forme de fichiers spéciaux représentant des périphériques en mode caractère.

Exercice 2

  • Activez la prise en charge du bus I2C sur le Raspberry :

$ sudo raspi-config

→ Advanced options → I2C → Enabled (tout accepter).

i2c raspi config
Figure 3. Raspi-config

OU

  • Supprimez du fichier /etc/modprobe.d/raspi-blacklist.conf les référence à l’I2C

  • Ajoutez i2c aux modules à charger

$ echo  i2c-dev >> /etc/modules
Normalement, les modules sont déja installés mais blacklistés. Cependant, dans certains cas, il peut être nécessaire de les installer manuellement : i2c_bcm2708 et i2c-dev
  • Vérifiez la prise charge du bus I2C, identifiez la vitesse de transmission sur le bus :

$ dmesg | grep i2c
  • Identifiez le(s) bus i2c-c disponible(nt) :

$ ls /dev/i2c*

2.1 Installation des outils de gestion du bus i2c

Exercice 3

  • Installez les outils de gestion du bus i2c

$ sudo apt-get install i2c-tools
  • Détectez des esclaves sur le bus :

$ i2cdetect  –y  1
-y pour répondre automatiquement yes et 1 pour le numéro de périphérique i2c
i2c detect
Figure 4. Affichage des périphériques i2c
Les commandes i2cset et i2cget permettent respectivement d’écrire et de lire sur le bus i2c.

3. Gestion d’un afficheur LCD RGB i2c

L’afficheur à gérer est un JHD1313M1 de chez Grove. Il est en fait composé d’un afficheur LCD basé sur le HD44780 d’Hitachi et d’un contrôleur du rétro-éclairage (RGB) PCA9633 de NXP Semiconductors. Il possède donc 2 adresses i2c :

  • LCD : 0x3e

  • RGB : 0x62

LCD RGB
Figure 5. Afficheur LCD RGB i2c

3.1 Par l’invite de commandes

Exercice 4

  • Connectez l’afficheur au connecteur de la carte Raspberry pi.

l’afficheur nécessite une tension de 5V pour fonctionner correctement.
  • Vérifiez la présence de l’afficheur sur le bus i2c.

La commande i2cset s’utilise de la manière suivante :

i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] ... [mode]
  • Procédez à la séquence d’initialisation de l’afficheur :

i2cset -y 1 0x3E 0x80 0x3C
i2cset -y 1 0x3E 0x80 0x0C
i2cset -y 1 0x3E 0x80 0x01
i2cset -y 1 0x3E 0x80 0x06
i2cset -y 1 0x62 0x00 0x00
i2cset -y 1 0x62 0x08 0xFF
i2cset -y 1 0x62 0x01 0x20
i2cset -y 1 0x62 0x04 0x00
i2cset -y 1 0x62 0x03 0x00
i2cset -y 1 0x62 0x02 0xFF
i2cset -y 1 0x3E 0x40 0x31
i2cset -y 1 0x3E 0x40 0x32
i2cset -y 1 0x3E 0x40 0x33
  • Identifiez les instructions passées à l’afficheur LCD (adresse i2c 0x3E) et au contrôleur de rétro-éclairage (adresse i2c 0x62):

  • Identifiez les registres de gestions des couleurs du contrôleur de rétro-éclairage.

  • Configurez le rétro-éclairage pour qu’il clignote à une fréquence de 1Hz avec un rapport cyclique de 50%.

3.2 Visualisation d’une trame i2c

Le bus i2c (Inter Integrated Circuit) fait partie des bus série : 3 fils pour faire tout passer. Il a été développé au début des années 1980, par Philips pour minimiser les liaisons entre les circuits intégrés numériques de ses produits (Téléviseurs, éléments HiFi, magnétoscopes, …​).
Le bus i2c

Le bus i2c permet de faire communiquer entre eux des composants électroniques très divers grâce à seulement trois fils : un signal de données (SDA), un signal d’horloge (SCL), et un signal de référence électrique (masse).

Il s’agit d’une liaison en mode série, ce qui signifie que la vitesse de transfert sera plus faible qu’avec un bus de type parallèle. Le bus i2c permet cependant des échanges à la vitesse de 100 kbits par seconde. Certes, la vitesse de transfert du bus i2c n’est pas fulgurante, mais dans bien des cas, la vitesse n’est pas l’élément prédominant.

L’utilisation d’un bus i2c permet de réduire la complexité des circuits imprimés à réaliser. Par exemple, pour connecter une EEPROM ou une RAM à un microcontrôleur classique, il faut relier entre eux les bits de données et les bits d’adresses des différents composants, et, en plus, il faut bien souvent ajouter une logique de sélection. Avec des composants prévus pour se connecter au bus i2c, il suffit de les relier par deux pistes seulement. Si, par la suite, on souhaite ajouter des composants sur le circuit, le nombre de pistes à ajouter sera vraiment plus réduit (essayez d’ajouter une EEPROM sur un circuit existant pour voir).

De nombreux fabricants ayant adopté le système la variété des systèmes disponibles disposant d’un port i2c est énorme :

  • microcontrôleurs

  • expandeurs de bus (entrée/sortie 8 bits)

  • convertisseurs A/N et N/A

  • mémoires (RAM, EPROM, EEPROM, etc.)

  • récepteurs infra-rouge (télécommande RC5)

  • capteurs de température

  • circuits audio (égaliseur, contrôle de volume, etc.)

  • drivers d’affichage LCD ou à LEDs

  • décodeurs télétexte

  • chargeurs de batterie

  • PLL pour tuner HF

  • etc.

Le protocole i2c

Le protocole du bus I²C définit la succession des états possibles sur les lignes SDA et SCL, et comment doivent réagir les circuits en cas de conflit.

La prise de contrôle du bus

Pour transmettre des données sur le bus I²C, il faut surveiller deux conditions particulières : la condition de départ et la condition d’arrêt.

i2c2

Avant de tenter de prendre le contrôle du bus, un circuit doit vérifier que les lignes SDA et SCL sont au repos, c’est-à-dire à l’état haut. Si c’est le cas, le circuit indique qu’il prend le contrôle du bus en mettant la ligne SDA à 0. A partir de ce moment là, les autres circuits savent que le bus est occupé et ils ne devraient pas tenter d’en prendre contrôle. Le circuit qui vient de prendre le contrôle du bus en devient le maître (en anglais « master »). C’est lui qui génère le signal d’horloge, quel que soit le sens du transfert.

La transmission d’une addresse

Le nombre de composants qu’il est possible de connecter sur un bus I²C étant largement supérieur à deux, le maître doit pouvoir choisir quel esclave est censé recevoir les données. Dans ce but, le premier octet que transmet le maître n’est pas une donnée mais une adresse. Le format de l’octet d’adresse est un peu particulier puisque le bit D0 est réservé pour indiquer si le maître demande une lecture â l’esclave ou bien au contraire si le maître impose une écriture à l’esclave. Adressage Chaque circuit connecté au bus I²C possède une adresse, qui doit être unique.

Une fois l’adresse envoyée sur le bus, l’esclave concerné doit répondre en plaçant le bit ACK à 0. Si le bit ACK vaut 1, le maître comprend qu’il y a une erreur de sélection et il génère la condition arrêt. En revanche, si le bit ACK vaut 0, le maître peut continuer les opérations.

i2c3
Écriture d’une donnée

Si le bit R/W précédemment envoyé était à 0, cela signifie que le maître doit transmettre un ou plusieurs octets de données. Après chaque bit ACK valide, le maître peut continuer d’envoyer des octets à l’esclave ou bien il peut décider de terminer le dialogue par une condition d’arrêt.

i2c4
Lecture d’une donnée

Si le bit R/W transmis en même temps que l’adresse est à 1, cela signifie que le maître veut lire des données issues de l’esclave. C’est toujours le maître qui va générer le signal d’horloge SCL. En revanche, après le bit ACK de l’adresse, c’est l’esclave qui va garder le contrôle de la ligne SDA. Pour cela, le maître va placer sa propre sortie SDA au niveau haut pour permettre à l’esclave de prendre le contrôle de la ligne SDA. L’esclave doit alors scruter la ligne SCL et attendre le niveau bas pour changer l’état de la ligne SDA, faute de quoi le maître détectera une condition arrêt et abandonnera le transfert (l’électronique intégrée dans l’esclave se doit de détecter aussi qu’il y a eu une condition arrêt, bien entendu).

Après que l’esclave a transmis les 8 bits de données, c’est le maître, cette fois-ci, qui va générer un bit d’acquittement. Si le maître désire lire des octets supplémentaires, il placera le bit d’acquittement à 0. En revanche, si le maître décide que la lecture est terminée, il placera le bit ACK au niveau 1. L’esclave comprendra alors que le transfert est terminé. Cette fois-ci, bien que le bit ACK soit au niveau 1, cela ne correspond pas à une condition d’erreur mais à une fin de transfert.

i2c5
Référence web :
— https://www.aurel32.net/elec/i2c.php

Exercice 4

  • A tour de rôle, vous vous rendrez sur le poste dédié à l’utilisation de l’analyseur de protocole Saelae

  • Exécutez le logiciel Logic qui permet de piloter l’analyseur Saelae

  • Paramétrez l’analyseur logique, connectez 2 de ses sondes aux broches SDA et SCL du Raspberry Pi et lancez une acquisition avec analyse du protocole i2c.

  • Envoyez la commande i2cset -y 1 0x3E 0x40 0x31

  • Analysez les trames obtenues et vérifiez qu’elles sont conformes au standard i2c

    • Vitesse de transfert (fréquence de SCL)

    • Condition de départ (START) et condition d’arrêt (STOP)

    • Adresse de l’esclave

    • Type d’opération (lecture/écriture)

    • Adresse du registre visé

    • Valeur de la donnée

    • bits ACK

3.3 Programmation C++

Vous allez écrire un programme permettant d’afficher un message sur votre afficheur en vous appuyant sur la librairie rgb_lcd de gestion de cet afficheur.

Mais il nous faut avant tout pouvoir utiliser le bus i2c. La librairie `BCM2835`que nous avons utilisé dans le précédent TP fournie des fonctions de gestions du bus :

Exercice 5

  • Codez le programme de test suivant :

testRGB.cpp
/**************************
 *   Test du rétro-éclairage
 *
 * i2cset -y 1 0x62 0x00 0x00
 * i2cset -y 1 0x62 0x08 0xFF
 * i2cset -y 1 0x62 0x01 0x20
 * i2cset -y 1 0x62 0x04 0x00
 * i2cset -y 1 0x62 0x03 0x00
 * i2cset -y 1 0x62 0x02 0xFF
 *
******************************/
#include <bcm2835.h>
#include <iostream>

using namespace std;


int main()
{
    char buf[1];

    if (!bcm2835_init())return 1;
    bcm2835_i2c_begin();                //Start I2C operations.
    cout << "Start I2C operations : ok" << endl;
    bcm2835_i2c_setSlaveAddress(0x62);  //I2C address
    cout << "I2C address : ok" << endl;

    bcm2835_i2c_set_baudrate(10000);    //10k baudrate
    cout << "Baudrate : ok" << endl;

    buf[0] = 0x00;
    buf[1] = 0x00;
    bcm2835_i2c_write(buf,2);

    buf[0] = 0x08;
    buf[1] = 0xFF;
    bcm2835_i2c_write(buf,2);

    buf[0] = 0x01;
    buf[1] = 0x20;
    bcm2835_i2c_write(buf,2);

    buf[0] = 0x02;
    buf[1] = 0xFF;
    bcm2835_i2c_write(buf,2);

    buf[0] = 0x03;
    buf[1] = 0xFF;
    bcm2835_i2c_write(buf,2);

    buf[0] = 0x04;
    buf[1] = 0;
    bcm2835_i2c_write(buf,2);

    bcm2835_i2c_end();
    bcm2835_close();
    return 0;
}
  • Donnez les définitions des fonctions suivantes :

    • bcm2835_init()

    • bcm2835_i2c_begin()

    • bcm2835_i2c_setSlaveAddress(0x62)

    • bcm2835_i2c_set_baudrate(10000)

    • bcm2835_i2c_write(buf,2)

    • bcm2835_i2c_end()

    • bcm2835_close()

Reportez vous à la documentation de la librairie BCM2835

La classe rgb_lcd permet de gérer l’afficheur et le rétro-éclairage.

Ci2cBcm2835

Exercice 6

  • Identifiez la méthode qui réalise l’initialisation de l’afficheur et montrez qu’elle est conforme à la séquence d’initialisation décrite par le fabricant.

  • Identifiez la méthode qui permet de gérer la couleur du rétro-éclairage.

  • Identifiez la méthode qui permet d’afficher des caractères sur l’afficheur.

  • Complétez les définitions du fichier d’entête rgb_lcd.h en remplaçant les "?" par les bonnes adresses :

// Device I2C Adrress
#define LCD_ADDRESS     ????
#define RGB_ADDRESS     ????

// color define
...

// RGB Registers
#define REG_RED         ????
#define REG_GREEN       ????
#define REG_BLUE        ????

#define REG_MODE1       ????
#define REG_MODE2       ????
#define REG_OUTPUT      ????
  • Quels objets de la classe Ci2cBcm2835 permettent l’accès au bus i2c ?

  • Codez la classe Ci2cBcm2835 qui définie par le diagramme de classe ci-dessous :

Ci2cBcm2835

Exercice 7

  • Codez le programme principal mainLcd.cpp qui répond à l’algorithme suivant :

Début
    Instancier un objet de la classe `rgb_lcd`
    Initialiser l'afficheur (16 caratères, 2 lignes)
    Configurer le rétro-éclairage en bleu
    Placer le curseur au caractère 5 de la première ligne
    Ecrire _"Bonjour"_
    Placer le curseur au caractère 1 de la seconde ligne
    Ecrire _"Afficheur i2c"_
    Attendre 5 secondes
    Effacer l'écran
    Configurer le rétro-éclairage en blanc
    Placer le curseur au caractère 0 de la première ligne
    Ecrire _"OH"_
    Placer le curseur au caractère 0 de la seconde ligne
    Ecrire _"YES !!!"_
    Tant que 1
        Décaler l'affichage vers la droite
        Attendre 250 ms
    Fin tant que
Fin
  • proposez une séquence personnelle d’affichage en jouant avec les couleurs et le clignotement du rétro-éclairage, des décalages vers la droite ou la gauche du texte à l’écran.

4. Conclusion

Le bus de données i2c permet d’échanger des données numériques entre un capteur (ou un actionneur) et un mini-PC tel que le Raspberry Pi. Le bus i2c est très répandu également sur les micro-contrôleurs (Arduino, ESP8266, ESP32). Par défaut, le bus i2c n’est pas activé sur la distribution Raspbian.

Les outils de diagnostic et de gestion en ligne de commandes sont très utiles.

La librairie BCM2835 fournie les fonctions nécessaires à la gestion du bus dans un programme C/C++