Piloter un clavier matriciel sur le bus I2C avec un PCF8574

Dans l'article précédent, le pilotage d'un clavier matriciel connecté à un Arduino avait été présenté. Le clavier utilisé possédait seize touches. Et on avait constaté que l'architecture matricielle de celui-ci avait permis de ne mobiliser que huit pins numériques. Or, huit pins sur un micro-contrôleur qui n'en comporte que douze demeure un nombre relativement élevé. Aussi est-il pertinent de rechercher une solution alternative pour réduire, voire annuler ce nombre.
La solution proposée dans cet article permet de libérer toutes les pins numériques de l'Arduino. Elle procède du même principe que celle présentée pour le module LCD 1602, à savoir de passer par le bus I2C à l'aide d'un circuit intégré PCF8574.

Principe du câblage du clavier avec un PCF8574

Schéma électronique

Comme pour le module LCD 1602, le principe consiste à intercaler un circuit PCF8574 entre le clavier et le micro-contrôleur. Coté Arduino la connexion se fait par les pins SDA et SCL du bus I2C. Coté clavier, celui-ci est connecté sur les huit pins P0 à P7 du PCF8574.
  • Les deux pins du bus I2C de l'Arduino SDA (A4) et SCL (A5 ) sont connectées aux broches SDA et SCL du PCF8574.  Deux résistances de de 10 KΩ, R3 et R4, assurent le pull-up des deux lignes SDA et SCL
  • Les quatre broches des lignes du clavier sont connectées aux broches P0 à P3 du PCF8574. Celles-ci doivent être configurées en écriture.
  • Les quatre broches des colonnes du clavier sont connectées broches P4 à P7 du PCF8574. Celles-ci doivent être configurées en lecture.  

Montage sur une platine d'expérimentation

Utilisation d'un module intégré

Il existe dans le commerce des modules intégrés, d'usage très courant en robotique ou en modélisme, qui facilitent la mise en oeuvre du PCF8574. La connexion se fait par l'intermédiaire de connecteurs. Celui-ci intègre les deux résistances de pull-up et l'adressage se fait par cavaliers switch. De plus, les connecteurs I2C permettent d'enchaîner plusieurs périphériques sur le même bus. Ce qui permet de d'éviter des câblages compliqués.
En ce qui concerne, le clavier matriciel, celui-ci se connecte directement sur le connecteur de sortie parallèle. Pour des claviers plus importants, il un autre circuit, le PCF8575, délivre 16 sorties parallèles ce qui permet de piloter un clavier de 64 touches. Mais, plus facilement, il est possible de chaîner deux modules pour le même résultat (mais cela mobilise deux adresses I2C).

Modification du driver MatrixKeypad pour que le clavier matriciel fonctionne sur le bus I2C

Pour faire fonctionner le clavier matriciel sur le bus I2C, il va falloir dériver la classe MatrixKeypad pour créer une nouvelle classe I2C_MatrixKeypad dans laquelle les méthodes de bas niveau _pinMode(), _pinWrite() et _pinRead(), chargées de l'écriture et de la lecture des pin numériques, sont surchargée pour opérer sur les pins d'un PCF8574 à la place de celles de l'Arduino.

Définition de la classe I2C_MatrixKeypad

La définition de la classe I2C_MatrixKeypad se trouve dans le fichier I2C_MatrixKeypad.h. Ce fichier doit être inclus dans les applications qui l'utilisent par une directive #include.
/* 1 */
#ifndef I2C_MatrixKeypad_h
#define I2C_MatrixKeypad_h

/* 2 */
#include <ICPCF8574.h>
#include "MatrixKeypad.h"

/* 3 */
class I2C_MatrixKeypad : public MatrixKeypad, public I2C_Connectable {
/* 4 */
  ICPCF8574 __ICpcf8574;
/* 5 */
  static byte DEFAULT_ROW_PINS[4];
  static byte DEFAULT_COL_PINS[4];
/* 6 */
  byte __pullUpValue;

  public:
/* 7 */
  I2C_MatrixKeypad(char*, uint8_t*, uint8_t*, uint8_t, uint8_t, uint8_t=0);
  I2C_MatrixKeypad(char*, uint8_t=0);
/* 8 */
  virtual uint8_t I2C_Address() { return this->__ICpcf8574.I2C_Address(); }
/* 9 */
  virtual void begin();


  protected:
/* 10 */
  virtual void _pinMode(uint8_t pin, uint8_t mode) {}
  virtual uint8_t _pinRead(uint8_t pin);
  virtual void _pinWrite(uint8_t pin, uint8_t value); 

