dimanche 25 novembre 2018

Afficheur MAX7219 et RTC DS3231 sur STM32








Afficheur MAX7219 et RTC DS3231

sur STM32


J'aborde une phase cruciale de mon existence : le dernier trimestre de ma vie professionnelle.

Il m'est venu à l'idée d'afficher sur mon bureau le décompte de secondes qui va s'écouler jusqu'à ce moment tant attendu.

Une espèce de QUILLE électronique.

1. Le matériel

Qu'allons-nous utiliser comme matériel ?
Un ARDUINO avec un MAX7219 + afficheur 8x7 segments ?
Oui mais c'est trop facile. Il fallait que je fasse un petit effort supplémentaire.

J'ai donc choisi cette carte : la "petite" BLUE PILL que l'on peut trouver ICI à un prix plus qu'intéressant.

Pour le même prix qu'une NANO :
  • cœur 32bits 72MHz
  • large gamme de tension d'utilisation de 2V à 3.6V
  • 128Ko de mémoire flash
  • 20Ko de SRAM
  • RTC interne
  • unité de calcul CRC, ID unique 96 bits
  • deux convertisseurs A/N 1µs 12 bits (jusqu'à 10 voies)
  • dontrôleur DMA 7 voies, 3 timers à usage général et 1 timer de contrôle avancé
  • 37 ports E/S rapides
  • interfaces de débogage fil série (SWD) et JTAG
  • interfaces : deux SPI, deux I2C, trois USART, une USB et une CAN
  • 50% des entrées sont tolérantes au 5V

Le module MAX7219 se présente sous cette forme :
Il est câblé comme suit :
  • GND : GND
  • VCC : 3.3V
  • DIN : PA1
  • CLK : PA2
  • CS : PA3
Afin de conserver l'heure et la date en cas de coupure, un module RTC DS3231 est utilisé :
 
Il est câblé ainsi :
  • GND : GND
  • VCC : 3.3V
  • SDA : PB7
  • SCL : PB6
Le STM32 a deux I2C et l'I2C1 peut utiliser soit PB9+PB8 ou PB7+PB6 en fonction de la configuration des GPIOs.
Le problème est qu'il faudrait lire le code de la librairie core pour savoir comment tout cela est configuré.

Remarque : le STM32F103C8 possède un RTC interne et un oscillateur 32.768KHz est implanté sur la carte BLUE PILL.
Mais j'avais envie de jouer avec l'I2C. :)

2. Le logiciel

Mon intention est de faire tourner ce petit sketch :

#include <TimeLib.h>
#include <LedControl.h>
#include <Wire.h>

#define TIME_HEADER  "T"   // Header tag for serial time sync message
#define TIME_REQUEST  7    // ASCII bell character requests a time sync message 

#define LED     PC13
#define DS3231_I2C_ADDRESS  0x68

LedControl lc = LedControl(PA1, PA2, PA3, 1);

const unsigned long DEFAULT_TIME = 1543138573; // Nov 25 2018

byte decToBcd(byte val)
{
  return ((val / 10 * 16) + (val % 10));
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ((val / 16 * 10) + (val % 16));
}

void setDS3231time(int second, int minute, int hour, int wday, int day, int month, int year)
{
  Serial.println("Writing DS3231: ");
  Serial.print(day);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.print(year);
  Serial.print(" ");
  Serial.print(hour);
  Serial.print(":");
  Serial.print(minute);
  Serial.print(":");
  Serial.println(second);
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(wday)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(day)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year-2000)); // set year (0 to 99)
  Wire.endTransmission();
}

void readDS3231time(int *second, int *minute, int *hour, int *wday, int *day, int *month, int *year)
{
  Serial.print("Reading DS3231: ");
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *wday = bcdToDec(Wire.read());
  *day = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read()) + 2000;
  Serial.print(*day);
  Serial.print("/");
  Serial.print(*month);
  Serial.print("/");
  Serial.print(*year);
  Serial.print(" ");
  Serial.print(*hour);
  Serial.print(":");
  Serial.print(*minute);
  Serial.print(":");
  Serial.println(*second);
}

void processSyncMessage() {
  time_t pctime;

  if (Serial.find(TIME_HEADER)) {
    pctime = Serial.parseInt();
    Serial.print("PC TIME: ");
    Serial.println(pctime);
    if ( pctime >= DEFAULT_TIME) { // check the integer is a valid time
      setTime(pctime); // Sync Arduino clock to the time received on the serial port
      setDS3231time(second(), minute(), hour(), weekday(), day(), month(), year());
    }
  }
}

