Horloge e-paper ESP32 & module RTC

Je rêvais d'une horloge numérique très lisible de loin en plein jour. La voici.

1 Description

Cette réalisation est basée sur une petite carte ESP32 TTGO-T5 sur laquelle un afficheur e-paper est implanté d'origine.
La base de temps est fournie par un module RTC DS1307 ayant sa propre pile de 3V (une classique 2032), sachant que l'horloge de la carte ESP32 est renommée pour ne pas être suffisamment précise pour garder l'heure. J'aurais aussi pu opter pour la solution qui consiste à câbler un quartz externe à la carte ESP32.
Il faut noter que la pile 2032 du module RTC n'alimente PAS la carte ESP32. Un connecteur pour accu LiPo est présent sur la carte.

2 Le schéma

La liaison se fait par le bus I2C (SDA - SLC).
Le fil qui relie le VCC du module RTC au +3V3 de la carte ESP est nécessaire pour la polarisation du bus I2C (par des résistances de pull-up présentes sur la carte) dont la connectique interne se fait avec des collecteurs ouverts et non des totem-pole, ce qui créerait plus que des conflits, des courts-jus !
Donc, bien que chaque carte possède sa propre alimentation, si vous omettez cette liaison ça ne marchera pas, le dialogue entre les carte ne se fera pas.

La LED (optionnelle) bat la mesure (1s), chose que l'on fait habituellement par clignotement des deux points entre les heures et les minutes, ce qui est tout à fait déconseillé dans le cas d'un afficheur e-paper. Non pas que la rapidité de rafraîchissement d'une si petite surface ne soit pas suffisante, il s'agit plutôt de ne pas abréger la durée de vie de l'afficheur (dit-on...)

3 Programme source en C++ : Horloge1.ino

