dimanche 19 avril 2020

Arduino - Horloge pixel bleu

Préambule


L'idée de ce projet est d'expérimenter un peu le Arduino et les modules qui gravitent autour, ainsi que marier cela avec l'impression 3D.
Je vais donc construire une petite horloge qui affichera l'heure et la date avec des pixels bleu, on pourra passer de l'heure à la date et vis versa à l'aide d'un bouton.

Matériels


Outils


  • Imprimente 3D Alfawise U30 Pro
  • Fer à souder
  • Pinces diverses
  • IDE Arduino
  • Breadboard et fils de teste

Conception


Pour mettre au point mon câblage et programmer mon arduino nano j'ai commencé par faire un montage sur une breadboard.

Câblage des composants


Une photo du câblage sur breadboard :


Ce qui donne schématiquement le montage suivant :


Les connexions sont les suivantes :

Max7219 Arduino
Vcc 5V
Gnd Gnd
DIN D11
CS D10
CLK D13

RTC DS3231 Arduino
Vcc 3V3
Gnd Gnd
SDA A4
SCL A5

Bouton Arduino
1 D3
2 Gnd


Programmation


Pour la programmation de notre horloge on utilise les librairies LedControl.h pour la gestion de l'affichage sur la matrice led et RTClib.h pour la lecture du module RTC.

Commençons par les déclarations :

#include "LedControl.h"
#include "RTClib.h"

#define CLK_PIN   13
#define DATA_PIN  11
#define CS_PIN    10

#define MAX_DEVICES 4
#define DELAY 50

#define button 3

LedControl lc = LedControl(DATA_PIN,CLK_PIN,CS_PIN,MAX_DEVICES);
RTC_DS3231 rtc;

On inclue les deux librairies précédemment citées, puis on défini le numéro des trois PIN digital de l'arduino qui vont commander les matrices led.
Ensuite on défini le nombre de matrices led qui constitue notre afficheur et un délai entre deux rafraîchissement de l'affichage. En dernier on défini sur quel PIN digital on va recevoir les appuis sur le bouton.
Enfin on récupère les références de LedControl et de RTC.

Déclaration des variables globales :

byte data[MAX_DEVICES][8];

// 4 x 7
byte numeric[][8] = {
  {0xF0,0x90,0x90,0x90,0x90,0x90,0xF0,0x00}, // 0
  {0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00}, // 1
  {0xF0,0x10,0x10,0xF0,0x80,0x80,0xF0,0x00}, // 2
  {0xF0,0x10,0x10,0x70,0x10,0x10,0xF0,0x00}, // 3
  {0x90,0x90,0x90,0xF0,0x10,0x10,0x10,0x00}, // 4
  {0xF0,0x80,0x80,0xF0,0x10,0x10,0xF0,0x00}, // 5
  {0xF0,0x80,0x80,0xF0,0x90,0x90,0xF0,0x00}, // 6
  {0xF0,0x10,0x10,0x10,0x10,0x10,0x10,0x00}, // 7
  {0xF0,0x90,0x90,0xF0,0x90,0x90,0xF0,0x00}, // 8
  {0xF0,0x90,0x90,0xF0,0x10,0x10,0xF0,0x00}, // 9
  {0x00,0x00,0x80,0x00,0x80,0x00,0x00,0x00}, // :
  {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}  // empty
};

byte tempc[][8] = {
  {0x00,0xF8,0x20,0x20,0x20,0x20,0x20,0x00}, // T (5)
  {0x00,0x00,0x70,0x80,0x80,0x80,0x70,0x00}, // c (4)
  {0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x00}  // : (1)
};

