Affichage zéros non significatifs

Voici un petit sketch pour afficher l’heure sur un écran oled avec une RTC DS3231 et un arduino mega , ce début de sketch servira pour un arrosage automatique, le programme sera développé par la suite.

Mon soucis c’est que l’heure affichée ne donne pas les minutes et les secondes avec le zéro devant ( 2 minutes au lieu de 02, idem pour les secondes).

Avez-vous une idée à me proposer ?



#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>

#define SCREEN_WIDTH 128  // OLED display width,  in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);  // // create SSD1306 display object connected to I2C
RTC_DS3231 rtc;

String time;

void setup() {
  Serial.begin(9600);

  // initialize OLED display with address 0x3C for 128x64
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true)
      ;
  }

  delay(2000);          // wait for initializing
  oled.clearDisplay();  // clear display

  oled.setTextSize(1);       // text size
  oled.setTextColor(WHITE);  // text color
  oled.setCursor(0, 10);     // position to display

  // SETUP RTC MODULE
  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (true)
      ;
  }

  // automatically sets the RTC to the date & time on PC this sketch was compiled 
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  time.reserve(10);  // to avoid fragmenting memory when using String
}

void loop() {
  DateTime now = rtc.now();

  time = "";
  time += now.hour();
  time += ':';
  time += now.minute();
  time += ':';
  time += now.second();

  oledDisplayCenter(time);
}

void oledDisplayCenter(String text) {
  int16_t x1;
  int16_t y1;
  uint16_t width;
  uint16_t height;

  oled.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);

  // display on horizontal and vertical center
  oled.clearDisplay();  // clear display
  
  //oled.setCursor((SCREEN_WIDTH - width) / 2, (SCREEN_HEIGHT - height) / 2);
  oled.setCursor (2,10);
  oled.print("Heure: ");
  oled.println(text);  // text to display
  
  oled.display();
}

