TP04 - Codeur rotatif et écran LCD – avec solutions
Dans ce “TP de Noël”, nous réalisons un compteur avec le joystick, le codeur rotatif, l’affichage 7-segments et l’écran LCD. Le but est de transformer le travail pratique précédent en lui ajoutant la possibilité de modifier la valeur du compteur avec le codeur rotatif. De plus, nous utilisons l’écran LCD pour afficher un arbre de Noël, grandissant ou rétrécissant en fonction du compteur.
Objectifs du TP
Ce TP a pour but de vous familiariser avec l’encodeur rotatif, avec l’écran LCD et d’exercer la conception d’un programme orienté objet en C++.
À la fin de ce TP, les étudiants :
- sauront utiliser le codeur rotatif;
- sauront utiliser l’écran LCD;
- auront mis en œuvre le concept de programmation orientée objet et auront implémenté plusieurs classes en C++;
- réutiliseront les concepts appris dans les TPs précédents;
- réutiliseront du code précédent pour faire des applications plus complexes;
- auront réalisé un nouveau mini-projet sur la cible;
- auront appliqué les bonnes pratiques en utilisant des assertions, en écrivant des tests unitaires et en validant systématiquement leur code avec le CI/CD.
Les livrables sont :
- un projet git (tp04) dans votre groupe sur gitlab.forge.hefr.ch avec le code du TP (le code du programme ainsi que celui de test);
- une configuration CI/CD de gitlab pour valider votre TP;
- un journal de travail déposé sur gitlab.
Temps accordé : 1 fois 4 périodes de 45 minutes en classe + travail personnel à la maison
Délai
Le rapport et le code doivent être rendus au plus tard 6 jours après la séance en classe. Ce rapport doit comporter tous les chapitres demandés ainsi que la version finale du code (fonctionnel et testé).
Analyse
Commençons par analyser le codeur rotatif (Rotary encoder). En plus du codeur rotatif, le “Click Board” contient 16 LEDs placés en cercle autour de l’axe de rotation du codeur et permettent de faire des animations visuelles.
Pour contrôler ces LEDs, le “Click Board” utilise le même registre à décalage que pour l’afficher 7 segments. La principale différence est que sur le “Rotary Click”, il n’y a pas moyen de moduler la luminosité des LEDs. Elles sont soit éteintes, soit allumées.
Lorsqu’on tourne le bouton du codeur rotatif, deux signaux “A” et “B” permettent de déterminer le sens de rotation. La figure ci-dessous montre comment ces signaux se comportent :
Dans les positions stables (quand on entend un “click”) les signaux “A” et “B” sont soit les deux à “0”, soit les deux à “1”. Entre les positions stables, il y a les positions intermédiaires où les signaux “A” et “B” sont décalés et c’est ce qui nous permet de déterminer le sens de rotation. Si on arrive sur la position 11 (“A” et “B” sont à “1”) et que la position précédente était “10” (“A” à “1” et “B” à “0”), ça veut dire qu’on tourne dans le sens horaire. Si par contre la position précédente était “01” (“A” à “0” et “B” à “1”) ça veut dire qu’on tourne dans le sens antihoraire. On peut faire la même réflexion pour la position stable “00”.
À faire
Dessinez une machine d’état pour détecter le sens de rotation du codeur rotatif. Dessinez aussi la table de transition correspondante.
Astuce
Pour dessiner des machines d’états, vous pouvez utiliser draw.io ou mermaid. Vous pouvez utiliser la version en ligne ou installer une extension pour vscode. Pour draw.io, il y a aussi une extension vscode
Solution
La figure ci-dessous montre ce qui se passe lorsque nous tournons le codeur. À première vue, le signal semble correspondre à ce que nous attendons :
Mais en “zoomant” sur le flanc du signal, on remarque un problème :
Le flanc n’est pas franc, mais oscille pendant environ 10 microsecondes avant de se stabiliser.
Comme vu lors des derniers TPs, la technique de polling périodique nous permet de résoudre ce problème de “rebond”. La fréquence du polling doit être plus petite que 100kHz. Cette fréquence doit aussi être suffisamment grande pour détecter tous les changements, même si on tourne le codeur rotatif rapidement. Dans la pratique, une fréquence de polling de 1kHz fonctionne très bien.
En plus des deux signaux “A” et “B” qui permettent de déterminer le sens de rotation du codeur rotatif, nous avons également un signal “SW” qui est activé lorsqu’on presse sur l’axe du codeur. Nous voyons sur le schéma que les deux signaux “A” et “B” sont déjà pourvus de résistance de “Pull Down”, mais que “SW” n’en a pas. Nous devrons alors en ajouter une interne au microcontrôleur.
Vous aviez déjà fait l’exercice de trouver les correspondances entre les pins et les ports GPIO du microcontrôleur lors du TP précédent et vous n’aurez donc pas de problème à le refaire ici :
Signal | GPIO (socle de gauche - Click 1) | GPIO (socle de droite - Click 2) |
---|---|---|
ENCA_OUT |
||
ENCB_OUT |
||
SW |
||
LATCH |
||
SCK |
||
SDO |
||
SDI |
Solution
Signal | GPIO (socle de gauche - Click 1) | GPIO (socle de droite - Click 2) |
---|---|---|
ENCA_OUT |
PF_3 | PF_10 |
ENCB_OUT |
PA_1 | PC_1 |
SW |
PG_13 | PF_4 |
LATCH |
PA_15 | PB_8 |
SCK |
PA_5 | PA_5 |
SDO |
PA_6 | PA_6 |
SDI |
PA_7 | PA_7 |
Concernant l’écran LCD, nous vous fournissons une librairie toute faite, basée sur la librairie GFX d’Adafruit.
Cette librairie est disponible sur le github de l’école.
Pour l’utiliser dans votre projet, il vous suffit d’ajouter une dépendance dans la section [env...]
du fichier platformio.ini
:
lib_deps =
https://github.com/heiafr-isc/stm32cube-f412disco-lcd.git#main
Voici un petit programme de test de l’écran LCD :
#include "DiscoLcd.hpp"
#include "Fonts/IBMPlexSansMedium12pt8b.h"
#include "Fonts/IBMPlexSansMedium18pt8b.h"
#include "assert.h"
#include "f412disco_ado.h"
#include "pwm.hpp"
/**************************************************************************/
/* ATTENTION: it may be that your PwmChannel class was named differently */
/* by you -> adapt it accordingly */
/**************************************************************************/
class PwmProxy : public DiscoLcdGFX::Pwm {
public:
PwmProxy() : DiscoLcdGFX::Pwm() {
channel_ = new PwmChannel(PwmChannel::kPF5);
}
~PwmProxy() { delete channel_; }
void SetDutyCycle(float duty_cycle) override {
channel_->SetDutyCycle(duty_cycle);
}
HAL_StatusTypeDef Start() override { return channel_->Start(); }
HAL_StatusTypeDef Stop() override { return channel_->Stop(); }
private:
PwmChannel* channel_;
PwmProxy(const PwmProxy& t);
PwmProxy operator=(PwmProxy);
};
int main() {
DiscoAdoInit();
auto gfx = DiscoLcdGFX(new PwmProxy());
gfx.setFont(&IBMPlexSansMedium12pt8b);
gfx.setTextColor(0xF800);
gfx.setCursor(10, 25);
gfx.write("Welcome to");
gfx.setCursor(10, 25 + 25);
gfx.write("Computer");
gfx.setCursor(10, 25 + 25 * 2);
gfx.write("Architecture");
gfx.setFont(&IBMPlexSansMedium18pt8b);
gfx.setTextColor(0xFFFF);
gfx.setCursor(35, 160);
gfx.write("ADO ->");
gfx.setCursor(35, 190);
gfx.write("Real fun ;-)");
gfx.fillCircle(185, 40, 30, 0xFFE0);
gfx.fillCircle(185, 40, 20, 0x001F);
while (true) {
asm volatile("nop");
}
}
Adaptez le code
Dans le code ci-dessus, la classe PwmChannel
est utilisée. Il se peut que
votre implémentation utilise un nom différent pour cette classe. Adaptez-le
donc de conséquence
Expérimentez et modifiez cet exemple pour voir comment l’écran LCD fonctionne.
Conception
Maintenant que nous avons fait connaissance avec le codeur rotatif et l’écran LCD, passons à la conception de notre projet. Nous nous basons sur le TP précédent pour lequel nous avions implémenté les classes suivantes :
Pour ce TP, les modifications suivantes sont demandées:
- Le registre à décalage est utilisé pour l’affichage 7-Segments ainsi que pour les LEDs du codeur rotatif. Généralisez son implémentation en réalisant une nouvelle classe
ShiftRegister
avec les méthodes propres à un registre à décalage (Reset
,SendData
,Latch
). Protégez au mieux les méthodes et les attributs (avecprivate
,protected
oupublic
). Pratiquement, vous devez déplacer des méthodes depuis la classeSeg7
vers une nouvelle classShiftRegister
. - Modifiez la classe
Seg7
pour utiliser la classeShiftRegister
par héritage. - Implémentez une classe
Rotary
pour gérer le comportement du codeur rotatif. Définissez une méthode virtuelleOnRotate
qui est appelée lorsque le codeur rotatif tourne. Cette classe est indépendante du matériel et peut donc facilement être testée. - Cette classe
Rotary
hérite, logiquement, deShiftRegister
ainsi que deButton
, qui traitera les événements du bouton, etPoller
car nous avons défini que nous voulons faire du polling
À faire
Dessinez un diagramme de classes pour le nouveau projet.
Astuce
mermaid permet aussi de dessiner
des diagrammes de classes. En rajoutant l’extension
mermaid editor à Visual Studio Code
vous pourrez facilement travailler avec cet outil
Solution
Implémentation
Le diagramme de classes devrait vous aider à faire l’implémentation.
Pour partir sur de bonnes bases, nous vous conseillons d’utiliser le code suivant pour commencer votre TP.
Étudiez et comprenez bien le code qui vous est fourni! À la fin du projet,
vous devez avoir remplacé toutes les occurrences du commentaire // TODO(student):
par votre code.
Le mini projet.
Nous avons maintenant tout ce dont nous avons besoin pour réaliser un mini projet. Nous souhaitons implémenter un compteur entre 0 et 99 qui s’incrémente et se décrémente avec le codeur rotatif. La valeur du compteur contrôle la taille de l’arbre de Noël affiché sur le display LCD. De plus, en pressant sur le bouton du codeur, on veut pouvoir modifier le comportement des 16 LEDs qui entourent l’axe du codeur (voir la vidéo ci-dessous). En pressant pendant 2 secondes sur l’axe de l’encodeur, nous souhaitons remettre le compteur à zéro ainsi que remettre le comportement des LEDs par défaut. On souhaite aussi garder le joystick pour incrémenter/décrémenter le compteur et pour faire varier la luminosité de l’affichage.
Les éléments vous permettant d’avancer plus rapidement dans l’implémentation du TP vous sont fournis. C’est-à-dire:
- code de la librairie contrôlant les couleurs
- code de l’application
- classe contrôlant la barre de progression
- classe permettant de contrôler un texte affiché à une position définie
- classe gérant l’arbre de Noël
Vous trouverez tout cela dans le dépôt dédié
À ne pas oublier
Gardez toujours en têtes les bonnes pratiques ainsi que les dix commandements du bon programmeur.
- Prenez du temps pour la conception sur papier avant de coder.
- Gérez votre temps. Vous devrez probablement faire du travail à la maison pour arriver à terminer le TP.
- 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 beaucoup de 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) selon le même modèle que pour le premier TP.
Déposez votre rapport dans un dossier /docs
de votre dépôt git (tp04) avec le nom report.pdf
(le chemin complet vers votre
rapport est donc /docs/report.pdf
)