// 3 x 5
byte numdate[][8] = {
  {0x00,0x00,0xE0,0xA0,0xA0,0xA0,0xE0,0x00}, // 0
  {0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x00}, // 1
  {0x00,0x00,0xE0,0x20,0xE0,0x80,0xE0,0x00}, // 2
  {0x00,0x00,0xE0,0x20,0xE0,0x20,0xE0,0x00}, // 3
  {0x00,0x00,0xA0,0xA0,0xE0,0x20,0x20,0x00}, // 4
  {0x00,0x00,0xE0,0x80,0xE0,0x20,0xE0,0x00}, // 5
  {0x00,0x00,0xE0,0x80,0xE0,0xA0,0xE0,0x00}, // 6
  {0x00,0x00,0xE0,0x20,0x20,0x20,0x20,0x00}, // 7
  {0x00,0x00,0xE0,0xA0,0xE0,0xA0,0xE0,0x00}, // 8
  {0x00,0x00,0xE0,0xA0,0xE0,0x20,0xE0,0x00}, // 9
  {0x00,0x00,0x20,0x40,0x40,0x40,0x80,0x00}  // /
};

int buttonVal = 1;

On commence par définir un tableau à deux dimensions de 4x8 pour accueillir l'état des led de chacune des quatre matrice led.
On construit ensuite des tableaux contenant les différentes combinaisons de valeurs des leds afin d'afficher les chiffres et les caractères nécessaires. Pour cela on donne l'état (1 ou 0) de chaque bit sur 8 lignes et 8 colonnes.
Pour définir les valeurs de chaque caractère je me suis aidé d'un petit outil permettant de dessiner graphiquement l'état de chaque led, il s'agit de PixelToMatrix que vous pouvez trouver à l'adresse suivante : http://generator1116.rssing.com/chan-36314998/all_p1.html
Enfin on déclare une variable qui nous servira à savoir quel affichage est demandé lors de l'appui sur le bouton.

Initialisation :

void setup() {
  pinMode(button, INPUT_PULLUP);
  
  for (int i = 0; i < MAX_DEVICES; i++)
  {
    lc.shutdown(i,false);
    lc.setIntensity(i,5);
    lc.clearDisplay(i);
    for (int j = 0; j < 8; j++)
    {
      data[i][j] = numeric[11][j];
    }
  }

  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // If the RTC have lost power it will sets the RTC to the date & time this sketch was compiled in the following line
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }
}

Dans la fonction setup() on initialise le PIN du bouton sur un mode de pullup interne, ensuite initialise des quatre matrices leds en les effaçant et en réglant leur intensité.
On initialise ensuite le module RTC en ajustant notamment la date et l'heure en fonction de celle de l'ordinateur qui téléverse le programme.

Boucle du programme :

void loop() {
  if (!digitalRead(button)) 
  {
    changeDisplay();
    delay(500);
  }
  fillData(true);
  display();
  delay(DELAY);
}

Dans la boucle du programme on commence par lire l'état du bouton (le bouton étant configuré en pullup on reçoit LOW quand il est appuyé et HIGH autrement), s'il est appuyé on change la valeur de display et on temporise pendant 500ms.
Ensuite on rempli le tableau de chaque matrice led en fonction de la valeur d'affichage sélectionnée par le bouton.
On envoi enfin les données à chaque matrice led pour l'affichage et on applique un délai correspondant à la valeur de délai défini au début du programme.

Remplissage des données :