  private:
/* 6 */
  void __pull_up_input();
};

/* 1 */
#endif // I2C_MatrixKeypad_h
  1.  Application de la bonne pratique qui consiste à définir les classes d'une bibliothèque entre deux directives #ifndef et #endif afin de prévenir la définition multiple de la même classe dans des projets composites.
  2. Inclusion de la définition des autres composants utilisés :
    • La classe utilise la classe ICPCF8574 définie dans ICPCF8574.h pour instancier un attribut.
    • La classe dérive la classe MatrixKeypad définie dans MatrixKeypad.h pour surcharger les méthodes _pinMode(), pinWrite() et pinRead().
  3. La classe I2C_MatrixKeypad  dérivant la classe MatrixKeypad, elle hérite de toutes les fonctionnalités de cette classe. De plus elle implémente l'interface I2C_Connectable. Cela oblige à implémenter une méthode I2C_Address() pour fournir l'adresse I2C du périphérique.
  4. Le clavier est connecté à un PCF8574. Sa programmation utilise nécessairement la classe ICPCF8574. Une instance de cette classe __ICpcf8574 est déclarée comme attribut privé.
  5. Le clavier se connecte directement sur les pin P0 à P7 du PCF8574. Cette connexion standard est archivée dans deux constantes de classe :
    • DEFAULT_ROW_PINS contient les pins du PCF8574 connectées aux lignes du clavier {7, 6, 5, 4}.
    • DEFAULT_COL_PINS contient les pin du PCF8574 connectées aux colonnes du clavier {3, 2, 1, 0}.
  6. Il n'existe pas de mode INPUT_PULLUP pour configurer les pins du PCF8570. De ce fait lorsqu'une touche est tapée, les pins ligne et colonne court-circuitées, vont modifier l'état de cette dernière en la mettant à 0V. Et celle-ci va conserver cet état après que la touche soit relâchée. Lors de la lecture suivante, la touche va donc toujours être considérée comme appuyée.  C'est pourquoi, avant toute lecture, toutes les pins colonnes doivent être positionnée à +5V. C'est le rôle de la méthode __pull_up_input().
    Pour ne pas avoir à recalculer les pins colonnes à  chaque fois, les numéros correspondants sont agrégés dans un seul nombre __pullUpValue, les bits à 1 de celui-ci correspondant à la position des pins en lecture. De ce fait le pull-up peut être effectué en une seule opération d'écriture.
  7. La classe I2C_MatrixKeypad possède deux constructeurs.
    Le premier reçoit les mêmes paramètres que la classe MatrixKeypad avec en plus le décalage de l'adresse I2C comme pour tous les composants connectés au bus I2C.
    Le second ne reçoit que la matrice des codes des touches et  le décalage de l'adresse I2C en assumant la connexion standard du clavier aux pins P0 à P7 du PCF8574. 
  8. L'implémentation de la méthode I2C_Address() est obligatoire pour calculer l'adresse I2C effective du clavier puisque la classe I2C_MatrixKeypad implémente l'interface I2C_Connectable.
  9. La méthode loop() est déjà implémentée dans l'interface MatrixKeypad pour scanner les touches du clavier. En revanche, l'implémentation de la méthode begin() est  nécessaire pour invoquer celle du composant PCF8574.
  10. Pour que le clavier matriciel puisse fonctionner sur le bus I2C, il est nécessaire de modifier le fonctionnement par défaut des méthodes de bas niveau :
    • La méthode _pinMode()  est déclarée vide. En effet, il n'est pas possible de configurer les pins du PCF8574 en écriture ou en lecture, le sens d'utilisation étant défini par la manière dont le bus est préempté au moment de l'opération. En effet, pour ne pas bloquer le bus et empêcher les autres périphériques de l'utiliser, celui-ci n'es préempté que pendant un temps très court à chaque opération. 
    • La méthode _pinWrite() modifie l'état de la pin passée en paramètre. Elle utilise le cycle d'écriture sur le bus I2C de la bibliothèque standard Wire.
    • La méthode _pinRead() récupère l'état de la pin passée en paramètre. Elle utilise le cycle de lecture du bus I2Cde la bibliothèque standard Wire

Implémentation de la classe I2C_MatrixKeypad

/* 1 */
#include "I2C_MatrixKeypad.h"

/* 2 */
byte I2C_MatrixKeypad::DEFAULT_ROW_PINS[4] = {7, 6, 5, 4};
byte I2C_MatrixKeypad::DEFAULT_COL_PINS[4] = {3, 2, 1, 0};

