La carte Arduino UNO est une carte matériellement libre dotée d’un microcontrôleur programmable. Celui-ci est composé d’un microprocesseur, de mémoire ROM pour stocker un programme et de mémoire vive pour stocker des états de variable.

Cette carte associée à d’autre composants, comme des LEDs par exemple, vont permettre de réaliser un infinité de projets électroniques différents. Et ce grâce à l’IDE Arduino qui permet de programmer dans un langage s’apparentant au C/C++.

Ce projet qui à la base était destiné aux débutants en électronique et informatique, est vite devenu une référence pour tous les passionnés et/ou les curieux d’informatique embarquée.

Après avoir épluché la documentation officielle, et fait quelques essais, je vous invite à un petit exercice simple et intuitif : construire et implémenter un traducteur de morse.

Le code morse

Alphabet morse

Alphabet morse

Inventé en 1832, le morse est un moyen de communication se basant sur des séries d’impulsions courtes ou longues. Nous utiliserons le code morse international comme référence.

Le matériel

Pour réaliser ce projet, on aura besoin de :

  • 1 carte Arduino Uno
  • 1 platine d’expérimentation
  • 1 écran LCD 2×16
  • 2 LEDs de différente couleur (verte et rouge)
  • 1 potentiomètre
  • 1 bouton poussoir
  • 1 résistance de 10k Ohms pour le bouton
  • 2 résistances de 560 Ohms pour les LEDs
  • 1 résistance de 220 Ohms pour l’écran LCD
  • quelque cables
Schéma Arduino

Schéma Arduino

Le programme

J’ai choisi une approche plus impérative en essayant de ne programmer qu’en langage C, pour éviter les questions de conception orientée objet, et ne me focaliser que sur l’algorithmique et les fonctionnalités de l’Arduino.

Rentrons maintenant dans le vif du sujet : le code.

Inclusion des bibliothèques

/* Inclusion de la bibliothèque standard */
#include <stdlib.h>

/* Inclusion de la bibliothèque des booléens */
#include <stdbool.h>

/* Inclusion de la bibliothèque de l'écran LCD */
#include <LiquidCrystal.h>

Définition des types

/* Définition des index des pins digitaux des éléments */
typedef enum
{
  BUTTON    = 2,
  GREENLED  = 3,
  REDLED    = 4,
  LCDRS     = 8,
  LCDENABLE = 9,
  LCDD4     = 10,
  LCDD5     = 11,
  LCDD6     = 12,
  LCDD7     = 13
} pinIndex;

/* Définition des status des LEDs */
typedef enum
{
  OFF,
  GREEN,
  RED
} ledStatus;

/* Définition des type de signaux en morse */
typedef enum
{
  NONE,
  SHORT,
  LONG
} morseSignal;

Gestion du temps

/* Définition du temps d'attente d'un appui long sur le button (en ms) */
#define LONGPRESS_DELAY 500

/* Définition du temps d'attente sans appui sur le button (en ms)  */
#define NOPRESS_DELAY   1000

/* Déclaration du temps passé */
unsigned long time;

/* Fonction de réinitialisation du temps */
void resetTime(void)
{
  time = millis(); // On récupère la valeur du nombre de millisecondes passé depuis le démarrage du programme
}

/* Fonction de récupération du temps passé depuis la dernière réinitialisation */
unsigned long getElapsedTime(void)
{
  return millis() - time; // On retourne la différence entre le nombre de millisecondes passé depuis le démarrage du programme et celui stocké
}

Gestion des caractères de morse

/* Définition du caractère non trouvé */
#define UNDEFINED_CHAR -1

/* Définition de la taille maximale d'un tableau de signaux */
#define MORSECHARSIGNALS_LENGTH 7

/* Définition de la structure d'association tableau de signaux / lettre */
typedef struct
{
  unsigned char caracter;
  morseSignal   signals[MORSECHARSIGNALS_LENGTH];
} morseCaracter;