void fillData(boolean sep) 
{
  DateTime now = rtc.now();
  if (buttonVal == 1) // Display hour
  {
    int num1 = now.hour() / 10;
    int num2 = now.hour() % 10;
    int num3 = now.minute() / 10;
    int num4 = now.minute() % 10;
    int num5 = now.second() / 10;
    int num6 = now.second() % 10;
    int separator = 10;
    if (!sep) {
      separator = 11;
    }
  
    for(int i = 0; i < 8; i++)
    {
      data[3][i] = (numeric[num1][i] >> 1) + (numeric[num2][i] >> 6);
      data[2][i] = (numeric[num2][i] << 2) + (numeric[separator][i] >> 2) + (numeric[num3][i] >> 3); //+ (numeric[num4][i] >> 7);
      data[1][i] = (numeric[num4][i]) + (numeric[separator][i] >> 4) + (numeric[num5][i] >> 5);
      data[0][i] = (numeric[num5][i] << 3) + (numeric[num6][i] >> 2);
    }
  }
  else if (buttonVal == 3) // Display temperature
  {
    int num1 = 0;
    int num2 = 0;
    int temperature = rtc.getTemperature();
    if (temperature < 10)
    {
      num2 = temperature;
    }
    else
    {
      num1 = temperature / 10;
      num2 = temperature % 10;
    }
    
    for(int i = 0; i < 8; i++)
    {
      data[3][i] = tempc[0][i] + (numdate[num1][i] >> 6);
      data[2][i] = (numdate[num1][i] << 2) + (numdate[num2][i] >> 2) + (tempc[1][i] >> 6);
      data[1][i] = (tempc[1][i] << 2);
      data[0][i] = numeric[11][i];
    }
  }
  else if (buttonVal == 2) // Display date
  {
    int jour1 = now.day() / 10;
    int jour2 = now.day() % 10;
    int mois1 = now.month() / 10;
    int mois2 = now.month() % 10;
    int annee1 = (now.year() % 100) / 10;
    int annee2 = (now.year() % 100) % 10;

    for(int i = 0; i < 8; i++)
    {
      data[3][i] = numdate[jour1][i] + (numdate[jour2][i] >> 4);
      data[2][i] = numdate[10][i] + (numdate[mois1][i] >> 4);
      data[1][i] = numdate[mois2][i] + (numdate[10][i] >> 4);
      data[0][i] = numdate[annee1][i] + (numdate[annee2][i] >> 4);
    }
  }
}

Dans la fonction filldata() on récupère l'heure et la date courante ou la température afin de préparer l'affichage sur chaque matrice led.
Pour l'heure par exemple on fait un découpage en des heures, minutes et secondes en deux chiffres puis on va chercher le caractère correspondant dans le tableau prédéfini pour l'afficher sur la matrice led. On effectue des décalage de bits pour placer chaque chiffre correctement sur l'affichage.

Affichage et changement de l'affichage :

void display()
{
  for (int matrix = 0; matrix < MAX_DEVICES; matrix++)
  {
    for (int row = 0; row < 8; row++)
    {
      lc.setRow(matrix,row,data[matrix][row]);
    }
  }
}

void changeDisplay() {
  buttonVal++;
  if (buttonVal > 3)
  {
    buttonVal = 1;
  }
}

Dans la fonction display() on parcoure le tableau de données pour l'envoyer aux matrices led qui constitues l'afficheur.
La fonction changeDisplay() quand à elle va changer la valeur de display lors de chaque appui sur le bouton pour passer de 1 à 3 puis reboucler lorsque la valeur 3 est atteinte.


Mise à jour de l'heure et de la date :

Pour finir sur la programmation j'ai intégré le code provenant de cet article : http://www.semageek.com/arduino-un-utilitaire-bien-pratique-pour-mettre-a-lheure-les-modules-rtc
Ce qui me permet de mettre à jour l'heure et la date sans avoir à re-téléverser le programme sur l'Arduino pour le changement d'heure d'été/hiver par exemple.


Impression 3D


Le boîtier est réalisé à l'aide de l'impression 3D et de la modélisation 3D avec le logiciel Fusion 360. Il se compose d'une partie supérieur et d'une partie inférieur, ainsi que d'une grille permettant de transformer les leds rondes en pixels carrés.


En haut de l'image la partie supérieur du boîtier avec la grille et un film opacifiant, en bas la partie inférieur avec en orange le bouton.


Les deux parties sous un autre angle de vue.


La photo avec le flash ne rend pas aussi bien que dans la réalité (affichage plus lumineux, etc..) mais cela donne une idée du résultat.
Un axe d'amélioration serait d'ajouter un film noir pour cacher le film opacifiant et assombrir l'écran.

Conclusion


Voici une petite réalisation qui ma permit d'expérimenter un peu avec un Arduino et quelques modules et de le combiner avec l'impression 3D.
J'espère que cet article vous aura intéressé et n’hésitez pas à laisser un commentaire si vous avez des questions ou des remarques constructives.
Merci d'avoir pris le temps de lire cet article et à bientôt.

Retrouvez les sources sur github.com