/* 3 */
I2C_MatrixKeypad::I2C_MatrixKeypad(char *userKeymap, uint8_t *rowPins, uint8_t *colPins, uint8_t countRows, uint8_t countCols, uint8_t offsetI2C) :
  MatrixKeypad(userKeymap, rowPins, colPins, countRows, countCols), __ICpcf8574(offsetI2C)
{
  this->__pullUpValue = 0;
  for (int col = 0; col < this->_countCols; col++) {
    this->__pullUpValue |= (0x01 << this->_getKey(0, col).colPin);
  }
}

/* 4 */
I2C_MatrixKeypad::I2C_MatrixKeypad(char *userKeymap, uint8_t offsetI2C) :
  I2C_MatrixKeypad(userKeymap, DEFAULT_ROW_PINS, DEFAULT_COL_PINS, 4, 4, offsetI2C)
{}

/* 5 */
void I2C_MatrixKeypad::begin() {
  this->__ICpcf8574.begin();
  this->__ICpcf8574.write(0xFF);
}

/* 6 */
byte I2C_MatrixKeypad::_pinRead(uint8_t pin) {
  this->__pull_up_input();
  return this->__ICpcf8574.pinRead(pin);
}

/* 7 */
void I2C_MatrixKeypad::_pinWrite(uint8_t pin, uint8_t value) {
  this->__ICpcf8574.pinWrite(pin, value);
}

/* 8 */
void I2C_MatrixKeypad::__pull_up_input() {
  byte PValue = this->__ICpcf8574.PValue();
  PValue |= this->__pullUpValue;
  this->__ICpcf8574.write(PValue);
}
  1. Inclusion de la définition de la classe contenue dans le fichier I2C_MatrixKeypad.h.
  2. Définitions des constantes de classes :
    • DEFAULT_ROW_PINS contient les pins du PCF8574 connectées aux lignes du clavier {7, 6, 5, 4}.
    • DEFAULT_COL_PINS contient les pin du PCF8574 connectées aux colonnes du clavier {3, 2, 1, 0}.
  3. Le premier constructeur de la classe I2C_MatrixKeypad reçoit les paramètres nécessaires à l'initialisation du clavier et du bus I2C.
    • Les cinq premiers paramètres sont transmis au constructeur de la classe parente MatrixKeypad.
      • userKeymap est un tableau de caractères correspondant à la valeur de chaque touche. Sa taille doit être égale à countCols*countRows.
      • rowPins est un tableau d'entiers contenant le numéro des pins de l'Arduino qui sont connecté au pin des lignes du clavier. Sa taille doit être égale à countRows.
      • colPins est un tableau d'entiers contenant le numéro des pins de l'Arduino qui sont connecté au pin des colonnes du clavier. Sa taille doit être égale à countCols.
      • countRows contient le nombre de ligne du clavier. Ce paramètre est rangé dans l''attribut _countRows.
      • countCols contient le nombre de colonnes du clavier. Ce paramètre est rangé dans l'attribut _countCols.
    • Le paramètre offsetI2C est transmis au constructeur de l'attribut __ICpcf8574 pour initialiser le PCF8574.
    • A partir des numéros des pins du PCF8574, collectés dans le tableau colPins passé en paramètre, auxquelles sont connectées les colonnes du clavier, l'attribut __pullUpValue est calculé pour que chaque bit à 1 corresponde à la position d'une pin connectée à une colonne du clavier.
  4. Le second constructeur de la classe I2C_MatrixKeypad ne prend en compte que les paramètres userKeyMap et offsetI2C en invoquant le constructeur précédent avec des valeur par défaut assumant une connexion standard d'un clavier matriciel à 16 touches sur le PCF8574.
  5. La méthode begin() de la classe I2C_Matrixkeypad invoque la méthode begin() sur l'instance __ICpcf8574. Il est inutile d'invoquer la méthode begin() de la classe parente MatrixKeypad. En effet, les invocations de la méthode _pinMode() qu'elle effectue sont sans effet sur le PCF8574. En revanche, toutes les pins de celui-ci sont positionnées à +5V en y écrivant la valeur 0xFF.
  6. Comme déjà invoqué, les pins du PCF8574 conservent leur état. Comme toutes les touches sont reliées entre colonne par colonne et ligne par ligne, l'état de la pin peut avoir été mis à 0V par la pression d'une autre touche située sur la même ligne. C'est pourquoi, avant toute lecture, les pins que l'on veut lire, celles connectées au colonnes du clavier, doivent être positionnée à +5V. Pour les touches qui sont relâchées, la tension reste à +5V. Mais pour celle qui est appuyée, elle court-circuite la pin qui reprend l'état 0V. Ce qui permet de détecter la pression.
    C'est pourquoi, la méthode _pinRead() invoque la méthode privée __pull_up_input(). Celle ci  positionne les quatre pins connectée aux colonnes à +5V. Il est alors possible de lire la pin relative à la colonne en invoquant la méthode pinRead() sur l'instance __ICpcf8574 qui pilote le PCF8574.
  7. Pour la méthode _pinWrite(), c'est beaucoup plus simple, il suffit d'invoquer la méthode pinWrite() sur l'instance __ICpcf8574.
  8. Le mode INPUT_PULLUP n'étant pas utilisable sur un PCF8574, la méthode privée __pull_up_input() effectue la même fonction par logiciel. A savoir de monter toutes les pins qui peuvent être lues à +5V avant la lecture. Il serait possible d'effectuer d'effectuer l'opération pin par pin en utilisant la méthode _pinWrite(). Mais à chaque invocation de _pinWrite(), un train de huit bit est envoyé sur le bus I2C. Afin d'optimiser l'opération, d'autant qu'elle est exécutée avant chaque lecture, il est préférable de procéder en une seule fois.
    Seulement, la position des pins en lecture est déterminée par le constructeur dans le paramètre colPins. Et il ne faut pas modifier les pins de lignes car l'une d'elle est positionne à LOW (0V) pour détecter si une touche de la ligne est enfoncée. L'attribut privé __pullUpValue, initialisé par le constructeur, dont la position des bits à 1 détermine la position des pins des colonnes, permet d'effectuer une opération OU logique avec la valeur effective de toutes les pins obtenue en invoquant la méthode PValue(). Ce qui permet de ne modifier que les pins des colonnes.