/* Initialisation du tableau des associations tableau de signaux / lettre */
static morseCaracter morseCaracters[] =
{
  { 'a', { SHORT, LONG } },
  { 'b', { LONG, SHORT, SHORT, SHORT } },
  { 'c', { LONG, SHORT, LONG, SHORT } },
  { 'd', { LONG, SHORT, SHORT } },
  { 'e', { SHORT } },
  { 'f', { SHORT, SHORT, LONG, SHORT } },
  { 'g', { LONG, LONG, SHORT } },
  { 'h', { SHORT, SHORT, SHORT, SHORT } },
  { 'i', { SHORT, SHORT } },
  { 'j', { SHORT, LONG, LONG, LONG } },
  { 'k', { LONG, SHORT, LONG } },
  { 'l', { SHORT, LONG, SHORT, SHORT} },
  { 'm', { LONG, LONG } },
  { 'n', { LONG, SHORT } },
  { 'o', { LONG, LONG, LONG } },
  { 'p', { SHORT, LONG, LONG, SHORT } },
  { 'q', { LONG, LONG, SHORT, LONG } },
  { 'r', { SHORT, LONG, SHORT } },
  { 's', { SHORT, SHORT, SHORT } },
  { 't', { LONG } },
  { 'u', { SHORT, SHORT, LONG } },
  { 'v', { SHORT, SHORT, SHORT, LONG } },
  { 'w', { SHORT, LONG, LONG } },
  { 'x', { LONG, SHORT, SHORT, LONG } },
  { 'y', { LONG, SHORT, LONG, LONG } },
  { 'z', { LONG, LONG, SHORT, SHORT } },
  { '0', { LONG, LONG, LONG, LONG, LONG } },
  { '1', { SHORT, LONG, LONG, LONG, LONG } },
  { '2', { SHORT, SHORT, LONG, LONG, LONG } },
  { '3', { SHORT, SHORT, SHORT, LONG, LONG } },
  { '4', { SHORT, SHORT, SHORT, SHORT, LONG } },
  { '5', { SHORT, SHORT, SHORT, SHORT, SHORT } },
  { '6', { LONG, SHORT, SHORT, SHORT, SHORT } },
  { '7', { LONG, LONG, SHORT, SHORT, SHORT } },
  { '8', { LONG, LONG, LONG, SHORT, SHORT } },
  { '9', { LONG, LONG, LONG, LONG, SHORT } },
  { '.', { SHORT, LONG, SHORT, LONG, SHORT, LONG } }
};

/* Fonction de récupération de la taille du tableau des associations */
unsigned int getMorseCaractersLength(void)
{
  return sizeof(morseCaracters) / sizeof(*morseCaracters); // On retourne la taille totale du tableau divisée par un de ses éléments
}

/* Fonction de récupération de la taille d'un des tableau de signaux du tableau des associations */
unsigned int getMorseCaracterSignalsLength(morseSignal* morseSignals)
{
  unsigned int n = 0;

  while (n < MORSECHARSIGNALS_LENGTH) // On parcourt la taille du tableau
  {
    if (morseSignals[n] == NONE) // Si le signal actuel n'est pas défini, on arrête
      break;
    ++n;
  }
  return n;
}

Gestion des signaux

/* Déclaration de notre tableau de signaux */
morseSignal* signals;

/* Déclaration de la position dans le tableau de signaux */
unsigned int signalsIndex;

/* Fonction de réinitialisation du tableau de signaux */
void resetSignals(void)
{
  if (signals) // Si le tableau n'est pas vide
    free(signals); // On libère la mémoire

  signalsIndex = 0; // On réinitialise la position
  signals = NULL; // On réinitialise le tableau
}

/* Fonction de récupération de la taille du tableau de signaux */
unsigned int getSignalsLength(void)
{
  return signalsIndex; // On retourne simplement la position
}

