PFD (Primary Flight Display) pour Flightgear - ESP32

Horizon artificiel +Compas, Altitude, Vitesse, réglages autopilot, ILS pour Flightgear, simulateur de vol Open Source. Puissance ESP32.
Cliquez sur une image ci-dessous pour visualiser l'album. Certaines sont commentées

1 Un aperçu en vidéo


Ceci est un test de la rapidité d'affichage avec des données calculées par des fonctions sinus... La vidéo quant-à-elle est prise en vitesse normale (!)

En réalité l'ESP32 se connecte au programme de simulation de vol FlightGear par liaison série USB. L'affichage devient alors plus calme et surtout réaliste.

2 Vue d'ensemble de cette réalisation :

L'intégration dans un boîtier maison (imprimé en 3D) est prévu, avec peut être d'autres instruments de bord.

3 Le schéma

A noter que les ports GPIO 34 et 35 ne sont pas pourvus de résistances de rappel à VCC (Pull-up) internes dans l'ESP32, d'où l'ajout des deux résistances externes de 10k.
La saisie des paramètres pour l'autopilot se fait avec deux classiques encodeurs rotatifs. Les quatre capas de 10nF sont en cms à souder au au plus près des encodeurs (hors implantation du board donc).

Il ne reste plus beaucoup de broches libres sur le module ESP32 !

4 Le programme pour l'ESP32 à compiler avec l'IDE Arduino

