Station météo avec un arduino mega2560

Avec affichage TFT 320x480 et enregistrement sur SDcard

1 Présentation

J'avais récupéré un capteur de température & humidité externe (transmettant par radio sur 433MHz) ainsi que son petit récepteur associé provenant d'un modèle du commerce. J'ai alors décidé de créer cette station météo, dont voici les principales caractéristiques :
  • réalisation autour d'une carte Arduino mega2560
  • Affichage sur écran TFT 320x480
  • heure
  • température intérieure
  • température extérieure
  • taux d'humidité
  • pression atmosphérique
  • Tracé des courbes en temps réel
  • Enregistrement des mesures sur carte mémoire micro SDcard
  • Envoie des infos en temps réel par la liaison USB (à capter avec le terminal série tel que CuteCom sous Linux)
  • Fonction de capture d'écran et enregistrement de l'image obtenue sur la SDcard (utilisable pour d'autres projets le cas échéant...)
Un switch câble directement sur l'afficheur après coupure d'une piste permet d'éteindre l'écran TFT durant la nuit.

Cela existe déjà tout fait ? Sans doute mais cette réalisation est totalement décrite, y compris le code source du programme. Le matériel est modulable. Et puis le tout est compris dans un très petit volume, facile à caser n'importe où dans la maison ou sur le bureau.

Remarque : Cette réalisation date de 2020. J'ai fait mieux depuis : une station météo basée sur un ESP32.

2 Le schema

3 Vue de dessous

Sous la carte Arduino mega2560 se trouve une fine plaque en plastique sur laquelle sont fixés les différents capteurs. Tous ces capteurs sont reliés électriquement par des connecteurs, ce qui rend possible le démontage très simplement. On remarquera que j'ai utilisé du fil de câblage trop gros : ça fait moche ! Je vais tout recâbler avec du fil à wrapper !

4 Programme source en C++

CODE SOURCE en C++
  1.  
  2. /***********************************************************************************
  3.  Mega2560
  4.  
  5. use the BREAKOUT BOARD only and use these 8bit data lines to the LCD,
  6. pin usage:
  7.   LCD_CS LCD_CD LCD_WR LCD_RD LCD_RST SD_SS SD_DI SD_DO SD_SCK
  8.   Arduino Uno A3 A2 A1 A0 A4 10 11 12 13
  9. Arduino Mega2560 A3 A2 A1 A0 A4 10 11 12 13
  10.  
  11.   LCD_D0 LCD_D1 LCD_D2 LCD_D3 LCD_D4 LCD_D5 LCD_D6 LCD_D7
  12.   Arduino Uno 8 9 2 3 4 5 6 7
  13. Arduino Mega2560 8 9 2 3 4 5 6 7
  14.  
  15. **********************************************************************************/
  16. // Donc toutes les I/O logiques de l'UNO sont prises -> on utilisera un Mega2560
  17.  
  18. /**
  19. RAPPEL ATmega 2560 :
  20. – 256KBytes of In-System Self-Programmable Flash
  21. – 4Kbytes EEPROM
  22. – 8Kbytes Internal SRAM
  23. **/
  24.  
  25.  
  26. #include "Arduino.h"
  27. #include <stdint.h>
  28. #include "Station_meteo.h"
  29.  
  30.  
  31. /** STATION METEO **/
  32.  
  33. #define version "16.4"
  34.  
  35.  
  36. #define portPIN_switch0 PINL
  37. #define pin_switch0 0b00000100
  38.  
  39. int sram;
  40. uint32_t bmp_offset = 0;
  41. uint16_t s_width = TFT480.Get_Display_Width();
  42. uint16_t s_heigh = TFT480.Get_Display_Height();
  43.  
  44. #define NOIR 0x0000
  45. #define ROUGE 0xF800
  46. #define ORANGE 0xFBC0
  47. #define JAUNE 0xFFE0
  48. #define JAUNE_PALE 0xF7F4
  49. #define VERT 0x07E0
  50. #define CYAN 0x07FF
  51. #define BLEU_CLAIR 0x455F
  52. #define BLEU 0x001F
  53. #define MAGENTA 0xF81F
  54. #define VIOLET 0x781A
  55. #define GRIS_TRES_CLAIR 0xDEFB
  56. #define GRIS_CLAIR 0xA534
  57. #define GRIS 0x8410
  58. #define GRIS_FONCE 0x5ACB
  59. #define BLANC 0xFFFF
  60.  
  61.  
  62. Etiq3 Etiq_1;
  63. Etiq3 Etiq_2;
  64. //Etiq3 Etiq_3;
  65. Etiq3 Etiq_4;
  66. Etiq3 Etiq_5;
  67. //Etiq3 Etiq_6;
  68.  
  69. //LED Led1;
  70. LED Led2;
  71. //LED Led3;
  72. //LED Led4;
  73.  
  74. Scope Scope_1;
  75.  
  76.  
  77. uint16_t aff_y;
  78.  
  79. /**--------------------------------------------------------
  80. déclarations RECEPTION & DECODAGE SONDE EXTERNE
  81. --------------------------------------------------------**/
  82.  
  83. /**
  84. Rappels :
  85. Scope_1.dx = 450
  86. lst_records[500]
  87. **/
  88.  
  89. record_meteo lst_records[460]; // voir la définition de la struct 'record_meteo' dans le .h
  90. uint16_t n_record=0;
  91. uint16_t n_record_max=450;
  92.  
  93.  
  94. const int INPUT_PIN = 22;
  95. uint8_t SDcardOk=0;
  96. byte etat = 0;
  97. byte memo_etat;
  98. uint32_t memo_micros1 = 0;
  99. uint32_t memo_micros2 = 0;
  100. uint32_t temps_ecoule;
  101. uint32_t pulseWidth;
  102. uint16_t i_buff;
  103. uint16_t TT1;
  104.  
  105. uint16_t nb_acqui433=0;
  106. uint16_t nb_wr_SD=0;
  107. uint8_t todo_init_record=0;
  108. uint8_t todo_wr_scope_on_sd=0;
  109.  
  110.  
  111. uint16_t annee_actuelle;
  112. uint8_t mois_actuel;
  113. uint8_t jour_actuel;
  114. uint8_t jour_de_la_semaine; // 0=dimanche
  115. uint8_t heure_actuelle;
  116. uint8_t minute_actuelle;
  117. uint8_t seconde_actuelle;
  118. float age_Lune;
  119.  
  120. //uint8_t premiere_passe;
  121. //uint8_t stable;
  122. float echelle_T=1; // pour l'affichage des températures sur le Scope
  123. float echelle_P=1; // pour l'affichage des pressions sur le Scope
  124.  
  125. int16_t gradu_minT, gradu_maxT;
  126. // int16_t gradu_minP, gradu_maxP;
  127.  
  128. uint8_t num_acquisition;
  129. uint8_t acqui_valide =0;
  130. uint8_t scope_dat_ok =0;
  131. int16_t T_EXT_lue[5]={NOIR,0,0}; // temperature extérieure (la sonde envoie des salves de 5 messages identiques)
  132. uint8_t confiance[5]; //indice de confiance = nb d'occurences identiques pour chaque valeur reçue
  133. int16_t T_EXT_retenue; // après comparaison des 5 valeurs reçues
  134. int16_t Tmin, Tmax;
  135. int16_t Tmoy; // pour 1 enregistrement, moyenne entre le jour et la nuit
  136. int16_t moyenne_T_sur_toutes; // température mohenne sur l'ensemble des enregistrments affichés
  137. uint16_t T_INTE_lue; // temperature intérieure
  138.  
  139. uint16_t Pression_lue;
  140. uint16_t Pmin, Pmax;
  141. uint16_t Pmoy;
  142.  
  143. uint8_t H_in_lue; // humidite
  144. uint8_t memo_H_in;
  145.  
  146. uint8_t H_out_lue; // humidite
  147. uint8_t memo_H_out;
  148.  
  149. // String dataString = "";
  150.  
  151. tmElements_t tm;
  152.  
  153. const int MAX_BUFFER = 120;
  154. String message;
  155.  
  156. char buffer[MAX_BUFFER];
  157. void changed() { }
  158.  
  159. void init_ports()
  160. {
  161. // 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  162.  
  163. DDRL = 0b11111011;
  164. PORTL = 0b00000100;
  165. }
  166.  
  167.  
  168. /** -------------------------------------------------------
  169.   SD card
  170. -----------------------------------------------------------
  171. ADAPT SD -> mega2560 pins
  172.  
  173. CS -> 53
  174. MISO -> 50
  175. MOSI -> 51
  176. SCK -> 52
  177. **/
  178.  
  179.  
  180. int freeRam()
  181. {
  182. extern int __heap_start, *__brkval;
  183. int v;
  184. return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
  185. }
  186.  
  187.  
  188.  
  189. void Init_SDcard()
  190. {
  191. Serial.print("Init SDcard...");
  192.  
  193. pinMode(53, OUTPUT);
  194. if (!SD.begin(53)) //ici l'appel (et test) de la fonction 'begin' effectue l'initialisation (si possible)
  195. {
  196. Serial.println("init failed ! (pas de SDcard ?)");
  197. // Led3.setCouleur(NOIR);
  198. TFT480.Set_Text_Back_colour(NOIR);
  199. TFT480.Set_Text_colour(VIOLET);
  200. TFT480.Set_Text_Size(2);
  201. TFT480.Print_String("init failed ! (pas de SDcard ?)", 50, 250);
  202. delay(1000);
  203.  
  204. TFT480.Set_Text_Back_colour(NOIR);
  205. TFT480.Set_Text_colour(ORANGE);
  206. TFT480.Set_Text_Size(2);
  207. TFT480.Print_String("demande de RAZ scope.dat", 80, 200);
  208. TFT480.Print_String("elle se fera sur la minute", 70, 230);
  209. todo_init_record =1;
  210. return;
  211. }
  212. else
  213. {
  214. SDcardOk=1;
  215. }
  216. Serial.println("init Ok.");
  217. if (SD.exists("data.txt")) {Serial.println("data.txt existe.");} else { Serial.println("pas de fichier data.txt");}
  218.  
  219. if (SD.exists("scope.dat"))
  220. {
  221. Serial.println("scope.dat existe.");
  222. scope_dat_ok=1;
  223. }
  224. else
  225. {
  226. scope_dat_ok=0;
  227. TFT480.Set_Text_Back_colour(NOIR);
  228. TFT480.Set_Text_colour(ORANGE);
  229. TFT480.Set_Text_Size(1);
  230. TFT480.Print_String("pas de fichier scope.dat, je vais le creer", 10, 260);
  231. Serial.println("pas de fichier scope.dat sur la SDcard, je vais le creer");
  232.  
  233. TFT480.Set_Text_Back_colour(NOIR);
  234. TFT480.Set_Text_colour(ORANGE);
  235. TFT480.Set_Text_Size(2);
  236. TFT480.Print_String("demande de RAZ scope.dat", 80, 200);
  237. TFT480.Print_String("elle se fera sur la minute", 70, 230);
  238. todo_init_record =1;
  239. todo_wr_scope_on_sd=1;
  240. }
  241. }
  242.  
  243.  
  244.  
  245. /** -------------------------------------------------------
  246.   Sensor local BMP280 (Pression & température)
  247. --------------------------------------------------------**/
  248.  
  249.  
  250. Adafruit_BMP280 bmp; // use I2C interface
  251. Adafruit_Sensor *bmp_temp = bmp.getTemperatureSensor();
  252. Adafruit_Sensor *bmp_pressure = bmp.getPressureSensor();
  253.  
  254. void init_BMP280()
  255. {
  256. Serial.println(F("BMP280 Sensor event test"));
  257.  
  258. if (!bmp.begin())
  259. {
  260. Serial.println(F("Could not find BMP280 sensor !"));
  261. while (1) delay(10);
  262. }
  263. else {Serial.println(F("BMP280 sensor OK!")); }
  264.  
  265. /* Default settings from datasheet. */
  266. bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */
  267. Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */
  268. Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */
  269. Adafruit_BMP280::FILTER_X16, /* Filtering. */
  270. Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */
  271.  
  272. bmp_temp->printSensorDetails();
  273. }
  274.  
  275.  
  276. void acqui_PressionTemperature()
  277. {
  278. Serial.println(" "); Serial.println("acqui_PressionTemperature()");
  279. /**
  280. pression athmosphérique normale au niveau de la mer = 1013 hPa
  281. 0m ->1013 hPa
  282. 1000m-> 899 hPa
  283. -soit 1016-899 = 114 hPa / 1000 m (attention la variation n'est pas linéaire, au dessus de 1000 m, ce n'est plus exact)
  284. -soit 11.4 hPa / 100 m
  285. vu que mon capteur est situé à 100 m d'altitude, je vais rajouter 11.4 hPa à la mesure
  286. afin d'obtenir des valeurs cohérentes par rapport aux information météorologiques
  287. */
  288.  
  289. sensors_event_t temp_event, pressure_event;
  290. bmp_temp->getEvent(&temp_event);
  291. bmp_pressure->getEvent(&pressure_event);
  292.  
  293. Serial.println();
  294.  
  295. Serial.print(F("Temperature IN = "));
  296. T_INTE_lue =(int16_t)(10*temp_event.temperature);
  297.  
  298. T_INTE_lue-=50; //c.a.d 5°C de moins, pour compenser le fait que la sonde interne soit chauffée par sa proximité avec l'afficheur etc...
  299. // une autre solution consisterait à l'aloigner du reste de l'électronique.
  300.  
  301.  
  302. Serial.print(T_INTE_lue);
  303. Serial.println(" *C");
  304.  
  305. Serial.print(F("Pression = "));
  306. float pression_acq = pressure_event.pressure;
  307. pression_acq += 11.4; // mon capteur est situé à 100 m d'altitude, voir commentaire ci-dessus
  308. Pression_lue = (int16_t)pression_acq;
  309. Serial.print(Pression_lue);
  310. Serial.println(" hPa");
  311.  
  312. Serial.println();
  313. }
  314.  
  315. /**--------------------------------------------------------
  316. fonctions AFFICHAGE TFT
  317. --------------------------------------------------------**/
  318.  
  319.  
  320. void clear_screen()
  321. {
  322. TFT480.Fill_Screen(40,40,40);
  323. }
  324.  
  325.  
  326. void dessine_triangles()
  327. {
  328. int i = 0;
  329. uint16_t L, H;
  330. L=TFT480.Get_Display_Width();
  331. H=TFT480.Get_Display_Height();
  332.  
  333. for(i=0; i<H/2; i+=5)
  334. {
  335. TFT480.Set_Draw_color(0,i+64,i+64);
  336. TFT480.Draw_Triangle(L/2-1,H/2-1-i,L/2-1-i,H/2-1+i,L/2-1+i,H/2-1+i);
  337. }
  338. }
  339.  
  340.  
  341. void dessine_degrade(uint16_t x, uint16_t y, uint8_t longueur, uint8_t dy, uint8_t RR1, uint8_t RR2, uint8_t GG1, uint8_t GG2, uint8_t BB1, uint8_t BB2)
  342. {
  343. // dessine un dégr&dé horizontal de couleurs (R1,G1,B1) -> (R2,G2,B2)
  344. uint16_t i;
  345. float R,G,B;
  346.  
  347. if(longueur==0) {return;}
  348.  
  349. i=0;
  350.  
  351. R=RR1;
  352. G=GG1;
  353. B=BB1;
  354.  
  355. while(i<longueur)
  356. {
  357. x++;
  358. R+=(RR2-RR1)/longueur;
  359. G+=(GG2-GG1)/longueur;
  360. B+=(BB2-BB1)/longueur;
  361. TFT480.Set_Draw_color((uint8_t)R,(uint8_t)G,(uint8_t)B);
  362. TFT480.Draw_Line(x, y, x, y+dy);
  363. i++;
  364. }
  365. }
  366.  
  367.  
  368.  
  369.  
  370. void dessine_arc_en_ciel()
  371. {
  372. int16_t i;
  373. float xr,xv,xb;
  374. float r,v,b;
  375. for (i=0; i<63; i++) // 10 * 2 pi radians
  376. {
  377. xr=(i+12)/10.0; r=140.0+120.0*sin(xr)-i; if (r<0) {r=0;} if (r>255) {r=255;}
  378. xv=(i+54)/10.0; v=130.0+150.0*sin(xv); if (v<0) {v=0;} if (v>255) {v=255;};
  379. xb=(i+38)/10.0; b=100.0+150.0*sin(xb); if (b<0) {b=0;} if (b>255) {b=255;};
  380.  
  381. /*
  382. //pour test du déphasage des courbes sinus...
  383. TFT480.Set_Draw_color(