time_t requestSync()
{
  Serial.write(TIME_REQUEST);
  return 0; // the time will be sent later in response to serial mesg
}

void setup()
{
  Serial.begin(115200);
  Wire.begin();
  pinMode(LED, OUTPUT);
  lc.shutdown(0, false);
  lc.setIntensity(0, 4);
  lc.clearDisplay(0);
  setTime(DEFAULT_TIME);
  setSyncProvider(requestSync);
}

tmElements_t t = {0, 0, 0, 6, 1, 2, 2019 - 1970};

void loop()
{
  int second = 0, minute = 0, hour = 0, wday = 0, day = 0, month = 0, year = 0;

  readDS3231time(&second, &minute, &hour, &wday, &day, &month, &year);
  if (second > 59 || minute > 59 || hour > 23 | day > 31 || year > 2030) {
    Serial.println("Invalid Time");
    setDS3231time(0, 0, 0, 1, 25, 11, 2018);
  }
  setTime(hour, minute, second, day, month, year);

  time_t retirement = makeTime(t);  // convert time elements into time_t
  if (Serial.available()) {
    processSyncMessage();
  }
  lc.clearDisplay(0);
  printNumber(0, retirement - now());
  if (timeStatus() == timeSet) {
    digitalWrite(LED, LOW); // LED on if synced
  } else {
    digitalWrite(LED, HIGH);  // LED off if needs refresh
  }
  delay(1000);
}

void printNumber(int addr, long num) {
  byte c;
  int j;
  int d;
  num = abs(num);
  Serial.print(":");
  Serial.println(num);
  d = countDigits(num);
  for (j = 0; j < d; j++) {
    c = num % 10;                      // Modulo division = remainder
    num /= 10;                         // Divide by 10 = next digit
    boolean dp = (j == 3);              // Add decimal point at 3rd digit.
    lc.setDigit(addr, j, c, dp);
  }
}

int countDigits(long num) {
  int c = 0;
  if (num == 0) {
    return 1;
  }
  while (num) {
    c++;
    num /= 10;
  }
  return c;
}

Comme vous pouvez le remarquer, la différence par rapport à un sketch ARDUINO ne saute pas aux yeux.
Ceci est normal, car je l'ai développé d'abord sur une NANO.

Seules ces deux lignes diffèrent :

// version NANO
#define LED     13
LedControl lc = LedControl(12, 11, 10, 1);

// version STM32
#define LED     PC13
LedControl lc = LedControl(PA1, PA2, PA3, 1);

3. Le chargement

Comment faire pour exécuter du code ARDUINO sur un STM32 ?
Toutes les explications sont ICI.
Un petit tuto personnel pour aider pas à pas les néophytes ICI.

Le sketch utilise Wire, TimeLib et LedControl, des librairies classiques qui ne devraient à priori pas poser de problème particulier.
De plus, le Board Package "Arduino SAM Boards" utilisé pour l'installation dans l'IDE ARDUINO - qui inclut l'ARDUINO DUE - nous laisse penser que nous avons toutes les chances que cela fonctionne.

Et cela fonctionne !


1er février 00H00 :

Maintenant :

4. Mise en route

La date par défaut dans le code est la date d'aujourd'hui.
Pour mettre à l'heure le montage, il faut utiliser le moniteur série de l'IDE ARDUINO ou n'importe quel autre terminal série.
Il faut envoyer la commande suivante :

TXXXXXXXXXX 

La commande se compose de la lettre T suivie du timestamp (nombre de secondes écoulées depuis le 1/1/1970) correspondant à l'heure courante.
Si vous possédez une machine sous LINUX entrez la commande shell suivante :

expr `date +%s` + 3600

La commande affiche un nombre correspondant à l'heure GMT + 1H:

1543157582

Vous pouvez utiliser aussi ce site :

http://www.timestamp.fr/

Il vous faudra également ajouter 3600.

Copiez / collez le timestamp dans votre moniteur série :

T1543157582

Le sketch mettra alors votre RTC à l'heure ainsi que votre horloge système.

5. Conclusion

Ce petit exemple démontre que l'utilisation de librairies prévues pour les cartes ARDUINO est possible sur d'autres cartes.
L'apport des librairies ARDUINO en matière de simplification est indéniable  et permet de gagner un temps fou sur le développement logiciel.
Les développeurs ayant déjà travaillé avec les librairies STM32 de chez ST MicroElectronics savent de quoi je parle.

Cordialement
Henri

Aucun commentaire:

Enregistrer un commentaire