CODE SOURCE en C++
  1.  
  2. /*
  3. FG_panel.ino - pour ESP32 - version Afficheur TFT 480x320
  4. todo: renommer en FG_PFD
  5.  
  6. par Silicium628
  7.  
  8. Ce logiciel est libre et gratuit sous licence GNU GPL
  9.  
  10. ---------------------------------------------------------------------------------------
  11. Pour les #includes issus de l'univers Arduino (que je ne fournis pas), il faut voir au cas par cas.
  12. (drivers d'affichage en particulier)
  13.  
  14. ---------------------------------------------------------------------------------------
  15. De petites images à placer sur la SDcard centrées sur les aérodromes proviennent de OpenStreetMap
  16.  
  17. OpenStreetMap® est un ensemble de données ouvertes,
  18. disponibles sous la licence libre Open Data Commons Open Database License (ODbL)
  19. accordée par la Fondation OpenStreetMap (OSMF).
  20.  
  21. Voir ce lien pour plus de détails :
  22. https://www.openstreetmap.org/copyright
  23.  
  24. ---------------------------------------------------------------------------------------
  25.  
  26.  
  27. */
  28.  
  29.  
  30. /**
  31. --------------------------------------------------------
  32. CONCERNANT L'AFFICHAGE TFT : connexion :
  33.  
  34. ( Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT_eSPI/ )
  35.  
  36. // ESP32 pins used for the parallel interface TFT
  37. #define TFT_CS 27 // Chip select control pin
  38. #define TFT_DC 14 // Data Command control pin - must use a pin in the range 0-31
  39. #define TFT_RST 26 // Reset pin
  40.  
  41. #define TFT_WR 12 // Write strobe control pin - must use a pin in the range 0-31
  42. #define TFT_RD 13
  43.  
  44. #define TFT_D0 16 // Must use pins in the range 0-31 for the data bus
  45. #define TFT_D1 4 // so a single register write sets/clears all bits
  46. #define TFT_D2 2 // 23
  47. #define TFT_D3 22
  48. #define TFT_D4 21
  49. #define TFT_D5 15 // 19
  50. #define TFT_D6 25 // 18
  51. #define TFT_D7 17
  52.  
  53.  
  54. **/
  55.  
  56. String version="12.3"; // fonction Autoland + Wifi Dialogue ND
  57.  
  58. // le numéro de version doit (depuis la 10.1) être identique à celui du ND (Navigation Display)
  59.  
  60. #include <stdint.h>
  61. #include <TFT_eSPI.h> // Hardware-specific library
  62. #include "Free_Fonts.h"
  63.  
  64. #include "FS.h"
  65. #include "SD.h"
  66. #include "SPI.h"
  67.  
  68. #include "FG_data.h"
  69.  
  70.  
  71. #include <WiFi.h>
  72.  
  73. // #include <WebServer.h>
  74. #include "ESPAsyncWebServer.h"
  75.  
  76.  
  77. /*
  78. #include "BluetoothSerial.h"
  79.  
  80. #if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
  81. #error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
  82. #endif
  83.  
  84. BluetoothSerial SerialBT;
  85. */
  86.  
  87.  
  88. const char* ssid = "FGpanel_srv";
  89. const char* password = "72r4TsJ28";
  90.  
  91. AsyncWebServer server(80); // Create AsyncWebServer object on port 80
  92.  
  93. String argument_recu1;
  94.  
  95. TFT_eSPI TFT = TFT_eSPI(); // Configurer le fichier User_Setup.h de la bibliothèque TFT_eSPI au préalable
  96.  
  97. TFT_eSprite SPR_E = TFT_eSprite(&TFT); // Declare Sprite object "SPR_11" with pointer to "TFT" object
  98. TFT_eSprite SPR_N = TFT_eSprite(&TFT);
  99. TFT_eSprite SPR_O = TFT_eSprite(&TFT);
  100. TFT_eSprite SPR_S = TFT_eSprite(&TFT);
  101.  
  102. //uint16_t s_width = TFT.Get_Display_Width();
  103. //uint16_t s_heigh = TFT.Get_Display_Height();
  104.  
  105. int16_t Ax_actu, Ay_actu;
  106. int16_t Bx_actu, By_actu;
  107.  
  108.  
  109. // COULEURS RGB565
  110. // Outil de sélection -> http://www.barth-dev.de/online/rgb565-color-picker/
  111.  
  112. #define NOIR 0x0000
  113. #define ROUGE 0xF800
  114. #define ROSE 0xFBDD
  115. #define ORANGE 0xFBC0
  116. #define JAUNE 0xFFE0
  117. #define JAUNE_PALE 0xF7F4
  118. #define VERT 0x07E0
  119. #define OLIVE 0x05A3
  120. #define CYAN 0x07FF
  121. #define BLEU_CLAIR 0x455F
  122. #define AZUR 0x1BF9
  123. #define BLEU 0x001F
  124. #define MAGENTA 0xF81F
  125. #define VIOLET1 0x781A
  126. #define VIOLET2 0xECBE
  127. #define GRIS_TRES_CLAIR 0xDEFB
  128. #define GRIS_CLAIR 0xA534
  129. #define GRIS 0x8410
  130. #define GRIS_FONCE 0x5ACB
  131. #define GRIS_TRES_FONCE 0x2124
  132.  
  133. #define GRIS_AF 0x51C5 // 0x3985
  134. #define BLANC 0xFFFF
  135.  
  136.  
  137. // AVIONIQUE : Horizon Artificiel (HA):
  138.  
  139. #define HA_CIEL 0x33FE
  140. #define HA_SOL 0xAA81 //0xDB60
  141.  
  142. //position et dimensions de l'horizon artificiel
  143.  
  144. #define HA_x0 210
  145. #define HA_y0 130
  146. #define HA_w 120 // demi largeur
  147. #define HA_h 100 // demi hauteur
  148.  
  149. #define x_autopilot 320
  150.  
  151. // Width and height of sprite
  152. #define SPR_W 14
  153. #define SPR_H 14
  154.  
  155.  
  156. /*
  157. #define taille1 100000 //480*320*2
  158. uint8_t data_ecran[taille1];
  159. */
  160.  
  161. uint32_t memo_micros = 0;
  162. uint32_t temps_ecoule;
  163. uint16_t nb_secondes=0;
  164.  
  165. float roulis;
  166. float tangage;
  167.  
  168. int32_t altitude;
  169. int32_t gnd_elv;
  170. int32_t vitesse;
  171. int32_t memo_vitesse;
  172. int16_t dV;
  173. int16_t acceleration;
  174. int16_t vspeed; // vitesse verticale
  175.  
  176. int16_t cap; // en degrés d'angle; direction actuelle du nez de l'avion
  177.  
  178.  
  179. int16_t hdg1 = 150; // en degrés d'angle; consigne cap = Heading (HDG) Bug
  180. int16_t memo_hdg1;
  181.  
  182. char var_array[5]; // 4 char + zero terminal - pour envoi par WiFi
  183.  
  184. // Nav1 -> pour VOR
  185. int32_t nav1dst; // en mètres
  186. uint32_t nav1frq;
  187. float nav1radial; // 0..35900
  188. uint16_t nav1radial_deg; // 0..359
  189.  
  190.  
  191. // Nav2 -> pour ILS
  192. int32_t nav2dst;
  193. uint32_t nav2frq;
  194. float nav2loc; // angle de déviation (erreur / axe de la piste) dans le plan horizontal
  195. float nav2glide; // angle de descente (pente) vue depuis la balise
  196. float nav2radial;
  197.  
  198. int16_t loc=0; // localizer
  199. int16_t memo_loc=0;
  200.  
  201. int16_t gld=0; // glide
  202. int16_t memo_gld=0;
  203.  
  204. int16_t num_bali=0;
  205.  
  206. int16_t asel1 = 30; // consigne altitude
  207.  
  208. uint8_t SDcardOk=0;
  209. uint16_t data_ok;
  210. uint8_t gs_ok;
  211.  
  212. uint8_t premier_passage =1;
  213. uint8_t attente_data=1;
  214.  
  215. uint8_t autoland=0;
  216.  
  217. //uint8_t premier_passage =1;
  218. //uint8_t take_off_ok =0; // passe à 1 si altitude atteinte = 3000ft
  219.  
  220.  
  221.  
  222. float memo_R2;
  223. int16_t memo_y0;
  224.  
  225. const int bouton1 = 36; // attention: le GPIO 36 n'a pas de R de pullup interne, il faut en câbler une au +3V3
  226. bool bouton1_etat;
  227. bool memo_bouton1_etat;
  228.  
  229.  
  230.  
  231. // deux encodeurs rotatifs pas à pas
  232. const int rot1a = 32; // GPIO32 -> câbler une R au +3V3
  233. const int rot1b = 33; // GPIO33 -> câbler une R au +3V3
  234.  
  235. const int rot2a = 35; // GPIO35 -> câbler une R au +3V3
  236. const int rot2b = 34; // GPIO34 -> câbler une R au +3V3
  237.  
  238. //const int led1 = 25; // GPIO15
  239.  
  240.  
  241. #define TEMPO 5 // tempo anti-rebond pour l'acquisition des encodeurs rotatifs
  242. volatile uint32_t timer1 = 0;
  243. volatile uint32_t timer2 = 0;
  244.  
  245. uint16_t compteur1;
  246.  
  247. // TFT.setTextColor(TFT_BLACK, TFT_WHITE);
  248. // TFT.drawLine(x0, y0, x1, y1, TFT_BLACK);
  249. // TFT.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN);
  250. // TFT.drawRect(5, 3, 230, 119, TFT_BLACK);
  251. // TFT.fillRect(5, 3, 230, 119, TFT_WHITE);
  252.  
  253.  
  254. void RAZ_variables()
  255. {
  256. roulis=0;
  257. tangage=0;
  258. altitude=0;
  259. gnd_elv=0;
  260. vitesse=0;
  261. vspeed=0;
  262. cap=0;
  263. memo_hdg1=0;
  264. nav1dst=0;
  265. nav1radial=0;
  266. nav1radial_deg=0;
  267. nav2dst=0;
  268. nav2loc=0;
  269. nav2glide=0;
  270. loc=0;
  271. memo_loc=0;
  272. gld=0;
  273. memo_gld=0;
  274. }
  275.  
  276.  
  277.  
  278. /** ***********************************************************************************
  279. IMAGE.bmp
  280. ***************************************************************************************/
  281.  
  282. /**
  283. Rappel et décryptage de la fonction Color_To_565 : (elle se trouve dans le fichier LCDWIKI_KBV.cpp)
  284.  
  285. //Pass 8-bit (each) R,G,B, get back 16-bit packed color
  286.  
  287. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  288. {
  289. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  290. }
  291.  
  292. 0xF8 = 11111000
  293. 0xFC = 11111100
  294.  
  295. (r & 0xF8) -> 5 bit de gauche de r (on ignore donc les 3 bits de poids faible)
  296. (g & 0xFC) -> 6 bit de gauche de g (on ignore donc les 2 bits de poids faible)
  297. (b & 0xF8) -> 5 bit de gauche de b (on ignore donc les 3 bits de poids faible)
  298.  
  299. rrrrr---
  300. gggggg--
  301. bbbbb---
  302.  
  303. après les décallages on obtient les 16 bits suivants:
  304.  
  305. rrrrr---========
  306.   gggggg--===
  307.   ===bbbbb
  308.  
  309. soit après le ou :
  310.  
  311. rrrrrggggggbbbbb
  312.  
  313. calcul de la Fonction inverse :
  314. RGB565_to_888
  315. **/
  316.  
  317.  
  318. void RGB565_to_888(uint16_t color565, uint8_t *R, uint8_t *G, uint8_t *B)
  319. {
  320. *R=(color565 & 0xFFFFF800) >> 8;
  321. *G=(color565 & 0x7E0) >> 3;
  322. *B=(color565 & 0x1F) << 3 ;
  323. }
  324.  
  325.  
  326.  
  327. /** -----------------------------------------------------------------------------------
  328. CAPTURE D'ECRAN vers SDcard
  329. /** ----------------------------------------------------------------------------------- */
  330.  
  331. void write_TFT_on_SDcard() // enregistre le fichier .bmp
  332. {
  333. if (SDcardOk==0) {return;}
  334.  
  335. String s1;
  336. uint16_t ys=200;
  337. TFT.setFreeFont(FF1);
  338. TFT.setTextColor(JAUNE, NOIR);
  339.  
  340. uint16_t x, y;
  341. uint16_t color565;
  342. uint16_t bmp_color;
  343. uint8_t R, G, B;
  344.  
  345. if( ! SD.exists("/bmp/capture2.bmp"))
  346. {
  347. TFT.fillRect(0, 0, 480, 320, NOIR); // efface
  348. TFT.setTextColor(ROUGE, NOIR);
  349. TFT.drawString("NO /bmp/capture2.bmp !", 100, ys);
  350. delay(300);
  351. TFT.fillRect(100, ys, 220, 20, NOIR); // efface
  352. return;
  353. }
  354.  
  355.  
  356. File File1 = SD.open("/bmp/capture2.bmp", FILE_WRITE); // ouverture du fichier binaire (vierge) en écriture
  357. if (File1)
  358. {
  359. /*
  360. Les images en couleurs réelles BMP888 utilisent 24 bits par pixel:
  361. Il faut 3 octets pour coder chaque pixel, en respectant l'ordre de l'alternance bleu, vert et rouge.
  362. */
  363. uint16_t bmp_offset = 138;
  364. File1.seek(bmp_offset);
  365.  
  366.  
  367. TFT.setTextColor(VERT, NOIR);;
  368.  
  369. for (y=320; y>0; y--)
  370. {
  371. for (x=0; x<480; x++)
  372. {
  373. color565=TFT.readPixel(x, y);
  374.  
  375. RGB565_to_888(color565, &R, &G, &B);
  376.  
  377. File1.write(B); //G
  378. File1.write(G); //R
  379. File1.write(R); //B
  380. }
  381.  
  382. s1=(String) (y/10);
  383. TFT.fillRect(450, 300, 20, 20, NOIR);
  384. TFT.drawString(s1, 450, 300);// affi compte à rebour
  385. }
  386.  
  387. File1.close(); // referme le fichier
  388.  
  389. TFT.fillRect(450, 300, 20, 20, NOIR); // efface le compte à rebour
  390. }
  391. }
  392.  
  393. /** ----------------------------------------------------------------------------------- */
  394.  
  395.  
  396.  
  397. void Draw_arc_elliptique(uint16_t x0, uint16_t y0, int16_t dx, int16_t dy, float alpha1, float alpha2, uint16_t couleur) // alpha1 et alpha2 en radians
  398. {
  399. /*
  400. REMARQUES :
  401. -cette fonction permet également de dessiner un arc de cercle (si dx=dy), voire le cercle complet
  402. - dx et dy sont du type int (et pas uint) et peuvent êtres négafifs, ou nuls.
  403. -alpha1 et alpha2 sont les angles (en radians) des caps des extrémités de l'arc
  404. */
  405. uint16_t n;
  406. float i;
  407. float x,y;
  408.  
  409. i=alpha1;
  410. while(i<alpha2)
  411. {
  412. x=x0+dx*cos(i);
  413. y=y0+dy*cos(i+M_PI/2.0);
  414. TFT.drawPixel(x,y, couleur);
  415. i+=0.01; // radians
  416. }
  417. }
  418.  
  419.  
  420. void affi_rayon1(uint16_t x0, uint16_t y0, uint16_t rayon, double angle, float pourcent, uint16_t couleur_i, bool gras)
  421. {
  422. // trace une portion de rayon de cercle depuis 100%...à pourcent du rayon du cercle
  423. // angle en radians - sens trigo
  424. float x1, x2;
  425. float y1, y2;
  426.  
  427. x1=x0+rayon* cos(angle);
  428. y1=y0-rayon* sin(angle);
  429.  
  430. x2=x0+pourcent*rayon* cos(angle);
  431. y2=y0-pourcent*rayon* sin(angle);
  432.  
  433. TFT.drawLine(x1, y1, x2, y2, couleur_i);
  434. if (gras)
  435. {
  436. TFT.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  437. TFT.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  438. }
  439. }
  440.  
  441.  
  442.  
  443. void affi_rayon2(uint16_t x0, uint16_t y0, float r1, float r2, float angle_i, uint16_t couleur_i, bool gras)
  444. {
  445. // trace une portion de rayon de cercle entre les distances r1 et r2 du centre
  446. // angle_i en degrés décimaux - sens trigo
  447.  
  448. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  449. int16_t x1, x2;
  450. int16_t y1, y2;
  451.  
  452. x1=x0+int16_t(r1* cos(angle));
  453. y1=y0-int16_t(r1* sin(angle));
  454.  
  455. x2=x0+int16_t(r2* cos(angle));
  456. y2=y0-int16_t(r2* sin(angle));
  457.  
  458. if ((x1>0) && (x2>0) && (y1>0) && (y2>0) && (x1<480) && (x2<480) && (y1<320) && (y2<320) )
  459. {
  460. TFT.drawLine(x1, y1, x2, y2, couleur_i);
  461.  
  462. if (gras)
  463. {
  464. TFT.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  465. TFT.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  466. }
  467. }
  468.  
  469. //TFT.fillCircle(x2, y2, 2, ROUGE);
  470. }
  471.  
  472.  
  473.  
  474.  
  475. void affi_tiret_H(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, uint16_t couleur_i)
  476. {
  477. // trace un tiret perpendiculaire à un rayon de cercle de rayon r
  478. // angle_i en degrés décimaux
  479.  
  480. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  481. float x1, x2;
  482. float y1, y2;
  483.  
  484. x1=x0+(r)* cos(angle-1);
  485. y1=y0-(r)* sin(angle-1);
  486.  
  487. x2=x0+(r)* cos(angle+1);
  488. y2=y0-(r)* sin(angle+1);
  489.  
  490. TFT.drawLine(x1, y1, x2, y2, couleur_i);
  491. }
  492.  
  493.  
  494.  
  495. void affi_pointe(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, uint16_t couleur_i)
  496. {
  497. // trace une pointe de flèche sur un cercle de rayon r
  498. // angle_i en degrés décimaux - sens trigo
  499.  
  500. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  501. int16_t x1, x2, x3;
  502. int16_t y1, y2, y3;
  503.  
  504. x1=x0+r* cos(angle); // pointe
  505. y1=y0-r* sin(angle); // pointe
  506.  
  507. x2=x0+(r-7)* cos(angle-0.1); // base A
  508. y2=y0-(r-7)* sin(angle-0.1); // base A
  509.  
  510. x3=x0+(r-7)* cos(angle+0.1); // base B
  511. y3=y0-(r-7)* sin(angle+0.1); // base B
  512.  
  513. TFT.fillTriangle(x1, y1, x2, y2, x3, y3, couleur_i);
  514. }
  515.  
  516.  
  517. void affi_rectangle_incline(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, uint16_t couleur_i)
  518. {
  519. //rectangle inscrit dans le cerce de rayon r
  520. // angle_i en degrés décimaux - sens trigo
  521.  
  522. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  523. int16_t x1, x2, x3, x4;
  524. int16_t y1, y2, y3, y4;
  525. float d_alpha=0.08; // détermine la largeur du rectangle
  526.  
  527. // point 1
  528. x1=x0+r*cos(angle-d_alpha);
  529. y1=y0+r*sin(angle-d_alpha);
  530. // point 2
  531. x2=x0+r*cos(angle+d_alpha);
  532. y2=y0+r*sin(angle+d_alpha);
  533. // point 3
  534. x3=x0+r*cos(M_PI + angle-d_alpha);
  535. y3=y0+r*sin(M_PI + angle-d_alpha);
  536. // point 4
  537. x4=x0+r*cos(M_PI + angle+d_alpha);
  538. y4=y0+r*sin(M_PI + angle+d_alpha);
  539.  
  540. TFT.drawLine(x1, y1, x2, y2, couleur_i);
  541. TFT.drawLine(x2, y2, x3, y3, couleur_i);
  542. TFT.drawLine(x3, y3, x4, y4, couleur_i);
  543. TFT.drawLine(x4, y4, x1, y1, couleur_i);
  544.  
  545. }
  546.  
  547.  
  548.  
  549. void affi_indexH(uint16_t x, uint16_t y, int8_t sens, uint16_t couleur)
  550. {
  551. // petite pointe de flèche horizontale
  552. // sens = +1 ou -1 pour orienter la pointe vers la droite ou vers la gauche
  553.  
  554. TFT.fillTriangle(x, y-4, x, y+4, x+8*sens, y, couleur);
  555. }
  556.  
  557.  
  558.  
  559. void affi_indexV(uint16_t x, uint16_t y, int8_t sens, uint16_t couleur)
  560. {
  561. // petite pointe de flèche verticale
  562. // sens = +1 ou -1 pour orienter la pointe vers le haut ou vers le bas
  563.  
  564. TFT.fillTriangle(x-4, y, x+4, y, x, y+8*sens, couleur);
  565. }
  566.  
  567.  
  568.  
  569. float degTOrad(float angle)
  570. {
  571. return (angle * M_PI / 180.0);
  572. }
  573.  
  574.  
  575.  
  576. void init_affi_HA()
  577. {
  578. TFT.fillRect(HA_x0-HA_w, HA_y0-HA_h-1, 2*HA_w, HA_h+1, HA_CIEL);
  579. TFT.fillRect(HA_x0-HA_w, HA_y0-HA_h + HA_h, 2*HA_w, HA_h, HA_SOL);
  580.  
  581. }
  582.  
  583.  
  584.  
  585.  
  586. void dessine_avion()
  587. {
  588. // aile gauche
  589. TFT.fillRect(HA_x0-102, HA_y0-3, 60, 10, BLANC); //H contour en blanc
  590. TFT.fillRect(HA_x0-42, HA_y0-3, 10, 19, BLANC); //V
  591.  
  592. TFT.fillRect(HA_x0-100, HA_y0-1, 60, 5, NOIR); //H
  593. TFT.fillRect(HA_x0-40, HA_y0-1, 5, 15, NOIR); //V
  594.  
  595.  
  596. // aile droite
  597. TFT.fillRect(HA_x0+28, HA_y0-3, 64, 10, BLANC); //H contour en blanc
  598. TFT.fillRect(HA_x0+28, HA_y0-3, 10, 19, BLANC); //V
  599.  
  600. TFT.fillRect(HA_x0+30, HA_y0-1, 60, 5, NOIR); //H
  601. TFT.fillRect(HA_x0+30, HA_y0-1, 5, 15, NOIR); //V
  602.  
  603. //carré blanc au centre
  604.  
  605. TFT.fillRect(HA_x0-4, HA_y0-3, 8, 2, BLANC);
  606. TFT.fillRect(HA_x0-4, HA_y0-3, 2, 8, BLANC);
  607.  
  608. TFT.fillRect(HA_x0-4, HA_y0+3, 10, 2, BLANC);
  609. TFT.fillRect(HA_x0+4, HA_y0-3, 2, 8, BLANC);
  610.  
  611. //affi_dst_NAV();
  612.  
  613. }
  614.  
  615.  
  616.  
  617. void lign_sep(uint16_t Ax, uint16_t Ay, uint16_t Bx, uint16_t By)
  618. {
  619. // actualise la ligne de séparation ciel-sol
  620.  
  621. TFT.drawLine(Ax, Ay-1, Bx, By-1, HA_CIEL);
  622. TFT.drawLine(Ax, Ay, Bx, By, BLANC);
  623. TFT.drawLine(Ax, Ay+1, Bx, By+1, HA_SOL);
  624. }
  625.  
  626.  
  627.  
  628. void arrondissement_coins()
  629. {
  630.  
  631. // fillTriangle(int32_t x1,int32_t y1, int32_t x2,int32_t y2, int32_t x3,int32_t y3, uint32_t color);
  632.  
  633. //HG
  634. TFT.fillTriangle(
  635. HA_x0-HA_w, HA_y0-HA_h-1,
  636. HA_x0-HA_w, HA_y0-HA_h+20,
  637. HA_x0-HA_w+20, HA_y0-HA_h-1,
  638. NOIR);
  639.  
  640.  
  641. //----------------------------------------------
  642. //HD
  643. TFT.fillTriangle(
  644. HA_x0+HA_w, HA_y0-HA_h-1,
  645. HA_x0+HA_w, HA_y0-HA_h+20,
  646. HA_x0+HA_w-20, HA_y0-HA_h-1,
  647. NOIR);
  648.  
  649.  
  650.  
  651. //----------------------------------------------
  652. //BG
  653. TFT.fillTriangle(
  654. HA_x0-HA_w, HA_y0+HA_h+1,
  655. HA_x0-HA_w, HA_y0+HA_h-20,
  656. HA_x0-HA_w+20, HA_y0+HA_h+1,
  657. NOIR);
  658.  
  659. //----------------------------------------------
  660. //BD
  661. TFT.fillTriangle(
  662. HA_x0+HA_w, HA_y0+HA_h+1,
  663. HA_x0+HA_w, HA_y0+HA_h-20,
  664. HA_x0+HA_w-20, HA_y0+HA_h+1,
  665. NOIR);
  666.  
  667. }
  668.  
  669.  
  670.  
  671. void affi_HA() // Horizon Artificiel
  672. {
  673. String s1;
  674.  
  675. ////String s1=(String) roulis;
  676. ////TFT.drawString(s1, 400, 20);
  677.  
  678.  
  679. // pivot
  680. int16_t x0=0;
  681. int16_t y0=0;
  682.  
  683. //points d'intersection avec le bord du carré
  684. int16_t Ax, Ay; // sur le bord gauche
  685. int16_t Bx, By; // sur le bord droit
  686. // Le dessin consistera à tracer des segments colorés entre les points A et B
  687.  
  688.  
  689. // roulis -> [-90..+90]
  690.  
  691. // normalisation de la valeur R2 -> toujours >0
  692. float R2 = -1*roulis;
  693. if (R2<0) {R2+=360;} // ce qui est un angle identique, de valeur positive (sens trigo)
  694.  
  695. // le pivot reste centré horizontalement mais se déplace verticalement en fonction du tangage
  696. y0 += 2*tangage;
  697.  
  698. //calcul & memorisation de ces deux facteurs, ce qui évitera 2 calculs de tangente à chaque passage dan la fonction
  699. float tgt_moins = tan(degTOrad(90-R2));
  700. float tgt_plus = tan(degTOrad(90+R2));
  701.  
  702. //-----------------------------------------------------------------------------
  703. // CALCUL COTE DROIT (point B)
  704.  
  705. // calcul du point B d'intersection
  706. Bx=HA_w;
  707. By=y0 + HA_w*tan(degTOrad(R2));
  708.  
  709. //test si le point d'intersection se trouve plus haut que le haut du carré :
  710.  
  711. if(By>HA_h)
  712. {
  713. By=HA_h;
  714. Bx = x0 + (HA_h-y0)*tgt_moins;
  715. }
  716.  
  717. if(By< -HA_h)
  718. {
  719. By= -HA_h;
  720. Bx = x0 + (HA_h+y0)*tgt_plus;
  721. }
  722. //-----------------------------------------------------------------------------
  723. // CALCUL COTE GAUCHE (point A)
  724.  
  725. Ax=-HA_w;
  726. Ay=y0 - HA_w*tan(degTOrad(R2));
  727.  
  728. if(Ay> HA_h)
  729. {
  730. Ay= HA_h;
  731. Ax = x0 + (HA_h-y0)*tgt_moins;
  732. }
  733.  
  734. if(Ay< -HA_h)
  735. {
  736. Ay= -HA_h;
  737. Ax = x0 + (HA_h+y0)*tgt_plus;
  738. }
  739.  
  740.  
  741. //-----------------------------------------------------------------------------
  742. // positionnement de l'ensemble sur l'écran
  743.  
  744. Ax += HA_x0;
  745. Ay += HA_y0;
  746.  
  747. Bx += HA_x0;
  748. By += HA_y0;
  749.  
  750. // pour éviter un tracé hors cadre au premier passage :
  751. if (premier_passage == 1)
  752. {
  753. Ax_actu = Ax;
  754. Ay_actu = Ay;
  755.  
  756. Bx_actu = Bx;
  757. By_actu = By;
  758. premier_passage=0;
  759. }
  760.  
  761.  
  762. //-----------------------------------------------------------------------------
  763. // ligne "verticale" d'inclinaison (tangage)
  764.  
  765. affi_rayon2(HA_x0, HA_y0, 85, -memo_y0, 90-memo_R2, HA_CIEL, false); // efface partie supérieure
  766. affi_rayon2(HA_x0, HA_y0, 85, -y0, 90-R2, BLANC, false); // retrace ligne partie supérieure
  767.  
  768. affi_rayon2(HA_x0, HA_y0, -85,-memo_y0, 90-memo_R2, HA_SOL, false); // efface partie inférieure
  769. affi_rayon2(HA_x0, HA_y0, -85,-y0, 90-R2, VERT, false); // retrace ligne partie inférieure
  770.  
  771. affi_pointe(HA_x0, HA_y0, 85, 90-memo_R2, HA_CIEL); // efface
  772. affi_pointe(HA_x0, HA_y0, 85, 90-R2, BLANC); // retrace
  773.  
  774.  
  775. affi_glide();
  776. affi_localizer();
  777. //affi_tiret_H(HA_x0, HA_y0, 50, degTOrad(90-memo_R2), HA_CIEL); // efface
  778. //affi_tiret_H(HA_x0, HA_y0, 50, degTOrad(90-R2), ROSE); // retrace
  779.  
  780. // graduation fixe
  781. TFT.setFreeFont(FF1);
  782. TFT.setTextColor(BLANC, GRIS_AF);
  783. TFT.drawString("30", HA_x0-70, HA_y0-98);
  784. TFT.drawString("60", HA_x0-120, HA_y0-55);
  785.  
  786. TFT.drawString("30", HA_x0+60, HA_y0-98);
  787. TFT.drawString("60", HA_x0+100, HA_y0-55);
  788.  
  789. //-----------------------------------------------------------------------------
  790. // animation de la ligne de séparation horizontale
  791.  
  792. while ((Bx_actu != Bx) || (By_actu != By) || (Ax_actu != Ax) || (Ay_actu != Ay))
  793. {
  794. // déplacements successifs de 1 pixel de chaque extrémités de la ligne
  795.  
  796. //TFT.drawLine(Bx, By, x2, y2, BLANC);
  797.  
  798. if (Bx_actu < Bx) { Bx_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  799. if (Bx_actu > Bx) { Bx_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  800.  
  801. if (Ax_actu < Ax) { Ax_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  802. if (Ax_actu > Ax) { Ax_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  803.  
  804. if (By_actu < By) { By_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  805. if (By_actu > By) { By_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  806.  
  807. if (Ay_actu < Ay) { Ay_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  808. if (Ay_actu > Ay) { Ay_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  809.  
  810. }
  811.  
  812.  
  813. // graduation roulis qui se déplace angulairement avec la ligne de tangage
  814. for (int8_t n=0; n<4; n++)
  815. {
  816. Draw_arc_elliptique(HA_x0, HA_y0, 20*n, 20*n, degTOrad(80-memo_R2+n), degTOrad(100-memo_R2-n), HA_CIEL); // efface bas
  817. Draw_arc_elliptique(HA_x0, HA_y0, 20*n, 20*n, degTOrad(80-R2+n), degTOrad(100-R2-n), BLANC); // trace bas
  818.  
  819. Draw_arc_elliptique(HA_x0, HA_y0, 20*n, 20*n, degTOrad(260-memo_R2+n), degTOrad(280-memo_R2-n), HA_SOL); // efface haut
  820. Draw_arc_elliptique(HA_x0, HA_y0, 20*n, 20*n, degTOrad(260-R2+n), degTOrad(280-R2-n), BLANC); // trace haut
  821. }
  822.  
  823. memo_R2 = R2;
  824. memo_y0 = y0;
  825.  
  826.  
  827.  
  828.  
  829. //-----------------------------------------------------------------------------
  830. arrondissement_coins();
  831.  
  832. //affi_frq_NAV1();
  833. affi_dst_NAV1();
  834. affi_dst_NAV2();
  835.  
  836. affi_alti_NAV2();
  837. }
  838.  
  839.  
  840.  
  841. void affi_acceleration()
  842. {
  843. // POUR TEST **********
  844. ////String s2= (String) acceleration;
  845. ////TFT.fillRect(100, 50, 200, 20, TFT_BLACK);
  846. ////TFT.setFreeFont(FF5);
  847. ////TFT.setTextColor(BLANC, NOIR);
  848. ////TFT.drawString("Acceleration=", 100, 50);
  849. ////TFT.drawString(s2, 250, 50);
  850. // ********************
  851.  
  852. //barres verticales colorées juste à droite de la vitesse indiquant sa variation
  853. uint16_t x0=60;
  854. uint16_t Y_zero=162;
  855.  
  856. int16_t dy=0;
  857.  
  858. //fleche haute
  859. TFT.fillRect(x0, 30, 8, Y_zero, GRIS_TRES_FONCE); // efface haut
  860. if (acceleration > 1)
  861. {
  862. dy= acceleration;
  863.  
  864. TFT.fillRect(x0, Y_zero-dy, 8, dy, VERT); // fleche
  865. }
  866.  
  867.  
  868. //fleche basse
  869. TFT.fillRect(x0, Y_zero, 8, 132, GRIS_TRES_FONCE); // efface bas
  870. if (acceleration < -1)
  871. {
  872. dy= -acceleration;
  873.  
  874. TFT.fillRect(x0, Y_zero, 8, dy, JAUNE); // fleche
  875. }
  876.  
  877. TFT.fillRect(x0, Y_zero, 10, 2, BLANC);
  878.  
  879. }
  880.  
  881.  
  882. void affi_vitesse()
  883. {
  884.  
  885. uint16_t x1;
  886. String s1;
  887.  
  888. TFT.setTextColor(BLANC); // Background is not defined so it is transparent
  889.  
  890. //---------------------------------------------------------------------------------------
  891. //bande verticale multicolore
  892.  
  893. #define vitesse_sol 40
  894. int16_t vitesse_mini1 =90;
  895. int16_t vitesse_mini2 =130;
  896. int16_t vitesse_maxi1 =200;
  897. int16_t vitesse_maxi2 =280;
  898.  
  899. //calcul de la position des limites entre les différentes couleurs verticales
  900. float d1=HA_y0+HA_w/2 + 3.2*((vitesse - vitesse_sol)); if (d1<30) {d1=30;} if (d1>290) {d1=290;}
  901. float d2=HA_y0+HA_w/2 + 3.2*((vitesse - vitesse_mini1)); if (d2<30) {d2=30;} if (d2>290) {d2=290;}
  902. float d3=HA_y0+HA_w/2 + 3.2*((vitesse - vitesse_mini2)); if (d3<30) {d3=30;} if (d3>290) {d3=290;}
  903. float d4=HA_y0+HA_w/2 + 3.2*((vitesse - vitesse_maxi1)); if (d4<30) {d4=30;} if (d4>290) {d4=290;}
  904. float d5=HA_y0+HA_w/2 + 3.2*((vitesse - vitesse_maxi2)); if (d5<30) {d5=30;} if (d5>290) {d5=290;}
  905.  
  906.  
  907. TFT.fillRect(50, 30, 6, (int16_t)d5, ORANGE);
  908. TFT.fillRect(50, (int16_t)d5, 6, d4-d5, JAUNE);
  909. TFT.fillRect(50, (int16_t)d4, 6, d3-d4, VERT);
  910. TFT.fillRect(50, (int16_t)d3, 6, d2-d3, ORANGE);
  911. TFT.fillRect(50, (int16_t)d2, 6, d1-d2, ROUGE);
  912. TFT.fillRect(50, (int16_t)d1, 6, 290-(int16_t)d1, GRIS);
  913.  
  914. //---------------------------------------------------------------------------------------
  915. //échelle verticale graduée glissante
  916.  
  917. uint16_t y0;
  918. uint16_t y_min=30;
  919. uint16_t y_max=290;
  920.  
  921. int16_t vit1;
  922. float d6;
  923.  
  924. TFT.setFreeFont(FF1);
  925.  
  926. y0=3.2*(vitesse%10);
  927.  
  928. TFT.fillRect(0, y_min, 50, y_max-30, GRIS_AF); // bande verticale à gauche
  929. for(int n=0; n<10; n++)
  930. {
  931. d6 =2+y0+32.0*n; // 24 pixels verticalement entre chaque trait -> 10*24 = 240px (hauteur de l'affiur)
  932. {
  933. if ( (d6>y_min) && (d6<y_max-10) && (vit1>=0) && (vit1<1000) )
  934. {
  935. TFT.fillRect(45, (int16_t)d6, 10, 2, BLANC); // petits tirets horizontaux
  936. }
  937.  
  938. vit1 = vitesse -10*(n-5);
  939. vit1 /= 10;
  940. vit1 *= 10;
  941. s1=(String) vit1;
  942.  
  943. if ( (d6>y_min) && (d6<y_max-10) && (vit1>=0) && (vit1<1000) )
  944. {
  945. TFT.setTextColor(BLANC, GRIS_AF);
  946. //TFT.drawString(" ", 9, d6);
  947. x1=0;
  948. if(vit1<100){x1=7;} // pour affichage centré
  949. if(vit1<10){x1=14;}
  950. if (vit1>=10)
  951. {
  952. TFT.drawString(s1, x1, (uint16_t)d6-5); // Graduation (tous les 20kts)
  953. }
  954. }
  955. }
  956. }
  957. //TFT.fillRect(0, 0, 65, 20, NOIR);
  958.  
  959. //---------------------------------------------------------------------------------------
  960. // affichage de la valeur principale
  961.  
  962. uint16_t VP_y0 = 150;
  963.  
  964. TFT.setTextColor(BLANC, NOIR);
  965. TFT.setFreeFont(FF18);
  966. s1=(String)vitesse;
  967.  
  968. TFT.fillRect(3, VP_y0, 42, 26, NOIR); // efface le nombre précédemment affiché (pour le cas où on passe de trois à deux chiffres)
  969.  
  970. if ((vitesse>=0) && (vitesse <1000))
  971. {
  972. x1=3;
  973. if(vitesse<100){x1=10;} // pour affichage centré
  974. if(vitesse<10){x1=20;}
  975. TFT.drawString(s1, x1, VP_y0+3); // affi le nombre
  976. } // affi en gros à mi-hauteur de l'écran
  977. else
  978. { TFT.fillRect(3, VP_y0, 42, 26, GRIS); }
  979.  
  980. TFT.drawRoundRect(1, VP_y0-1, 45, 28, 5, BLANC); // encadrement de la valeur centrale affichée
  981.  
  982. TFT.fillTriangle(45, VP_y0+7, 45, VP_y0+17, 55, VP_y0+12, NOIR); // petit triangle (curseur) noir
  983. }
  984.  
  985.  
  986.  
  987. void affi_asel()
  988. {
  989. // consigne ALTITUDE de l'autopilot
  990. // ( chiffres en rose en haut à droite )
  991.  
  992. String s2 =(String)(asel1*100);
  993. TFT.setTextColor(ROSE, NOIR);
  994. TFT.setFreeFont(FF5);
  995. uint16_t x1;
  996. x1=340;
  997. TFT.fillRect(x1+7, 0, 70, 20, NOIR); // efface
  998. if(asel1<10000){x1+=7;}
  999. if(asel1<1000){x1+=7;}
  1000. if(asel1<100){x1+=7;} // pour affichage centré
  1001. if(asel1<10){x1+=7;}
  1002.  
  1003. TFT.drawString(s2, x1, 5);
  1004. }
  1005.  
  1006.  
  1007.  
  1008. void affi_vt_verticale()
  1009. {
  1010. // affichage analogique sur la droite de l'écran
  1011.  
  1012. uint16_t x0=435;
  1013. uint16_t y0=165;
  1014.  
  1015. float y1;
  1016.  
  1017. uint16_t x1;
  1018. String s1;
  1019.  
  1020. TFT.fillRect(x0, y0-90, 45, 180, GRIS_AF); // barre grise
  1021. TFT.fillRect(x0, y0, 25, 2, BLEU_CLAIR); // centre
  1022.  
  1023. // ------------------------
  1024. // graduations sur un arc vertical
  1025. TFT.setFreeFont(FF1);
  1026.  
  1027. TFT.setTextColor(BLANC, NOIR);
  1028. TFT.drawString("ft/mn", x0-8, y0+125);
  1029.  
  1030.  
  1031. TFT.setTextColor(BLANC, GRIS_AF);
  1032.  
  1033.  
  1034.  
  1035. float angle;
  1036. for(uint8_t n=0; n<7; n++ )
  1037. {
  1038. angle =135+ n*15; // 1 tiret tous les 15 degrés
  1039. affi_rayon1(HA_x0+340, y0, 110, degTOrad(angle), 0.9, BLANC, false); // tirets de graduation
  1040. }
  1041.  
  1042.  
  1043. TFT.drawString("3", x0+9, y0-90);
  1044. TFT.drawString("2", x0-3, y0-65);
  1045. TFT.drawString("1", x0-8, y0-35);
  1046. TFT.drawString("0", x0-3, y0-5 + 0);
  1047. TFT.drawString("1", x0-8, y0+25);
  1048. TFT.drawString("2", x0-3, y0+50);
  1049. TFT.drawString("3", x0+9, y0+75);
  1050.  
  1051. // ------------------------
  1052. // aiguille à droite de l'écran
  1053. float angle2;
  1054.  
  1055. TFT.setFreeFont(FF1);
  1056. s1=(String) (vspeed*60);
  1057.  
  1058. angle2 = 180.0 - vspeed *0.92;
  1059.  
  1060. TFT.fillRect(x0-10, y0-110, 55, 15, GRIS_TRES_FONCE); // efface haut
  1061. TFT.fillRect(x0-10, y0+105, 55, 15, GRIS_TRES_FONCE); // efface bas
  1062.  
  1063. if ((vspeed > -50) && (vspeed < 50))
  1064. {
  1065. affi_rayon1(HA_x0+330, y0, 100, degTOrad(angle2), 0.7, JAUNE, true);
  1066.  
  1067. TFT.setTextColor(JAUNE, NOIR);
  1068. }
  1069. else if (vspeed > 50)
  1070. {
  1071. affi_rayon1(HA_x0+330, y0, 110, degTOrad(132), 0.7, JAUNE, true);
  1072. TFT.setTextColor(JAUNE, NOIR);
  1073.  
  1074. TFT.drawString(s1, x0-10, y0-110);
  1075. }
  1076. else if (vspeed < -50)
  1077. {
  1078. affi_rayon1(HA_x0+330, y0, 110, degTOrad(228), 0.7, JAUNE, true);
  1079. TFT.setTextColor(JAUNE, NOIR);
  1080.  
  1081. TFT.drawString(s1, x0-10, y0+105);
  1082. }
  1083.  
  1084.  
  1085. // affichage digital de la valeur
  1086.  
  1087.  
  1088. /*
  1089. // = vitesse ascensionnelle, sous forme de barres verticales vertes, à droite, près de l'echelle d'altitude
  1090. uint16_t x0=405;
  1091. uint16_t y0=40;
  1092.  
  1093. int16_t dy=0;
  1094.  
  1095. //fleche haute
  1096. TFT.fillRect(x0, 0, 10, 140, GRIS_FONCE); // efface haut
  1097. if (vspeed > 1)
  1098. {
  1099. dy= vspeed;
  1100.  
  1101. TFT.fillRect(x0, y0+100-dy, 10, dy, VERT); // fleche
  1102. }
  1103.  
  1104.  
  1105. //fleche basse
  1106. TFT.fillRect(x0, y0+150, 10, 135, GRIS_FONCE); // efface bas
  1107. if (vspeed < -1)
  1108. {
  1109. dy= -vspeed;
  1110.  
  1111. TFT.fillRect(x0, y0+150, 10, dy, VERT); // fleche
  1112. }
  1113. */
  1114.  
  1115. }
  1116.  
  1117.  
  1118.  
  1119.  
  1120. void affi_cap()
  1121. {
  1122. // cercle tournant de CAP gradué en bas au centre de l'écran
  1123. // Les lettres 'N' 'S' 'E' 'O' pour Nord Sud Est Ouset sont initialisées sous forme de sprites dans la fonction setup()
  1124.  
  1125. uint16_t x02 = 200;
  1126. uint16_t y02 = 350;
  1127. float angle; // en radians
  1128. //float cap_RD; // en radians (le cap fourni par FG étant en degrés d'angle)
  1129. uint16_t x_spr;
  1130. uint16_t y_spr;
  1131.  
  1132. uint16_t x_hdg;
  1133. uint16_t y_hdg;
  1134.  
  1135. uint8_t R =70;
  1136. uint8_t R2 =R-6;
  1137. /**
  1138. 360° =2 pi rad
  1139. 1° = 2 pi/360 rad = pi/180 rad
  1140. **/
  1141.  
  1142. TFT.fillCircle(x02,y02, R, GRIS_AF);
  1143.  
  1144. for(uint8_t n=0; n<24; n++ )
  1145. {
  1146. angle = cap+15 + n*15; // 1 tiret tous les 15 degrés
  1147. affi_rayon1(x02, y02, (R-5), degTOrad(angle), 0.9, BLANC, false); // tirets de graduation
  1148. }
  1149. x_hdg = x02 + R2*cos(degTOrad(hdg1-90-cap));
  1150. y_hdg = y02 + R2*sin(degTOrad(hdg1-90-cap));
  1151.  
  1152. TFT.drawLine(x02, y02, x_hdg, y_hdg, VERT);
  1153. TFT.drawCircle(x_hdg, y_hdg, 5, VERT); // rond vert sur le cercle = consigne de cap de l'autopilot
  1154.  
  1155. x_spr = x02+R2 * cos(degTOrad(angle));
  1156. y_spr = y02-R2 * sin(degTOrad(angle));
  1157. TFT.setPivot(x_spr, y_spr);
  1158. SPR_E.pushRotated(-cap+90, TFT_BLACK); // Plot rotated Sprite, black = transparent
  1159.  
  1160. x_spr = x02+R2* cos(degTOrad(angle+90));
  1161. y_spr = y02-R2 * sin(degTOrad(angle+90));
  1162. TFT.setPivot(x_spr, y_spr);
  1163. SPR_N.pushRotated(-cap, TFT_BLACK);
  1164.  
  1165. x_spr = x02+R2 * cos(degTOrad(angle+180));
  1166. y_spr = y02-R2 * sin(degTOrad(angle+180));
  1167. TFT.setPivot(x_spr, y_spr);
  1168. SPR_O.pushRotated(-cap-90, TFT_BLACK);
  1169.  
  1170. x_spr = x02+R2 * cos(degTOrad(angle-90));
  1171. y_spr = y02-R2 * sin(degTOrad(angle-90));
  1172. TFT.setPivot(x_spr, y_spr);
  1173. SPR_S.pushRotated(-cap, TFT_BLACK);
  1174.  
  1175.  
  1176. // petite "maison" dans le cercle (valeur du cap)
  1177.  
  1178. #define a 180 // x général
  1179. #define b a+20
  1180. #define c b+20
  1181. #define d 288 // y général
  1182. #define e d+10
  1183. #define f e+20
  1184.  
  1185. TFT.drawLine(a, f, c, f, BLANC); // sol
  1186. TFT.drawLine(a, f, a, e, BLANC); // mur de gauche
  1187. TFT.drawLine(c, f, c, e, BLANC); // mur de droite
  1188. TFT.drawLine(a, e, b, d, BLANC); // toit pente gauche
  1189. TFT.drawLine(c, e, b, d, BLANC); // toit pente droite
  1190.  
  1191.  
  1192. // affi la valeur
  1193. String s1;
  1194. uint16_t x0 = a+2;
  1195. uint16_t y0 = e;
  1196.  
  1197. uint16_t x1= x0;
  1198. if(cap<100){x1+=5;} // pour affichage centré
  1199. if(cap<10){x1+=5;}
  1200.  
  1201. s1=(String) cap;
  1202.  
  1203. TFT.fillRect(x0, y0, 35, 20, NOIR); // efface le nombre précédemment affiché
  1204. TFT.setTextColor(BLANC, NOIR);
  1205. TFT.setFreeFont(FM9);
  1206. TFT.drawString(s1, x1, y0);
  1207.  
  1208.  
  1209. }
  1210.  
  1211.  
  1212.  
  1213. void affi_altitude()
  1214. {
  1215. String s1;
  1216. uint16_t x0 =365;
  1217. //---------------------------------------------------------------------------------------
  1218. //échelle verticale graduée glissante
  1219.  
  1220. uint16_t x1;
  1221. uint16_t y0;
  1222. int16_t alt1;
  1223. float d5;
  1224.  
  1225. TFT.setFreeFont(FF1);
  1226.  
  1227. y0=3.2*(altitude%10);
  1228.  
  1229. TFT.fillRect(x0, 20, 60, 319, GRIS_AF); //efface bande verticale à droite
  1230.  
  1231.  
  1232. for(int n=0; n<10; n++)
  1233. {
  1234. d5 =0+y0+32.0*n; // pixels verticalement entre chaque trait -> 10*24 = 240px (hauteur de l'affi)
  1235. {
  1236. if (d5>=20) // marge en haut
  1237. {
  1238. TFT.fillRect(x0, (int16_t)d5+5, 5, 2, BLANC); // petits tirets horizontaux
  1239.  
  1240. alt1 = altitude -10*(n-5);
  1241. alt1 /= 10;
  1242. alt1 *= 10;
  1243. s1=(String) alt1;
  1244.  
  1245. TFT.setTextColor(BLANC, GRIS_AF);
  1246. //TFT.drawString(" ", 9, d5);
  1247. x1=x0;
  1248. if(alt1<10000){x1+=7;} // pour affichage centré
  1249. if(alt1<1000){x1+=7;}
  1250. if(alt1<100){x1+=7;}
  1251. if(alt1<10){x1+=7;}
  1252. TFT.drawString(s1, x1, (uint16_t)d5); // Graduation (tous les 20kts)
  1253. }
  1254. }
  1255. }
  1256.  
  1257.  
  1258. //---------------------------------------------------------------------------------------
  1259. // affichage de la valeur principale
  1260.  
  1261.  
  1262.  
  1263. uint16_t x2;
  1264. uint16_t y0b = 155;
  1265. TFT.fillRect(x0-20, y0b, 80, 25, NOIR); // efface le nombre précédemment affiché
  1266. TFT.setTextColor(BLANC, NOIR);
  1267. TFT.setFreeFont(FF18);
  1268.  
  1269. if ((altitude >=0) && (altitude < 60000))
  1270. {
  1271. s1=(String) altitude;
  1272. }
  1273. else {s1="----";}
  1274. x2=x0-20;
  1275. if(altitude<10000){x2+=10;} // pour affichage centré
  1276. if(altitude<1000){x2+=10;}
  1277. if(altitude<100){x2+=10;}
  1278. if(altitude<10){x2+=10;}
  1279. TFT.drawString(s1, x2, y0b);
  1280. TFT.drawRoundRect(x0-20, y0b-3, 75, 28, 5, BLANC); // encadrement de la valeur centrale affichée
  1281.  
  1282.  
  1283. }
  1284.  
  1285.  
  1286. void affi_frq_NAV1()
  1287. {
  1288. String s1;
  1289. uint16_t x0=120;
  1290. uint16_t y0=234;
  1291. float f1;
  1292.  
  1293. f1 = nav1frq/1000.0;
  1294. s1= String(f1, 2);
  1295.  
  1296. TFT.fillRect(x0, y0, 55, 18, NOIR); // efface
  1297. TFT.setFreeFont(FM9);
  1298. TFT.setTextColor(VERT, NOIR);
  1299. TFT.drawString(s1, x0, y0);
  1300. TFT.drawString("MHz", x0+70, y0);
  1301. }
  1302.  
  1303.  
  1304. void affi_dst_NAV1()
  1305. {
  1306.  
  1307. if (nav1dst<0) {nav1dst=0;}
  1308. String s1;
  1309. uint16_t x0=180;
  1310. uint16_t y0=236;
  1311. // rappel: 1 mile marin (NM nautical mile) = 1852m
  1312. float nav1_nm = (float)nav1dst / 1852.0;
  1313. //Serial.println(nav1_nm);
  1314.  
  1315. TFT.setTextColor(JAUNE, NOIR);
  1316. TFT.drawString("Nv1", x0-40, y0);
  1317. TFT.setTextColor(BLANC, NOIR);
  1318.  
  1319. uint8_t nb_decimales =1;
  1320. if (nav1_nm >99) {nb_decimales=0;}
  1321.  
  1322. s1 = String( nav1_nm, nb_decimales);
  1323. TFT.fillRect(x0, y0, 52, 18, NOIR); // efface
  1324. TFT.setFreeFont(FM9);
  1325. TFT.drawString(s1, x0, y0);
  1326. TFT.drawRoundRect(x0, y0-2, 50, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1327.  
  1328. TFT.drawString("NM", x0+55, y0);
  1329. }
  1330.  
  1331.  
  1332. void affi_radial_NAV1()
  1333. {
  1334.  
  1335. if (nav1radial<0) {nav1radial=0;}
  1336. String s;
  1337. uint16_t x0=260;
  1338. uint16_t y0=236;
  1339.  
  1340. int16_t alpha1 = nav1radial/100.0;
  1341.  
  1342. int16_t alpha2 = alpha1 + 180;
  1343. if (alpha2>360) {alpha2 -= 360;}
  1344.  
  1345. if (alpha1 == 360) {alpha1 =0;}
  1346. if (alpha2 == 360) {alpha2 =0;}
  1347.  
  1348.  
  1349. //affichage numérique de l'angle
  1350. TFT.setTextColor(VERT, NOIR);
  1351. //s = String (alpha1 , 0); // 0 décimales
  1352. s = (String) alpha1;
  1353. TFT.fillRect(x0, y0, 50, 18, NOIR); // efface
  1354. TFT.setFreeFont(FM9);
  1355. TFT.drawString(s, x0+5, y0);
  1356. TFT.drawRoundRect(x0, y0-2, 40, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1357. TFT.drawCircle(x0+46, y0, 2, JAUNE); // caractère 'degré'
  1358.  
  1359. //affichage numérique de l'angle opposé
  1360. x0+=50;
  1361. TFT.setTextColor(VERT, NOIR);
  1362. //s = String (alpha2 , 0); // 0 décimales
  1363. s = (String) alpha2;
  1364. TFT.fillRect(x0, y0, 50, 18, NOIR); // efface
  1365. TFT.setFreeFont(FM9);
  1366. TFT.drawString(s, x0+5, y0);
  1367. TFT.drawRoundRect(x0, y0-2, 40, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1368. TFT.drawCircle(x0+46, y0, 2, JAUNE);
  1369.  
  1370.  
  1371. }
  1372.  
  1373.  
  1374. void affi_dst_NAV2()
  1375. {
  1376.  
  1377. if (nav2dst<0) {nav2dst=0;}
  1378. String s1;
  1379. uint16_t x0=180;
  1380. uint16_t y0=255;
  1381. float nav_nm;
  1382. // rappel: 1 mile marin (NM nautical mile) = 1852m
  1383. nav_nm = (float)nav2dst / 1852.0;
  1384. if (nav_nm >99) {nav_nm=0;}
  1385.  
  1386. TFT.setTextColor(JAUNE, NOIR);
  1387. TFT.drawString("Nv2", x0-40, y0);
  1388. TFT.drawString("NM", x0+55, y0);
  1389.  
  1390. TFT.setTextColor(BLANC, NOIR);
  1391. s1 = String( nav_nm, 1); // 1 -> 0 décimale
  1392. TFT.fillRect(x0, y0, 52, 18, NOIR); // efface
  1393. TFT.setFreeFont(FM9);
  1394. TFT.drawString(s1, x0, y0);
  1395. TFT.drawRoundRect(x0, y0-2, 50, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1396.  
  1397. }
  1398.  
  1399.  
  1400. void affi_radial_NAV2()
  1401. {
  1402.  
  1403. if (nav2radial<0) {nav2radial=0;}
  1404. String s1;
  1405. uint16_t x0=260;
  1406. uint16_t y0=255;
  1407.  
  1408. //affichage numérique
  1409. TFT.setTextColor(BLEU_CLAIR, NOIR);
  1410. s1 = String (nav2radial/100, 0); // 0 décimales
  1411. TFT.fillRect(x0, y0, 50, 18, NOIR); // efface
  1412. TFT.setFreeFont(FM9);
  1413. TFT.drawString(s1, x0+5, y0);
  1414. TFT.drawRoundRect(x0, y0-2, 40, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1415. TFT.drawCircle(x0+46, y0, 2, JAUNE);
  1416. }
  1417.  
  1418.  
  1419. void affi_alti_NAV2()
  1420. {
  1421. // hauteur (en ft) conseillée pour l'approche IFR à la distance en nautiques (NM) de la balise Nav2
  1422. String s1;
  1423. uint16_t x=425;
  1424. uint16_t y=5;
  1425.  
  1426. float nav_nm;
  1427. float hauteur; // attention: ne pas confondre hauteur (au dessus du sol) et altitude (au dessus du niveau de la mer)
  1428. nav_nm = (float)nav2dst / 1852.0;
  1429. hauteur = 300.0*nav_nm;
  1430.  
  1431. s1 = String( hauteur, 0); // 0 -> 0 décimale
  1432. TFT.fillRect(x, y, 55, 18, NOIR); // efface
  1433.  
  1434. if (gs_ok==1)
  1435. {
  1436. TFT.setTextColor(VERT, NOIR);
  1437. TFT.setFreeFont(FF1);
  1438. //TFT.setFreeFont(FM9);
  1439. //TFT.setFreeFont(FS9);
  1440. //TFT.setFreeFont(FF5);
  1441. TFT.drawString(s1, x, y);
  1442. }
  1443. }
  1444.  
  1445.  
  1446.  
  1447. void affi_indicateurs()
  1448. {
  1449.  
  1450. TFT.setFreeFont(FSS9);
  1451.  
  1452. /*
  1453. TFT.drawString("CLB | IAS |", 100, 0, 1);
  1454.  
  1455. TFT.setTextColor(AZUR, NOIR);
  1456. TFT.drawString("ALT", 210, 0, 1);
  1457.  
  1458. TFT.setTextColor(VERT, NOIR);
  1459. TFT.drawString(" |", 245, 0, 1);
  1460.  
  1461. TFT.setTextColor(AZUR, NOIR);
  1462. TFT.drawString("HDG", 260, 0, 1);
  1463.  
  1464. TFT.setTextColor(VERT, NOIR);
  1465. TFT.drawString(" |", 300, 0, 1);
  1466. */
  1467.  
  1468. //if (gs_ok != 0) { TFT.setTextColor(VERT, NOIR); } else { TFT.setTextColor(GRIS_FONCE, NOIR); }
  1469. //TFT.drawString("ILS", 110, 0);
  1470.  
  1471. }
  1472.  
  1473.  
  1474. void affi_Airport()
  1475. {
  1476. uint16_t n;
  1477. float v1;
  1478. String s1;
  1479.  
  1480. TFT.fillRect(270, 280, 40, 40, NOIR); // efface
  1481. TFT.setTextFont(1);
  1482. TFT.setTextColor(BLEU_CLAIR, NOIR);
  1483.  
  1484.  
  1485. s1= liste_bali[num_bali].ID_OACI;
  1486. TFT.drawString(s1, 270, 280);
  1487.  
  1488. TFT.fillRect(255, 290, 105, 10, NOIR); // efface
  1489. s1= liste_bali[num_bali].nom;
  1490. TFT.drawString(s1, 255, 290);
  1491.  
  1492. TFT.setTextColor(VERT, NOIR);
  1493.  
  1494. v1= liste_bali[num_bali].frq_VOR/100.0;
  1495. s1 = String(v1, 2);
  1496. TFT.drawString(s1, 270, 300);
  1497.  
  1498. v1= liste_bali[num_bali].frq_ILS/100.0;
  1499. s1 = String(v1, 2);
  1500. TFT.drawString(s1, 270, 310);
  1501.  
  1502. }
  1503.  
  1504.  
  1505.  
  1506. void auto_landing()
  1507. {
  1508. /**
  1509.  voir: https://en.wikipedia.org/wiki/Autoland
  1510.  
  1511.  Attérrissage automatique, contrôlé par le bouton1 (si compilé avec la bonne config, voir fonction loop)
  1512.  CAPTURE l'avion et le POSE !
  1513.  vitesse conseillée : 160kts max
  1514.  distance conseillée : entre 20 et 10 nautiques
  1515.  hauteur conseillée : 3000ft (= niveau 30)
  1516.  
  1517.  à ceci près que l'autopilot se déconnecte automatiquement (par FlightGear) sous 100ft de hauteur
  1518.  ( Donc garder le manche en main pour le touché final, en surveillant la vitesse et le pitch
  1519.  parce que si le trim de profondeur est mal réglé, à la déconnexion de l'autopilot c'est le plongeon près du sol = crash !
  1520.  avec ma manette Thrustmaster et le Citation X, je n'arrive pas à régler le trim !! )
  1521.  
  1522. **/
  1523.  
  1524. TFT.setFreeFont(FSS9);
  1525.  
  1526. TFT.setTextColor(GRIS_FONCE, NOIR);
  1527. TFT.fillRect(110, 0, 220, 16, NOIR); // efface tout le bandeau d'information en haut
  1528.  
  1529. if (autoland != 0)
  1530. {
  1531. // (si bouton1 en position basse, sinon on ne fait rien de plus)
  1532.  
  1533. TFT.setTextColor(BLANC, ROUGE);
  1534. TFT.setFreeFont(FF1);
  1535. TFT.drawString("Landing Auto", HA_x0-60, HA_y0+65);
  1536.  
  1537. TFT.setTextColor(GRIS, NOIR);
  1538. TFT.drawString("ILS", 110, 0);
  1539.  
  1540. float nav2_nm =(float)nav2dst / 1852.0;
  1541. float nav2_err = nav2loc / 1000.0;
  1542.  
  1543. ////String s1 = String (nav2_err, 1);
  1544. ////TFT.setTextColor(BLEU_CLAIR, NOIR);
  1545. ////TFT.drawString(s1, 220, 0);
  1546.  
  1547.  
  1548. if ((gs_ok != 0) && (altitude < 4000) && (nav2_nm < 20.0))
  1549. {
  1550. // effectif (capture) seulement si l'avion est suffisamment proche de la balise et suffisamment bas
  1551.  
  1552. TFT.setTextColor(BLANC, NOIR);
  1553. TFT.drawString("ILS", 110, 0);
  1554.  
  1555. if((nav2_err > -9.5) && (nav2_err < +9.5)) // attention: |nav2_err| jamais > 10, quoi qu'il arrive !
  1556. {
  1557.  
  1558. TFT.setTextColor(VERT, NOIR);
  1559. TFT.drawString("ILS", 110, 0);
  1560.  
  1561. TFT.setTextColor(VERT, NOIR); // Autolanding en cours, ok
  1562. TFT.drawString("APP", 150, 0);
  1563.  
  1564. // ici l'avion est proche, bas, et dans un angle serré (9.5° max) mais pas forcément bien orienté
  1565. hdg1 = (uint16_t)((nav2radial/100.0) + 2.0 *nav2_err); //corrige la consigne de cap de l'autopilot pour rejoindre puis rester dans l'axe de la piste
  1566.  
  1567. float alti1 = 3.0*nav2_nm + gnd_elv/100.0; // altitude avion doit être = hauteur avion (souhaitée) + altitude terrain (réelle)
  1568. String s1 = String(alti1,0);
  1569. TFT.setTextColor(JAUNE, NOIR);
  1570. TFT.drawString(s1, 220, 0);
  1571.  
  1572. //correction en conséquence de la consigne d'altitude du pilote auto
  1573. if (alti1 < asel1) //empêche de (re)monter lors de la capture ILS, reste en palier le cas échéant
  1574. {
  1575. asel1 = alti1;
  1576. }
  1577. /**
  1578. REMARQUES:
  1579. la hauteur avion (souhaitée) [= 3.0*nav_nm ] est donc un simple calcul en fonction de la distance: niv=30 à 10NM -> pente à 5%.
  1580. On aurait aussi pu exploiter le glide, mais alors il faudrait :
  1581. - agir sur la gouverne de profondeur plutôt que sur le pilote auto
  1582. - ou bien règler l'autopilot (F11) sur PTCH (pitch = tangage) pour rester dans le plan incliné à 3° du glide de l'ILS,
  1583. et règler ce pitch en fonction du glide.
  1584. ... à voir
  1585. **/
  1586. }
  1587. else
  1588. {
  1589. TFT.setTextColor(GRIS, NOIR); // indique conditions Autolanding non remplies
  1590. TFT.drawString("APP", 150, 0);
  1591. }
  1592. }
  1593.  
  1594. if (altitude < 100)
  1595. {
  1596. TFT.setTextColor(BLANC, ROUGE);
  1597. TFT.drawString("APP", 150, 0); // en grisé si bouton1 enclenché MAIS conditions non satisfaites; En vert si tout est ok
  1598. } // évite de décoller avec l'autoland activé !!
  1599. }
  1600.  
  1601. }
  1602.  
  1603.  
  1604.  
  1605. void affi_localizer()
  1606. {
  1607. //ILS dans le plan horizontal ; affiche l'erreur de position par rapport à l'axe de la piste
  1608.  
  1609. uint16_t y1 = HA_y0-HA_h-14;
  1610.  
  1611. // nav2loc = -10 *1000 .. +10 *1000
  1612. // l'affichage doit se faire en HA_x0 +/- HA_w
  1613. // 10000/HA_w = 10000/120 = 83
  1614.  
  1615. uint16_t couleur1 = ROSE;
  1616. loc = HA_x0 + nav2loc / 83;
  1617. if ( loc < (HA_x0-HA_w+5)) {loc = HA_x0-HA_w+5; couleur1 = GRIS;}
  1618. if ( loc > (HA_x0+HA_w-5)) {loc= HA_x0+HA_w-5; couleur1 = GRIS;}
  1619.  
  1620.  
  1621. TFT.fillRect(HA_x0-HA_w, y1, 2*HA_w, 9, GRIS_TRES_FONCE);
  1622.  
  1623. TFT.drawLine(HA_x0, y1-5, HA_x0, y1+5, BLANC);
  1624. if (gs_ok != 0)
  1625. {
  1626. affi_indexV(loc, y1, 1, couleur1);
  1627. }
  1628. memo_loc=loc;
  1629. }
  1630.  
  1631.  
  1632.  
  1633. void affi_glide() // Nav2
  1634. {
  1635. /*
  1636.  "Glide Path-pente" = ILS dans le plan vertical
  1637.  trace des index horizontaux sur les bords de l'HA
  1638.  
  1639.  Remarque : dans FG toutes les pistes ne sont pas équipées d'un faisceau ILS GLIDE dans les deux sens d'approche...
  1640.  L'affichage de la carte (interne ou externe dans le navigateur) permet de visualiser les faisceaux en question si existants.
  1641.  par exemple, dans FG et pour LFMT, seule l'approche par le sud (par la mer) est fonctionnelle
  1642.  
  1643.  D'autre part, il faut avoir réglé le récepteur NAV2 sur la bonne fréquence.
  1644.  Voir le pannel F12 de FD ainsi que les cartes d'aéroport.
  1645.  
  1646.  On peut aussi lancer FG avec les options suivantes dans FGo! (par exemple):
  1647. --nav1=114.45 (pour le VOR)
  1648. --nav2=305:108.55 (pour l'ILS de LFMT piste 31R)
  1649. */
  1650.  
  1651. uint16_t x1 = 75;
  1652. uint16_t x2 = 332;
  1653.  
  1654. gld = HA_y0 - 50*(3.0-nav2glide/1000.0);
  1655.  
  1656.  
  1657. TFT.fillRect(x1, 30, 9, 2*HA_h, GRIS_TRES_FONCE); // efface
  1658. TFT.fillRect(x2, 30, 9, 2*HA_h, GRIS_TRES_FONCE); // efface
  1659.  
  1660. TFT.drawRect(x1, HA_y0, 12, 5, BLANC);
  1661. TFT.drawRect(x2, HA_y0, 12, 5, BLANC);
  1662.  
  1663. uint16_t couleur1 = ROSE;
  1664. if ( gld < (HA_y0-HA_h+5)) {gld = HA_y0-HA_h+5; couleur1 = GRIS;}
  1665. if ( gld > (HA_y0+HA_h-5)) {gld = HA_y0+HA_h-5; couleur1 = GRIS;}
  1666.  
  1667. affi_indexH(x1, gld, 1, couleur1);
  1668. affi_indexH(x2+8, gld, -1, couleur1);
  1669.  
  1670. memo_gld=gld;
  1671. }
  1672.  
  1673.  
  1674.  
  1675. void trace_arc_gradu()
  1676. {
  1677. //arc gradué en haut au centre, indiquant la valeur de l'inclinaison
  1678.  
  1679. float angle;
  1680. //Draw_arc_elliptique(HA_x0, 120, 120, 80, 0.6, 2.6, BLANC);
  1681.  
  1682.  
  1683. for(uint8_t n=0; n<9; n++ )
  1684. {
  1685. angle =30+ n*15; // 1 tiret tous les 15 degrés
  1686. float pourcent = 0.9;
  1687. if (((n+2)%2) == 0) {pourcent = 0.8;}
  1688.  
  1689. affi_rayon1(HA_x0, HA_y0+10, 110, degTOrad(angle), pourcent, BLANC, false); // tirets de graduation
  1690. }
  1691. }
  1692.  
  1693.  
  1694.  
  1695. void rotation1()
  1696. {
  1697. // consigne de cap
  1698. // acquisition de l'encodeur pas à pas (1)
  1699. if ( millis() - TEMPO >= timer1 )
  1700. {
  1701. timer1 = millis();
  1702. bool etat = digitalRead(rot1b);
  1703. if(etat == 0) { hdg1+=1;} else { hdg1-=1;}
  1704. if (hdg1<0){hdg1=355;}
  1705.  
  1706. if (hdg1>359){hdg1=0;}
  1707. }
  1708. }
  1709.  
  1710.  
  1711.  
  1712. void rotation2()
  1713. {
  1714. // consigne d'altitude
  1715. // acquisition de l'encodeur pas à pas (2)
  1716. if ( millis() - TEMPO >= timer2 )
  1717. {
  1718. timer2 = millis();
  1719. bool etat = digitalRead(rot2b);
  1720. if(etat == 0) { asel1+=1; } else { asel1-=1; }
  1721. if (asel1<1){asel1=1;} // 100 pieds -> 30m
  1722. if (asel1>600){asel1=600;}
  1723. }
  1724. }
  1725.  
  1726.  
  1727.  
  1728. void init_SDcard()
  1729. {
  1730. String s1;
  1731.  
  1732. TFT.fillRect(0, 0, 480, 320, NOIR); // efface
  1733. TFT.setTextColor(BLANC, NOIR);
  1734. TFT.setFreeFont(FF1);
  1735.  
  1736. uint16_t y=0;
  1737.  
  1738. TFT.drawString("PRIMARY FLIGHT DISPLAY", 0, y);
  1739. y+=20;
  1740.  
  1741. s1="version " + version;
  1742. TFT.drawString(s1, 0, y);
  1743.  
  1744. y+=40;
  1745. TFT.setTextColor(VERT, NOIR);
  1746. TFT.drawString("Init SDcard", 0, y);
  1747. y+=20;
  1748.  
  1749.  
  1750. if(!SD.begin())
  1751. {
  1752. TFT.drawString("Card Mount Failed", 0, y);
  1753. delay (2000);
  1754. TFT.fillRect(0, 0, 480, 320, NOIR); // efface
  1755. return;
  1756. }
  1757.  
  1758.  
  1759. uint8_t cardType = SD.cardType();
  1760.  
  1761. if(cardType == CARD_NONE)
  1762. {
  1763. TFT.drawString("No SDcard", 0, y);
  1764. delay (2000);
  1765. TFT.fillRect(0, 0, 480, 320, NOIR); // efface
  1766. return;
  1767. }
  1768.  
  1769. SDcardOk=1;
  1770.  
  1771. TFT.drawString("SDcard Type: ", 0, y);
  1772. if(cardType == CARD_SD) {TFT.drawString("SDSC", 150, y);}
  1773. else if(cardType == CARD_SDHC) {TFT.drawString("SDHC", 150, y);}
  1774.  
  1775. y+=20;
  1776.  
  1777. uint32_t cardSize = SD.cardSize() / (1024 * 1024);
  1778. s1=(String)cardSize + " GB";
  1779. TFT.drawString("SDcard size: ", 0, y);
  1780. TFT.drawString(s1, 150, y);
  1781.  
  1782. // listDir(SD, "/", 0);
  1783.  
  1784. //Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  1785. //Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
  1786.  
  1787. delay (1000);
  1788. TFT.fillRect(0, 0, 480, 320, NOIR); // efface
  1789.  
  1790. }
  1791.  
  1792.  
  1793. void init_sprites()
  1794. {
  1795. // sprites représentant les lettres 'N' 'S' 'E' 'O' qui seront affichées sur un cercle, inclinées donc.
  1796.  
  1797. SPR_E.setFreeFont(FF1);
  1798. SPR_E.setTextColor(JAUNE);
  1799. SPR_E.createSprite(SPR_W, SPR_H);
  1800. SPR_E.setPivot(SPR_W/2, SPR_H/2); // Set pivot relative to top left corner of Sprite
  1801. SPR_E.fillSprite(GRIS_TRES_FONCE);
  1802. SPR_E.drawString("E", 2, 1 );
  1803.  
  1804. SPR_N.setFreeFont(FF1);
  1805. SPR_N.setTextColor(JAUNE);
  1806. SPR_N.createSprite(SPR_W, SPR_H);
  1807. SPR_N.setPivot(SPR_W/2, SPR_H/2);
  1808. SPR_N.fillSprite(GRIS_TRES_FONCE);
  1809. SPR_N.drawString("N", 2, 1 );
  1810.  
  1811. SPR_O.setFreeFont(FF1);
  1812. SPR_O.setTextColor(JAUNE);
  1813. SPR_O.createSprite(SPR_W, SPR_H);
  1814. SPR_O.setPivot(SPR_W/2, SPR_H/2);
  1815. SPR_O.fillSprite(GRIS_TRES_FONCE);
  1816. SPR_O.drawString("W", 2, 1 );
  1817.  
  1818. SPR_S.setFreeFont(FF1);
  1819. SPR_S.setTextColor(JAUNE);
  1820. SPR_S.createSprite(SPR_W, SPR_H);
  1821. SPR_S.setPivot(SPR_W/2, SPR_H/2);
  1822. SPR_S.fillSprite(GRIS_TRES_FONCE);
  1823. SPR_S.drawString("S", 2, 1 );
  1824. }
  1825.  
  1826.  
  1827. void int_to_array(uint16_t valeur_i)
  1828. {
  1829. String s1= (String) valeur_i;
  1830. var_array[0]=s1[0];
  1831. var_array[1]=s1[1];
  1832. var_array[2]=s1[2];
  1833. var_array[3]=s1[3];
  1834. var_array[4]=0; // zéro terminal -> string
  1835. }
  1836.  
  1837.  
  1838.  
  1839. void setup()
  1840. {
  1841. Serial.begin(19200);
  1842.  
  1843.  
  1844. WiFi.persistent(false);
  1845. WiFi.softAP(ssid, password); // ok, ça marche, crée un réseau. mode privé
  1846. IPAddress IP = WiFi.softAPIP();
  1847.  
  1848. server.on("/hdg", HTTP_GET, [](AsyncWebServerRequest *request) // consigne de cap
  1849. {
  1850. // attention: ce code est appelé par une interruption WiFi qui intervient hors timing. Donc pas d'affichage ici !!
  1851. //size_t n=request->params(); // get arguments count
  1852.  
  1853. argument_recu1 = request->arg("a1"); // reception de l'argument n°1 de la requête
  1854. num_bali=argument_recu1.toInt();
  1855.  
  1856. int_to_array(hdg1);
  1857.  
  1858. //cet array because la fonction "request->send_P()" n'accèpte pas directement le string
  1859. //rappel :
  1860. //void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
  1861. //void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
  1862.  
  1863. request->send_P(200, "text/plain", var_array); // envoie hdg1 comme réponse au client
  1864. });
  1865.  
  1866. // réponses aux requêtes :
  1867. server.on("/cap", HTTP_GET, [](AsyncWebServerRequest *request)
  1868. {
  1869. int_to_array(cap);
  1870. request->send_P(200, "text/plain", var_array); // envoie réponse au client
  1871. });
  1872.  
  1873. server.on("/rd1", HTTP_GET, [](AsyncWebServerRequest *request)
  1874. {
  1875. int_to_array(nav1radial_deg);
  1876. request->send_P(200, "text/plain", var_array); // envoie réponse au client
  1877. });
  1878.  
  1879. server.on("/VORnm", HTTP_GET, [](AsyncWebServerRequest *request)
  1880. {
  1881. uint16_t nav1_nm10 = (uint16_t) (nav1dst / 185.2);
  1882. //if (nav1_nm10 >1000) {nav1_nm10=0;}
  1883.  
  1884. int_to_array(nav1_nm10);
  1885. request->send_P(200, "text/plain", var_array); // envoie réponse au client
  1886. });
  1887.  
  1888.  
  1889. server.begin();
  1890.  
  1891. /*
  1892. // ************* BLUETOOTH *************
  1893. SerialBT.begin("FGpanel_BT01"); //Bluetooth device name
  1894. //Serial.println("The device started, now you can pair it with bluetooth!");
  1895. // *************************************
  1896. */
  1897. pinMode(bouton1, INPUT);
  1898.  
  1899. //pinMode(led1, OUTPUT);
  1900.  
  1901. pinMode(rot1a, INPUT_PULLUP);
  1902. pinMode(rot1b, INPUT_PULLUP);
  1903. pinMode(rot2a, INPUT_PULLUP);
  1904. pinMode(rot2b, INPUT_PULLUP);
  1905.  
  1906.  
  1907. attachInterrupt(rot1a, rotation1, RISING);
  1908. attachInterrupt(rot2a, rotation2, RISING);
  1909.  
  1910. TFT.init();
  1911. TFT.setRotation(1); // 1 ou 3 -> in[0..3] à voir, suivant disposition de l'afficheur
  1912. TFT.fillScreen(TFT_BLACK);
  1913.  
  1914. init_SDcard();
  1915.  
  1916. init_sprites();
  1917.  
  1918. delay(100);
  1919.  
  1920.  
  1921.  
  1922. TFT.setTextColor(NOIR, BLANC);
  1923.  
  1924. //TFT.setFreeFont(FF19);
  1925.  
  1926. init_affi_HA();
  1927.  
  1928. delay(100);
  1929.  
  1930. //TFT.fillRect(48, 0, 6, 100, 0xFFE0);
  1931.  
  1932. // TFT.fillRect(0, 0, 479, 30, NOIR);
  1933. TFT.setTextColor(BLANC, NOIR);
  1934. TFT.setFreeFont(FF19);
  1935. String s1 = "PFD v";
  1936. s1+= version;
  1937. // TFT.drawString(s1, 70, 3);
  1938.  
  1939.  
  1940. Ay_actu=120;
  1941. By_actu=120;
  1942.  
  1943. altitude =0;
  1944. vitesse =0;
  1945. roulis =0;
  1946. tangage =0;
  1947. cap=0;
  1948. vspeed=0; // vitesse verticale
  1949.  
  1950. nav1frq=123500;
  1951. nav2frq=108000;
  1952.  
  1953. nav1dst=1852*102; // 102km
  1954. nav1radial_deg=45;
  1955. nav1radial=45.0 * 100.0;
  1956. nav2dst=20000; // 20km
  1957. // affichages();
  1958.  
  1959. bouton1_etat = digitalRead(bouton1);
  1960. memo_bouton1_etat = bouton1_etat;
  1961.  
  1962. init_FG_bali();
  1963.  
  1964. init_affi_autopilot();
  1965. //affi_indicateurs();
  1966. //affi_frq_NAV1();
  1967.  
  1968. affi_dst_NAV1();
  1969. affi_radial_NAV1();
  1970.  
  1971. affi_dst_NAV2();
  1972. affi_radial_NAV2();
  1973.  
  1974. affi_Airport();
  1975. }
  1976.  
  1977.  
  1978.  
  1979.  
  1980. //uint8_t buffer1[200];
  1981.  
  1982. uint16_t L=0;
  1983. uint16_t C=0;
  1984. uint16_t x, y;
  1985. uint16_t i1 = 0;
  1986.  
  1987. uint8_t p1;
  1988.  
  1989. int32_t number = 0;
  1990.  
  1991. String parametre;
  1992. String s1;
  1993. String s2;
  1994.  
  1995.  
  1996. void acquisitions()
  1997. {
  1998. TFT.setFreeFont(FM9);
  1999. TFT.setTextColor(VERT, NOIR);
  2000.  
  2001. if(Serial.available() > 14)
  2002. {
  2003. parametre="";
  2004. s1="";
  2005. char octet_in;
  2006.  
  2007. while(octet_in != '=')
  2008. {
  2009. octet_in = Serial.read();
  2010. if(octet_in != '=') {parametre += octet_in; }
  2011. }
  2012. while(octet_in != '\n')
  2013. {
  2014. octet_in = Serial.read();
  2015. if(octet_in != '\n') {s1 += octet_in; }
  2016. }
  2017.  
  2018. if(parametre == "alti" )
  2019. {
  2020. char buf[50];
  2021. s1.toCharArray(buf, 50);
  2022. altitude = atol(buf);
  2023. if (altitude <0) {altitude =0;} //car pendant la phase d'init du programme Flightgear, altitude << 0 !
  2024. data_ok |= 1; // positionne bit0
  2025.  
  2026. ////TFT.drawString("Altitude", 0, 40);
  2027. ////s2= (String) altitude;
  2028. ////TFT.fillRect(120, 40, 100, 30, TFT_BLACK);
  2029. ////TFT.drawString(s2, 120, 40);
  2030. }
  2031.  
  2032.  
  2033. if(parametre == "gnd_elv" )
  2034. {
  2035. char buf[50];
  2036. s1.toCharArray(buf, 50);
  2037. gnd_elv = atol(buf);
  2038. if (gnd_elv <0) {gnd_elv =0;}
  2039. data_ok |= 2; // positionne bit1
  2040.  
  2041. ////TFT.drawString("gnd_elv", 0, 0);
  2042. ////s2= (String) gnd_elv;
  2043. ////TFT.fillRect(120, 0, 100, 30, TFT_BLACK);
  2044. ////TFT.drawString(s2, 120, 0);
  2045. }
  2046.  
  2047.  
  2048.  
  2049. if(parametre == "speed" )
  2050. {
  2051. char buf[50];
  2052. s1.toCharArray(buf, 50);
  2053. vitesse = atol(buf);
  2054. data_ok |= 4; // positionne bit2
  2055.  
  2056. ////TFT.drawString("Vitesse", 0, 70);
  2057. ////s2= (String) vitesse;
  2058. ////TFT.fillRect(120, 70, 100, 30, TFT_BLACK);
  2059. ////TFT.drawString(s2, 120, 70);
  2060. }
  2061.  
  2062.  
  2063. if(parametre == "pitch" )
  2064. {
  2065. char buf[50];
  2066. s1.toCharArray(buf, 50);
  2067. tangage = atol(buf);
  2068. data_ok |= 8; // positionne bit3
  2069.  
  2070. ////TFT.drawString("Tangage", 0, 100);
  2071. ////s2= (String) tangage;
  2072. ////TFT.fillRect(120, 100, 100, 30, TFT_BLACK);
  2073. ////TFT.drawString(s2, 120, 100);
  2074. }
  2075.  
  2076. if(parametre == "roll" )
  2077. {
  2078. char buf[50];
  2079. s1.toCharArray(buf, 50);
  2080. roulis = atol(buf);
  2081. data_ok |= 16; // positionne bit4
  2082.  
  2083. ////TFT.drawString("Roulis", 0, 130);
  2084. ////s2= (String) roulis;
  2085. ////TFT.fillRect(120, 130, 100, 30, TFT_BLACK);
  2086. ////TFT.drawString(s2, 120, 130);
  2087. }
  2088.  
  2089.  
  2090.  
  2091. if(parametre == "heading" ) // /orientation/heading-deg = cap actuel de l'avion ; ne pas confondre avec HDG bug !
  2092. {
  2093. char buf[50];
  2094. s1.toCharArray(buf, 50);
  2095. cap = atol(buf);
  2096. data_ok |= 32; // positionne bit5
  2097.  
  2098. ////TFT.drawString("Cap", 0, 160);
  2099. ////s2= (String) roulis;
  2100. ////TFT.fillRect(120, 160, 100, 30, TFT_BLACK);
  2101. ////TFT.drawString(s2, 120, 160);
  2102. }
  2103.  
  2104.  
  2105. if(parametre == "vspeed" )
  2106. {
  2107. char buf[50];
  2108. s1.toCharArray(buf, 50);
  2109. vspeed = atol(buf);
  2110. data_ok |= 64; // positionne bit6
  2111.  
  2112. ////TFT.drawString("vspeed", 0, 170);
  2113. ////s2= (String) vspeed;
  2114. ////TFT.fillRect(120, 170, 100, 30, TFT_BLACK);
  2115. ////TFT.drawString(s2, 120, 170);
  2116. }
  2117.  
  2118.  
  2119. if(parametre == "nav1frq" )
  2120. {
  2121. char buf[50];
  2122. s1.toCharArray(buf, 50);
  2123. nav1frq = atol(buf);
  2124. data_ok |= 128; // positionne bit7
  2125.  
  2126. ////TFT.drawString("nav1frq", 100, 170, 1);
  2127. ////s2= String(nav1frq, 2); // String(navfrq, 2);
  2128. ////TFT.fillRect(180, 170, 100, 20, TFT_BLACK);
  2129. ////TFT.drawString(s2, 180, 170);
  2130. }
  2131.  
  2132.  
  2133. if(parametre == "nav1dst" )
  2134. {
  2135. char buf[50];
  2136. s1.toCharArray(buf, 50);
  2137. nav1dst = atol(buf); // en mètres
  2138.  
  2139. // Serial.println(nav1dst);
  2140.  
  2141. data_ok |= 256; // positionne bit8
  2142.  
  2143. ////TFT.drawString("nav1dst", 0, 170, 1);
  2144. ////s2= (String) nav1dst;
  2145. ////TFT.fillRect(120, 170, 100, 30, TFT_BLACK);
  2146. ////TFT.drawString(s2, 120, 170);
  2147. }
  2148.  
  2149.  
  2150. x=100;
  2151.  
  2152. if(parametre == "nav2dst" )
  2153. {
  2154. char buf[50];
  2155. s1.toCharArray(buf, 50);
  2156. nav2dst = atol(buf);
  2157. data_ok |= 512; // positionne bit9
  2158.  
  2159. ////y=160;
  2160. ////TFT.drawString("dst:", x, y);
  2161. ////s2= (String) nav2dst;
  2162.  
  2163. ////TFT.fillRect(x+70, y, 100, 20, TFT_BLACK);
  2164. ////TFT.drawString(s2, x+70, y);
  2165.  
  2166. }
  2167.  
  2168.  
  2169. if(parametre == "nav2loc" )
  2170. {
  2171. // erreur d'angle (/ à l'axe de la piste) dans le plan horizontal sous lequel l'antenne ILS au sol voit l'avion; Doit être 0°
  2172. // en fait varie dans la plage [-10.0 .. +10.0] avec un facteur 1000 écrit dans "hardvare4.xml"
  2173. char buf[50];
  2174. s1.toCharArray(buf, 50);
  2175.  
  2176. nav2loc = atol(buf); // = 1000* [-10.0 .. +10.0] c.a.d = [-10000 .. +10000];
  2177. //Attention : |nav2loc| jamais > 10, même si l'angle réel est bien plus grand, de l'ordre de 90° !!
  2178.  
  2179. data_ok |= 1024; // positionne bit10
  2180.  
  2181. ////y=160;
  2182. ////TFT.drawString("angl_H", x, y);
  2183. ////s2= String (nav2loc/1000, 2);
  2184.  
  2185. ////TFT.fillRect(x+70, y, 100, 20, TFT_BLACK);
  2186. ////TFT.drawString(s2, x+70, y);
  2187. }
  2188.  
  2189.  
  2190. if(parametre == "nav2glide" )
  2191. {
  2192. //angle dans le plan vertical sous lequel l'antenne au sol (ILS GLIDE) voit l'avion; Doit être de 3° en finale
  2193. //c'est l'inclinaison du plan de descente effectif
  2194. char buf[50];
  2195. s1.toCharArray(buf, 50);
  2196. nav2glide = atol(buf);
  2197. data_ok |= 2048; // positionne bit11
  2198.  
  2199. ////y=180;
  2200. ////TFT.drawString("angl_V", x, y);
  2201. ////s2= String (nav2glide/1000, 2);
  2202.  
  2203. ////TFT.fillRect(x+70, y, 100, 20, TFT_BLACK);
  2204. ////TFT.drawString(s2, x+70, y);
  2205. }
  2206.  
  2207.  
  2208. if(parametre == "gs_ok" )
  2209. {
  2210. // indique si le signal ILS est reçu
  2211. // un test en vol permet de voir que le signal décroche au delà de 27.2 nautiques soit 50km de distance
  2212. char buf[50];
  2213. s1.toCharArray(buf, 50);
  2214. gs_ok = atol(buf);
  2215. data_ok |= 4096; // positionne bit12
  2216.  
  2217. ////y=180;
  2218. ////TFT.drawString("angl_V", x, y);
  2219. ////s2= String (nav2glide/1000, 2);
  2220.  
  2221. ////TFT.fillRect(x+70, y, 100, 20, TFT_BLACK);
  2222. ////TFT.drawString(s2, x+70, y);
  2223. }
  2224.  
  2225.  
  2226. if(parametre == "nav1radial" )
  2227. {
  2228. //angle dans le plan horizontal sous lequel la station (VOR) voit l'avion
  2229. char buf[50];
  2230. s1.toCharArray(buf, 50);
  2231. nav1radial = atol(buf);
  2232. nav1radial_deg = nav1radial/100;
  2233. data_ok |= 8192; // positionne bit13
  2234.  
  2235. ////y=180;
  2236. ////TFT.drawString("radial", x, y);
  2237. ////s2= String (nav2radial/100, 2);
  2238.  
  2239. ////TFT.fillRect(x+70, y, 100, 20, TFT_BLACK);
  2240. ////TFT.drawString(s2, x+70, y);
  2241. }
  2242.  
  2243.  
  2244. if(parametre == "nav2radial" )
  2245. {
  2246. //angle dans le plan horizontal sous lequel l'antenne au sol (LOC) voit l'avion
  2247. char buf[50];
  2248. s1.toCharArray(buf, 50);
  2249. nav2radial = atol(buf);
  2250. data_ok |= 16384; // positionne bit14
  2251.  
  2252. ////y=180;
  2253. ////TFT.drawString("radial", x, y);
  2254. ////s2= String (nav2radial/100, 2);
  2255.  
  2256. ////TFT.fillRect(x+70, y, 100, 20, TFT_BLACK);
  2257. ////TFT.drawString(s2, x+70, y);
  2258. }
  2259.  
  2260. }
  2261.  
  2262.  
  2263.  
  2264. delay(3); // important sinon ne recevra pas la totalité des données (qui se fait en plusieurs passes)
  2265.  
  2266. // pour test
  2267. ////TFT.drawString("data= ", 90, 40);
  2268. ////s2= (String) data_ok;
  2269. ////TFT.fillRect(140, 40, 50, 30, TFT_BLACK);
  2270. ////TFT.drawString(s2, 150, 40);
  2271.  
  2272. }
  2273.  
  2274.  
  2275.  
  2276. void data_out()
  2277. {
  2278.  
  2279. // destination FlightGear par la liaison série USB
  2280.  
  2281. Serial.print(hdg1); // consigne de Cap -> autopilot
  2282. Serial.print(',');
  2283.  
  2284. Serial.print(asel1); // consigne d'altitude -> autopilot
  2285. Serial.print(',');
  2286.  
  2287. float v1 = liste_bali[num_bali].frq_VOR/100.0;
  2288. Serial.print(v1); // écrit la fréquence VOR dans Nav1
  2289. Serial.print(',');
  2290.  
  2291. float v2 = liste_bali[num_bali].frq_ILS/100.0;
  2292. Serial.print(v2); // écrit la fréquence ILS dans Nav2
  2293. Serial.print('\n');
  2294.  
  2295. }
  2296.  
  2297.  
  2298.  
  2299. void affichages()
  2300. {
  2301. if (roulis < -45) {roulis = -45;}
  2302. if (roulis > 45) {roulis = 45;}
  2303.  
  2304. if (tangage < -30) {tangage = -30;}
  2305. if (tangage > 30) {tangage = 30;}
  2306.  
  2307.  
  2308. affi_HA();
  2309. affi_vitesse();
  2310. affi_altitude();
  2311. trace_arc_gradu();
  2312. affi_cap();
  2313. affi_vt_verticale();
  2314. affi_acceleration();
  2315. affi_autopilot();
  2316. affi_asel();
  2317. dessine_avion();
  2318. affi_indicateurs();
  2319. affi_radial_NAV1();
  2320. affi_radial_NAV2();
  2321. affi_Airport();
  2322. }
  2323.  
  2324.  
  2325. void affi_nop()
  2326. {
  2327. for(int8_t dy=-2; dy<3; dy++)
  2328. {
  2329. TFT.drawLine(HA_x0-HA_w, HA_y0-HA_h +dy, HA_x0 +HA_w, HA_y0 +HA_h +dy, ROUGE);
  2330. TFT.drawLine(HA_x0-HA_w, HA_y0+HA_h +dy, HA_x0 +HA_w, HA_y0 -HA_h +dy, ROUGE);
  2331. }
  2332.  
  2333. //TFT.fillRect(0, 0, 239, 30, NOIR);
  2334. TFT.setTextColor(BLANC, ROUGE);
  2335. TFT.setFreeFont(FF18);
  2336. TFT.drawString("No Data", HA_x0-34, HA_y0+30);
  2337. }
  2338.  
  2339.  
  2340.  
  2341.  
  2342. void init_affi_autopilot()
  2343. {
  2344.  
  2345. TFT.setFreeFont(FF1);
  2346. TFT.setTextColor(JAUNE, GRIS_AF);
  2347.  
  2348. // ALT
  2349.  
  2350. //TFT.drawString("ALT", x_autopilot, 260, 1);
  2351. //TFT.drawRoundRect(x_autopilot-4, 255, 45, 42, 5, BLANC);
  2352. }
  2353.  
  2354.  
  2355.  
  2356. void affi_autopilot()
  2357. {
  2358. // dans le petit cercle en bas à gauche :
  2359. // affiche HDG (flèche jaune), piste (rectangle bleu), VOR (Nav1, ligne verte)
  2360.  
  2361. uint16_t x0=70; // 70
  2362. uint16_t y0=248; // 255
  2363.  
  2364. TFT.setFreeFont(FF1);
  2365.  
  2366. TFT.fillRect(x0, y0+2, 70, 80, NOIR); // efface
  2367.  
  2368. TFT.setTextColor(JAUNE, NOIR);
  2369. TFT.drawString("HDG", x0, y0-18);
  2370. //TFT.drawRoundRect(x0-4, 255, 45, 42, 5, BLANC);
  2371.  
  2372. TFT.drawCircle(x0+35, y0+34, 30, BLANC);
  2373.  
  2374. String s1 =(String)hdg1;
  2375. TFT.setTextColor(BLANC, NOIR);
  2376.  
  2377. TFT.drawString(s1, x0+18, y0+35);
  2378.  
  2379. TFT.setTextColor(BLANC, NOIR);
  2380. TFT.drawString("N", x0+30, y0-5);
  2381.  
  2382. TFT.setTextColor(BLANC, NOIR);
  2383. TFT.drawString("S", x0+30, y0+60);
  2384.  
  2385. TFT.setTextColor(BLANC, NOIR);
  2386. TFT.drawString("E", x0+60, y0+27);
  2387. TFT.drawString("W", x0, y0+27);
  2388.  
  2389. uint16_t x1,y1;
  2390.  
  2391.  
  2392. // flèche jaune = règlage HDG de l'autopilot
  2393. float angle1 = 90-hdg1;
  2394. affi_rayon2(x0+35, y0+34, 0, 30, angle1, JAUNE, 0); // tige de la flèche
  2395. affi_pointe(x0+35, y0+34, 30, angle1, JAUNE); // pointe triangulaire en bout de flèche
  2396.  
  2397. // rectangle bleu, très fin -> orientation ('radial') de l'axe de la piste
  2398. float angle2 = 90+nav2radial/100.0;
  2399. affi_rectangle_incline(x0+35, y0+34, 35, angle2, BLEU_CLAIR);
  2400.  
  2401. // trait vert -> radial du VOR (dont la fréquence radio est réglée dans Nav1)
  2402. float angle3 = 90+nav1radial/100.0;
  2403. //affi_rectangle_incline(x0+35, y0+34, 35, angle3, ROUGE);
  2404. affi_rayon2(x0+35, y0+34, -28, 28, -angle3, VERT, 0);
  2405.  
  2406.  
  2407.  
  2408. // ALTITUDE
  2409.  
  2410. // ce paramétrage permet de régler la valeur de 0 à 200 (soit 0 à 20000 pieds soit 6000m environ)
  2411. //pour aller plus haut on peut toujours utiliser l'interface de FG (touche F11...)
  2412.  
  2413. /*
  2414. String s2 =(String)asel1;
  2415. TFT.setTextColor(ROSE, NOIR);
  2416. TFT.fillRect(x_autopilot, 280, 40, 15, NOIR); // efface
  2417. TFT.drawString(s2, x_autopilot, 278);
  2418. */
  2419. affi_asel();
  2420.  
  2421. data_out(); // ** à commenter le cas échéant pour libérer le port série lors de la mise au point **
  2422.  
  2423. }
  2424.  
  2425.  
  2426.  
  2427. void toutes_les_10s()
  2428. {
  2429. /*
  2430. uint8_t aa1;
  2431. aa1=70;
  2432. SerialBT.write(aa1);
  2433. */
  2434. //TFT.fillCircle(450, 310, 5, VERT);
  2435. //delay (100);
  2436. //TFT.fillCircle(450, 310, 5, NOIR);
  2437.  
  2438. // write_TFT_on_SDcard(); // commenté le temps de trouver le moyen de lancer par bouton poussoir (manque pin libre...)
  2439.  
  2440.  
  2441. }
  2442.  
  2443.  
  2444.  
  2445. void toutes_les_1s()
  2446. {
  2447. nb_secondes++;
  2448.  
  2449.  
  2450. //// size_t write(uint8_t c);
  2451. //// size_t write(const uint8_t *buffer, size_t size);
  2452.  
  2453.  
  2454. if(nb_secondes>10)
  2455. {
  2456. nb_secondes=0;
  2457. toutes_les_10s();
  2458. }
  2459.  
  2460. dV =10*(vitesse - memo_vitesse);
  2461. memo_vitesse = vitesse;
  2462.  
  2463. auto_landing(); // ne sera effectif que si la variable autoland==1; toutefois la fonction est toujours appelée afin d'actualiser les affichages
  2464.  
  2465. }
  2466.  
  2467. /** ==================================================================
  2468.  variables à gérer obligatoirement */
  2469.  
  2470. uint8_t TEST_AFFI=0;// =0 pour un fonctionnement normal; =1 pour tester les affichages et la fluidité
  2471.  
  2472. //le nombre de ports GPIO libres étant atteint, on va utiiser un switch unique pour deux fonctions :
  2473. uint8_t fonction_bt1 = 1; // 0=saisie écran ; 1=autoland (atterrissage automatique)
  2474.  
  2475. /** ================================================================== */
  2476.  
  2477. uint16_t t=0; // temps -> rebouclera si dépassement
  2478. void loop()
  2479. {
  2480.  
  2481.  
  2482.  
  2483. //if (SerialBT.available()) { Serial.write(SerialBT.read()); }
  2484.  
  2485.  
  2486.  
  2487. //le bouton1 est partagé entre deux fonctions, voir ci-dessus "variables à gérer obligatoirement"
  2488. bouton1_etat = digitalRead(bouton1);
  2489.  
  2490. if (bouton1_etat != memo_bouton1_etat)
  2491. {
  2492. memo_bouton1_etat = bouton1_etat;
  2493. if (bouton1_etat==0)
  2494. {
  2495. TFT.fillCircle(455, 310, 5, VERT);
  2496.  
  2497.  
  2498. if (fonction_bt1 == 0) {write_TFT_on_SDcard(); }
  2499. if (fonction_bt1 == 1)
  2500. {
  2501. memo_hdg1 = hdg1; // mémorise le cap avant de passer la main à l'autoland
  2502. autoland=1;
  2503. }
  2504. }
  2505. if (bouton1_etat==1)
  2506. {
  2507. TFT.fillCircle(455, 310, 5, NOIR);
  2508. if (fonction_bt1=1)
  2509. {
  2510. // si, suite à un enclenchement non correct de l'autoland (sur un mauvais sens du localizer en particulier) le cap a été boulversé
  2511. // on le remet à sa valeur mémorisée. Evite de devoir tourner le bouton pendant une heure avec l'avion qui part à l'aventure !
  2512. hdg1 = memo_hdg1;
  2513. autoland=0;
  2514. init_affi_HA();
  2515. }
  2516. }
  2517. }
  2518.  
  2519. temps_ecoule = micros() - memo_micros;
  2520. if (temps_ecoule >= 1E6) // (>=) et pas strictement égal (==) parce qu'alors on rate l'instant en question
  2521. {
  2522. memo_micros = micros();
  2523. toutes_les_1s();
  2524. }
  2525.  
  2526. if (dV > acceleration) {acceleration++;}
  2527. if (dV < acceleration) {acceleration--;}
  2528.  
  2529.  
  2530. //---------------------------------------------------------------------------------------
  2531. if(TEST_AFFI==1) // pour test des affichages:
  2532. {
  2533.  
  2534. // les valeurs de ces paramètres peuvent être modifiées afin de tester tel ou tel point particulier
  2535.  
  2536. vitesse = 100 - 100*cos(t/200.0);
  2537. //vitesse = 100;
  2538.  
  2539. altitude = 1000 - 800*cos(t/400.0);
  2540. vspeed = 80*sin(t/100.0);
  2541.  
  2542. tangage =20.0*sin(t/87.0);
  2543. roulis = 35.0*sin(t/35.0);
  2544.  
  2545. cap =180.0+180.0*sin(t/300.0);
  2546.  
  2547. nav1dst=200000+200000*sin(t/100.0) ;// test 400 000 nautiques max = 741km
  2548. nav1radial= 100*45;// 100*(180 + 180.0*sin(t/250.0));
  2549.  
  2550. nav2dst-=30;
  2551. nav2radial=304.0 *100.0;
  2552.  
  2553.  
  2554. data_ok=32767;
  2555. nav2glide= (int16_t) (3000 + 2000*sin(t/77.0) ); // soit 5°... 4°
  2556. nav2loc = 10E+3*sin(t/63.0); // soit -10°... +10°
  2557. gs_ok=1;
  2558.  
  2559. affichages();
  2560.  
  2561. t++;
  2562.  
  2563. TFT.setTextColor(JAUNE, BLEU);
  2564. TFT.setFreeFont(FF1);
  2565. TFT.drawString("mode TEST", 0, 0);
  2566.  
  2567.  
  2568. if (dV > acceleration) {acceleration++;}
  2569. if (dV < acceleration) {acceleration--;}
  2570. delay(20);
  2571.  
  2572. }
  2573.  
  2574. //---------------------------------------------------------------------------------------
  2575. else // FONCTIONNEMENT NORMAL
  2576. {
  2577. compteur1+=1;
  2578. if (compteur1 >= 100) // tous les 1000 passages dans la boucle
  2579. {
  2580. compteur1=0;
  2581. affi_autopilot();
  2582. }
  2583.  
  2584. acquisitions();
  2585.  
  2586. /** ----------------------------------------------
  2587.  pour tester si data_ok à bien la valeur attendue, c.a.d si toutes les acquisitions sont effectuées
  2588.  ce qui est le cas lorsque le dialogue avec le serveur de protocole FG est correctement programmé
  2589.  sachant que ce ne sont pas les pièges qui manquent !
  2590. */
  2591. ////TFT.setFreeFont(FM9);
  2592. ////TFT.setTextColor(VERT, NOIR);
  2593. ////TFT.drawString("d_sum:", 0, 0);
  2594. ////s2= (String) data_ok;
  2595. ////TFT.fillRect(70, 0, 50, 30, TFT_BLACK);
  2596. ////TFT.drawString(s2, 70, 0);
  2597.  
  2598. /** ---------------------------------------------- **/
  2599.  
  2600. if (data_ok == 32767)
  2601. // 32767 = 1+2+4+8+16+32+64+128+256+512+1024+2048+4096+8192+16384 = 0b 1111111 11111111 = (2^15)-1 voir fonction "acquisitions()"
  2602. {
  2603. TFT.fillCircle(440, 310, 5, VERT);
  2604. if (attente_data == 1)
  2605. {
  2606. attente_data=0;
  2607. TFT.fillScreen(TFT_BLACK);
  2608. init_affi_HA();
  2609. init_affi_autopilot();
  2610. }
  2611.  
  2612. affichages();
  2613. data_ok=0;
  2614. }
  2615. else
  2616. {
  2617. TFT.fillCircle(440, 310, 5, ROUGE);
  2618. if(attente_data==1)
  2619. {
  2620. affi_nop();
  2621. RAZ_variables();
  2622. affichages();
  2623. delay(300);
  2624. }
  2625. }
  2626. }
  2627.  
  2628. //---------------------------------------------------------------------------------------
  2629.  
  2630.  
  2631.  
  2632. }
  2633.  
  2634.  
  2635.  
  2636. /**
  2637. -----------------------------------------------------------------------------------------------------------
  2638.   ci-dessous contenu du fichier (pour Linux): "/usr/share/games/protocol/hardware4.xml" qui va bien pour CE programme
  2639.  
  2640.   IMPORTANT : Lorsque je fais évoluer le programme principal (ci-dessus) je modifie également (le cas échéant) le fichier hardware4.xml (ci-dessous)
  2641.   je ne numérote pas le fichier ci-dessous. Donc pensez à toujours utiliser celui qui est présent ici
  2642.   (en le recopiant au bon endroit, ici ce n'est qu'un commentaire non fonctionnel en tant que tel).
  2643.   en effet les variables transmises par FG doivent correspondre exactement à ce qu'attend l'ESP32, voir la fonction "void acquisitions()"
  2644.  
  2645.  
  2646.   FG doit être lancé avec les paramètres suivants :
  2647.  
  2648. --generic=serial,in,2,/dev/ttyUSB0,9600,hardware4
  2649. --generic=serial,out,2,/dev/ttyUSB0,9600,hardware4
  2650.  
  2651. IMPORTANT :
  2652. Il faut ajouter les DEUX lignes dans un lanceur (tel que "FGo!") pour obtenir un fonctionnement bidirectionnel
  2653.  
  2654. Remarques :
  2655.   - le nom du fichier "hardware4.xml" peut être autre chose pourvu que le paramètre de lancement corresponde exactement au nom choisi
  2656.   - le fichier en question n'existe pas par défaut dans ce répertoire, il faut le créer.
  2657.  
  2658. -----------------------------------------------------------------------------------------------------------
  2659.  
  2660.  
  2661. <?xml version="1.0"?>
  2662.  
  2663. <PropertyList>
  2664.  
  2665. <generic>
  2666.   <output>
  2667.   <binary_mode>false</binary_mode>
  2668.   <line_separator>\n</line_separator>
  2669.   <var_separator>\n</var_separator>
  2670.   <preamble></preamble>
  2671.   <postamble></postamble>
  2672.  
  2673.   <chunk>
  2674.   <name>Altitude</name>
  2675.   <node>/position/altitude-ft</node>
  2676.   <type>integer</type>
  2677.   <format>alti=%i</format>
  2678.   </chunk>
  2679.  
  2680. <chunk>
  2681. <name>ground_elevation</name>
  2682. <type>integer</type>
  2683. <node>/position/ground-elev-ft</node>
  2684. <format>gnd_elv=%i</format>
  2685. </chunk>
  2686.  
  2687.  
  2688.   <chunk>
  2689.   <name>Speed</name>
  2690.   <node>/velocities/airspeed-kt</node>
  2691.   <type>integer</type>
  2692.   <format>speed=%i</format>
  2693.   </chunk>
  2694.  
  2695.   <chunk>
  2696.   <name>Tangage</name>
  2697.   <node>/orientation/pitch-deg</node>
  2698.   <type>integer</type>
  2699.   <format>pitch=%i</format>
  2700.   </chunk>
  2701.  
  2702.   <chunk>
  2703.   <name>Roulis</name>
  2704.   <node>/orientation/roll-deg</node>
  2705.   <type>integer</type>
  2706.   <format>roll=%i</format>
  2707.   </chunk>
  2708.  
  2709.   <chunk>
  2710.   <name>Cap</name>
  2711.   <node>/orientation/heading-deg</node>
  2712.   <type>integer</type>
  2713.   <format>heading=%i</format>
  2714.   </chunk>
  2715.  
  2716.   <chunk>
  2717.   <name>Vertical_speed</name>
  2718.   <node>/velocities/vertical-speed-fps</node>
  2719.   <type>integer</type>
  2720.   <format>vspeed=%i</format>
  2721.   </chunk>
  2722.  
  2723.   <chunk>
  2724. <name>Nav1_frq</name>
  2725. <type>integer</type>
  2726. <node>/instrumentation/nav/frequencies/selected-mhz</node>
  2727. <factor>1000</factor>
  2728. <format>nav1frq=%i</format>
  2729. </chunk>
  2730.  
  2731.  
  2732.   <chunk>
  2733.   <name>Nav1_distance</name>
  2734.   <node>/instrumentation/nav/nav-distance</node>
  2735.   <type>integer</type>
  2736.   <format>nav1dst=%i</format>
  2737.   </chunk>
  2738.  
  2739.  
  2740. <chunk>
  2741. <name>Nav2_dist</name>
  2742. <type>integer</type>
  2743. <node>/instrumentation/nav[1]/gs-distance</node>
  2744. <format>nav2dst=%i</format>
  2745. </chunk>
  2746.  
  2747.  
  2748. <chunk>
  2749. <name>Nav2_loc</name>
  2750. <type>integer</type>
  2751. <node>/instrumentation/nav[1]/heading-needle-deflection</node>
  2752. <factor>1000</factor>
  2753. <format>nav2loc=%i</format>
  2754. </chunk>
  2755.  
  2756.  
  2757. <chunk>
  2758. <name>Nav2_glide</name>
  2759. <type>integer</type>
  2760. <node>/instrumentation/nav[1]/gs-direct-deg</node>
  2761. <factor>1000</factor>
  2762. <format>nav2glide=%i</format>
  2763. </chunk>
  2764.  
  2765.  
  2766. <chunk>
  2767. <name>GS_OK</name>
  2768. <type>integer</type>
  2769. <node>/instrumentation/nav[1]/gs-in-range</node>
  2770. <format>gs_ok=%i</format>
  2771. </chunk>
  2772.  
  2773.  
  2774. <chunk>
  2775. <name>Nav1_radial</name>
  2776. <type>integer</type>
  2777. <node>/instrumentation/nav/radials/actual-deg</node>
  2778. <factor>100</factor>
  2779. <format>nav1radial=%i</format>
  2780. </chunk>
  2781.  
  2782.  
  2783. <chunk>
  2784. <name>Nav2_radial</name>
  2785. <type>integer</type>
  2786. <node>/instrumentation/nav[1]/radials/target-radial-deg</node>
  2787. <factor>100</factor>
  2788. <format>nav2radial=%i</format>
  2789. </chunk>
  2790.  
  2791.  
  2792.  
  2793.  
  2794.   </output>
  2795.  
  2796.  
  2797.   <input>
  2798. <line_separator>\n</line_separator>
  2799.   <var_separator>,</var_separator>
  2800.  
  2801.   <chunk>
  2802.   <name>heading_bug</name>
  2803.   <node>/autopilot/settings/heading-bug-deg</node>
  2804.   <type>integer</type>
  2805.   <relative>false</relative>
  2806.   </chunk>
  2807.  
  2808.   <chunk>
  2809.   <name>asel</name>
  2810.   <node>/autopilot/settings/asel</node>
  2811.   <type>integer</type>
  2812.   <relative>false</relative>
  2813.   </chunk>
  2814.  
  2815.  
  2816.   <chunk>
  2817.   <name>Nav1_frq</name>
  2818.   <node>/instrumentation/nav/frequencies/selected-mhz</node>
  2819.   <type>float</type>
  2820.   <relative>false</relative>
  2821.   </chunk>
  2822.  
  2823.  
  2824.   <chunk>
  2825.   <name>Nav2_frq</name>
  2826.   <node>/instrumentation/nav[1]/frequencies/selected-mhz</node>
  2827.   <type>float</type>
  2828.   <relative>false</relative>
  2829.   </chunk>
  2830.  
  2831.  
  2832.  
  2833.  
  2834.   </input>
  2835.  
  2836.  
  2837.  
  2838. </generic>
  2839.  
  2840. </PropertyList>
  2841.  
  2842. **/
  2843.  

J'ai fait un effort pour commenter en détail le code source.

A noter : juste au dessus de la boucle principale (loop) figure cette ligne que l'on doit gérer absolument (affichage animé automatiquement, ou affichage normal)

uint8_t TEST_AFFI=1;// =0 pour un fonctionnement normal; =1 pour tester les affichages et la fluidité


5 Le principe

Le simulateur FlightGear Open-source, gratuit, (pour Linux) comprend outre un serveur html, des serveurs de protocoles série (telnet, mais aussi "générique" qui nous intéresse ici). Encore faut-il définir ce protocole générique précisément. Il faut pour cela créer un fichier xml et le placer dans le répertoire de "protocol" de FlightGear.

Sous linux ça donne ça :

/usr/share/games/protocol/hardware4.xml


J'ai recopié le contenu de ce fichier au bas du code source. Il comprend deux parties, "output" et "input".

Ce n'est pas tout : pour que le programme FG tienne compte de ce fichier, il faut le lancer avec les deux options suivantes (les 2 options sont indispensables afin d'obtenir un fonctionnement bidirectionnel):

--generic=serial,in,2,/dev/ttyUSB0,9600,hardware4
--generic=serial,out,2,/dev/ttyUSB0,9600,hardware4


La partie "output" sert ici à publier, à destination de l'ESP :
  • l'altitude
  • la vitesse
  • le tangage
  • le roulis
  • le cap
  • la vitesse verticale
  • la fréquence inscrite dans le panel radio nav1 de l'avion (ex:11445 pour 114.45MHz)
  • la distance en nautiques de la balise (VOR-DME) correspondante
  • les données ILS (glide + localizer)
La partie "input" permet de régler à l'aide de deux encodeurs rotatifs pas à pas reliés à l'ESP, les consignes d'altitude et de cap du pilote automatique de FG. (avant de décoller, on doit aller dans la fenêtre de paramétrage de l'autopilot avec la touche de fonction F11, et sélectionner HDG et ALT. ensuite après prise d'altitude minimale, on peut lancer l'autopilot avec par exemple un bouton de la manette de jeux...)

6 L'afficheur utilisé :

C'est un TFT LCD 480 x 320 que l'on pilote ici par son interface parallèle, d'où la très grande rapidité.

ATTENTION : il existe des afficheurs d'apparence identique sauf pour le tracé des pistes qui et différent : je n'ai pas réussi à les faire fonctionner. Donc il faut utiliser celui de la photo ci-dessus exclusivement.

30 juin 2021 :
En fait, après maintes expérimentations matérielles et logicielles, les afficheurs ne fonctionnent pas tous, il s'avère ceci :
  • ce n'est pas une question de tracé des pistes au dos des afficheurs
  • c'est en fait un problème de driver utilisé par l'afficheur
  • certains afficheurs fonctionnent avec la library TFT_eSPI
  • certains autres ne fonctionnent qu'avec la library MCUFRIEND_kbv dérivée de ADAFRUIT
  • certains fonctionnent avec tout
  • oui j'ai configuré le plan de câblage des pins correctement dans les fichiers concernés, la preuve en permutant les afficheurs sur le même montage et avec le même programme et même config
  • tous les afficheurs fonctionnent sur Arduino Mega2560

Vu que les fonctions diffèrent entre les deux library, surtout concernant les polices de caractères disponibles, vu que j'ai tout développé avec la TFT_eSPI, vu que j'utilise une ESP32 et pas un Arduino, et vu que rien, visuellement, ne permet de discerner les types d'afficheurs, et surtout pas la sérigraphie au dos, je me retrouve une fois sur deux avec un afficheur inutilisable pour ce projet. Et là je viens de perdre une journée à galérer pour en arriver à cette conclusion.

7 Dans la jungle des afficheurs

10 juillet 2021 :
Ayant rencontré des difficultés à trouver des afficheurs 3"5 - 480x320 "bleus" compatibles avec la library TFT_eSPI (qui me convient), j'ai commandé et reçu un tel afficheur dont la carte est rouge. Par chance il fonctionne très bien avec cette library TFT_eSPI, ouf ! Toutefois il faut l'utiliser avec le driver ILI9488 et non pas le ILI9486 (sinon affiche des couleurs bizarres).
Donc je récapitule : voici les paramètres à inscrire dans le fichier de configuration "User_Setup.h" situé dans le dossier "/home/login/Arduino/libraries/TFT_eSPI/" :

#define ESP32_PARALLEL

#define ILI9488_DRIVER

//pour les afficheurs à CI bleu -> ILI9486_DRIVER
//pour les afficheurs à CI rouge -> ILI9488_DRIVER

Comme on peut le constater sur la photo, cette fois le fabriquant s'est donné la peine de sérigraphier le type de driver au dos de l'afficheur, et l'indication est exacte.

8 Vidéo - Décollage LFMT


9 Le Lac du Salagou


J'ai plaqué une texture image sur le relief de la presqu’île ce qui donne un rendu bien plus réaliste, avec la couleur rouge caractéristique des roches, qu'on appelle dans l'Hérault la "ruffe".

10 Utilité du panel ESP32

Cela permet de piloter l'avion calmement, en pilotage automatique, sans afficher le tableau de bord sur la moité de l'écran de l'ordinateur, ou même en affichage extérieur sans le tableau de bord, par exemple, et profiter de la vue. Parce que oui, FlightGear bien configuré et très beau, comme on peut le constater avec les copies d'écran que j'ai rajoutées au début de cet article. J'ai blenderisé en 3D pas mal de choses pour FGFS.

11 Evolution

28 mai 2021 : Cette réalisation est en phase d'évolution, j'améliore chaque jour l'affichage et j'ajoute de nouvelles fonctions.

Actuellement nous avons :
  • horizon artificiel représentant à la fois le roulis et le tangage
  • à gauche : la vitesse /air (ici : 113 kts)
  • une règle verticale glissante graduée des vitesses (ici: 70..160)
  • une colonne colorée des vitesses particulières: mini, maxi...
  • L'altitude (ici: 1525 ft)
  • une règle verticale glissante graduée des altitudes (ici: 1480..1560)
  • à droite : L'indicateur analogique de la vitesse verticale (ici ~1200 ft/mn); Si l'aiguille arrive au taquet, la valeur (>3000) s'affiche numériquement au dessus (ou si < -3000, en dessous)
  • au centre en bas : le cap (ici: 228°) avec compas tournant.
  • en bas à gauche: la saisie de la consigne de cap pour le pilote auto ("HDG" ici 150°) réglable par encodeur pas à pas; Cette valeur est représenté sur le compas à sa position relative correcte (non visible ici).
  • en bas à droite: la saisie de la consigne d'altitude pour le pilote auto ("ALT" ici 30 => 3000ft en rose) réglable par encodeur pas à pas. Cette valeur est recopiée au dessus de l'échelle des altitudes

12 Circuit imprimé

4 juin 2021 :
J'ai réalisé en ce moment, sous Kicad, un circuit imprimé qui assemble facilement le module ESP32 et l'afficheur TFT.
Je confirme le bon fonctionnement de l'ensemble.

13 Le boitier imprimé en 3D

Vous trouverez les fichiers source 3D du boîtier et des différents supports internes dans le .zip au bas de l'article.

14 Disposition des éléments sur le circuit imprimé

Sur la photo ci-contre :
  • L'afficheur, en bleu sombre au fond, tenu par les petites cales [A, B, C ,D] est vu de dos
  • Les pistes sont vues par transparence à travers le circuit imprimé en verre époxy très fin (0.8mm)
  • Le lecteur de carte SD (avec une carte dedans) se retrouve du même côté que le connecteur mini-usb d'alimentation de la carte ESP32


  • Je précise tout ça parce qu'il y a plusieurs façons de souder les connecteurs femelles au pas de 2.54 sur le circuit imprimé (ceux de l'afficheur doivent être soudés côté cuivre, si si, c'est faisable en décalant un peu et en utilisant une panne pour cms), plusieurs façons disais-je, et une seule qui est la bonne.

15 Captures d'écran sur SDcard

02 juin 2021 :

Grande nouvelle : J'ai réussi à :
  • utiliser le lecteur de micro SDcard (TFcard) situé au dos de l'afficheur
  • effectuer une capture de l'écran TFT
  • enregistrer cette capture dans un fichier .bmp sur la SDcard
La capture est déclenchée par appui sur le petit switch (voir schéma).

IMPORTANT : Un fichier image 480x320px format paysage, 24bits RGB888, nommé capture2.bmp doit être présent sur la SDcard, dans le dossier "bmp" au préalable, afin de fournir l'entête "qui va bien" (dans un premier temps j'ai codé la création de l'entête, ce qui n'est pas très compliqué, et puis voyant que c'est une trouvaille à la sauce Windows... j'ai laissé tomber) à la fonction de capture). J'en fournis une, vous pouvez aussi la créer (de couleur unie par exemple) avec The Gimp. La fonction de capture ne fait qu'écrire dans ce fichier à partir de la position (offset) 138; c'est à dire après la fin de l'entête. Pour plus de détails, voir la fonction
void write_TFT_on_SDcard()
Et voici ci-contre le résultat ; C'est quand même autre chose qu'une photo prise avec un APN ou un smartphone !

L'ensemble constitue maintenant un module universel puissant, avec affichage, capture d'écran très utile pour vous montrer sur le site les résultats, accès à de gros volumes de data, sans oublier les fonctions WiFi de l'ESP...



Un p'tit gif animé qui montre tout ce qui bouge :

16 Evolution 2

14 juin 2021 :
Je travaille sur l'acquisition des données 'ILS' en vue d'afficher les indicateurs permettant un atterrissage aux instruments. Il va falloir interpréter les data suivantes :
  • /instrumentation/nav[1]/frequencies/selected-mhz
  • /instrumentation/nav[1]/gs-distance
  • instrumentation/nav[1]/gs-direct-deg

Je peux déjà dire que l'exportation de ces données supplémentaires par FG ne le ralentit pas, nous verrons si côté ESP32 il n'y a pas d'indigestion. La suite devrait marcher comme sur des roulettes ;)

16 juin 2021 :
version 7.0 -> L'affichage de la fonction GLIDE de l'ILS est maintenant fonctionnel, par un double curseur se déplaçant de haut en bas sur les côté de l'horizon artificiel. Cela permet de descendre (manuellement) vers la piste avec un angle de 3° (= pente 5%) précis. Reste à traiter le "localizer" (azimut)

Voici ce que je conseille pour le Cessna Citation X :
  • approche avec une hauteur de 3000ft à 10 nautiques, afin de d'accrocher le faisceau
  • ralentir l'avion à 160 kts
  • stopper le pilote automatique
  • volets 2 crans (surtout pas sortis à fond !)
  • sortir le train afin de subir toutes les traînées et de stabiliser les paramètres sans risquer une surprise au dernier moment...
  • gérer TRÈS précisément la vitesse entre 140 et 160 kts (gaz)- moins vite on décroche, plus vite on avale toute la piste et on finit dans les champs
  • respecter la pente de descente à la profondeur.
  • toucher si possible en début de piste
  • éviter tout rebond
  • Si on ne dispose pas d'un palonnier ça devient vite Rock’n’roll !

17 l'ILS est fonctionnel

17 juin 2021 :

Cette fois tout fonctionne pour l'ILS à savoir le "localizer" qui indique la position de l'avion dans le plan horizontal par rapport à l'axe de la piste, et le "glide" qui indique la position de l'avion par rapport au plan de descente à 3° (soit une pente de 5%) finissant sur la piste. Ces indications sont fournies sous forme de petits curseurs roses qui se déplacent sur les bords de l'horizon artificiel. Et je confirme que j'ai pu poser le Citation X sur la piste à la seule vue de cet afficheur 3.5".

Voici ci-contre l'état actuel de l'affichage, compte tenu de ces dernières fonctionnalités.

18 Affichage de l'orientation de la piste

21 juin 2021 :
J'ai ajouté la représentation de l'orientation exacte de la piste (en bleu) telle que déterminée par la fréquence radio de son ILS saisi dans la variable Nav2. Cela constitue une aide précieuse lors de l'approche finale avec le pilote automatique (réglé manuellement, pas en mode atterrissage automatique*), afin de ne pas zigzaguer en sur-corrigeant la trajectoire.

(*)Le mode atterrissage automatique est présent dans FlightGear pour le Cessna Citation X, ça fonctionne très bien, mais pour le coup on devient passif, et puis si on a mal programmé l'affaire on creuse un grand trou par terre après un virage sur l'aile à 180 degrés.

19 Fonction Autopilot (Attérrissage automatique)

27 juin 2021 : version 9.0
Je viens d'ajouter une fonction Autopilot enclenchée (et donc débrayable) par un switch. Comme il me manque des pins libres, je l'ai connecté à la place du bouton poussoir qui servait à faire des captures d'écran (sans supprimer cette fonction, il faut juste choisir lors de la compilation).
Il faut se présenter à 10 nautiques de la piste à une altitude de 3000ft et avec une vitesse d'environ 160kts (non critique). Et alors... La fonction capture l'avion, le fait descendre, pilote sa trajectoire, et le pose nickel sur la piste et dans l'axe. Comme un vrai.

Ça existait déjà ? Oui mais celui si c'est moi qui l'ai programmé, et on peut le modifier facilement, vous avez le code source commenté.

Bon faut que je vous dise... Il y un un hic ! J'ai pris en compte l'altitude et non la hauteur, et tant que je faisais mes tours de piste à Fréjorgues (= Montpellier Méditerranée) situé au niveau de la mer, pas de problème. Et puis je suis allé faire un tour à Nimes-Garons situé à 309ft d'altitude... Et évidemment ça s'est terminé dans un champ à 2km de la piste.

Mais pas de panique : j'ai déjà ajouté l'acquisition de la donnée "/position/ground-elev-ft" qu'il me suffira de retrancher à l'altitude pour avoir la hauteur qui, pour l’atterrissage constitue la donné pertinente.

28 juin 2021 : version 9.1
C'est fait, l'atterrissage se fait bien automatiquement à la bonne altitude, celle de la piste. L'avion s'est ainsi posé sans problème à LFCK (Castres-Mazamet) sur la piste située à 755ft d'altitude.
J'ai toutefois un problème de réglage de trim de profondeur lors de la déconnexion automatique de l'autopilot (sur le Citation X), en finale près du sol. Rien à voir avec cette réalisation, mais c'est gênant. Si on ne s'y attends pas, c'est le crash. Il faut en effet tirer sur le manche comme un fou, ce n'est pas un simple manque d'arrondi.

20 Deuxième instrument : ND (Navigation Display)

3 juillet 2021 :
J'ai pensé que ce Primary Flight Display devait se sentir un peu seul... Aussi je vais lui offrir un petit frère : Un second écran TFT de même taille (3.5" - 320x480px) affichant les aides à la navigation : Le ND (Navigation Display):
  • Compas
  • VOR + radial
  • DME

etc...
L'étude est lancée, voici un aperçu ->
Je ne sais pas encore si je vais y consacrer un article distinct ou bien continuer sa description ici à la suite. Et comme je me connais, ça risque de déboucher à terme sur un tableau de bord complet !!

11 Juillet 2021 :
J'ai finalement choisi d'utiliser les fonctions WiFi des ESP32 pour assurer la communication entre les deux instruments et ça fonctionne très bien. Actuellement le PFD transmet le cap au deuxième instrument, et la donnée est correctement reçue en temps réel. Donc le lien (1)->(2) est OK. Il me reste à coder le lien inverse (2)->(1) ce qui revient à faire une liaison WiFi client-serveur bidirectionnelle. Je vais m'y atteler ces jours-ci, et je pense que ça devrait fonctionner, je crains juste quelques difficultés dans la gestion du timing de l'ensemble (eh oui, les deux ESP ne font pas QUE cela, il gèrent aussi tous les affichages, assez complexes, en temps réel).

Je trouve donc que cette réalisation est très intéressante et si je la maîtrise bien c'est aussi parce que je ne suis pas avare de commentaires dans le code source.

Je publierai prochainement une version de l'ensemble, pour l'instant seul le PFD figure ici.

A suivre...

13 Juillet 2021 :
La transmission bidirectionnelle par WiFi des données entre le PFD( Primary Flight display) et le second instrument ND (Navigation Display) est résolue, très simplement :
Pour le sens (PFD)->(ND) : Le ND envoie une requête vers le PFD qui répond avec la donnée requise
Pour le sens (ND)-> (PFD): La donnée est tout simplement transmise sous forme d'argument dans l'URL de la requête. (après le "?" dans l'URL)

Ceci n'est pas juste de la théorie, ça fonctionne très bien, je publierai l'ensemble des deux codes sources prochainement, lorsque le traitement des données côté PFD sera finalisé. 14 Juillet 2021 :
Je viens de rajouter ci-dessous le code source de ce deuxième instrument. Toutefois il va encore évoluer.
Si vous aimez la trigo et les sprites, il y a là ce qu'il faut !

Remarque : le schéma et le circuit imprimé sont (volontairement) identiques à ceux du PFD.

21 Vers un troisième instrument ?

19 juillet 2021 :
Je suis en mesure actuellement d'afficher une image bmp couleur 24bits sur tout ou partie de ces afficheurs, donc il me vient l'idée d'afficher la carte ( OpenStreetMap par exemple), sous forme de vignettes pré-enregistrées sur la SDcard. Et vu la place qu'il y a sur une SDcard de plusieurs dizaines de GB,.. la perspective est immense.

J'ai donc décidé de le faire, mais j'hésite entre incorporer cette image sur 1/4 environ de l'écran du ND, ou alors ajouter un troisième écran et son ESP32 !

Vu le prix de ces afficheurs (entre 7 et 10€), et celui des ESP32 (10€ également)... c'est plus que tentant.

A suivre...

Pour l'instant je code la version intégrée dans le ND. La carte centrée sur l'aérodrome sélectionné (par un encodeur rotatif) s'affiche à différentes échelles de zoom suivant la distance de l'avion. Il reste à superposer des calques (avion, trajectoire, balises, faisceau ILS...) afin de rendre la chose utile.

hum, qui a dit "la météo" ? et pourquoi pas une vraie carte aéronautique avec la TMA ??

22 Code source de l'instrument ND (Navigateur Display)

CODE SOURCE en C++
  1.  
  2. /*
  3. ---------------------------------------------------------------------------------------
  4. FG_radio.ino - Filght Display pour ESP32 - pour Afficheur TFT480 480x320
  5. todo: renommer en FG_FD
  6. par Silicium628
  7.  
  8. Ce logiciel est libre et gratuit sous licence GNU GPL
  9.  
  10. ---------------------------------------------------------------------------------------
  11. Pour les #includes issus de l'univers Arduino (que je ne fournis pas), il faut voir au cas par cas.
  12. (drivers d'affichage en particulier)
  13.  
  14. ---------------------------------------------------------------------------------------
  15. De petites images à placer sur la SDcard centrées sur les aérodromes proviennent de OpenStreetMap
  16.  
  17. OpenStreetMap® est un ensemble de données ouvertes,
  18. disponibles sous la licence libre Open Data Commons Open Database License (ODbL)
  19. accordée par la Fondation OpenStreetMap (OSMF).
  20.  
  21. Voir ce lien pour plus de détails :
  22. https://www.openstreetmap.org/copyright
  23.  
  24. ---------------------------------------------------------------------------------------
  25.  
  26. */
  27.  
  28.  
  29. /**
  30. --------------------------------------------------------
  31. CONCERNANT L'AFFICHAGE TFT480 : connexion :
  32.  
  33. ( Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT480_eSPI/ )
  34.  
  35. // ESP32 pins used for the parallel interface TFT480
  36. #define TFT480_CS 27 // Chip select control pin
  37. #define TFT480_DC 14 // Data Command control pin - must use a pin in the range 0-31
  38. #define TFT480_RST 26 // Reset pin
  39.  
  40. #define TFT480_WR 12 // Write strobe control pin - must use a pin in the range 0-31
  41. #define TFT480_RD 13
  42.  
  43. #define TFT480_D0 16 // Must use pins in the range 0-31 for the data bus
  44. #define TFT480_D1 4 // so a single register write sets/clears all bits
  45. #define TFT480_D2 2 // 23
  46. #define TFT480_D3 22
  47. #define TFT480_D4 21
  48. #define TFT480_D5 15 // 19
  49. #define TFT480_D6 25 // 18
  50. #define TFT480_D7 17
  51.  
  52.  
  53. **/
  54. String version="12.3"; // affichage carte + zoom
  55.  
  56. // le numéro de version doit (depuis la 10.1) être identique à celui du PFD (Primary Flight Display)
  57.  
  58. #include <stdint.h>
  59. #include <TFT_eSPI.h> // Hardware-specific library
  60.  
  61. #include "Free_Fonts.h"
  62.  
  63. #include "FS.h"
  64. #include "SD.h"
  65. #include "SPI.h"
  66.  
  67. #include "FG_radio.h"
  68. #include "FG_data.h"
  69.  
  70.  
  71. #include <WiFi.h>
  72. #include <HTTPClient.h> // Remarque: les fonctions WiFi consomment 50% de la mémoire flash (de programme)
  73. const char* ssid = "FGpanel_srv";
  74. const char* password = "72r4TsJ28";
  75.  
  76. //IP address with URL path
  77. const char* srvName_hdg = "http://192.168.4.1/hdg";
  78. const char* srvName_cap = "http://192.168.4.1/cap";
  79. const char* srvName_rd1 = "http://192.168.4.1/rd1";
  80. const char* srvName_vor_nm = "http://192.168.4.1/VORnm";
  81.  
  82.  
  83. String recp_hdg; // heading bug - HDG = consigne de cap
  84. String recp_cap; // cap actuel (orientation/Nord du nez de l'avion) - sens horaire
  85. String recp_rd1; // radial 1
  86. String recp_vor1nm;
  87.  
  88. TFT_eSprite SPR_VOR = TFT_eSprite(&TFT480);
  89.  
  90. TFT_eSprite SPR_E = TFT_eSprite(&SPR_VOR); // Declare Sprite object "SPR_11" with pointer to "TFT480" object
  91. TFT_eSprite SPR_N = TFT_eSprite(&SPR_VOR);
  92. TFT_eSprite SPR_O = TFT_eSprite(&SPR_VOR);
  93. TFT_eSprite SPR_S = TFT_eSprite(&SPR_VOR);
  94.  
  95. TFT_eSprite SPR_3 = TFT_eSprite(&SPR_VOR);
  96. TFT_eSprite SPR_6 = TFT_eSprite(&SPR_VOR);
  97. TFT_eSprite SPR_12 = TFT_eSprite(&SPR_VOR);
  98. TFT_eSprite SPR_15 = TFT_eSprite(&SPR_VOR);
  99. TFT_eSprite SPR_21 = TFT_eSprite(&SPR_VOR);
  100. TFT_eSprite SPR_24 = TFT_eSprite(&SPR_VOR);
  101. TFT_eSprite SPR_30 = TFT_eSprite(&SPR_VOR);
  102. TFT_eSprite SPR_33 = TFT_eSprite(&SPR_VOR);
  103.  
  104. Cadre cadre_top1;
  105. Cadre cadre_top2;
  106.  
  107. Cadre cadre_00;
  108. Cadre cadre_01;
  109.  
  110. Cadre cadre_10;
  111. Cadre cadre_11;
  112.  
  113. Cadre cadre_20;
  114. Cadre cadre_21;
  115.  
  116. Cadre cadre_cap1;
  117. Cadre cadre_hdg1;
  118. Cadre cadre_rd1;
  119.  
  120.  
  121. //uint16_t s_width = TFT480.Get_Display_Width();
  122. //uint16_t s_heigh = TFT480.Get_Display_Height();
  123.  
  124.  
  125. // COULEURS RGB565
  126. // Outil de sélection -> http://www.barth-dev.de/online/rgb565-color-picker/
  127.  
  128. #define NOIR 0x0000
  129. #define ROUGE 0xF800
  130. #define ROSE 0xFBDD
  131. #define ORANGE 0xFBC0
  132. #define JAUNE 0xFFE0
  133. #define JAUNE_PALE 0xF7F4
  134. #define VERT 0x07E0
  135. #define OLIVE 0x05A3
  136. #define CYAN 0x07FF
  137. #define BLEU_CLAIR 0x455F
  138. #define AZUR 0x1BF9
  139. #define BLEU 0x001F
  140. #define MAGENTA 0xF81F
  141. #define VIOLET1 0x781A
  142. #define VIOLET2 0xECBE
  143. #define GRIS_TRES_CLAIR 0xDEFB
  144. #define GRIS_CLAIR 0xA534
  145. #define GRIS 0x8410
  146. #define GRIS_FONCE 0x5ACB
  147. #define GRIS_TRES_FONCE 0x2124
  148.  
  149. #define GRIS_AF 0x51C5 // 0x3985
  150. #define BLANC 0xFFFF
  151.  
  152.  
  153.  
  154.  
  155. // Width and height of sprite
  156. #define SPR_W 25
  157. #define SPR_H 16
  158.  
  159. /*
  160. #define taille1 100000 //480*320*2
  161. uint8_t data_ecran[taille1];
  162. */
  163.  
  164. int16_t num_bali=0; // peut être négatif lors de l'int de rotation de l'encodeur
  165.  
  166.  
  167. uint32_t memo_micros = 0;
  168. uint32_t temps_ecoule;
  169. uint16_t nb_100ms=0;
  170. uint16_t nb_secondes=0;
  171.  
  172. float cap;
  173. float memo_cap;
  174.  
  175. int32_t nav1dst;
  176. uint32_t nav1frq;
  177. uint32_t nav1radial; // 0..360
  178. uint32_t memo_nav1radial;
  179.  
  180. int32_t nav2dst;
  181. uint32_t nav2frq;
  182. float nav2radial;
  183.  
  184. int16_t hdg1 = 150; // consigne cap
  185. int16_t memo1_hdg1; // pour le changement manuel
  186. int16_t memo2_hdg1; // pour l'autoland
  187.  
  188. uint8_t SDcardOk=0;
  189. uint8_t gs_ok;
  190.  
  191. uint8_t refresh_ND =1;
  192. uint8_t refresh_Encoche =1;
  193. uint8_t refresh_carte =1;
  194.  
  195. int8_t zoom;
  196.  
  197.  
  198. const int bouton1 = 36; // attention: le GPIO 36 n'a pas de R de pullup interne, il faut en câbler une au +3V3
  199. bool bouton1_etat;
  200. bool memo_bouton1_etat;
  201.  
  202.  
  203. // deux encodeurs rotatifs pas à pas
  204. const int rot1a = 32; // GPIO32 -> câbler une R au +3V3
  205. const int rot1b = 33; // GPIO33 -> câbler une R au +3V3
  206.  
  207. const int rot2a = 35; // GPIO35 -> câbler une R au +3V3
  208. const int rot2b = 34; // GPIO34 -> câbler une R au +3V3
  209.  
  210. //const int led1 = 25; // GPIO15
  211.  
  212.  
  213. #define TEMPO 20 // tempo anti-rebond pour l'acquisition des encodeurs rotatifs
  214. volatile uint32_t timer1 = 0;
  215. volatile uint32_t timer2 = 0;
  216.  
  217. uint16_t compteur1;
  218. uint8_t premier_passage=1;
  219.  
  220. uint32_t bmp_offset = 0;
  221.  
  222. void RAZ_variables()
  223. {
  224. nav1dst=0;
  225. nav1radial=0;
  226. memo_nav1radial=0;
  227. nav2dst=0;
  228. hdg1=0; // consigne cap
  229. }
  230.  
  231.  
  232.  
  233.  
  234. void httpGetHDG()
  235. {
  236. HTTPClient http;
  237. char c1[4];
  238.  
  239. String s1= "?a1="; // a1 (nom arbitraire..) pour désigner l'argument n°1 envoyé dans l'URL de la requête
  240. s1+= num_bali;
  241.  
  242. //n_balise_array[0]=s1[0];
  243. //n_balise_array[1]=s1[1];
  244. //n_balise_array[2]=s1[2];
  245. //n_balise_array[3]=0; // zéro terminal -> string
  246.  
  247. srvName_hdg;
  248. http.begin(srvName_hdg+s1);
  249. int httpResponseCode = http.GET();
  250. if (httpResponseCode>0) { recp_hdg = http.getString();}
  251. http.end();
  252. }
  253.  
  254.  
  255.  
  256. void httpGetCap()
  257. {
  258. HTTPClient http;
  259. http.begin(srvName_cap);
  260. int httpResponseCode = http.GET();
  261. if (httpResponseCode>0) { recp_cap = http.getString(); }
  262. http.end();
  263. }
  264.  
  265.  
  266. void httpGetRd1() // radial 1
  267. {
  268. HTTPClient http;
  269. http.begin(srvName_rd1);
  270. int httpResponseCode = http.GET();
  271. if (httpResponseCode>0) { recp_rd1 = http.getString(); }
  272. http.end();
  273. }
  274.  
  275.  
  276. void httpGetVor1nm() // Nv1 (nautiques x 10)
  277. {
  278. HTTPClient http;
  279. http.begin(srvName_vor_nm);
  280. int httpResponseCode = http.GET();
  281. if (httpResponseCode>0) { recp_vor1nm = http.getString(); }
  282.  
  283. //Serial.println(recp_vor1nm);
  284.  
  285. http.end();
  286. }
  287.  
  288.  
  289. /** ***********************************************************************************
  290. IMAGE.bmp
  291. ***************************************************************************************/
  292.  
  293.  
  294. /**
  295. Rappel et décryptage de la fonction Color_To_565 : (elle se trouve dans le fichier LCDWIKI_KBV.cpp)
  296.  
  297. //Pass 8-bit (each) R,G,B, get back 16-bit packed color
  298.  
  299. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  300. {
  301. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  302. }
  303.  
  304. 0xF8 = 11111000
  305. 0xFC = 11111100
  306.  
  307. (r & 0xF8) -> 5 bit de gauche de r (on ignore donc les 3 bits de poids faible)
  308. (g & 0xFC) -> 6 bit de gauche de g (on ignore donc les 2 bits de poids faible)
  309. (b & 0xF8) -> 5 bit de gauche de b (on ignore donc les 3 bits de poids faible)
  310.  
  311. rrrrr---
  312. gggggg--
  313. bbbbb---
  314.  
  315. après les décalages on obtient les 16 bits suivants:
  316.  
  317. rrrrr---========
  318.   gggggg--===
  319.   ===bbbbb
  320.  
  321. soit après le ou :
  322.  
  323. rrrrrggggggbbbbb
  324.  
  325. calcul de la Fonction inverse :
  326. RGB565_to_888
  327. **/
  328.  
  329. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  330. {
  331. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  332. }
  333.  
  334.  
  335. void RGB565_to_888(uint16_t color565, uint8_t *R, uint8_t *G, uint8_t *B)
  336. {
  337. *R=(color565 & 0xFFFFF800) >> 8;
  338. *G=(color565 & 0x7E0) >> 3;
  339. *B=(color565 & 0x1F) << 3 ;
  340. }
  341.  
  342.  
  343.  
  344.  
  345. uint16_t bmp_width;
  346. uint16_t bmp_heigh;
  347.  
  348.  
  349. uint16_t read_16(File fp)
  350. {
  351. uint8_t low;
  352. uint16_t high;
  353. low = fp.read();
  354. high = fp.read();
  355. return (high<<8)|low;
  356. }
  357.  
  358.  
  359.  
  360. uint32_t read_32(File fp)
  361. {
  362. uint16_t low;
  363. uint32_t high;
  364. low = read_16(fp);
  365. high = read_16(fp);
  366. return (high<<8)|low;
  367. }
  368.  
  369.  
  370.  
  371. void write_16(uint16_t v16, File fp)
  372. {
  373. uint8_t low, high;
  374.  
  375. low = v16 & 0xFF;
  376. high= v16 >>8;
  377.  
  378. fp.write(low);
  379. fp.write(high);
  380. }
  381.  
  382.  
  383. void write_32(uint32_t v32, File fp)
  384. {
  385. uint16_t low, high;
  386.  
  387. low = v32 & 0xFFFF;
  388. high= v32 >>16;
  389.  
  390. write_16(low, fp);
  391. write_16(high, fp);
  392. }
  393.  
  394.  
  395.  
  396.  
  397. bool teste_bmp_header(File fp)
  398. {
  399. if(read_16(fp) != 0x4D42) { return false; } // (2 bytes) The header field used to identify the BMP
  400. read_32(fp); // (4 bytes) get bmp size (nombre total d'octets)
  401. read_32(fp); // (4 bytes) get creator information
  402. bmp_offset = read_32(fp); // (4 bytes) get offset information
  403. read_32(fp);//get DIB information
  404.  
  405. // ici on a lu 16 octets
  406. bmp_width = read_32(fp); //(4 bytes) get width and heigh information
  407. bmp_heigh = read_32(fp); //(4 bytes)
  408.  
  409. // ici on a lu 24 octets
  410. //if(read_16(fp)!= 1) {return false;}
  411. read_16(fp);
  412. //if(read_32(fp)!= 0) {return false;}
  413. return true;
  414. }
  415.  
  416.  
  417.  
  418.  
  419.  
  420. void draw_bmp(uint16_t x0, uint16_t y0, File* fp)
  421. {
  422.  
  423. //sram = freeRam(); Serial.print("03-freeRam="); Serial.println(sram);
  424. uint16_t i,j,k,p,m=0;
  425. uint8_t bmp_data[2*3]={0};
  426. uint16_t bmp_color[2];
  427.  
  428. fp->seek(bmp_offset);
  429. for(i=0; i<bmp_heigh; i