Aller au contenu

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.

Rotary schema

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 :

Rotary signals

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

state machine

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 :

signal1

Mais en “zoomant” sur le flanc du signal, on remarque un problème :

signal2

Le flanc n’est pas franc, mais oscille pendant environ 10 microsecondes avant de se stabiliser.

signal3

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 :

Classes du TP 3

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 (avec private, protected ou public). Pratiquement, vous devez déplacer des méthodes depuis la classe Seg7 vers une nouvelle class ShiftRegister.
  • Modifiez la classe Seg7 pour utiliser la classe ShiftRegister par héritage.
  • Implémentez une classe Rotary pour gérer le comportement du codeur rotatif. Définissez une méthode virtuelle OnRotate 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, de ShiftRegister ainsi que de Button, qui traitera les événements du bouton, et Poller 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

Diagram de classes

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)