CODE SOURCE en C++
  1.  
  2. /**
  3. Horloge1
  4.  
  5. pour 02- LILYGO TTGO T5 + 2.13" E-Paper Screen (250 x 122 px)
  6. OK compilé avec type de carte = "ESP32 Dev Module"
  7. **/
  8.  
  9. #define version "3.3"
  10.  
  11.  
  12. #include <stdint.h>
  13. #include <GxEPD.h> // note: cette lib inclue la lib "Adafruit_GFX.h" dans laquelle se trouvent les fonctions de base
  14. #include "SPI.h"
  15. #include <Wire.h>
  16. #include "uRTCLib.h"
  17. #include "DHT.h"
  18.  
  19.  
  20. uRTCLib rtc(0x68);
  21.  
  22. //! There are three versions of the 2.13 screen
  23. //#include <GxGDE0213B1/GxGDE0213B1.h> // 2.13" b/w
  24. //#include <GxGDEH0213B72/GxGDEH0213B72.h> // 2.13" b/w new panel
  25. #include <GxGDEH0213B73/GxGDEH0213B73.h> // 2.13" b/w newer panel
  26.  
  27. #include "chiffres/60_75/chiffres60_75.c"
  28.  
  29. // FreeFonts from Adafruit_GFX
  30. #include <Fonts/FreeMonoBold9pt7b.h>
  31. #include <Fonts/FreeMonoBold12pt7b.h>
  32. #include <Fonts/FreeMonoBold18pt7b.h>
  33. #include <Fonts/FreeMonoBold24pt7b.h>
  34.  
  35.  
  36. #include <GxIO/GxIO_SPI/GxIO_SPI.h>
  37. #include <GxIO/GxIO.h>
  38.  
  39. #define SPI_MOSI 23
  40. #define SPI_MISO -1
  41. #define SPI_CLK 18
  42.  
  43. #define ELINK_SS 5
  44. #define ELINK_BUSY 4
  45. #define ELINK_RESET 16
  46. #define ELINK_DC 17
  47.  
  48. #define SDCARD_SS 13
  49. #define SDCARD_CLK 14
  50. #define SDCARD_MOSI 15
  51. #define SDCARD_MISO 2
  52.  
  53. #define DHTTYPE DHT11 // DHT 11
  54. //#define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321
  55. //#define DHTTYPE DHT21 // DHT 21 (AM2301)
  56. #define DHTPIN 19 // Digital pin connected to the DHT sensor
  57.  
  58.  
  59. //#define bouton1 39
  60. //#define led1 12 //const int led1 = 12;
  61.  
  62. const int led1 = 12;
  63. const int bouton1 = 39;
  64.  
  65. bool led1_etat = LOW;
  66. bool bouton1_etat; // état bouton1
  67.  
  68. const uint32_t partial_update_period_s = 1;
  69. const uint32_t full_update_period_s = 6 * 60 * 60;
  70.  
  71.  
  72. uint32_t start_time;
  73. uint32_t next_time;
  74. uint32_t previous_time;
  75. uint32_t previous_full_update;
  76.  
  77. uint32_t memoM1 = 0;
  78. uint32_t memoM2 = 0;
  79. uint32_t currentMillis=0;
  80. const uint32_t tempo1 = 2000; // 2000 ms = 2s
  81.  
  82. #define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
  83. #define TIME_TO_SLEEP 2 /* Time ESP32 will go to sleep (secondes) */
  84.  
  85.  
  86. uint8_t annee;
  87. uint8_t mois;
  88. uint8_t jour;
  89. uint8_t jour_de_la_semaine;
  90. uint8_t heure=0;
  91. uint8_t minute=0;
  92. uint8_t memo_minute=0;
  93. uint8_t seconde=0;
  94.  
  95.  
  96. GxIO_Class io(SPI, ELINK_SS, ELINK_DC, ELINK_RESET);
  97. GxEPD_Class display(io, ELINK_RESET, ELINK_BUSY);
  98.  
  99. SPIClass sdSPI(VSPI);
  100.  
  101. const char *skuNum = "SKU:H239";
  102. int startX = 40, startY = 10;
  103.  
  104. DHT dht(DHTPIN, DHTTYPE);
  105.  
  106.  
  107. void Set_Time()
  108. {
  109. // appelée par appui sur le bouton (en bord de carte) après avoir correctement paramétré ces données.
  110.  
  111. uint8_t second = 3;
  112. uint8_t minute = 39;
  113. uint8_t hour = 5;
  114. uint8_t dayOfWeek = 4; // toutefois le jour de la semaine sera recalculé plus bas, en temps réel
  115. uint8_t dayOfMonth = 20;
  116. uint8_t month = 1;
  117. uint8_t year = 21; // 2021
  118.  
  119. // ensuite je re-commente la ligne ci-dessous pour éviter de reprogrammer l'heure en appuyant sur le bouton par inadvertance...
  120. // rtc.set(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
  121. }
  122.  
  123.  
  124.  
  125. void setup()
  126. {
  127. esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  128.  
  129. delay (2000);
  130. pinMode(bouton1, INPUT);
  131. pinMode(led1, OUTPUT);
  132.  
  133. //Serial.begin(115200);
  134. //Serial.println("//Serial OK");
  135.  
  136. #ifdef ARDUINO_ARCH_ESP8266
  137. URTCLIB_WIRE.begin(0, 2); // D3 and D4 on ESP8266
  138. #else
  139. URTCLIB_WIRE.begin();
  140. #endif
  141.  
  142. start_time = next_time = previous_time = previous_full_update = millis();
  143.  
  144. display.init();
  145.  
  146. display.setRotation(1);
  147. display.fillScreen(GxEPD_WHITE);
  148. display.setTextColor(GxEPD_BLACK);
  149. display.setFont(&FreeMonoBold12pt7b);
  150. display.setCursor(0, 0);
  151.  
  152. //esp_sleep_enable_ext0_wakeup((gpio_num_t)BUTTON_PIN, LOW);
  153.  
  154. display.fillScreen(GxEPD_WHITE);
  155. // display.eraseDisplay();
  156.  
  157. display.update();
  158.  
  159.  
  160.  
  161. dht.begin();
  162.  
  163. }
  164.  
  165.  
  166. void print_big_chiffre(uint8_t nb, uint16_t x0 ,uint16_t y0) //avec bitmaps perso
  167. {
  168. uint16_t w0=60;
  169. uint16_t h0=75;
  170.  
  171. switch (nb)
  172. {
  173. case 0: {display.drawBitmap(chiffre0, x0, y0, w0, h0, GxEPD_BLACK); } break;
  174. case 1: {display.drawBitmap(chiffre1, x0, y0, w0, h0, GxEPD_BLACK); } break;
  175. case 2: {display.drawBitmap(chiffre2, x0, y0, w0, h0, GxEPD_BLACK); } break;
  176. case 3: {display.drawBitmap(chiffre3, x0, y0, w0, h0, GxEPD_BLACK); } break;
  177. case 4: {display.drawBitmap(chiffre4, x0, y0, w0, h0, GxEPD_BLACK); } break;
  178. case 5: {display.drawBitmap(chiffre5, x0, y0, w0, h0, GxEPD_BLACK); } break;
  179. case 6: {display.drawBitmap(chiffre6, x0, y0, w0, h0, GxEPD_BLACK); } break;
  180. case 7: {display.drawBitmap(chiffre7, x0, y0, w0, h0, GxEPD_BLACK); } break;
  181. case 8: {display.drawBitmap(chiffre8, x0, y0, w0, h0, GxEPD_BLACK); } break;
  182. case 9: {display.drawBitmap(chiffre9, x0, y0, w0, h0, GxEPD_BLACK); } break;
  183. }
  184. }
  185.  
  186.  
  187.  
  188. void affiche_heure()
  189. {
  190. uint16_t box1_x = 0;
  191. uint16_t box1_y = 5;
  192. uint16_t box1_w = 245;
  193. uint16_t box1_h = 85;
  194. uint16_t cursor_y = box1_y;
  195. display.fillRect(box1_x, box1_y, box1_w, box1_h, GxEPD_WHITE);
  196. display.setCursor(box1_x, cursor_y);
  197.  
  198. uint8_t x0= box1_x;
  199.  
  200.  
  201. uint8_t heures_unites;
  202. uint8_t heures_dizaines;
  203. heures_unites = heure %10;
  204. heures_dizaines = heure /10;
  205. print_big_chiffre(heures_dizaines, x0, cursor_y);
  206. print_big_chiffre(heures_unites, x0+56, cursor_y);
  207.  
  208. x0+=133;
  209.  
  210. uint8_t minutes_unites ;
  211. uint8_t minutes_dizaines;
  212. minutes_unites = minute %10;
  213. minutes_dizaines = minute /10;
  214. print_big_chiffre(minutes_dizaines, x0, cursor_y);
  215. print_big_chiffre(minutes_unites, x0+56, cursor_y);
  216.  
  217. display.setCursor(106, 70);
  218. display.setTextSize(3);
  219. display.println(":");
  220.  
  221.  
  222. display.updateWindow(box1_x, box1_y, box1_w, box1_h, true);
  223. }
  224.  
  225.  
  226. void calcul_jour_de_la_semaine()
  227. {
  228. // d'après l'Algorithme de Mike Keith
  229. uint16_t d, m, y, z, jds;
  230.  
  231. d=jour;
  232. m=mois;
  233. y=annee;
  234.  
  235. if (m>=3)
  236. {
  237. jds = ( ((23*m)/9) + d + 4 + y + (y/4) - (y/100) + (y/400) - 2 ) % 7;
  238. }
  239. else
  240. {
  241. z = y-1;
  242. jds = ( ((23*m)/9) + d + 4 + y + (z/4) - (z/100) + (z/400) ) % 7;
  243. }
  244. jour_de_la_semaine = jds;
  245. }
  246.  
  247.  
  248.  
  249. uint8_t decToBcd( int val )
  250. {
  251. return (uint8_t) ((val / 10 * 16) + (val % 10));
  252. }
  253.  
  254.  
  255.  
  256. String conv_time(uint8_t t)
  257. {
  258. String r;
  259. r=String(t);
  260. if (t<10) {r="0"+r;}
  261. return r;
  262. }
  263.  
  264.  
  265. void affiche_date()
  266. {
  267. uint16_t box2_x = 0;
  268. uint16_t box2_y = 121-20;
  269. uint16_t box2_w = 175;
  270. uint16_t box2_h = 20;
  271.  
  272. display.fillRect(box2_x, box2_y, box2_w, box2_h, GxEPD_WHITE);
  273.  
  274. String date;
  275.  
  276. calcul_jour_de_la_semaine();
  277.  
  278.  
  279. switch (jour_de_la_semaine)
  280. {
  281. case 0: { date+="DIM ";} break;
  282. case 1: { date+="LUN ";} break;
  283. case 2: { date+="MAR ";} break;
  284. case 3: { date+="MER ";} break;
  285. case 4: { date+="JEU ";} break;;
  286. case 5: { date+="VEN ";} break;
  287. case 6: { date+="SAM ";} break;
  288. }
  289.  
  290. date += String(conv_time(jour))+" ";
  291.  
  292. switch (mois)
  293. {
  294. case 1: {date+="JAN"; } break;
  295. case 2: {date+="FEV"; } break;
  296. case 3: {date+="MARS";} break;
  297. case 4: {date+="AVR"; } break;
  298. case 5: {date+="MAI"; } break;
  299. case 6: {date+="JUIN";} break;
  300. case 7: {date+="JUIL";} break;
  301. case 8: {date+="AOUT";} break;
  302. case 9: {date+="SEPT";} break;
  303. case 10: {date+="OCT"; } break;
  304. case 11: {date+="NOV"; } break;
  305. case 12: {date+="DEC"; } break;
  306. }
  307. date += " 20"+String(annee);
  308. //date;
  309.  
  310. uint16_t x0=290, y0=75;
  311.  
  312. display.setTextColor(GxEPD_BLACK);
  313. display.setFont(&FreeMonoBold9pt7b);
  314. display.setTextSize(1);
  315. display.setCursor(0, display.height() -2);
  316. display.print(date);
  317.  
  318. display.updateWindow(box2_x, box2_y, box2_w, box2_h, true);
  319.  
  320. currentMillis = millis();
  321. if (currentMillis > 2000) memoM1= currentMillis - 2000; // pour afficher +rapidement au départ
  322.  
  323. }
  324.  
  325.  
  326.  
  327. void affiche_temperature(double T)
  328. {
  329. uint16_t y0 = 122; // position verticale du bord supérieur de la box
  330. uint16_t dy = 34; // dimension verticale de la box
  331. uint16_t box3_x = 180; // position horizontale du bord gauche de la box
  332. uint16_t box3_y = y0-dy;
  333. uint16_t box3_w = 65; // largeur de la box
  334. uint16_t box3_h = dy;
  335.  
  336. display.fillRect(box3_x, box3_y, box3_w, box3_h, GxEPD_WHITE); // efface la surface de la box (en RAM de l'afficheur)
  337.  
  338. display.setTextColor(GxEPD_BLACK);
  339. display.setFont(&FreeMonoBold12pt7b);
  340. display.setTextSize(1,2);
  341. display.setCursor(box3_x, y0-4);
  342.  
  343. display.print(T,1);
  344.  
  345. display.setFont(&FreeMonoBold9pt7b);
  346. display.setTextSize(1);
  347. display.setCursor(box3_x +30, display.height() -20);
  348. display.print("o"); // le signe 'degré' (° Celsius en l'occurence) , absent de la police de caractères
  349.  
  350. // display.drawRect(box3_x, box3_y, box3_w, box3_h, GxEPD_BLACK); // pour test visuel de la position et taille de la box
  351.  
  352. display.updateWindow(box3_x, box3_y, box3_w, box3_h, true); // affichage physique sur l'e-paper
  353.  
  354. }
  355.  
  356.  
  357.  
  358. void affichages()
  359. {
  360. affiche_heure();
  361. affiche_date();
  362.  
  363. float H1 = dht.readHumidity();
  364. double T1 = dht.readTemperature(); // °C par defaut
  365. //Serial.print(F("Temperature: ")); ////Serial.println(T1);
  366. affiche_temperature(T1);
  367.  
  368. }
  369.  
  370.  
  371.  
  372. void loop()
  373. {
  374. bouton1_etat = digitalRead(bouton1);
  375. if (bouton1_etat == 0)
  376. {
  377. Set_Time();
  378.  
  379. for(int n=0; n<20; n++)
  380. {
  381. digitalWrite(led1, HIGH);
  382. delay(20);
  383. digitalWrite(led1, LOW);
  384. delay(20);
  385. }
  386. }
  387.  
  388. currentMillis = millis();
  389.  
  390. if(currentMillis - memoM1 >= tempo1)
  391. {
  392. memoM1 = currentMillis;
  393. // B = digitalRead(BUTTON_PIN);
  394.  
  395. digitalWrite(led1, HIGH); // allume la led (ajoutée) (pour un bref petit flash) -> test visuel du fonctionnement
  396. rtc.refresh();
  397.  
  398. annee=rtc.year();
  399. mois=rtc.month();
  400. jour=rtc.day();
  401. jour_de_la_semaine=rtc.dayOfWeek();
  402. heure = rtc.hour();
  403.  
  404. memo_minute = minute;
  405. minute = rtc.minute();
  406.  
  407. seconde = rtc.second();
  408.  
  409. digitalWrite(led1, LOW); // éteint la led ajoutée
  410.  
  411. if(minute != memo_minute)
  412. {
  413. affichages();
  414. }
  415. }
  416. esp_light_sleep_start();
  417. }
  418.  
  419.  
  420.  
  421.  


