TP02 : Station météo, écran LCD et threads
Dans ce TP, nous intégrerons un nouveau capteur environnemental, le BME 280, qui fournit des mesures de température, d’humidité relative et de pression atmosphérique. Nous intégrerons également l’affichage sur l’écran LCD de la cible. Finalement, l’application sera transformée afin d’intégrer un modèle de programmation multi-tâches.
Objectifs du TP
Ce TP a pour but de réaliser le programme de collecte de données qui sera la base des TPs suivants permettant de publier ces données sur le cloud.
À la fin de ce TP, les étudiants :
- auront intégré le capteur BME 280 permettant la collecte des données environnementales;
- auront intégré l’affichage des valeurs collectées sur l’écran LCD;
- auront pris connaissance des outils multi-tâches du système d’exploitation Mbed OS;
- auront réalisé un test unitaire qui tourne sur une cible pour chaque composant logiciel développé;
- auront rédigé un journal de travail et déposé le PDF dans le dépôt git.
Les livrables sont :
- un projet git (tp02) dans votre groupe sur gitlab.forge.hefr.ch avec le code du TP et le code du programme de test;
- une configuration CI/CD de gitlab pour valider votre TP;
- un journal de travail déposé sur gitlab.
Temps accordé : 8 périodes de 45 minutes en classe + travail personnel à la maison
Délai
Le TPs doit être rendu au plus tard 9 jours après la deuxième séance en classe à 23:59.
Pilote pour le capteur Weather Click
Pour installer le pilote du Weather Click, ouvrez PIO Home, cliquez sur l’icône “Libraries” (sur la gauche) et cherchez : framework:mbed bme280
Choisissez la bibliothèque “BME280” de Paul Staron, installez-la et ajoutez-la à votre projet.
Vous pouvez vérifier que l’installation se soit bien passée en vérifiant le fichier platformio.ini
de votre projet, il doit contenir une ligne qui mentionne le projet mbed-star297/BME280
:
lib_deps =
https://github.com/heia-fr/embsys-target-disco-heiafr.git#v0.2.0
mbed-star297/BME280@0.0.0+sha.eaf30b268430
Attention
La bibliothèque “BME280” n’est pas “thread safe”.
Vous trouverez de la documentation sur les méthodes disponibles sur la page web du pilote
Ne pas oublier d’initialiser le capteur avant de lire les valeurs environnementales.
Pilote pour l’écran LCD
Pour le pilote de l’écran LCD, nous utilisons la même base que pour le cours “Architecture des ordinateurs :
La bibliothèque GFX de Adafruit.
Nous avons adapté cette bibliothèque à l’écran de la cible ainsi qu’a Mbed OS.
Pour installer ce pilote, ajoutez manuellement la ligne https://github.com/heia-fr/embsys-disco-gfx.git#
suivi de la version souhaitée à votre fichier platformio.ini
.
La version actuelle est la 0.1.1
:
lib_deps =
https://github.com/heia-fr/embsys-target-disco-heiafr.git#v0.2.0
mbed-star297/BME280@0.0.0+sha.eaf30b268430
https://github.com/heia-fr/embsys-disco-gfx.git#v0.1.1
Attention
La bibliothèque “GFX” n’est pas “thread safe”.
Le pilote est fourni avec beaucoup de polices d’écriture. Le nom du fichier de la police est structuré comme ceci:
<FONT-NAME><SIZE>pt<BITS>b.h
<FONT-NAME>
est le nom de la police sans espace (par exempleIBMPlexMonoBold
)<SIZE>
est la taille de la police en points<BIT>
est soit7
pour une police qui ne contient que les caractères ASCII ou8
pour une police avec les caractères latin-1 (ou ISO 8859-1).
Avec la bibliothèque GFX de Adafruit, la taille totale d’une police ne peut pas dépasser 64KiByte. C’est pourquoi certaines grosses polices ne sont disponibles qu’en ASCII.
Voici un petit programme qui démontre les fonctionnalités de base du pilote:
#include "Fonts/IBMPlexSansBold24pt8b.h"
#include "Fonts/IBMPlexSansMedium12pt8b.h"
#include "disco_lcd_gfx.h"
#include "mbed.h"
void printCenter(DiscoLcdGFX& lcd, const char* text, int16_t x, int16_t y)
{
int16_t x1, y1;
uint16_t w, h;
lcd.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
lcd.setCursor(x - w / 2, y);
lcd.write(text);
}
int main()
{
DiscoLcdGFX lcd;
lcd.fillScreen(LCD_COLOR_BLACK);
lcd.setFont(&IBMPlexSansBold24pt8b);
lcd.fillCircle(150, 70, 60, LCD_COLOR_DARKRED);
for (int i = 5; i < lcd.height(); i+=25) {
lcd.drawLine(0, i, lcd.width(), i, LCD_COLOR_DARKBLUE);
lcd.drawLine(i, 0, i, lcd.width(), LCD_COLOR_DARKBLUE);
}
lcd.setTextColor(LCD_COLOR_YELLOW);
printCenter(lcd, "36.5\260C", lcd.width() / 2, 60);
lcd.setFont(&IBMPlexSansMedium12pt8b);
lcd.setTextColor(LCD_COLOR_WHITE);
printCenter(lcd, "Haute \351cole", lcd.width() / 2, 100);
printCenter(lcd, "d'ing\351nierie", lcd.width() / 2, 125);
printCenter(lcd, "et d'architecture", lcd.width() / 2, 150);
printCenter(lcd, "de Fribourg", lcd.width() / 2, 175);
while (true) {
ThisThread::sleep_for(Kernel::wait_for_u32_forever);
}
}
Et le résultat obtenu:
Classe pour l’écran de la station météo
L’illustration suivante montre le genre d’affichage que nous souhaitons avoir sur notre application :
Réalisez une classe nommée LCDDisplay
(dans le dossier lib/lcd_display
de votre projet) qui se charge d’afficher les bons
éléments aux bons endroits. La déclaration de la classe que vous devez réaliser est donnée ci-dessous :
#ifndef LCD_DISPLAY_HPP_
#define LCD_DISPLAY_HPP_
#include <mbed.h>
#include "disco_lcd_gfx.h"
class LCDDisplay {
public:
// The constructor allocates a new screen and initialises it.
LCDDisplay();
// Initialize the screen and print labels.
void init();
// Delete the previous value and print the new temperature
// at the correct position.
void printTemperature(double t);
// Delete the previous value and print the new humidity
// at the correct position.
void printHumidity(double h);
// Deletes the previous value and print the new preasure
// at the correct position.
void printPressure(double p);
private:
// TODO: add methods and attributes if needed
// Set the font used for the next print operations
void setFont(const GFXfont *f);
// Print text centered at position (x,y)
void printCenter(const char* text, int16_t x, int16_t y);
// Display a filled rectangle with clearColor covering a text
// centered at position (x,y).
// Use the bounding box ot the text to compute the size and the
// position of the rectangle.
void clearCenterText(
const char* text,
int16_t x, int16_t y,
uint16_t clearColor = LCD_COLOR_BLACK);
DiscoLcdGFX lcd_;
};
#endif /* LCD_DISPLAY_HPP_ */
lcd_display.cpp
.
Classe pour l’acquisition des données environnementales
Réalisez une classe nommée SensorDataServer
(dans le dossier lib/sensor_data_server
de votre projet) qui se charge de récolter les données du capteur et les
mettre à disposition d’autres modules. La classe doit être réalisée selon les contraintes suivantes:
- Les données doivent être échangées avec les clients de la classe à l’aide d’un Mail. Le constructeur de la classe
SensorDataServer
doit recevoir une référence à l’instance deMail
utilisée pour l’échange de données. - Concernant la réalisation à l’aide de l’API
Mail
, les contraintes de réalisation sont les suivantes:Mail
est une classetemplate
avec les paramètrestemplate
T
etqueue_sz
.T
détermine le type de la structure de données des données échangées etqueue_sz
détermine le nombre d’éléments utilisés pour cet échange.queue_sz
doit être défini par une constante définie dans la classeSensorDataServer
.- Un type de données est défini dans la classe
SensorDataServer
afin de représenter le typeT
duMail
utilisé. Ce type de données doit être unestruct
et doit permettre de sauvegarder les données provenant des capteurs avec un temps d’acquisition de ces données (de typetime_t
) - Le temps d’acquisition des données doit être obtenu à l’aide de la fonction
time
comme documenté sur Time API. - Si aucun
mail
n’est disponible afin de sauvegarder la dernière acquisition, les données les plus anciennes doivent être effacées.
- Un thread dédié doit être créé afin d’obtenir les données des capteurs périodiquement.
- La classe fournit une méthode
start
qui permet de démarrer l’acquisition de données des capteurs et qui reçoit en paramètre l’intervalle de temps avec lequel les données des capteurs doivent être récoltées. - La classe fournit une méthode
stop
qui permet de stopper l’acquisition de données.
Modification du programme main
Votre programme principal et votre fonction main
doivent être modifiés de la façon suivante:
- Dans la fonction
main
, vous devez créer une instance deSensorDataServer
sur le stack et démarrer l’acquisition de données selon un intervalle de temps. L’instanceSensorDataServer
reçoit en paramètre une instance deMail
qui sera également créée sur le stack. - Dans le programme principal, vous devez créer une fonction
displaySensorData
qui permet d’afficher les données environnementales sur l’écran LCD. Cette fonction utilise l’instance deMail
en tant que consommateur. - Dans la fonction
main
, vous devez appeler la fonctiondisplaySensorData
. Soyez attentifs au fait que le thread principal (le main thread) ne doit jamais se terminer pour un comportement adéquat de votre programme.
Mode d’acquisition urgente
Dans le but de mettre en pratique le mécanisme de priorité supporté par Mbed OS, vous devez réaliser le comportement suivant:
- Lorsque l’utilisateur presse sur le bouton, une prise de mesures des données environnementales doit être effectuée le plus rapidement possible.
- Pour réaliser ce mécanisme, vous devez réaliser une routine ISR qui confie (defer) le travail à un thread dédié qui travaille avec une priorité supérieure aux autres threads de l’application.
- Comme ce thread est de plus haute priorité, il est possible que le système préempte un thread effectuant une opération de plus basse priorité. En vous inspirant de l’exemple vu en cours, vous devez identifier les sections critiques qui doivent être protégées dans les modules développés, de façon à éviter qu’une préemption produise un résultat erroné.
- En reproduisant l’exemple vu en cours, vous devez démontrer l’occurrence d’un problème de section critique. Vous devez ensuite protéger cette section critique d’accès concurrents.
Tests et validations
Vous devez d’abord valider votre réalisation en l’instrumentant de différentes manières:
- Vous devez instrumenter votre code afin d’afficher la période d’acquisition des données. Au début de l’exécution de la tâche de lecture des données de capteurs, vous devez afficher le temps écoulé depuis la mesure précédente. Cette instrumentation peut être réalisée à l’aide d’un
Timer
. - Vous devez instrumenter votre code de façon à démontrer les proportions de temps que le CPU passe en mode actif, en mode sleep et en mode deep sleep. Cette instrumentation doit être réalisée à l’aide de l’API Mbed Statistics (voir également l’exemple sous example cpu-stats).
Comme votre réalisation a été modularisée et conçue en tâches concurrentes, il est utile de tester les modules et tâches de façon isolée des autres:
- Pour le module
SensorDataServer
, vous devez valider que l’absence de capteur est détectée correctement par l’application. Vous pouvez choisir le comportement désiré dans ce cas-là et le comportement désiré doit être testé. - Pour le module
SensorDataServer
, vous devez valider que l’acquisition fonctionne correctement en absence de consommateur de données. - Pour le module
SensorDataServer
, vous devez valider que la dernière donnée acquise n’est pas plus ancienne que le temps courant moins la période d’acquisition, avec ou sans consommateur de données. - Pour le module
LCDDisplay
, vous devez valider que le comportement est correct en absence de données produites. - Pour votre application et les modules concernés, vous devez ajouter au moins 1 test supplémentaire que vous concevez et documentez dans le rapport.
Pour les tests unitaires automatiquement dans l’infrastructure CI/CD de l’école. Vous pouvez reprendre la configuration .gitlab-ci.yml
du tp01 dans laquelle vous remplacer le tag embsys-thermo
par le tag embsys-bme
. L’infrastructure de test dispose pour le moment de 6 runners équipés du capteur Thermo 3 et de 6 runners équipés du capteur BME. Nous adapterons celle-ci en fonction des besoins des laboratoires au cours du semestre.
À ne pas oublier
Gardez toujours en têtes les bonnes pratiques ainsi que les dix commandements du bon programmeur.
- Choisissez de bons noms pour les classes, les méthodes et les variables.
- Implémentez les bibliothèques avec un haut niveau d’abstraction pour pouvoir réutiliser les méthodes dans d’autres projets.
- Faites des “git commit” régulièrement avec de bons commentaires.
- Configurez le CI/CD de gitlab et testez automatiquement le plus de choses possibles.
- Implémentez des tests unitaires.
- Utilisez des assertions dans votre code pour le documenter et le rendre plus robuste.
Journal de travail
- Rédigez un rapport (journal de travail) avec les indications suivantes :
- Une page de titre avec au minimum :
- le nom et le logo officiel de l’école
- le nom du cours : Informatique Embarquée
- le titre de votre document : Travail Pratique 2 : Capteur météo, écran LCD et threads
- le numéro de votre groupe
- les noms des auteurs (vous) avec la classe dans laquelle vous êtes
- la date à laquelle vous avez terminé le rapport
- éventuellement la version du rapport
- Une introduction pour poser le contexte
- Un résumé des notions que vous avez apprises pendant ce TP en précisant si c’est
- non acquis
- acquis, mais encore à exercer
- parfaitement acquis
- Un résumé des points qui vous semblent importants et que vous devez retenir
- Les réponses aux questions.
- Le code source bien formaté et avec du “syntax highlighting” de votre code source.
- Une conclusion par laquelle vous donnez vos impressions sur le TP, ce que vous avez aimé, ce que vous avez moins aimé, et éventuellement des suggestions pour des changements. Indiquez également le nombre d’heures que vous avez passées, par personne, en dehors des heures de TP en classe.
Important
Déposez votre rapport dans un dossier /docs
de votre dépôt git (tp02) avec le nom report02.pdf
(le chemin complet vers votre
rapport est donc /docs/report02.pdf
)