/* Fonction d'ajout d'un signal dans le tableau de signaux */
void addSignal(morseSignal signal)
{
  signals = (morseSignal*)realloc(signals, (signalsIndex + 1) * sizeof(*signals)); // On agrandi la taille du tableau de signaux de 1
  signals[signalsIndex++] = signal; // On stocke le nouveau signal
}

/* Fonction de récupération d'une lettre du tableau des associations en fonction de notre tableau de signaux */
char getMorseCaracter(void)
{
  unsigned int signalsLength = getSignalsLength(); // On récupère la taille de notre tableau de signaux

  for (unsigned int i = 0; i < getMorseCaractersLength(); ++i) // On parcourt le tableau des associations
  {
    if (signalsLength == getMorseCaracterSignalsLength(morseCaracters[i].signals)) // Si la taille de notre tableau de signaux est égale à la taille de celui de l'association
    {
      for (unsigned int j = 0; j < signalsLength; ++j) // On parcourt notre tableau de signaux et celui de l'association
      {
        if (signals[j] != morseCaracters[i].signals[j]) // Si les caractères sont différents
          break; // On passe à une autre association
        else if (j + 1 == signalsLength) // Si on est arrivé au bout des 2 tableaux
          return morseCaracters[i].caracter; // On retourne la lettre de l'association
      }
    }
  }
  return UNDEFINED_CHAR; // Sinon on retourne caractère non trouvé
}

Contrôle de l’écran LCD

/* Définition des nombres de lignes et de colonnes de l'écran LCD */
#define LCD_NUMROWS 2
#define LCD_NUMCOLS 16

/* Définition des chaînes de caractères à afficher */
#define MORSE_LINE  "Listening morse"
#define BLANK_LINE  "                "

/* Initialisation de la librairie de l'écran LCD */
LiquidCrystal lcd(LCDRS, LCDENABLE, LCDD4, LCDD5, LCDD6, LCDD7);

/* Fonction d'initialisation de l'écran LCD */
void initLcd(void)
{
  lcd.begin(LCD_NUMCOLS, LCD_NUMROWS); // On définie le nombre de lignes et de colonnes de l'écran LCD
  lcd.print(MORSE_LINE); // On affiche le titre sur la 1ère ligne
  resetLcd(); // On réinitialise la 2ème ligne
}

/* Fonction de réinitialisation de la 2ème ligne l'écran LCD */
void resetLcd(void)
{
  lcd.setCursor(0, 1); // On place le curseur au début de la 2ème ligne
  lcd.print(BLANK_LINE); // On affiche une ligne vide
  lcd.setCursor(0, 1); // On replace le curseur au début de la 2ème ligne
}

/* Fonction d'affichage d'un caractère sur l'écran LCD */
void displayCaracterOnLcd(char caracter)
{
  static unsigned int cursorIndex = 0; // Postion horizontale

  if (caracter != UNDEFINED_CHAR) // Si le caractère n'est pas non trouvé
  {
    lcd.print(caracter); // On affiche le caractère
    ++cursorIndex; // On incrémente la position horizontale
  }

  if (cursorIndex == LCD_NUMCOLS) // Si la position horizontale est égale au nombre de colonne
  {
    resetLcd(); // On réinitialise la deuxième ligne
    cursorIndex = 0; // On réinitialise la position
  }
}

Contrôle des LEDs

/* Fonction d'initialisation des pins digitaux des LEDs */
void initLedPinMode(void)
{
  pinMode(GREENLED, OUTPUT); // On met le pin digital de la LED verte dans un état de basse impédance
  pinMode(REDLED, OUTPUT); // On met le pin digital de la LED rouge dans un état de basse impédance
}

/* Fonction d'allumage / extinction des LEDs */
void switchLed(ledStatus status)
{
  digitalWrite(GREENLED, (status == GREEN)); // On passe le voltage de la LED verte à 5 Volt si le status est correspondant à celle-ci, sinon on le passe à 0 Volt
  digitalWrite(REDLED, (status == RED)); // On passe le voltage de la LED rouge à 5 Volt si le status est correspondant à celle-ci, sinon on le passe à 0 Volt
}