Ce code est à compiler avec l'EDI Arduino en ayant pris soin de le configurer pour "Type de carte" = "ESP32 Dev Module"
Il faut bien entendu avoir chargé les plugings ESP avec le "Gestionnaire de modules".
Pour plus de détails, je vous renvoie vers mon article précédent :

4 Mise à l'heure du module RTC 1307

Il suffit d'appuyer sur le bouton de la carte (celui le plus proche du bord de la carte). En ayant au préalable paramétré correctement la fonction : "void Set_Time()"

void Set_Time() //appelée par appui sur le bouton tout à droite (après avoir correctement paramétré ces données)
{
// rappel format: RTCLib::set(second, minute, hour, dayOfWeek, dayOfMonth, month, year)
   rtc.set(3, 20, 15, 7, 16, 1, 21); // Only used once, then disabled
}


L'appel de cette fonction se fait en début de la boucle "Loop"

5 Que diriez vous d'un petit boitier ?

Vous avez une imprimante 3D ? Alors je vous ai créé ce petit boitier sur mesure en PLA blanc.

Le câble noir est branché sur le connecteur USB de la carte et ne sert ici qu'à l'alimentation électrique en 5V. Mais on peut tout à fait alimenter cette carte par un autre moyen, batterie par exemple (attention à la tension toutefois).

