Vérification de fonctionnement de la librairie bcm2835 avec Qt
Ce paragraphe vérifie le bon fonctionnement de la librairie bcm2835 sous Qt en language C++. Je le recommande aux nouveaux venus à Qt et/ou bcm2835. Il leurs permettra de mettre le pied à l'étrier car j'en profiterai pour donner des détails sur l'architecture des programmes C++ sous Qt. En tous cas les trois grands concepts sous jacents de Qt : QMAKE, QWidget et le concept de signal/slot.
Matériel
Pour tester ce fonctionnement nous allons utiliser une broche GPIO comme entrée numérique, et une broche comme sortie numérique. Elle seront reliées entre elles. Ainsi lorsque l'on modifiera la sortie numérique, on pourra relire cette modification par l'entrée numérique. Pour mettre cette liaison en oeuvre, reliez la broche GPIO4 (broche N°7) à la broche GPIO17 (broche N°11) tel que le schéma ci-dessous le montre :

Création du projet
- Démarrez QtCreator. Dans la barre de menu principal, choisissez la commande "File/New File or Project" (raccourci : Ctrl+N).
- Dans la fenêtre de dialogue "New File or Project - Qt Creator" choisissez un projet de type "Application (Qt) / Qt Widgets Application" et cliquez sur le bouton "Choose...".
- Dans la fenêtre suivante "Location", dans le champ "Name", entrez la valeur "test2835". Dans le champ "Create in", saisissez la valeur "/home/pi"
puis cliquez sur le bouton "Next >".
- Dans la fenêtre suivante "Build System" dans le champ "Build System", assurez vous que la valeur est "qmake" puis cliquez sur le bouton "Next >". La fenêtre suivante est alors affichée :

- Dans le champ "Class Name" entrez la valeur "MainDialog".
- Dans le champ "Base Class" sélectionnez l'option "QDialog".
- Décochez la case à cocher "Generate Form".La fenêtre est alors la suivante :

- Cliquez sur le bouton "Next >".
- Cliquez sur le bouton "Next >" à deux reprises pour valider directement les 2 fenêtres suivantes.
- Cliquez sur le bouton "Finish" pour mettre fin à la création du projet.
L'arbre du projet dans Qt Creator doit maintenant être conforme à celle-ci :

Nous pouvons immédiatement tester que le projet est bien créé en le compilant et l'exécutant. Pour cela, lancer tout d'abord une compilation par le raccourci Ctrl+B. puis lancer le programme par le raccourci Ctrl+R. Une fenêtre vide avec pour titre "bcm2835" doit être affichée. Fermez cette fenêtre.
Inclusion de la librairie bcm2835 dans le projet
Pour une description des librairies rendez vous à la page Librairie bcm2835.
Qt Creator facilite le travail d'inclusion des librairies dans un projet. Pour intégrer une librairie, sélectionnez le projet "test2835" dans l'arbre du projet. Pour être plus précis C'est l'élément en tête de liste et écrit en caractères gras. Cliquez avec le bouton droit de la souris sur cet élément et choisissez l'option "Add Library". La fenêtre suivante s'affiche :

Bcm2835 est une librairie externe, donc sélectionnez l'option "External Library" puis cliquez sur le bouton "Next >". La fenêtre suivante s'affiche :

Nous ne développons ce projet que pour Linux. En effet le port GPIO n'est disponible ni sur un PC, ni sur un MAC !! Vous pouvez décocher les cases à cocher "Mac et Windows". Cela allègera notre fichier projet. Sur la ligne "Library File" cliquez sur le bouton "Browse...." Dans le dialogue de sélection, naviguez jusqu'au répertoire "/usr/local/lib" et sélectionnez le fichier "libbcm2835.a". Cliquez sur le bouton "Open". De retour dans la fenêtre Qt Creator, les champs "Library File et Include Path" ont été mis à jour. Les valeurs sont correctes. La fenêtre doit être la suivante :