Contrôle du bouton

/* Fonction d'initialisation du pin digital du bouton */
void initButtonPinMode(void)
{
  pinMode(BUTTON, INPUT); // On met le pin digital du bouton dans un état de haute impédance
}

/* Fonction pour savoir si le bouton est-il pressé */
bool isButtonPressed(void)
{
  return (digitalRead(BUTTON) == LOW); // Le pin digital du bouton a t'il un voltage inférieur à 1.5 Volt
}

/* Fonction de récupération du type de signal en morse en fonction du temps de pression sur le bouton */
morseSignal getButtonSignal(void)
{
  return (getElapsedTime() >= LONGPRESS_DELAY) ? LONG : SHORT; // On retourne le type de pression en fonction du temps défini
}

Gestion des états du bouton

/* Fonction de management d'une nouvelle pression sur le bouton */
void manageNewlyPressedButton(void)
{
  switchLed(GREEN); // On allume la LED verte
  resetTime(); // On réinitialise le temps à partir de maintenant
}

/* Fonction de management d'un nouveau relâchement du bouton */
void manageNewlyUnpressedButton(void)
{
  addSignal(getButtonSignal()); // On ajoute le signal produit à notre tableau de signaux
  switchLed(OFF); // On éteind les LEDs
  resetTime(); // On réinitialise le temps à partir de maintenant
}

/* Fonction de management d'une continuation de pression sur le bouton */
void manageContinuallyPressedButton(void)
{
  switchLed((getElapsedTime() < LONGPRESS_DELAY) ? GREEN : RED); // On allume la LED correspondante au type de pression sur le bouton
}

/* Fonction de management d'une continuation de relâchement du bouton */
void manageContinuallyUnpressedButton(void)
{
  if (getElapsedTime() >= NOPRESS_DELAY) // Si le temps écoulé est supérieur à celui défini
  {
    displayCaracterOnLcd(getMorseCaracter()); // On essaye de trouver et d'afficher la lettre produite avec les signaux
    resetSignals(); // On réinitialise notre tableaux de signaux
    resetTime(); // On réinitialise le temps à partir de maintenant
  } 
}

Base du programme

/* Fonction de paramétrage du programme */
void setup(void)
{
  initLedPinMode(); // On initialise les pins digitaux des LEDs
  initButtonPinMode(); // On initialise le pin digital du bouton
  initLcd(); // On initialise l'écran LCD

  resetSignals(); // On initialise notre tableaux de signaux
  resetTime(); // On initialise le temps à partir de maintenant
}

/* Fonction de boucle du programme */
void loop(void)
{
  static bool isButtonPreviouslyPressed; // Le bouton était-il pressé pendant la dernière boucle
  static bool isButtonCurrentlyPressed; // Le bouton est-il pressé actuellement

  isButtonPreviouslyPressed = isButtonCurrentlyPressed; // On met la valeur actuelle dans celle de la dernière boucle
  isButtonCurrentlyPressed = isButtonPressed(); // On récupère la valeur actuelle

  if (isButtonCurrentlyPressed) // Si le bouton est pressé
  {
    if (isButtonPreviouslyPressed) // Si il l'était aussi avant
      manageContinuallyPressedButton();
    else // Si il ne l'était pas avant
      manageNewlyPressedButton();
  }
  else // Si le bouton n'est pas pressé
  {
    if (!isButtonPreviouslyPressed) // Si il ne l'était pas non plus avant
      manageContinuallyUnpressedButton();
    else // Si il l'était avant
      manageNewlyUnpressedButton();
  }
}

Résultat

Kortes.io en morse

Kortes.io en morse

Et voilà le travail ! Si cela a éveillé votre curiosité, je vous invite à commencer dés maintenant avec le Starter Kit d’Arduino, disponible ici. Et si vous souhaitez vous entraîner, vous trouverez ci-dessous le fichier en téléchargement :

Il n'y a pas de commentaire pour l'instant.