6 qui tient dans la main

Ben oui, ces cartes ESP32 sont toute-petites.

7 Voyons ce qu'il y a dans la boite :

le module RTC fixé sur une petite plaque de séparation

8 positionneurs de la carte ESP

Cette plaque est elle même fixée sur deux éléments servant à positionner la carte ESP32 et son afficheur e-paper précisément derrière la fenêtre.

9 L'ensemble des éléments

On voit ici l'arrière de la carte ESP32, son afficheur est situé sur l'autre face.

Vous trouverez plus bas sur cette page tous les fichiers source 3D servant à fabriquer (et/ou modifier) ce boîtier :
- fichier source pour le dessin avec le logiciel libre et gratuit Freecad
- fichier .stl pour le programme Cura
- fichier .gcode pour l'imprimante 3D "Ender 3"

10 Capteur de température

9 Fev 2021 :
Ajout d'un capteur de température DHT11 relié à l'entrée GPIO 19 de l'ESP32. J'ai complété le programme en conséquence.

Il y a lieu de placer ce capteur en dehors du boîtier principal afin d'éviter de mesurer une température faussé par la chaleur dégagée par la carte ESP32 (plusieurs °C en plus).

11 Mode basse consommation

14 fev 2021 :
J'ai programmé le mode "esp_light_sleep" qui se déclenche à intervalles réguliers, le réveil se faisant par un timer prévu à cet effet :

esp_sleep_enable_timer_wakeup()

ce qui réduit drastiquement la consommation électrique de la carte, et laisse envisager de replacer le capteur de température à l'intérieur du boitier et d'alimenter le tout par batterie LiPo miniature. Toutefois je ne confirme pas ce dernier point avant de l'avoir expérimenté...

12 Documents

13 -

Liens...

Me contacter à propos de cet article :

Pseudo : Les messages envoyés font l'objet d'une modération à priori.
Question mathématique :
Click to reload image
=
cliquez sur l'image pour un faire autre calcul.
Réponses à certaines de vos questions...


291