Cliquez sur le bouton "Next >", puis validez la dernière fenêtre en cliquant sur le bouton "Finish".
Le fichier modifié par ces opérations est le fichier "test2835.pro". A ce stade il est ouvert dans l'éditeur de Qt Creator. La première opération consiste à sauver les modifications par l'utilisation du raccourci Ctrl+S.
Les fichiers ".PRO" dans Qt servent à définir un projet. Cela sert tout aussi bien à l'éditeur de texte de Qt Creator qu'à la chaine de compilation. En fait ces fichiers vous isolent des fichiers de directives de compilation que sont les "makefile". Le fichier "makefile" qui sera utilisé pour compiler votre application est automatiquement généré par un utilitaire de Qt nommé "QMAKE". A chaque fois que cela est nécessaire, c'est à dire à chaque fois que le fichier .PRO est modifié, lors du démarrage de la compilation cet utilitaire est appelé automatiquement et crée le makefile qui sera utilisé pour générer le programme exécutable. Votre fichier "test2835.pro" doit être proche de celui-ci :

Les lignes ajoutées au fichier par l'inclusion de la librairie sont les 6 dernières.
Sauf cas particuliers, ces fichiers ne contiennent aucune ligne qui "exécute" un tâche. Ils se contentent d'attribuer des valeurs à des variables qui sont connues de tout l'environnement de Qt et de Qt Creator. La signification des variables qui apparaissent dans le fichier sont les suivantes :
- QT : Cette variable définie les modules, c'est à dire les librairies internes de Qt que votre application utilise. Dans cet exemple, les modules "core, gui et widgets"
sont utilisés via les lignes 1 à 3 du fichier.
- CONFIG : Cette variable définie les paramètres de compilation. Dans certains cas particuliers, il peut y en avoir beaucoup. Ici le seul paramètre indique que la compilation
devra être au moins conforme au standard 11 du language C++.
- SOURCES : Définie vos fichiers sources.
- HEADERS : Définie vos fichiers d'entêtes.
- TARGET et INSTALLS : Définissent la cible d'installation lorsque vous déployez votre programme sur un autre ordinateur. Dans cet exemple, elles sont inutiles. Si vous reprenez
la page Librairie bcm2835, ces variables sont utilisées lors de la commande "sudo make install".
- LIBS : Définie les librairies externes utlisées par votre application. Ici, la librairie "libbcm2835" apparait bien.
- INCLUDEPATH : Lorsque vous incluez un fichier d'entête .h dans un de vos codes sources. vous devez normalement spécifier son chemin d'accès complet dans les répertoires.
Cette variable vous évite cela. Le répertoire ajouté à cette variable fait alors partie de la liste de répertoires par défaut que le système connait.
- DEPENDPATH : Cette variable ajoute un répertoire à la liste de répertoire ou, si le code est modifié, indique que le projet soit être recompilé. Imaginons que pour une
raison particulière le fichier "bcm2835.h" vient à être modifié cela indique à Qt crreator que votre programme doit être recompilé.
- PRE_TARGETDEPS : Variable utlilitaire définissant tous les fichiers externes dont dépend votre application. Aussi utilisé lors d'un "sudo make install", mais dans
le cas présent, ce ne serait pas le cas car la librairie bcm2835 est du type statique, donc il n'est pas nécessaire de déployer le fichier "libbcm2835.a".
Maintenant que nous avons inclus notre librairie, il faut ajouter l'inclusion de son fichier d'entête dans un fichier source ou d'entête qui utilisera la librairie. C'est la classe "MainDialog" qui utilisera la librairie. Le mieux est donc de placer cette inclusion dans son fichier d'entête "maindialog.h". Ouvrez ce fichier en double cliquant sur celui-di dans l'arbre du projet. Apportez la modification suivante juste après la ligne "#include <QDialog>" :
#include <QDialog>
#include "bcm2835.h"
Sauvegardez le fichier par Ctrl+S. Lancez une compilation par Ctrl+B pour vérifier l'absence d'erreurs.
A partir de maintenant le fichier "MainDialog.cpp" pourra utiliser toutes les fonctions fournies par la librairie. Lui et le fichier "MainDialog.h" pourront définir des variables dont le type est définie par la librairie.
Début et fin d'utilisation de la librairie
La documentation de bcm2835 (ici) nous indique que lorsque l'on souhaite commencer à utiliser ses fonctions il faut appeler la fonction "bcm2835_init()", et que lorsque l'on termine, il faut appeler la fonction "bcm2835_close()". Les meilleurs endroits pour cela sont le constructeur et le destructeur de la classe MainDialog. Ouvrez le fichier "maindialog.cpp" et modifiez le comme suit :
MainDialog::MainDialog(QWidget *parent)
: QDialog(parent)
{
MainDialog::~MainDialog()
{
Sauvez le fichier, compiler et exécutez pour vérifier le bon fonctionnement.
Eléments de l'interface opérateur
Nous devons ajouter un bouton poussoir (via la classe "QPushButton") pour déclencher l'écriture sur la sortie numérique puis lire l'entrée numérique. Le résultat de la lecture sera affichée dans un label (via la classe "QLabel"). Pour organiser ces deux éléments dans le fenêtre, le moyen le plus simple est le concept de layout (via la classe "QHBoxLayout"). Modifiez le fichier "maindialog.h" comme suit :
#define MAINDIALOG_H
#include <QDialog>
class MainDialog : public QDialog
{
Q_OBJECT
public:
MainDialog(QWidget *parent = nullptr);
~MainDialog();
private:
};
#endif // MAINDIALOG_H
Modifiez le constructeur de la classe "MainDialog" dans le fichier "maindialog.cpp" comme suit :
: QDialog(parent)
{
m_pButton = new QPushButton();
m_pLabel = new QLabel();
m_pButton->setText("Trigger");
m_pLabel->setText(QString::number(0));
m_pLayout->addWidget(m_pButton);
m_pLayout->addWidget(m_pLabel);
setLayout(m_pLayout);
}
Sauvez le fichier, compiler et exécutez pour vérifier le bon fonctionnement.
Concept Signal/Slot de Qt
La programmation dans un environnement graphique est du type évènementiel. Cela indique que la grande majorité des fonctions du programme sont exécutées lorsqu'un évènement survient. Qt, grâce au concept de signal/slot simplifie largement ce type de programmation. A titre d'exemple, un bouton poussoir comme celui inséré dans notre application lors du dernier paragraphe est un des exemples les plus simples. Lorsque l'utilisateur clique sur le bouton, celui-ci emet un signal. Notre application doit simplement fournir un slot et connecter ce dernier au signal emis par le bouton. La classe "QPushbutton", donc notre bouton, emet le signal "clicked" lorsque le bouton est cliqué. Nous allons lui fournir un slot que nous nommerons "onClick". Puis nous connecterons le slot "onClick" au signal "clicked" via la fonction "connect" disponible dans toutes les classes de Qt qui héritent de la class "QObject", ce qui est la cas de la classe "QDialog" dont notre classe "MainDialog" hérite.
Dans le fichier "maindialog.h", modifiez la fin de la déclaration de la classe "MainDialog" comme suit :
private:
QHBoxLayout* m_pLayout;
QPushButton* m_pButton;
QLabel* m_pLabel;
void onClick();
};
#endif // MAINDIALOG_H
Ajouter la définition de la fonction "onClick" à la fin du fichier "maindialog.cpp" :
{
}
La prochaine opération consiste à appeler la fonction "connect" pour connecter le slot au signal. Le meilleur endroit est la fin du constructeur de la classe "MainDialog". La fonction "connect" reçoit 4 paramètres : Un pointeur vers l'objet emetteur du signal, un pointeur vers le signal émis, un pointeur vers la classe à laquelle appartient le slot, un pointeur vers les slot. L'emetteur du signal est le "QPushButton" déjà déclaré sous forme de pointeur. L'objet implémentant le slot est la classe "MainDialog", étant donné que l'appel à la fonction "connect" se fait par cette classe, nous pouvons utiliser le pointeur "this". Signal et slot sont des fonctions, un pointeur vers ces derniers est donc un pointeur de fonctions. Afin de simplifier la syntaxe, Qt met à notre disposition des macros nommées "SIGNAL" et "SLOT".
Modifier la fin du constructeur de la classe "CMainDialog" à la fin du fichier "maindialog.cpp" :
bcm2835_init();
A partir de maintenant, à chaque fois que l'utilisateur cliquera sur le bouton, la fonction "onClick" sera exécutée.
Sauvez le fichier, compiler et exécutez pour vérifier le bon fonctionnement.
Ecriture et lecture des entrées et sorties numériques
Dans notre application, l'écriture de la sortie numérique GPIO4 et la lecture de l'entrée numérique GPIO17 seront effectuées dans le slot "onClick". A chaque fois que le bouton sera cliqué, la valeur écrite sur la sortie numérique sera l'inverse de celle émise lors de l'appui précédent. Pour cela nous allons déclarer une variable booléenne qui sera initialisée dans le constructeur et inversée dans le slot.
Avant de pouvoir lire et/ou écrire en entrée/sortie numérique sur une broche de la prise GPIO 40 broches du Raspberry Pi, il faut configurer cette broche pour la fonction qui lui sera attribuée. Ainsi GPIO4 sera configuré en sortie numérique, GPIO17 sera configuré en entrée numérique avec une résistance de pull-down. A la lecture de la documentation de la librairie bcm2835, il sera donc nécessaire de configurer ces broches via les fonctions "bcm2835_gpio_fsel et bcm2835_gpio_set_pud". Cette configuration sera effectuée dans le constructeur de la classe "CMainDialog".
Le slot "onCLick" recevra, selon la documentation de la librairie bcm2835, recevra le fonction "bcm2835_gpio_write" pour écrire sur GPIO4, et "bcm2835_gpio_lev" pour la lecture de l'entrée numérique.
Le code final de notre application sera donc le suivant. Modifiez le code comme suit, sauvez, compilez et testez.
Fichier "maindialog.h" :
#define MAINDIALOG_H
#include <QDialog>
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include "bcm2835.h"
class MainDialog : public QDialog
{
Q_OBJECT
public:
MainDialog(QWidget *parent = nullptr);
~MainDialog();
private:
QHBoxLayout* m_pLayout;
QPushButton* m_pButton;
QLabel* m_pLabel;
void onClick();
};
#endif // MAINDIALOG_H
Fichier "maindialog.cpp" :
MainDialog::MainDialog(QWidget *parent)
: QDialog(parent)
{
m_pLayout = new QHBoxLayout();
m_pButton = new QPushButton();
m_pLabel = new QLabel();
m_pButton->setText("Trigger");
m_pLabel->setText(QString::number(0));
m_pLayout->addWidget(m_pButton);
m_pLayout->addWidget(m_pLabel);
setLayout(m_pLayout);
bcm2835_init();
bcm2835_gpio_fsel(RPI_BPLUS_GPIO_J8_07, BCM2835_GPIO_FSEL_OUTP);
// GPIO17 (Pin 11) : Entrée numérique
bcm2835_gpio_fsel(RPI_BPLUS_GPIO_J8_11, BCM2835_GPIO_FSEL_INPT);
// GPIO17 (Pin 11) : Entrée numérique avec résistance de pull down
bcm2835_gpio_set_pud(RPI_BPLUS_GPIO_J8_11, BCM2835_GPIO_PUD_DOWN);
connect(m_pButton, SIGNAL(clicked()), this, SLOT(onClick()));
}
MainDialog::~MainDialog()
{
bcm2835_close();
}
void MainDialog::onClick()
{
uint8_t DigitalReadValue;
// Inversion de la valeur à écrire sur la sortie numérique
m_pDigitalState = !m_pDigitalState;
// Ecriture sortie numérique. Cast bool vers uint8_t (type attendu par la fonction)
bcm2835_gpio_write(RPI_BPLUS_GPIO_J8_07, static_cast<uint8_t>(m_pDigitalState));
// Lecture de l'entrée numérique
DigitalReadValue = bcm2835_gpio_lev(RPI_BPLUS_GPIO_J8_11);
// Mise à jour du label avec la valeur lue
m_pLabel->setText(QString::number(DigitalReadValue));