Ce que vous allez faire
Le CH32V307 dispose d'un périphérique appelé horloge temps réel que je souhaite vous faire utiliser dans une application concrète, un réveil matin un peu sophistiqué. Plutôt que la poignée de boutons poussoirs habituels, notre réveil exploitera le clavier matriciel que nous avons déjà utilisé, facilitant ainsi énormément le réglage de l'heure et de l'alarme.
Je vais répartir ce travail sur plusieurs cours afin de vous faire pratiquer la démarche qu'on utilise pour développer une application plus complexe, c'est-à-dire décomposer l'application en modules indépendants qu'on fait ensuite travailler ensemble.
Dans ce cours, vous allez développer uniquement le module de gestion du clavier en ajoutant des fonctionnalités qui n'étaient pas présentes dans la version très simplifiée réalisée précédemment. Voici votre cahier des charges :
Le principe de base reste le même, balayer les colonnes de la matrice et détecter quelle touche est appuyée sur les lignes.
Vous devez éliminer les rebonds. Pour cela, vous vérifierez que la même touche est toujours appuyée au bout de 1/20 s.
Vous devez détecter s'il s'agit d'un appui court ou long (plus de 1 s) afin d'augmenter les possibilités d'interaction à partir d'un nombre de touches limité.
Vous devez déterminer à partir de la position de la touche le code du caractère correspondant.
Vous utiliserez printf() pour afficher le caractère de la touche appuyée et le type d'appui. Notez que sur le CH32V307, USART1_TX est sur PA9 et USART1_RX sur PA10 (au lieu de PD5 et PD6 sur le CH32V003).
Notre CH32V307 ayant 100 broches, il dispose de nombreuses lignes de GPIO et celles qui n'existent pas sur les modèles plus petits n'ont pas (ou peu) de fonctions secondaires ("alternate functions" en anglais). Il est donc judicieux de les utiliser pour connecter notre clavier, que nous relierons donc au port GPIO D. Vous câblerez votre clavier de la façon suivante :
Méthode de travail
Nous avons déjà abordé l'analyse orientée objet dans le cours CH32V003 : I2C et afficheur LCD. Nous avons traité sans les nommer de l'encapsulation, de l'abstraction et de l'héritage, ainsi que du polymorphisme. En effet, ce n'est pas parce que le langage C n'est pas un langage objet qu'on doit se priver pour autant des bénéfices de l'analyse orientée objet.
En complément avec ce que nous avons déjà vu, je vous recommande de vous imprégner des principes généraux suivants :
Le principe de moindre étonnement (POLA)
Le principe DRY
Ce sont des idées très simples, faciles à retenir et elles offrent immédiatement d'immenses bénéfices dès qu'on les applique. Elles sont également applicables dans beaucoup de domaines hors informatique.
GRASP est un autre acronyme très important en développement informatique. Notre exercice actuel étant un peu plus élaboré que celui sur le module LCD, il va nous permettre d'aborder certains aspects de GRASP.
Notre futur réveil matin comportera les éléments suivants :
Un clavier à membrane 12 touches
Un affichage 7 segments à 8 chiffres
Un buzzer passif pour produire des bips (sonnerie, erreurs)
Une horloge temps réel pour gérer l'heure
Sur le plan logiciel, on sait déjà que chaque élément physique sera représenté par un module C exposant ses fonctionnalités :
Clavier :
- Balayer le clavier pour détecter une touche
- Obtenir le code de la touche appuyée, le cas échéant, ainsi que le type d'appui (court ou long)
Affichage :
- Balayer les chiffres pour afficher le texte
- Définir le texte à afficher
Beeper :
- Émettre un bip court pour signaler une erreur de saisie
- Émettre un bip continu pour réveiller le dormeur
- Arrêter le bip de réveil
- Déterminer si le bip de réveil est en cours d'émission
Horloge temps réel :
- Définir l'heure
- Lire l'heure
- Définir l'heure de l'alarme
- Lire l'heure de l'alarme
- Activer / désactiver l'alarme
- Lire l'état de l'alarme
- Déterminer si l'alarme s'est déclenchée
Toutes ces opérations sont liées uniquement à la nature des éléments et à l'usage qui en est fait. Cette description est vraie quel que soit le micro-contrôleur utilisé, par exemple, et elle peut être établie sans connaître précisément les différents composants du système.
Maintenant, puisque nous connaissons les principes généraux de fonctionnement des micro-contrôleurs, nous savons aussi que que nous aurons besoin d'initialiser chaque élément, donc nous pouvons rajouter une fonction "initialisation" à chaque module.
Pour pouvoir aller plus loin, il nous faut à présent décider de la manière dont nous voulons pouvoir utiliser notre réveil. Pour ce projet, j'ai décidé d'utiliser les touches '*' et '#' du clavier pour provoquer des changements d'état, en distinguant les appuis courts et longs pour doubler les possibilités. J'arrive ainsi au diagramme d'états suivant, qui décrit la logique de l'application :
Il est clair que cette logique applicative sera représentée par du code qu'on isolera dans un module C dédié. Enfin, il nous reste encore notre "super loop", qui prendra en charge les balayages périodiques du clavier et de l'affichage, ainsi que l'invocation de la logique applicative lorsqu'une touche est relâchée.
L'ensemble de ces réflexions et décisions nous a amené à distinguer différents composants logiciels et à définir leurs responsabilités, ce qui est précisément l'objet de GRASP. Voici les concepts GRASP mobilisés dans cette analyse :
Information expert : une responsabilité doit être assignée au module qui détient les informations nécessaires pour l'implémenter. Ainsi, le module "clavier" est celui qui connaît l'état du clavier (touche appuyée ou non, depuis combien de temps), donc c'est lui qui prendra en charge toutes les opérations sur le clavier. Rien que de très logique.
Low coupling : on appelle "couplage" le degré de connaissance qu'un module détient sur un autre. Pour qu'un système complexe puisse être développé, le couplage entre ses modules doit être le plus faible (on dit aussi "lâche") possible.
Dans notre cas, chaque module exerce une autorité exclusive sur un élément donné du système et met à disposition du reste de l'application une interface d'utilisation de haut niveau. De plus, nous avons isolé toutes les relations entre module dans le module supplémentaire contenant la logique applicative. Ainsi, nous avons atteint le degré de couplage le plus faible possible.
Cela signifie que nous pouvons changer complètement l'implémentation d'un module donné. Tant que son interface publique (les fonctions qu'il expose au reste du système dans son .h) n'est pas modifiée, nous avons la garantie que le reste du système continuera de fonctionner.
Ca signifie aussi que corriger un bug dans un module n'introduira pas de bug collatéral ailleurs. :)
High cohesion : ce terme désigne le fait de concentrer les responsabilités d'un module sur un seul sujet. Dit autrement, "chacun chez soi et les vaches seront bien gardées". Si on a respecté le principe Information expert, celui-ci sera en principe également respecté et vice-versa.
Controller : un contrôleur est un module qui gère des événements. Dans notre cas, ce rôle incombe au module "logique applicative", qui gère les événements créés dans la "super loop".
Indirection : consiste à réduire le couplage entre 2 modules en représentant leur relation dans un troisième module, qui fait l'intermédiaire. C'est exactement ce que nous avons fait avec le module "logique applicative", notre contrôleur. Sans ce module, l'état de l'application aurait dû être connu de tous les autres modules et ils auraient dû s'appeler les uns les autres aux bons moments. Autant dire que le résultat aurait été une sorte de plat de spaghetti totalement incompréhensible - et n'aurait sans doute même jamais fonctionné !
Vous voyez qu'avec du bon sens et de la logique, on applique spontanément GRASP sans même connaître son existence. Je n'ai pas abordé les autres concepts de GRASP simplement parce qu'ils ne s'appliquent pas dans ce cas précis.
A vous de jouer maintenant ! Et si vous séchez pour cette fois, j'ai joint une solution possible, voir le lien en haut à droite de la page. Ainsi, vous ne serez pas bloqué pour la suite.