```

Il faut tester si vous êtes inférieur à 10 et si oui mettre le 0

void loop() {
  DateTime now = rtc.now();

  String time = "";

  if (now.hour() < 10) time += '0';
  time += now.hour();
  time += ':';

  if (now.minute() < 10) time += '0';
  time += now.minute();
  time += ':';

  if (now.second() < 10) time += '0';
  time += now.second();

  oledDisplayCenter(time);
}

C’est un peu dommage de passer la String par copie au lieu de par référence.

vérifie si la bibli que tu utilises ne propose pas des fonctions d'extraction directe des chiffres (digits) : c'est codé en BCD dans la RAM de la RTC, ce serait idiot de ne pas le faire, j'espère qu'ils y ont pensé

il utilise

Ce doit être la bibliothèque de Adafruit. La doc de l'API pour la DS3231 est ici et même si en dessous la bibliothèque lit du BCD, elle ne rend pas dispo chaque digit individuellement.

Bonjour,

Une autre solution consiste à utiliser la méthode sprintf() qui permet de formater directement les "zéros" non significatifs à gauche et séparer les champs avec le caractère ':'

void loop() {
  DateTime now = rtc.now();

  char l__buffer[16];     // Taille max pour accueillir la string "HH:MM:SS\0"
 
  sprintf(l__buffer, "%02d:%02d:%02d",
      now.hour(), now.minute(), now.second());

  String l__time = l__buffer;
  oledDisplayCenter(l__time);
}

A suivre...

c'est un peu large pour HH:MM:SS\0 :slight_smile: et vous payez deux fois le prix en mémoire puisqu'ensuite vous avez une String qui duplique ce contenu...

dans l'absolu il vaut mieux utiliser snprintf(), c'est plus sûr

Qu'à cela ne tienne

void loop() {
  DateTime now = rtc.now();

  char *l__buffer = strdup("HH:MM:SS");     // Allocation minimale pour la string "HH:MM:SS\0"
 
  sprintf(l__buffer, "%02d:%02d:%02d",
      now.hour(), now.minute(), now.second());

  String l__time = l__buffer;
  oledDisplayCenter(l__time);

  free(l__buffer);
}

Maintenant, la cible est un arduino mega ;-)
Mon propos était de proposer une autre solution pour le formatage en seulement 3 lignes de code...

j'ai bien vu qu'il utilise

mais je ne l'ai jamais utilisée : quand j'ai voulu découvrir le DS3231 je n'avais jamais mis en œuvre l'I2C alors j'ai écrit mes propres fonctions (au moins je sais comment ça fonctionne maintenant) pour les 2.

c'est tellement difficile de lire un octet et de le découper avec des masques et des décalages !!!


finalement, les (autres) solutions que vous évoquez (@J-M-L , @claudius01 ) sont peut-être plus à la portée de @electronn2002 ...

Bonjour,

Sauf erreur de ma part :

void loop() {
    DateTime now = rtc.now();

    char buffer[9];   // HH:MM:SS + \0

    snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d",
             now.hour(),
             now.minute(),
             now.second());

    oledDisplayCenter(buffer);
}

void oledDisplayCenter(const char* text) {
    int16_t x1;
    int16_t y1;
    uint16_t width;
    uint16_t height;

    oled.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);

    oled.clearDisplay();
    oled.setCursor(2,10);
    oled.print("Heure: ");
    oled.println(text);
    oled.display();
}

Et du coup si on reprend votre code :

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
RTC_DS3231 rtc;

void setup() {
  Serial.begin(9600);

  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }

  delay(2000);
  oled.clearDisplay();

  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  oled.setCursor(0, 10);

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

  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

void loop() {
    DateTime now = rtc.now();

    char buffer[9]; // HH:MM:SS + '\0'

    snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d",
             now.hour(), now.minute(), now.second());

    oledDisplayCenter(buffer);
}

void oledDisplayCenter(const char* text) {
    int16_t x1;
    int16_t y1;
    uint16_t width;
    uint16_t height;

    oled.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);

    oled.clearDisplay();
    oled.setCursor(2, 10);
    oled.print("Heure: ");
    oled.println(text);
    oled.display();
}

Bonne journée

ce n'est pas difficile, ils le font dans leur code. Le mettre en API est un choix de design. Je pense que je ne l'aurai pas fait non plus car la valeur ajoutée semble peu importante.

Ils ont une classe vraiment plus adaptée, qui est la DateTime

cette classe sait mettre en forme la date/heure avec le format que l'on veut, cf la fonction toString()

toString()

char * DateTime::toString ( char * buffer ) const

Writes the DateTime as a string in a user-defined format.

The buffer parameter should be initialized by the caller with a string specifying the requested format. This format string may contain any of the following specifiers:

specifier output
YYYY the year as a 4-digit number (2000–2099)
YY the year as a 2-digit number (00–99)
MM the month as a 2-digit number (01–12)
MMM the abbreviated English month name ("Jan"–"Dec")
DD the day as a 2-digit number (01–31)
DDD the abbreviated English day of the week ("Mon"–"Sun")
AP either "AM" or "PM"
ap either "am" or "pm"
hh the hour as a 2-digit number (00–23 or 01–12)
mm the minute as a 2-digit number (00–59)
ss the second as a 2-digit number (00–59)

If either "AP" or "ap" is used, the "hh" specifier uses 12-hour mode (range: 01–12). Otherwise it works in 24-hour mode (range: 00–23).

The specifiers within buffer will be overwritten with the appropriate values from the DateTime. Any characters not belonging to one of the above specifiers are left as-is.

Example: The format "DDD, DD MMM YYYY hh:mm:ss" generates an output of the form "Thu, 16 Apr 2020 18:34:56.

See also
The timestamp() method provides similar functionnality, but it returns a String object and supports a limited choice of predefined formats.

Parameters

[in,out] buffer Array of char for holding the format description and the formatted DateTime. Before calling this method, the buffer should be initialized by the user with the format string. The method will overwrite the buffer with the formatted date and/or time.

Returns
A pointer to the provided buffer. This is returned for convenience, in order to enable idioms such as Serial.println(now.toString(buffer));

➜ suffit d'utiliser le buffer avec "hh:mm:ss"

c'est bien, je suis content pour toi
personnellement je préfère mes routines, certainement moins volumineuses et plus rapides
[sujet clos pour moi]

Bien sûr, c'est forcément plus rapide et moins couteux en mémoire, vous avez raison pour ce besoin.


Quand on développe des classes, on essaye de généraliser l'usage aussi. Avec la classe DateTime vous pouvez bien sûr représenter une date mais aussi faire des opérations entre dates pour obtenir ce qu'ils appellent un TimeSpan (un intervalle de temps).

Imaginez que vous vouliez que votre horloge représente le temps qui reste avant le 1er janvier et que vous ayez cette DS3231.

La bibliothèque permet de faire un truc du genre

const DateTime premierJanvier2026(2026, 1, 1, 0, 0, 0);

... // le setup etc

void loop() {
  DateTime maintenant = rtc.now();
  TimeSpan tempsRestant = premierJanvier2026 - maintenant;

  Serial.print("Il reste ");
  Serial.print(tempsRestant.days());     Serial.print(" jours, ");
  Serial.print(tempsRestant.hours());    Serial.print(" heure, ");
  Serial.print(tempsRestant.minutes());  Serial.print(" minutes, ");
  Serial.print(tempsRestant.seconds());  Serial.println(" secondes avant l'année prochaine");

  ...
}

et vous avez dans tempsRestant l'info souhaitée.

Si la "bibliothèque" n'offre que le BCD de chaque chiffre, ce sera sans doute plus long à écrire.

Ensuite, oui vous pourriez aussi envisager d'offrir les deux. Si vous regardez la fonction qui retourne l'heure de leur bibliothèque, on voit très bien qu'ils ont le BCD pour fabriquer leur instance de DateTime.

Bonsoir @J-M-L

Merci , je ne connaissais pas cette méthode.
L’implémentation est lisible et elle fonctionne directement avec RTClib en utilisant un buffer c-string :

void loop() {
DateTime now = rtc.now();

char buffer[] = "hh:mm:ss";  
now.toString(buffer);        

oledDisplayCenter(buffer);

}

Affichage centré avec :

void oledDisplayCenter(const char* text) {
int16_t x1, y1;
uint16_t width, height;

oled.getTextBounds(text, 0, 0, &x1, &y1, &width, &height);

oled.clearDisplay();
oled.setCursor(2,10);
oled.print("Heure: ");
oled.println(text);
oled.display();

}

Cela permet d’éviter l’utilisation de String tout en gardant un code simple. :wink:

oui c'est pas mal comme fonction de haut niveau.

Après comme le dit @5_cylindres, si on ne veut que les digits séparément et qu'on veut faire efficace, l'accès direct aux registres de la RTC est tout ce qu'il faut.

Oui d’ailleurs je ne m’oppose ni à l’une ni à l’autre des deux approches. D’un côté c’est bien de programmer bas niveau et d’optimiser à l’octet près :wink: de l’autre RTClib offre bien plus qu’un simple affichage de l’heure.

Chaque point de vue vise des objectifs différents et est défendable.

Bonne soirée à vous et à @5_cylindres

Bonjour,

Woah, je ne m’attendais pas à tant de réponses et d’explications, qui m’ont bien aidées et guidées dans la compréhension.

Merci à tous

Franchement, avec un composant aussi simple qu’un DS3231 on peut écrire soi même le code, surtout quand il ne s’agit que de simplement faire une lecture.

Le point un peu delicat est la conversion du BCD, il suffit d’ouvrir une bibliothèque et d’en extraire deux ou trois lignes de code.

L’obtention individuellement des centaines, dizaines et unités est alors trivial.

Oui - on trouve ça facilement

==> @J-M-L

***** pas de centaine dans le DS3231 *****
dizaine = octet >> 4 ; // pour char à afficher : +48
unité = octet & 0xF ; // idem
==> temps d'exécution et taille programme négligeables

Oui en fait et pour faire simple, on peut prendre pour exemple le registre minutes du DS3231 à l’adresse 0x01 :

Il fait 1 octet donc si il correspond à 0x47 soit 0100 0111 →

On a 0100 : dizaines = 4 et 0111 : unités = 7

→ 47 minutes

L’octet est codé en BCD :

  • les 4 bits de gauche représentent les dizaines
  • Les 4 bits de droite représentent les unités

Bonne soirée