Utilisation de la classe I2C_MatrixKeypad

La classe I2C_MatrixKeypad peut être utilisée à la place de la classe MatrixKeypad. Pour le programme est le même.
#include "I2C_MatrixKeypad.h"

char keyValues[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'},
};

I2C_MatrixKeypad keyboard(makeKeymap(keyValues)); 

struct : public KeypadListener {
  void onChar(MatrixKeypad* keypad, char value) {
    Serial.println(value);
  }
} keypadListener;

void setup() {
  Serial.begin(9600);
  keyboard.addListener(&keypadListener);
  keyboard.begin();
  I2C_Device::scanI2CDeviceBus();
}

void loop() {
  keyboard.loop();
}
  • En premier, il faut inclure la définition de la classe I2C_MatrixKeypad. Celle-ci inclut les classes de la bibliothèque MatrixKeypad.
  • Une instance keyboard de la classe I2C_MatrixKeypad est instanciée à la place de la classe MatrixKeypad. Les paramètres suivants sont communiqué au constructeur de la classe pour initialiser l'instance :
    • Le premier paramètre est le tableau keyValues contenant le code des touches. En C++ (comme en C) les tableaux sont identifiés par l'adresse de leur premier membre. keyValues est donc un pointeur, mais un pointeur sur un tableau donc un pointeur sur un pointeur. Or le premier paramètre du constructeur est déclaré char*keyValues étant un tableau de char*, les deux types sont normalement compatibles. Pour éviter un message désagréable du compilateur, la macro ci-dessous, définie dans MatrixKeypad.h assure la conversion :
      #define makeKeymap(map) ((char *)map) 
    • Le second paramètre est le décalage d'adresse I2C déterminé par le câblage des pin A0 à A2 du PCF8574. Lorsqu'il est omis, le PCF8574 est adressé à son adresse de base 0x20.
  • Dans la fonction setup(), il a été ajouté un appel à la méthode scanI2CDeviceBus() pour vérifier que le PCF8574 est bien reconnu à l'adresse I2C prévue, dans le cas où un dysfonctionnement serait constaté.
  • Pour le reste, c'est exactement la même chose que pour la classe MatrixKeypad.

Conclusion

La classe I2C_MatrixKeypad  peut remplacer la classe MatrixKeypad pour piloter un clavier matriciel connecté sur le bus I2C. Cela permet de libérer toutes les pins numérique de l'Arduino. Et utilisé avec le module intégré, le câblage devient ridiculement simple. D'autant qu'on peut chaîner derrière d'autres périphériques, comme le module LCD 1602 piloté aussi par un PCF8574. Il faut juste contrôler d'éventuels conflits d'adresse, qui peuvent être réglés aisément en modifiant la position de quelques cavaliers switch et de paramétrer les constructeurs des classes utilisées en conséquence.

Commentaires

Posts les plus consultés de ce blog

Afficheur à LED 7 segments (classe SegmentLedDisplay)

Utiliser Visual Studio Code pour développer sur Arduino