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.
Table des matières :
1 - Un aperçu en vidéo (LFMT)
2 - Vue d'ensemble de cette réalisation :
3 - Le schéma
4 - Le programme pour l'ESP32 à compiler avec l'IDE Arduino
5 - Le principe
6 - L'afficheur utilisé :
7 - Dans la jungle des afficheurs
8 - Utilité du panel ESP32
9 - Evolution
10 - Circuit imprimé
11 - Le boitier imprimé en 3D
12 - Disposition des éléments sur le circuit imprimé
13 - Captures d'écran sur SDcard
14 - Evolution 2
15 - l'ILS est fonctionnel
16 - Affichage de l'orientation de la piste
17 - Fonction Autoland (Atterrissage automatique)
18 - Deuxième instrument : ND (Navigation Display)
19 - Vers un troisième instrument ?
20 - Code source de l'instrument ND (Navigateur Display)
21 - Planche de bord
22 - La voici la planche de bord
23 - Utilisation
24 - Un troisième ESP32 gérant des boutons
25 - Le panel des boutons
26 - Les boutons sont dans la boite
27 - Code source du panel des boutons (SW)
28 - Sérigraphie
29 - Un pas de plus vers une planche de bord
30 - Nouvelle version, nouvelles fonctions...
31 - Ecrans de la version 17.3
32 - Version 18.0
33 - Schéma de la version 18.0 du module ND
34 - Version 19.0
35 - Version 21.0
36 - version 24.0
37 - Version 25.0 - Plan des installations
38 - version 28.0
39 - Un nouveau module ! le MCDU
40 - Détail du MCDU
41 - Code source du MCDU
42 - Le fichier d'entête MCDU.h
43 - Remplacer OpenStreetMap par OpenTopoMap dans l'interface Phi
44 - Documents et code source complet :
45 - Utiliser Geany comme éditeur
46 - Voici un exemple de l'édition du code source avec Geany
47 - Vidéo - Décollage LFMT
48 - Le Lac du Salagou

1 Un aperçu en vidéo (LFMT)

L'ESP32 se connecte au programme de simulation de vol FlightGear par liaison série USB et permet, outre l'affichage des paramètres de vol, de gérer le pilote automatique de Flightgear en temps réel, et de contrôler totalement l'approche et atterrissage la piste choisie.

Cette vidéo est un montage montrant, en incrustation, le fonctionnement de cette réalisation et le comportement de l'avion, ici le Citation X, lors d'un vol complet : Décollage de LFMT, mise en palier, engagement de l'autopilot de FG, puis tour de piste vent arrière, approche et posé ILS totalement automatique.


.

Les vidéos sont un peu "crénelées" afin d'éviter un fichier trop gros, mais bien entendu, sur le PC sous Linux, avec un écran FUll HD, l'image est bien plus belle. Vous avez quand même intérêt à visionner celle-ci en tout écran.

Concernant les paysages : il en est de bien plus beau (dans Flightgear j'entends) que celui-ci, je pense à la Corse (après mise à jour de la scène), ou Annecy, voire Tarbes, c'est à dire près des montagnes (pour ce qui concerne la France)...

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 du PFD en C++
  1. //
  2. // ==================================
  3. String version="35.3";
  4. // ==================================
  5.  
  6. /*
  7. PFD.ino - Primary Flight Display pour Flightgear et ESP32 - version Afficheur TFT 480x320
  8. Ne concerne pas un avion réel ! (ni ULM...)
  9. N'est pas destiné à un quelconque apprentissage du pilotage d'un avion réel (les instruments sont une vue d'artiste)
  10. l'artiste c'est moi !
  11. Les appellations des modules (SD, MCDU) ne correspondent pas non plus exactement à la réalité.
  12. Mais rien ne vous interdit de modifier tout ça le cas échéant, vous avez le code source.
  13. Fonctionne avec le simulateur FlightGear sous Linux et avec l'avion Citation X (mon préféré!)
  14. Les autres avions ont un autopilot différent et donc une "Property tree" différente, il faudrait adapter le programme
  15. - en particulier ne fonctionne pas tel-quel avec les B7xx ni les A3xx, à vous de jouer !
  16.  
  17. par Silicium628
  18.  
  19. */
  20. /**---------------------------------------------------------------------------------------
  21. Logiciel libre et gratuit : Pour les #includes issus de l'univers Arduino (que je ne fournis pas), il faut voir au cas par cas.
  22. (drivers d'affichage en particulier)
  23.  
  24. ---------------------------------------------------------------------------------------
  25. De petites images à placer sur la SDcard centrées sur les aérodromes proviennent de OpenStreetMap
  26.  
  27. OpenStreetMap® est un ensemble de données ouvertes,
  28. disponibles sous la licence libre Open Data Commons Open Database License (ODbL)
  29. accordée par la Fondation OpenStreetMap (OSMF).
  30.  
  31. Voir ce lien pour plus de détails :
  32. https://www.openstreetmap.org/copyright
  33. --------------------------------------------------------------------------------------**/
  34.  
  35. /*=====================================================================================================
  36. CONCERNANT L'AFFICHAGE TFT : connexion :
  37.  
  38. ( Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT_eSPI/ )
  39.  
  40. les lignes qui suivent ne sont q'un commentaire pour vous indiquer la config à utiliser
  41. placée ici, elle ne sont pas fonctionnelles
  42. Il FAUT modifier le fichier User_Setup.h installé par le système Arduino dans ~/Arduino/libraries/TFT_eSPI/
  43.  
  44. // ESP32 pins used for the parallel interface TFT
  45. #define TFT_CS 27 // Chip select control pin
  46. #define TFT_DC 14 // Data Command control pin - must use a pin in the range 0-31
  47. #define TFT_RST 26 // Reset pin
  48.  
  49. #define TFT_WR 12 // Write strobe control pin - must use a pin in the range 0-31
  50. #define TFT_RD 13
  51.  
  52. #define TFT_D0 16 // Must use pins in the range 0-31 for the data bus
  53. #define TFT_D1 4 // so a single register write sets/clears all bits
  54. #define TFT_D2 2 // 23
  55. #define TFT_D3 22
  56. #define TFT_D4 21
  57. #define TFT_D5 15 // 19
  58. #define TFT_D6 25 // 18
  59. #define TFT_D7 17
  60. =====================================================================================================
  61.  
  62. Notes :
  63. - Si les data de FG ne sont pas reçues, il faut vérifier que le PFD est bien connecté sur le port USB0 (et pas USB1 ou autre...)
  64. - Le module PFD doit impérativement être lancé et connecté à l'USB_0 avant de lancer Flightgear et non l'inverse
  65. sinon le dialogue USB avec l'ordinateur ne se fera pas.
  66. */
  67.  
  68. // v14 la fonction Autoland() utilise le glide pour la descente et non plus une altitude calculée théorique
  69. // v15.2 modif du fichier hardware4.xml (nav-distance)
  70. // v15.3 revu autoland - possibilité de poser auto si localizer seul (aérodrome non équipé de glide de glide).
  71. // v15.6 nombreuses modifs dans tous les fichiers, dont fichier FG_data.h
  72. // v16.0 prise en charge du module "SW" (boutons poussoirs) par WiFi
  73. // v16.2 prise en charge des 2 nouveaux boutons (target_speed) + le fichier "hardware4.xml" a été modifié en conséquence
  74. // v20.0 autoland possible (par GPS au lieu de l'ILS) même pour aérodromes non équipé ILS
  75. // v21.0 autoland toujours calculé par GPS, avec prise en compte de l'altitude de l'aérodrome
  76. // v22.0 modifs fonction autolang & fichier 'hardware4.xml' (prise en charge du trim de profondeur, voir 'desengage_autoland()'
  77. // v24.2 tous les calculs de position et de direction se font directement avec les coordonnées GPS,
  78. // sans passer par système LAMBERT
  79. // v26.0 prise en compte de la longueur de la piste pour l'autolanding
  80. // v26.2 asservissement (facultatif) de la gouverne de direction et de la roue avant au décollage et à l'atterrissage
  81. // v30.3 le module MCDU associé à ce PFD et au module ND permettent :
  82. // -un décollage entièrement automatique, avec engagement automatique de l'autopilot et la mise en palier
  83. // -une route automatique vers un point d'entrée en finale situé à 10NM d'une autre piste choisie pendant le vol
  84. // -la gestion de la finale avec pente à 5%, l'arrondi, le touché de roues sur train principal et le guidage en lacet sur la piste
  85. // Un aérodrome m'a posé quelques soucis : LFLB Chambéry. La finale se fait avec un pente nettement plus accentuée,
  86. // au dessus du lac, en évitant d'abimer la montagne ! Le point d'entrée en finale se situe alors
  87. // à 8 NM de la piste depuis une hauteur + importante (voir le fichier FG_data.h)
  88. // v30.9 entré-sortie auto du train d'atterrissage (et modif dans le fichie "hardware4.xml")
  89. // v31.0 gestion des freins (freine automatiquement à l'atterrissage)
  90. // v35.0 Asservissement du roulis (maintient les ailes à plat) lors de phase de décollage et d'atterrissage
  91. // deux modes d'atterrissage : long (=normal) et court (freine à fond + reverses).
  92. // tous les fichiers sont donc affectés (même le fichier hardware4.xml qui comporte de nouvelles entrées)
  93.  
  94.  
  95. /* les numéros de version de tous les modules doivent être identiques (le contenu du .zip est cohérent) */
  96.  
  97.  
  98. /** un peu de théorie : ****************************
  99.  
  100. ALTITUDE vs HAUTEUR
  101.  
  102. Une hauteur est la distance verticale entre un aéronef et la surface qu'il survole (terre ou eau).
  103.  
  104. Pour exprimer une hauteur, il est défini les hauteurs AGL (Above Ground Level) ou ASFC (Above Surface).
  105. Il s'agit de la hauteur entre l'avion et le sol juste en dessous de sa position. Elle suit donc le relief.
  106.  
  107. Pour exprimer une hauteur au dessus de l'aérodrome, il est défini la hauteur AAL (Above Aerodrome Level).
  108. Il s'agit de la hauteur entre l'avion et le point de référence de l'aérodrome comme s'il était en dessous de la
  109. position de l'appareil (même s'il n'y est pas). Cette hauteur ne suit pas le relief.
  110.  
  111. source : https://aeroclub-narbonne.com/download/2017/04/BASE_ALT.pdf
  112. IVAO TM © ELH FLA septembre 2014
  113. je conseille de lire et relire l'ensemble de ce PDF.
  114.  
  115. *****************************************************/
  116.  
  117. #include "PFD.h"
  118. #include <stdint.h>
  119.  
  120. #include <TFT_eSPI.h> // Hardware-specific library
  121.  
  122. #include "Free_Fonts.h"
  123.  
  124. #include "FS.h"
  125. #include "SD.h"
  126. #include "SPI.h"
  127.  
  128. #include "FG_data.h"
  129. #include "Fonctions1.h"
  130.  
  131. #include <WiFi.h> // Ce PFD est un serveur WiFi
  132. #include "ESPAsyncWebServer.h"
  133.  
  134.  
  135. const char* ssid = "PFD_srv";
  136. const char* password = "72r4TsJ28";
  137.  
  138. AsyncWebServer server(80); // Create AsyncWebServer object on port 80
  139.  
  140. String argument_recu1;
  141. String argument_recu2;
  142. String argument_recu3;
  143.  
  144. TFT_eSprite SPR_E = TFT_eSprite(&TFT480); // Declare Sprite object "SPR_11" with pointer to "TFT" object
  145. TFT_eSprite SPR_N = TFT_eSprite(&TFT480);
  146. TFT_eSprite SPR_O = TFT_eSprite(&TFT480);
  147. TFT_eSprite SPR_S = TFT_eSprite(&TFT480);
  148.  
  149. TFT_eSprite SPR_trajectoire = TFT_eSprite(&TFT480);
  150.  
  151. VOYANT voyant_L; // Localizer (azimut)
  152. VOYANT voyant_G; // Glide (hauteur)
  153. VOYANT voyant_APP; // Approche auto (= attéro ILS)
  154. VOYANT voyant_route;
  155. VOYANT voyant_RD; // Auto rudder (asservissement de la gouverne de direction (lacet) et de la roulettes de nez au sol)
  156. VOYANT voyant_ATT; // atterrissage en cours
  157.  
  158. Led Led1;
  159. Led Led2;
  160. Led Led3;
  161. Led Led4;
  162. Led Led5;
  163.  
  164.  
  165. uint16_t hauteur_mini_autopilot = 100; // ou 100 ou 300 ou 500 à tester...
  166.  
  167.  
  168. int16_t Ax_actu, Ay_actu;
  169. int16_t Bx_actu, By_actu;
  170.  
  171. //position et dimensions de l'horizon artificiel
  172. #define HA_x0 210
  173. #define HA_y0 130
  174. #define HA_w 120 // demi largeur
  175. #define HA_h 100 // demi hauteur
  176.  
  177. #define x_autopilot 320
  178.  
  179. // Width and height of sprite
  180. #define SPR_W 14
  181. #define SPR_H 14
  182.  
  183.  
  184. // =====================================================================
  185. //mémorisation dex pixels deux lignes H et de deux lignes V
  186. //ce qui permet d'afficher un rectangle mobile sur l'image sans l'abimer
  187.  
  188. uint16_t data_L1[480]; // pixels d'une ligne Horizontale
  189. uint16_t data_L2[480]; // pixels d'une autre ligne Horizontale
  190. uint16_t data_C1[320]; // pixels d'une ligne Verticale ('C' comme colonne)
  191. uint16_t data_C2[320]; // pixels d'une autre ligne Verticale
  192.  
  193. uint16_t x_1; // position reçu du module positionneur_XY
  194. uint16_t x_2; // position reçu du module positionneur_XY
  195. uint16_t y_1;
  196. uint16_t y_2;
  197.  
  198. uint16_t memo_x1;
  199. uint16_t memo_y1; // position de la ligne
  200. uint16_t memo_x2;
  201. uint16_t memo_y2;
  202.  
  203. // =====================================================================
  204.  
  205.  
  206. uint32_t memo_micros = 0;
  207. uint32_t temps_ecoule;
  208. uint16_t nb_secondes=0;
  209. uint8_t nb_acqui;
  210.  
  211. String parametre; //garde en mémoire les données reçues par USB entre les passages dans la fonction "void acquisitions()"
  212.  
  213. uint8_t landing_light1=0;
  214. uint8_t landing_light2=0;
  215.  
  216. float roulis;
  217. float tangage;
  218.  
  219.  
  220. float altitude_GPS_float;
  221. int32_t altitude_GPS; // accepte les valeurs négatives (par exemple si QNH mal réglé avant décollage)
  222. int32_t hauteur_AAL; // (Above Aerodrome Level)
  223.  
  224. #define ASL 0
  225. #define AAL 1
  226.  
  227. uint8_t mode_affi_hauteur = ASL;
  228.  
  229. int32_t gnd_elv; // feet ; // [hauteur de la surface du terrain]/mer situé sous l'avion
  230. int32_t alti_agl; // feet ; hauteur de l'avion par rapport au terrain (pas la piste, le relief !) situé au dessous de lui
  231. int32_t vitesse; // kts
  232. int32_t memo_vitesse;
  233. int16_t target_speed =180; // consigne de vitesse pour l'autopilot
  234. int16_t dV;
  235. int16_t acceleration;
  236. int16_t vspeed; // vitesse verticale
  237.  
  238. float cap; // en degrés d'angle; direction actuelle du nez de l'avion
  239.  
  240. int16_t hdg1 = 150; // en degrés d'angle; consigne cap = Heading (HDG) Bug // PROBLEME: imprécision de 1° -> trop imprécis !!!
  241. int16_t memo_hdg1;
  242. uint8_t flag_refresh_hdg=0;
  243. uint8_t flag_traiter_SW=0;
  244. uint8_t flag_traiter_MCDU=0;
  245.  
  246.  
  247. float lat_avion; // WGS84
  248. float lon_avion; // WGS84
  249.  
  250. float px_par_km;
  251. float px_par_NM;
  252.  
  253.  
  254. //Les points ptAA et ptBB sont les points d'insertion en finale, situés à 10NM de chaque côté dans l'axe de la piste
  255. //Leurs coordonnées sont calculées en fonction de celles de la piste
  256.  
  257. float lat_ptA;
  258. float lon_ptA;
  259.  
  260. float lat_ptB;
  261. float lon_ptB;
  262.  
  263. float lon_ptAA;
  264. float lat_ptAA;
  265.  
  266. float lon_ptBB;
  267. float lat_ptBB;
  268.  
  269. float lon_pti;
  270. float lat_pti;
  271.  
  272. float GPS_distance_piste; // en NM
  273. float memo_GPS_distance_piste; // servira à savoir si on se rapproche du centre de la piste ou si on s'en éloigne après
  274. // l'avoir dépassé (lors du décollage)
  275.  
  276. float GPS_distance_ptAA; // point situé à 10 ou 15 NM dans l'axe de la piste pour amorcer l'approche
  277. float GPS_distance_ptBB; // point situé à 10 ou 15 NM dans l'axe de la piste, dans l'autre sens
  278. float GPS_distance_pti; // point quelconque
  279.  
  280. float erreur_axe=0;
  281.  
  282.  
  283. #define sens_AB 0
  284. #define sens_BA 1
  285. uint8_t sens_app_effectif; // effectif pour l'attero (ne concerne pas le décollage)
  286.  
  287. float lat_centre_pst;
  288. float lon_centre_pst;
  289. float longueur_piste;
  290.  
  291. float orient_pisteAB;
  292. float orient_pisteBA;
  293.  
  294. float GPS_azimut_piste;
  295. float GPS_azimut_ptA;
  296. float GPS_azimut_ptB;
  297. float GPS_azimut_ptAA;
  298. float GPS_azimut_ptBB;
  299. float GPS_azimut_pti;
  300.  
  301.  
  302.  
  303. char extremite_pst ='X'; // le bout le plus éloigné lors de l'approche, = 'A' ou 'B' sert aussi au décollage (au roulage)
  304. // ce paramètre est calculé en fonction de la position réelle de l'avion lors de la prise de décision
  305.  
  306. uint8_t choix_aleatoire;
  307.  
  308.  
  309. int16_t asel1 = 30; // consigne altitude ('niveau de vol' en centaines de pieds) 30 -> 3000ft (ASL)
  310. float climb_rate=0; // taux de montée (négatif pour descendre - sert pour attérissage automatique)
  311.  
  312. float joystick1; // valeur reçue de Flightgear par la liaison USB (lue dans le properties tree de FG)
  313. float trim_elevator;
  314. float elevator; // valeur à envoyer à FG, qui fixera la position de la gouverne de profondeur (val <0 pour monter)
  315. float throttle;
  316. bool reverser1=0;
  317. bool reverser2=0;
  318. int8_t flaps=0; // 0..4
  319. float speedbrake=0; // 0 = rentré, 1 = sorti
  320. bool gear_down=1;
  321. float brake_left;
  322. float brake_right;
  323.  
  324.  
  325. float ailerons=0;
  326. float rudder=0;
  327. float rudder_manuel; // fonction directe du potentiomètre
  328.  
  329.  
  330. uint8_t view_number=0;
  331.  
  332. String locks_type; // "ALT" ou "VS"
  333. String AP_status; // "" ou "AP" permet d'engager ou de désengager l'autopilot de FlightGear
  334. bool speed_ctrl;
  335.  
  336. int16_t num_bali=0;
  337. int16_t memo_num_bali=0;
  338.  
  339. uint8_t flag_SDcardOk=0;
  340. uint32_t data_ok=0; // ce n'est pas un flag
  341. //uint8_t gs_ok=0;
  342. uint8_t QNH_ok=0;
  343.  
  344. uint8_t flag_1er_passage =1;
  345. uint8_t attente_data=1;
  346. uint8_t inc_num_pt1_autorisee=1;
  347.  
  348. int16_t loc=0; // localizer
  349. int16_t memo_loc=0;
  350.  
  351. float memo_R2;
  352. int16_t memo_y0;
  353.  
  354. uint16_t memo_x_avion=0; // pour fonction "affi_approche()"
  355. uint16_t memo_y_avion=0;
  356.  
  357. const int bouton1 = 36; // attention: le GPIO 36 n'a pas de R de pullup interne, il faut en câbler une (10k) au +3V3
  358. bool bouton1_etat;
  359. bool memo_bouton1_etat;
  360.  
  361. const int bouton2 = 39; // attention: le GPIO 39 n'a pas de R de pullup interne, il faut en câbler une (10k) au +3V3
  362. bool bouton2_etat;
  363. bool memo_bouton2_etat;
  364.  
  365. String switches; // boutons connectés au 3eme ESP32 (SW), reçus par WiFi
  366. uint16_t v_switches=0;
  367. uint16_t memo_v_switches=0;
  368.  
  369. String switches_ND; // boutons connectés au 2eme ESP32 (ND), reçus par WiFi
  370. uint16_t v_switches_ND=0;
  371. uint16_t memo_v_switches_ND=0;
  372.  
  373. String bt_MCDU; // boutons connectés au 4eme ESP32 (MCDU), reçus par WiFi
  374. uint16_t v_bt_MCDU=0;
  375. uint16_t memo_v_bt_MCDU=0;
  376.  
  377. uint8_t options_route=0;
  378. uint8_t num_pti=1;
  379.  
  380. String msg_to_send = "null";
  381.  
  382. String potar1;
  383. int16_t v_potar1=0; // peut être négatif -127..+127
  384. float f_potar1=0;
  385.  
  386. // deux encodeurs rotatifs pas à pas
  387. const int rot1a = 32; // GPIO32 -> câbler une R au +3V3
  388. const int rot1b = 33; // GPIO33 -> câbler une R au +3V3
  389.  
  390. const int rot2a = 35; // GPIO35 -> câbler une R au +3V3
  391. const int rot2b = 34; // GPIO34 -> câbler une R au +3V3
  392.  
  393.  
  394. //const int led1 = 25; // GPIO15
  395.  
  396.  
  397. #define TEMPO 5 // tempo anti-rebond pour l'acquisition des encodeurs rotatifs
  398. volatile uint32_t timer1 = 0;
  399. volatile uint32_t timer2 = 0;
  400.  
  401. uint16_t compteur1;
  402.  
  403. uint8_t heures=0;
  404. uint8_t minutes=0;
  405. uint8_t secondes=0;
  406.  
  407. float v_test1=-1.0;
  408.  
  409.  
  410. void RAZ_variables()
  411. {
  412. roulis=0;
  413. tangage=0;
  414. altitude_GPS=0;
  415. gnd_elv=0;
  416. vitesse=0;
  417. vspeed=0;
  418. cap=0;
  419. memo_hdg1=0;
  420.  
  421. loc=0;
  422. memo_loc=0;
  423. }
  424.  
  425.  
  426.  
  427. /** ***********************************************************************************
  428. IMAGE.bmp
  429. ***************************************************************************************/
  430.  
  431. /**
  432. Rappel et décryptage de la fonction Color_To_565 : (elle se trouve dans le fichier LCDWIKI_KBV.cpp)
  433.  
  434. //Pass 8-bit (each) R,G,B, get back 16-bit packed color
  435.  
  436. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  437. {
  438. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  439. }
  440.  
  441. 0xF8 = 11111000
  442. 0xFC = 11111100
  443.  
  444. (r & 0xF8) -> 5 bit de gauche de r (on ignore donc les 3 bits de poids faible)
  445. (g & 0xFC) -> 6 bit de gauche de g (on ignore donc les 2 bits de poids faible)
  446. (b & 0xF8) -> 5 bit de gauche de b (on ignore donc les 3 bits de poids faible)
  447.  
  448. rrrrr---
  449. gggggg--
  450. bbbbb---
  451.  
  452. après les décallages on obtient les 16 bits suivants:
  453.  
  454. rrrrr---========
  455.   gggggg--===
  456.   ===bbbbb
  457.  
  458. soit après le ou :
  459.  
  460. rrrrrggggggbbbbb
  461.  
  462. calcul de la Fonction inverse :
  463. RGB565_to_888
  464. **/
  465.  
  466.  
  467. void RGB565_to_888(uint16_t color565, uint8_t *R, uint8_t *G, uint8_t *B)
  468. {
  469. *R=(color565 & 0xFFFFF800) >> 8;
  470. *G=(color565 & 0x7E0) >> 3;
  471. *B=(color565 & 0x1F) << 3 ;
  472. }
  473.  
  474.  
  475.  
  476. /** -----------------------------------------------------------------------------------
  477. CAPTURE D'ECRAN vers SDcard
  478. /** ----------------------------------------------------------------------------------- */
  479.  
  480. void write_TFT_on_SDcard() // enregistre le fichier .bmp
  481. {
  482.  
  483. //TFT480.setTextColor(VERT, NOIR);
  484. //TFT480.drawString("CP", 450, 300);
  485.  
  486. if (flag_SDcardOk==0) {return;}
  487.  
  488. String s1;
  489. uint16_t ys=200;
  490. TFT480.setFreeFont(FF1);
  491. TFT480.setTextColor(JAUNE, NOIR);
  492.  
  493. uint16_t x, y;
  494. uint16_t color565;
  495. uint16_t bmp_color;
  496. uint8_t R, G, B;
  497.  
  498. if( ! SD.exists("/bmp/capture2.bmp"))
  499. {
  500. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  501. TFT480.setTextColor(ROUGE, NOIR);
  502. TFT480.drawString("NO /bmp/capture2.bmp !", 100, ys);
  503. delay(300);
  504. TFT480.fillRect(100, ys, 220, 20, NOIR); // efface
  505. return;
  506. }
  507.  
  508.  
  509. File File1 = SD.open("/bmp/capture2.bmp", FILE_WRITE); // ouverture du fichier binaire (vierge) en écriture
  510. if (File1)
  511. {
  512. /*
  513. Les images en couleurs réelles BMP888 utilisent 24 bits par pixel:
  514. Il faut 3 octets pour coder chaque pixel, en respectant l'ordre de l'alternance bleu, vert et rouge.
  515. */
  516. uint16_t bmp_offset = 138;
  517. File1.seek(bmp_offset);
  518.  
  519.  
  520. TFT480.setTextColor(VERT, NOIR);;
  521.  
  522. for (y=320; y>0; y--)
  523. {
  524. for (x=0; x<480; x++)
  525. {
  526. color565=TFT480.readPixel(x, y);
  527.  
  528. RGB565_to_888(color565, &R, &G, &B);
  529.  
  530. File1.write(B); //G
  531. File1.write(G); //R
  532. File1.write(R); //B
  533. }
  534.  
  535. s1=(String) (y/10);
  536. TFT480.fillRect(450, 300, 20, 20, NOIR);
  537. TFT480.drawString(s1, 450, 300);// affi compte à rebour
  538. }
  539.  
  540. File1.close(); // referme le fichier
  541. TFT480.fillRect(450, 300, 20, 20, NOIR); // efface le compte à rebour
  542. }
  543. }
  544.  
  545. /** ----------------------------------------------------------------------------------- */
  546.  
  547.  
  548.  
  549. void Draw_arc_elliptique(uint16_t x0, uint16_t y0, int16_t dx, int16_t dy, float alpha1, float alpha2, uint16_t couleur)
  550. // alpha1 et alpha2 en radians
  551. {
  552. /*
  553. REMARQUES :
  554. -cette fonction permet également de dessiner un arc de cercle (si dx=dy), voire le cercle complet
  555. - dx et dy sont du type int (et pas uint) et peuvent êtres négafifs, ou nuls.
  556. -alpha1 et alpha2 sont les angles (en radians) des caps des extrémités de l'arc
  557. */
  558. uint16_t n;
  559. float i;
  560. float x,y;
  561.  
  562. i=alpha1;
  563. while(i<alpha2)
  564. {
  565. x=x0+dx*cos(i);
  566. y=y0+dy*cos(i+M_PI/2.0);
  567. TFT480.drawPixel(x,y, couleur);
  568. i+=0.01; // radians
  569. }
  570. }
  571.  
  572.  
  573. void affi_rayon1(uint16_t x0, uint16_t y0, uint16_t rayon, double angle, float pourcent, uint16_t couleur_i, bool gras)
  574. {
  575. // trace une portion de rayon de cercle depuis 100%...à pourcent du rayon du cercle
  576. // angle en radians - sens trigo
  577. float x1, x2;
  578. float y1, y2;
  579.  
  580. x1=x0+rayon* cos(angle);
  581. y1=y0-rayon* sin(angle);
  582.  
  583. x2=x0+pourcent*rayon* cos(angle);
  584. y2=y0-pourcent*rayon* sin(angle);
  585.  
  586. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  587. if (gras)
  588. {
  589. TFT480.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  590. TFT480.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  591. }
  592. }
  593.  
  594.  
  595.  
  596. void affi_rayon2(uint16_t x0, uint16_t y0, float r1, float r2, float angle_i, uint16_t couleur_i, bool gras)
  597. {
  598. // trace une portion de rayon de cercle entre les distances r1 et r2 du centre
  599. // angle_i en degrés décimaux - sens trigo
  600.  
  601. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  602. int16_t x1, x2;
  603. int16_t y1, y2;
  604.  
  605. x1=x0+int16_t(r1* cos(angle));
  606. y1=y0-int16_t(r1* sin(angle));
  607.  
  608. x2=x0+int16_t(r2* cos(angle));
  609. y2=y0-int16_t(r2* sin(angle));
  610.  
  611. if ((x1>0) && (x2>0) && (y1>0) && (y2>0) && (x1<480) && (x2<480) && (y1<320) && (y2<320) )
  612. {
  613. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  614.  
  615. if (gras)
  616. {
  617. TFT480.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  618. TFT480.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  619. }
  620. }
  621. //TFT480.fillCircle(x2, y2, 2, ROUGE);
  622. }
  623.  
  624.  
  625.  
  626.  
  627. void affi_tiret_H(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, uint16_t couleur_i)
  628. {
  629. // trace un tiret perpendiculaire à un rayon de cercle de rayon r
  630. // angle_i en degrés décimaux
  631.  
  632. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  633. float x1, x2;
  634. float y1, y2;
  635.  
  636. x1=x0+(r)* cos(angle-1);
  637. y1=y0-(r)* sin(angle-1);
  638.  
  639. x2=x0+(r)* cos(angle+1);
  640. y2=y0-(r)* sin(angle+1);
  641.  
  642. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  643. }
  644.  
  645.  
  646.  
  647. void affi_pointe(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, float taille, uint16_t couleur_i)
  648. {
  649. // trace une pointe de flèche sur un cercle de rayon r
  650. // angle_i en degrés décimaux - sens trigo
  651.  
  652. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  653. int16_t x1, x2, x3;
  654. int16_t y1, y2, y3;
  655.  
  656. x1=x0+r* cos(angle); // pointe
  657. y1=y0-r* sin(angle); // pointe
  658.  
  659. x2=x0+(r-7)* cos(angle-taille); // base A
  660. y2=y0-(r-7)* sin(angle-taille); // base A
  661.  
  662. x3=x0+(r-7)* cos(angle+taille); // base B
  663. y3=y0-(r-7)* sin(angle+taille); // base B
  664.  
  665. TFT480.fillTriangle(x1, y1, x2, y2, x3, y3, couleur_i);
  666. }
  667.  
  668.  
  669. void affi_rectangle_incline(uint16_t x0, uint16_t y0, uint16_t r, double angle_i, uint16_t couleur_i)
  670. {
  671. //rectangle inscrit dans le cerce de rayon r
  672. // angle_i en degrés décimaux - sens trigo
  673.  
  674. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  675. int16_t x1, x2, x3, x4;
  676. int16_t y1, y2, y3, y4;
  677. float d_alpha=0.08; // détermine la largeur du rectangle
  678.  
  679. // point 1
  680. x1=x0+r*cos(angle-d_alpha);
  681. y1=y0+r*sin(angle-d_alpha);
  682. // point 2
  683. x2=x0+r*cos(angle+d_alpha);
  684. y2=y0+r*sin(angle+d_alpha);
  685. // point 3
  686. x3=x0+r*cos(M_PI + angle-d_alpha);
  687. y3=y0+r*sin(M_PI + angle-d_alpha);
  688. // point 4
  689. x4=x0+r*cos(M_PI + angle+d_alpha);
  690. y4=y0+r*sin(M_PI + angle+d_alpha);
  691.  
  692. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  693. TFT480.drawLine(x2, y2, x3, y3, couleur_i);
  694. TFT480.drawLine(x3, y3, x4, y4, couleur_i);
  695. TFT480.drawLine(x4, y4, x1, y1, couleur_i);
  696.  
  697. }
  698.  
  699.  
  700.  
  701.  
  702. float degTOrad(float angle)
  703. {
  704. return (angle * M_PI / 180.0);
  705. }
  706.  
  707.  
  708.  
  709. void init_affi_HA()
  710. {
  711. TFT480.fillRect(HA_x0-HA_w, HA_y0-HA_h-1, 2*HA_w, HA_h+1, HA_CIEL);
  712. TFT480.fillRect(HA_x0-HA_w, HA_y0-HA_h + HA_h, 2*HA_w, HA_h, HA_SOL);
  713.  
  714. }
  715.  
  716.  
  717.  
  718.  
  719. void dessine_avion() // sous forme d'équerres horizontales noires entourées de blanc
  720. {
  721. // aile gauche
  722. TFT480.fillRect(HA_x0-102, HA_y0-3, 60, 10, BLANC); //H contour en blanc
  723. TFT480.fillRect(HA_x0-42, HA_y0-3, 10, 19, BLANC); //V
  724.  
  725. TFT480.fillRect(HA_x0-100, HA_y0-1, 60, 5, NOIR); //H
  726. TFT480.fillRect(HA_x0-40, HA_y0-1, 5, 15, NOIR); //V
  727.  
  728.  
  729. // aile droite
  730. TFT480.fillRect(HA_x0+28, HA_y0-3, 64, 10, BLANC); //H contour en blanc
  731. TFT480.fillRect(HA_x0+28, HA_y0-3, 10, 19, BLANC); //V
  732.  
  733. TFT480.fillRect(HA_x0+30, HA_y0-1, 60, 5, NOIR); //H
  734. TFT480.fillRect(HA_x0+30, HA_y0-1, 5, 15, NOIR); //V
  735.  
  736. //carré blanc au centre
  737.  
  738. TFT480.fillRect(HA_x0-4, HA_y0-3, 8, 2, BLANC);
  739. TFT480.fillRect(HA_x0-4, HA_y0-3, 2, 8, BLANC);
  740.  
  741. TFT480.fillRect(HA_x0-4, HA_y0+3, 10, 2, BLANC);
  742. TFT480.fillRect(HA_x0+4, HA_y0-3, 2, 8, BLANC);
  743.  
  744. //affi_dst_NAV();
  745.  
  746. }
  747.  
  748.  
  749. void affiche_chrono()
  750. {
  751. uint16_t x0=200;
  752. uint16_t y0=0;
  753. TFT480.setFreeFont(FM9);
  754. TFT480.setTextColor(JAUNE);
  755. String s1;
  756. ////if(heures<10){s1+="0";}
  757. ////s1+=String(heures);
  758. ////s1+=":";
  759. if(minutes<10){s1+="0";}
  760. s1+=String(minutes);
  761. s1+=":";
  762. if(secondes<10){s1+="0";}
  763. s1+=String(secondes);
  764. TFT480.fillRect(x0, y0, 55, 15, BLEU); //efface
  765. TFT480.drawString(s1, x0, y0);
  766.  
  767. }
  768.  
  769.  
  770. void inc_chrono()
  771. {
  772. secondes++;
  773. if (secondes>59)
  774. {
  775. secondes=0;
  776. minutes++;
  777. if(minutes>59)
  778. {
  779. minutes=0;
  780. heures++;
  781. if (heures>23)
  782. heures=0;
  783. }
  784. }
  785. }
  786.  
  787.  
  788. void RAZ_chrono()
  789. {
  790. heures=0;
  791. minutes=0;
  792. secondes=0;
  793. }
  794.  
  795.  
  796.  
  797. void lign_sep(uint16_t Ax, uint16_t Ay, uint16_t Bx, uint16_t By)
  798. {
  799. // actualise la ligne de séparation ciel-sol
  800.  
  801. TFT480.drawLine(Ax, Ay-1, Bx, By-1, HA_CIEL);
  802. TFT480.drawLine(Ax, Ay, Bx, By, BLANC);
  803. TFT480.drawLine(Ax, Ay+1, Bx, By+1, HA_SOL);
  804. }
  805.  
  806.  
  807.  
  808. void arrondissement_coins()
  809. {
  810.  
  811. // fillTriangle(int32_t x1,int32_t y1, int32_t x2,int32_t y2, int32_t x3,int32_t y3, uint32_t color);
  812.  
  813. //HG
  814. TFT480.fillTriangle(
  815. HA_x0-HA_w, HA_y0-HA_h-1,
  816. HA_x0-HA_w, HA_y0-HA_h+20,
  817. HA_x0-HA_w+20, HA_y0-HA_h-1,
  818. NOIR);
  819.  
  820.  
  821. //----------------------------------------------
  822. //HD
  823. TFT480.fillTriangle(
  824. HA_x0+HA_w, HA_y0-HA_h-1,
  825. HA_x0+HA_w, HA_y0-HA_h+20,
  826. HA_x0+HA_w-20, HA_y0-HA_h-1,
  827. NOIR);
  828.  
  829.  
  830.  
  831. //----------------------------------------------
  832. //BG
  833. TFT480.fillTriangle(
  834. HA_x0-HA_w, HA_y0+HA_h+1,
  835. HA_x0-HA_w, HA_y0+HA_h-20,
  836. HA_x0-HA_w+20, HA_y0+HA_h+1,
  837. NOIR);
  838.  
  839. //----------------------------------------------
  840. //BD
  841. TFT480.fillTriangle(
  842. HA_x0+HA_w, HA_y0+HA_h+1,
  843. HA_x0+HA_w, HA_y0+HA_h-20,
  844. HA_x0+HA_w-20, HA_y0+HA_h+1,
  845. NOIR);
  846.  
  847. }
  848.  
  849.  
  850.  
  851.  
  852. void affi_HA() // Horizon Artificiel
  853. {
  854. String s1;
  855.  
  856. ////String s1=(String) roulis;
  857. ////TFT480.drawString(s1, 400, 20);
  858.  
  859.  
  860. // pivot
  861. int16_t x0=0;
  862. int16_t y0=0;
  863.  
  864. //points d'intersection avec le bord du carré
  865. int16_t Ax, Ay; // sur le bord gauche
  866. int16_t Bx, By; // sur le bord droit
  867. // Le dessin consistera à tracer des segments colorés entre les points A et B
  868.  
  869.  
  870. // roulis -> [-90..+90]
  871.  
  872. // normalisation de la valeur R2 -> toujours >0
  873. float R2 = -1*roulis;
  874. if (R2<0) {R2+=360;} // ce qui est un angle identique, de valeur positive (sens trigo)
  875.  
  876. // le pivot reste centré horizontalement mais se déplace verticalement en fonction du tangage
  877. y0 += 2*tangage;
  878.  
  879. //calcul & memorisation de ces deux facteurs, ce qui évitera 2 calculs de tangente à chaque passage dan la fonction
  880. float tgt_moins = tan(degTOrad(90-R2));
  881. float tgt_plus = tan(degTOrad(90+R2));
  882.  
  883. //-----------------------------------------------------------------------------
  884. // CALCUL COTE DROIT (point B)
  885.  
  886. // calcul du point B d'intersection
  887. Bx=HA_w;
  888. By=y0 + HA_w*tan(degTOrad(R2));
  889.  
  890. //test si le point d'intersection se trouve plus haut que le haut du carré :
  891.  
  892. if(By>HA_h)
  893. {
  894. By=HA_h;
  895. Bx = x0 + (HA_h-y0)*tgt_moins;
  896. }
  897.  
  898. if(By< -HA_h)
  899. {
  900. By= -HA_h;
  901. Bx = x0 + (HA_h+y0)*tgt_plus;
  902. }
  903. //-----------------------------------------------------------------------------
  904. // CALCUL COTE GAUCHE (point A)
  905.  
  906. Ax=-HA_w;
  907. Ay=y0 - HA_w*tan(degTOrad(R2));
  908.  
  909. if(Ay> HA_h)
  910. {
  911. Ay= HA_h;
  912. Ax = x0 + (HA_h-y0)*tgt_moins;
  913. }
  914.  
  915. if(Ay< -HA_h)
  916. {
  917. Ay= -HA_h;
  918. Ax = x0 + (HA_h+y0)*tgt_plus;
  919. }
  920.  
  921.  
  922. //-----------------------------------------------------------------------------
  923. // positionnement de l'ensemble sur l'écran
  924.  
  925. Ax += HA_x0;
  926. Ay += HA_y0;
  927.  
  928. Bx += HA_x0;
  929. By += HA_y0;
  930.  
  931. // pour éviter un tracé hors cadre au premier passage :
  932. if (flag_1er_passage == 1)
  933. {
  934. Ax_actu = Ax;
  935. Ay_actu = Ay;
  936.  
  937. Bx_actu = Bx;
  938. By_actu = By;
  939. flag_1er_passage=0;
  940. }
  941.  
  942.  
  943. //-----------------------------------------------------------------------------
  944. // ligne "verticale" d'inclinaison (tangage)
  945.  
  946. affi_rayon2(HA_x0, HA_y0, 85, -memo_y0, 90-memo_R2, HA_CIEL, false); // efface partie supérieure
  947. affi_rayon2(HA_x0, HA_y0, 85, -y0, 90-R2, BLANC, false); // retrace ligne partie supérieure
  948.  
  949. affi_rayon2(HA_x0, HA_y0, -85,-memo_y0, 90-memo_R2, HA_SOL, false); // efface partie inférieure
  950. affi_rayon2(HA_x0, HA_y0, -85,-y0, 90-R2, VERT, false); // retrace ligne partie inférieure
  951.  
  952. affi_pointe(HA_x0, HA_y0, 85, 90-memo_R2, 0.1, HA_CIEL); // efface
  953. affi_pointe(HA_x0, HA_y0, 85, 90-R2, 0.1, BLANC); // retrace
  954.  
  955.  
  956. //-----------------------------------------------------------------------------
  957. // graduation fixe
  958. TFT480.setFreeFont(FF1);
  959. TFT480.setTextColor(BLANC, GRIS_AF);
  960. TFT480.drawString("30", HA_x0-70, HA_y0-98);
  961. TFT480.drawString("60", HA_x0-120, HA_y0-55);
  962.  
  963. TFT480.drawString("30", HA_x0+60, HA_y0-98);
  964. TFT480.drawString("60", HA_x0+100, HA_y0-55);
  965.  
  966. //-----------------------------------------------------------------------------
  967. // animation de la ligne de séparation horizontale
  968.  
  969. while ((Bx_actu != Bx) || (By_actu != By) || (Ax_actu != Ax) || (Ay_actu != Ay))
  970. {
  971. // déplacements successifs de 1 pixel de chaque extrémités de la ligne
  972.  
  973. //TFT480.drawLine(Bx, By, x2, y2, BLANC);
  974.  
  975. if (Bx_actu < Bx) { Bx_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  976. if (Bx_actu > Bx) { Bx_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  977.  
  978. if (Ax_actu < Ax) { Ax_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  979. if (Ax_actu > Ax) { Ax_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  980.  
  981. if (By_actu < By) { By_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  982. if (By_actu > By) { By_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  983.  
  984. if (Ay_actu < Ay) { Ay_actu++; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  985. if (Ay_actu > Ay) { Ay_actu--; lign_sep(Ax_actu, Ay_actu, Bx_actu, By_actu); }
  986. }
  987.  
  988.  
  989. // graduation roulis qui se déplace angulairement avec la ligne de tangage
  990. for (int8_t n=0; n<4; n++)
  991. {
  992. 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
  993. Draw_arc_elliptique(HA_x0, HA_y0, 20*n, 20*n, degTOrad(80-R2+n), degTOrad(100-R2-n), BLANC); // trace bas
  994.  
  995. 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
  996. Draw_arc_elliptique(HA_x0, HA_y0, 20*n, 20*n, degTOrad(260-R2+n), degTOrad(280-R2-n), BLANC); // trace haut
  997. }
  998.  
  999. memo_R2 = R2;
  1000. memo_y0 = y0;
  1001.  
  1002. //-----------------------------------------------------------------------------
  1003. arrondissement_coins();
  1004.  
  1005. if (read_bit(flags, bit_autoland) == 0)
  1006. {
  1007. affi_distance_piste();
  1008. }
  1009. }
  1010.  
  1011.  
  1012.  
  1013. void affi_acceleration()
  1014. {
  1015. // POUR TEST **********
  1016. ////String s2= (String) acceleration;
  1017. ////TFT480.fillRect(100, 50, 200, 20, TFT_BLACK);
  1018. ////TFT480.setFreeFont(FF5);
  1019. ////TFT480.setTextColor(BLANC, NOIR);
  1020. ////TFT480.drawString("Acceleration=", 100, 50);
  1021. ////TFT480.drawString(s2, 250, 50);
  1022. // ********************
  1023.  
  1024. //barres verticales colorées juste à droite de la vitesse indiquant sa variation
  1025. uint16_t x0=60;
  1026. uint16_t Y_zero=162;
  1027.  
  1028. int16_t dy=0;
  1029.  
  1030. //"fleche" haute
  1031. TFT480.fillRect(x0, 40, 8, Y_zero, GRIS_TRES_FONCE); // efface haut
  1032. if (acceleration > 1)
  1033. {
  1034. dy= acceleration;
  1035.  
  1036. TFT480.fillRect(x0, Y_zero-dy, 8, dy, VERT); // fleche
  1037. }
  1038.  
  1039.  
  1040. //"fleche" basse
  1041. TFT480.fillRect(x0, Y_zero, 8, 150, GRIS_TRES_FONCE); // efface bas
  1042. if (acceleration < -1)
  1043. {
  1044. dy= -acceleration;
  1045.  
  1046. TFT480.fillRect(x0, Y_zero, 8, dy, JAUNE); // fleche
  1047. }
  1048.  
  1049. TFT480.fillRect(x0, Y_zero, 10, 2, BLANC); // tiret horizontal blanc
  1050.  
  1051. TFT480.fillRect(x0, 310, 8, 20, NOIR);
  1052.  
  1053. }
  1054.  
  1055.  
  1056. void bride(int16_t *valeur)
  1057. {
  1058. int16_t y_min =40;
  1059. int16_t y_max =310;
  1060. if (*valeur<y_min) {*valeur=y_min;}
  1061. if (*valeur>y_max) {*valeur=y_max;}
  1062. }
  1063.  
  1064.  
  1065. void affi_switches() // en haut à droite
  1066. {
  1067. TFT480.setTextFont(1);
  1068. TFT480.setTextColor(GRIS);
  1069.  
  1070. TFT480.fillRect(430, 0, 25, 10, NOIR); // efface le nombre précédemment affiché
  1071. TFT480.drawString(switches, 430, 0);
  1072.  
  1073. TFT480.fillRect(430, 10, 25, 10, NOIR); // efface le nombre précédemment affiché
  1074. TFT480.drawString(switches_ND, 430, 10);
  1075. }
  1076.  
  1077.  
  1078.  
  1079. void affi_elevator()
  1080. {
  1081. bargraph_V_float(elevator, 340, 130, JAUNE);
  1082. }
  1083.  
  1084.  
  1085.  
  1086. void affi_rudder()
  1087. {
  1088. TFT480.setTextFont(1);
  1089. TFT480.fillRect(430, 20, 25, 8, NOIR); // efface le nombre précédemment affiché
  1090. TFT480.setTextColor(ORANGE);
  1091. TFT480.drawString(potar1, 430, 20); // nombre orange en haut à droite
  1092.  
  1093. float v1 = rudder;
  1094. bargraph_H_float(v1, 210, 235, JAUNE); // barre horizontale sous l'horizon artificiel
  1095. }
  1096.  
  1097.  
  1098. void affi_flags() // nombre jaune en haut à droite
  1099. {
  1100. TFT480.fillRect(430, 30, 25, 8, NOIR); // efface le nombre précédemment affiché
  1101. TFT480.setTextFont(1);
  1102. TFT480.setTextColor(JAUNE);
  1103. String s1 = String(flags);
  1104. TFT480.drawString(s1, 430, 30);
  1105.  
  1106. }
  1107.  
  1108.  
  1109. void affi_etats_bt_MCDU() // nombre vert en haut à droite
  1110. {
  1111. TFT480.fillRect(430, 40, 25, 8, NOIR); // efface le nombre précédemment affiché
  1112. TFT480.setTextFont(1);
  1113. TFT480.setTextColor(VERT);
  1114. String s1 = String(v_bt_MCDU);
  1115. TFT480.drawString(s1, 430, 38);
  1116. }
  1117.  
  1118.  
  1119. void affi_extremite() // en haut à droite
  1120. {
  1121. TFT480.fillRect(430, 50, 25, 6, NOIR); // efface le nombre précédemment affiché
  1122. TFT480.setTextFont(1);
  1123. TFT480.setTextColor(BLEU_CLAIR);
  1124. String s1 = String(extremite_pst); // 'A' ou 'B' (la plus éloignée de l'avion)
  1125. TFT480.drawString(s1, 430, 45);
  1126. }
  1127.  
  1128.  
  1129. void affi_sens_APP() // en haut à droite
  1130. {
  1131. TFT480.fillRect(445, 50, 35, 6, NOIR); // efface le nombre précédemment affiché
  1132. TFT480.setTextFont(1);
  1133. TFT480.setTextColor(VERT);
  1134. String s1;
  1135. if (sens_app_effectif == sens_AB) {s1 = "A->B";} else {s1 = "B->A";}
  1136. TFT480.drawString(s1, 445, 48);
  1137. }
  1138.  
  1139.  
  1140. void affi_vitesse()
  1141. {
  1142. uint16_t x1;
  1143. String s1;
  1144.  
  1145. int16_t y_min =40;
  1146. int16_t y_max =300;
  1147.  
  1148. TFT480.setTextColor(BLANC); // Background is not defined so it is transparent
  1149.  
  1150. //---------------------------------------------------------------------------------------
  1151. //bande verticale multicolore
  1152.  
  1153. #define vitesse_sol 40
  1154. int16_t vitesse_mini1 =90;
  1155. int16_t vitesse_mini2 =130;
  1156. int16_t vitesse_maxi1 =200;
  1157. int16_t vitesse_maxi2 =280;
  1158.  
  1159.  
  1160. //calcul de la position des limites entre les différentes couleurs verticales
  1161.  
  1162. int16_t d1, d2, d3, d4, d5;
  1163.  
  1164. d1=(int16_t)(100 + 3.2*((vitesse - vitesse_sol)));
  1165. d2=(int16_t)(100 + 3.2*((vitesse - vitesse_mini1)));
  1166. d3=(int16_t)(100 + 3.2*((vitesse - vitesse_mini2)));
  1167. d4=(int16_t)(100 + 3.2*((vitesse - vitesse_maxi1)));
  1168. d5=(int16_t)(100 + 3.2*((vitesse - vitesse_maxi2)));
  1169.  
  1170. bride(&d1);
  1171. bride(&d2);
  1172. bride(&d3);
  1173. bride(&d4);
  1174. bride(&d5);
  1175.  
  1176. int16_t h1, h2, h3, h4, h5;
  1177.  
  1178. h1 = y_max-(int16_t)d1;
  1179. h2 = d1-d2;
  1180. h3 = d2-d3;
  1181. h4 = d3-d4;
  1182. h5 = d4-d5;
  1183.  
  1184. TFT480.fillRect(50, 40, 6, (int16_t)d5, ORANGE);
  1185. TFT480.fillRect(50, d5, 6, h5, JAUNE);
  1186. TFT480.fillRect(50, d4, 6, h4, VERT);
  1187. TFT480.fillRect(50, d3, 6, h3, ORANGE);
  1188. TFT480.fillRect(50, d2, 6, h2, ROUGE);
  1189. TFT480.fillRect(50, d1, 6, 300-(int16_t)d1, GRIS);
  1190.  
  1191. TFT480.fillRect(50, 300, 6, 20, NOIR);
  1192.  
  1193. //---------------------------------------------------------------------------------------
  1194. //échelle verticale graduée glissante
  1195.  
  1196. uint16_t y0;
  1197.  
  1198. int16_t vit1;
  1199. float d6;
  1200.  
  1201. TFT480.setFreeFont(FF1);
  1202.  
  1203. y0=3.2*(vitesse%10);
  1204.  
  1205. TFT480.fillRect(0, y_min, 50, y_max-30, GRIS_AF); // bande verticale à gauche
  1206. for(int n=0; n<10; n++)
  1207. {
  1208. d6 =2+y0+32.0*n; // 24 pixels verticalement entre chaque trait -> 10*24 = 240px (hauteur de l'affiur)
  1209. {
  1210. if ( (d6>y_min) && (d6<y_max-10) && (vit1>=0) && (vit1<1000) )
  1211. {
  1212. TFT480.fillRect(45, (int16_t)d6, 10, 2, BLANC); // petits tirets horizontaux
  1213. }
  1214.  
  1215. vit1 = vitesse -10*(n-5);
  1216. vit1 /= 10;
  1217. vit1 *= 10;
  1218. s1=(String) vit1;
  1219.  
  1220. if ( (d6>y_min) && (d6<y_max-10) && (vit1>=0) && (vit1<1000) )
  1221. {
  1222. TFT480.setTextColor(BLANC, GRIS_AF);
  1223. //TFT480.drawString(" ", 9, d6);
  1224. x1=0;
  1225. if(vit1<100){x1=7;} // pour affichage centré
  1226. if(vit1<10){x1=14;}
  1227. if (vit1>=10)
  1228. {
  1229. TFT480.drawString(s1, x1, (uint16_t)d6-5); // Graduation (tous les 20kts)
  1230. }
  1231. }
  1232. }
  1233. }
  1234. TFT480.fillRect(0, 38, 68, 2, NOIR); // efface ; BLEU pour test
  1235.  
  1236. //---------------------------------------------------------------------------------------
  1237. // affichage de la valeur principale
  1238.  
  1239. uint16_t VP_y0 = 150;
  1240.  
  1241. TFT480.setTextColor(BLANC, NOIR);
  1242. TFT480.setFreeFont(FF18);
  1243. s1=(String)vitesse;
  1244.  
  1245. TFT480.fillRect(3, VP_y0, 42, 26, NOIR); //efface le nombre précédemment affiché (pour le cas où on passe de 3 à 2 chiffres)
  1246.  
  1247. if ((vitesse>=0) && (vitesse <1000))
  1248. {
  1249. x1=3;
  1250. if(vitesse<100){x1=10;} // pour affichage centré
  1251. if(vitesse<10){x1=20;}
  1252. TFT480.drawString(s1, x1, VP_y0+3); // affi le nombre
  1253. } // affi en gros à mi-hauteur de l'écran
  1254. else
  1255. { TFT480.fillRect(3, VP_y0, 42, 26, GRIS); }
  1256.  
  1257. TFT480.drawRoundRect(1, VP_y0-1, 45, 28, 5, BLANC); // encadrement de la valeur centrale affichée
  1258.  
  1259. TFT480.fillTriangle(45, VP_y0+7, 45, VP_y0+17, 55, VP_y0+12, NOIR); // petit triangle (curseur) noir
  1260. }
  1261.  
  1262.  
  1263.  
  1264. void affi_asel(int32_t asel_i)
  1265. {
  1266. // consigne ALTITUDE de l'autopilot (en rose en haut à droite)
  1267. uint16_t x1 =360;
  1268. TFT480.setFreeFont(FF5);
  1269.  
  1270. if(asel_i >=0)
  1271. {
  1272. // ( chiffres en roses en haut à droite)
  1273. String s2 =(String)(asel_i);
  1274. TFT480.setTextColor(ROSE, NOIR);
  1275.  
  1276. TFT480.fillRect(x1, 0, 77, 20, NOIR); // efface
  1277. if(asel_i<10000){x1+=7;}
  1278. if(asel_i<1000){x1+=7;} // pour affichage centré
  1279. if(asel_i<100){x1+=7;}
  1280. if(asel_i<10){x1+=7;}
  1281.  
  1282. TFT480.drawString(s2, x1, 5);
  1283. }
  1284. }
  1285.  
  1286.  
  1287. void affi_alti_agl()
  1288. {
  1289. // consigne ALTITUDE de l'autopilot (couleur du texte = HA_SOL, en bas à droite)
  1290. uint16_t x1 =360;
  1291. TFT480.setFreeFont(FF5);
  1292.  
  1293. if(alti_agl >=0)
  1294. {
  1295. // ( chiffres en roses en haut à droite)
  1296. String s2 =(String)(alti_agl);
  1297. TFT480.setTextColor(HA_SOL, NOIR);
  1298.  
  1299. TFT480.fillRect(x1, 300, 77, 20, NOIR); // efface
  1300. if(alti_agl<10000){x1+=7;}
  1301. if(alti_agl<1000){x1+=7;} // pour affichage centré
  1302. if(alti_agl<100){x1+=7;}
  1303. if(alti_agl<10){x1+=7;}
  1304.  
  1305. TFT480.drawString(s2, x1, 303);
  1306.  
  1307. TFT480.setTextFont(1);
  1308. TFT480.setTextColor(BLANC, NOIR);
  1309. TFT480.drawString("hauteur/gnd:", 290, 307);
  1310. TFT480.drawRoundRect(287, 302, 136, 17, 3, GRIS_FONCE); // encadrement de la valeur affichée
  1311. }
  1312. }
  1313.  
  1314.  
  1315. void affi_target_speed()
  1316. {
  1317. // consigne de vitesse de l'autopilot
  1318. // ( chiffres en rose en haut à gauche )
  1319.  
  1320. String s2 =(String)(target_speed);
  1321. TFT480.setTextColor(ROSE, NOIR);
  1322. TFT480.setFreeFont(FF5);
  1323. uint8_t x1=7;
  1324. TFT480.fillRect(x1, 20, 60, 15, NOIR); // efface
  1325. TFT480.drawString(s2, x1, 20);
  1326. }
  1327.  
  1328.  
  1329.  
  1330.  
  1331. void affi_vt_verticale()
  1332. {
  1333. // affichage analogique sur la droite de l'écran
  1334.  
  1335. uint16_t x0=435;
  1336. uint16_t y0=165;
  1337.  
  1338. float y1;
  1339.  
  1340. uint16_t x1;
  1341. String s1;
  1342.  
  1343. TFT480.fillRect(x0, y0-90, 45, 180, GRIS_AF); // barre grise
  1344. TFT480.fillRect(x0, y0, 25, 2, BLEU_CLAIR); // centre
  1345.  
  1346. // ------------------------
  1347. // graduations sur un arc vertical
  1348. TFT480.setFreeFont(FF1);
  1349.  
  1350. TFT480.setTextColor(BLANC, NOIR);
  1351. TFT480.drawString("ft/mn", x0-8, y0+125);
  1352.  
  1353. TFT480.setTextColor(BLANC, GRIS_AF);
  1354.  
  1355. float angle;
  1356. for(uint8_t n=0; n<7; n++ )
  1357. {
  1358. angle =135+ n*15; // 1 tiret tous les 15 degrés
  1359. affi_rayon1(HA_x0+340, y0, 110, degTOrad(angle), 0.9, BLANC, false); // tirets de graduation
  1360. }
  1361.  
  1362. TFT480.drawString("3", x0+9, y0-90);
  1363. TFT480.drawString("2", x0-3, y0-65);
  1364. TFT480.drawString("1", x0-8, y0-35);
  1365. TFT480.drawString("0", x0-3, y0-5 + 0);
  1366. TFT480.drawString("1", x0-8, y0+25);
  1367. TFT480.drawString("2", x0-3, y0+50);
  1368. TFT480.drawString("3", x0+9, y0+75);
  1369.  
  1370. // ------------------------
  1371. // aiguille à droite de l'écran
  1372. float angle2;
  1373.  
  1374. TFT480.setFreeFont(FF1);
  1375. s1=(String) (vspeed*60);
  1376.  
  1377. angle2 = 180.0 - vspeed *0.92;
  1378.  
  1379. TFT480.fillRect(x0-10, y0-110, 55, 15, GRIS_TRES_FONCE); // efface haut
  1380. TFT480.fillRect(x0-10, y0+105, 55, 15, GRIS_TRES_FONCE); // efface bas
  1381.  
  1382. if ((vspeed > -50) && (vspeed < 50))
  1383. {
  1384. affi_rayon1(HA_x0+330, y0, 100, degTOrad(angle2), 0.7, JAUNE, true);
  1385.  
  1386. TFT480.setTextColor(JAUNE, NOIR);
  1387. }
  1388. else if (vspeed > 50)
  1389. {
  1390. affi_rayon1(HA_x0+330, y0, 110, degTOrad(132), 0.7, JAUNE, true);
  1391. TFT480.setTextColor(JAUNE, NOIR);
  1392.  
  1393. TFT480.drawString(s1, x0-10, y0-110);
  1394. }
  1395. else if (vspeed < -50)
  1396. {
  1397. affi_rayon1(HA_x0+330, y0, 110, degTOrad(228), 0.7, JAUNE, true);
  1398. TFT480.setTextColor(JAUNE, NOIR);
  1399.  
  1400. TFT480.drawString(s1, x0-10, y0+105);
  1401. }
  1402.  
  1403.  
  1404. // affichage digital de la valeur
  1405.  
  1406.  
  1407. /*
  1408. // = vitesse ascensionnelle, sous forme de barres verticales vertes, à droite, près de l'echelle d'altitude
  1409. uint16_t x0=405;
  1410. uint16_t y0=40;
  1411.  
  1412. int16_t dy=0;
  1413.  
  1414. //fleche haute
  1415. TFT480.fillRect(x0, 0, 10, 140, GRIS_FONCE); // efface haut
  1416. if (vspeed > 1)
  1417. {
  1418. dy= vspeed;
  1419.  
  1420. TFT480.fillRect(x0, y0+100-dy, 10, dy, VERT); // fleche
  1421. }
  1422.  
  1423.  
  1424. //fleche basse
  1425. TFT480.fillRect(x0, y0+150, 10, 135, GRIS_FONCE); // efface bas
  1426. if (vspeed < -1)
  1427. {
  1428. dy= -vspeed;
  1429.  
  1430. TFT480.fillRect(x0, y0+150, 10, dy, VERT); // fleche
  1431. }
  1432. */
  1433.  
  1434. }
  1435.  
  1436.  
  1437.  
  1438.  
  1439. void affi_cap()
  1440. {
  1441. // cercle tournant de CAP gradué en bas au centre de l'écran
  1442. // Les lettres 'N' 'S' 'E' 'O' pour Nord Sud Est Ouset sont initialisées sous forme de sprites dans la fonction setup()
  1443.  
  1444. uint16_t x02 = 200;
  1445. uint16_t y02 = 350;
  1446. float angle; // en radians
  1447. //float cap_RD; // en radians (le cap fourni par FG étant en degrés d'angle)
  1448. uint16_t x_spr;
  1449. uint16_t y_spr;
  1450.  
  1451. uint16_t x_hdg;
  1452. uint16_t y_hdg;
  1453.  
  1454. uint8_t R =70;
  1455. uint8_t R2 =R-6;
  1456. /**
  1457. 360° =2 pi rad
  1458. 1° = 2 pi/360 rad = pi/180 rad
  1459. **/
  1460.  
  1461. TFT480.fillCircle(x02,y02, R, GRIS_AF);
  1462.  
  1463. for(uint8_t n=0; n<24; n++ )
  1464. {
  1465. angle = (int16_t)cap+15 + n*15; // 1 tiret tous les 15 degrés
  1466. affi_rayon1(x02, y02, (R-5), degTOrad(angle), 0.9, BLANC, false); // tirets de graduation
  1467. }
  1468. x_hdg = x02 + R2*cos(degTOrad(hdg1-90-cap));
  1469. y_hdg = y02 + R2*sin(degTOrad(hdg1-90-cap));
  1470.  
  1471. TFT480.drawLine(x02, y02, x_hdg, y_hdg, VERT);
  1472. TFT480.drawCircle(x_hdg, y_hdg, 5, VERT); // rond vert sur le cercle = consigne de cap de l'autopilot
  1473.  
  1474. x_spr = x02+R2 * cos(degTOrad(angle));
  1475. y_spr = y02-R2 * sin(degTOrad(angle));
  1476. TFT480.setPivot(x_spr, y_spr);
  1477. SPR_E.pushRotated(-cap+90, TFT_BLACK); // Plot rotated Sprite, black = transparent
  1478.  
  1479. x_spr = x02+R2* cos(degTOrad(angle+90));
  1480. y_spr = y02-R2 * sin(degTOrad(angle+90));
  1481. TFT480.setPivot(x_spr, y_spr);
  1482. SPR_N.pushRotated(-cap, TFT_BLACK);
  1483.  
  1484. x_spr = x02+R2 * cos(degTOrad(angle+180));
  1485. y_spr = y02-R2 * sin(degTOrad(angle+180));
  1486. TFT480.setPivot(x_spr, y_spr);
  1487. SPR_O.pushRotated(-cap-90, TFT_BLACK);
  1488.  
  1489. x_spr = x02+R2 * cos(degTOrad(angle-90));
  1490. y_spr = y02-R2 * sin(degTOrad(angle-90));
  1491. TFT480.setPivot(x_spr, y_spr);
  1492. SPR_S.pushRotated(-cap, TFT_BLACK);
  1493.  
  1494.  
  1495. // petite "maison" dans le cercle (valeur du cap)
  1496.  
  1497. #define a 170 // x général
  1498. #define b a+30
  1499. #define c b+30
  1500. #define d 288 // y général
  1501. #define e d+10
  1502. #define f e+20
  1503.  
  1504. TFT480.drawLine(a, f, c, f, BLANC); // sol
  1505. TFT480.drawLine(a, f, a, e, BLANC); // mur de gauche
  1506. TFT480.drawLine(c, f, c, e, BLANC); // mur de droite
  1507. TFT480.drawLine(a, e, b, d, BLANC); // toit pente gauche
  1508. TFT480.drawLine(c, e, b, d, BLANC); // toit pente droite
  1509.  
  1510. // affi la valeur
  1511. String s1;
  1512. uint16_t x0 = a+1;
  1513. uint16_t y0 = e;
  1514.  
  1515. uint16_t x1= x0;
  1516. if(cap<100){x1+=5;} // pour affichage centré
  1517. if(cap<10){x1+=5;}
  1518.  
  1519. s1=String (cap, 1);
  1520.  
  1521. TFT480.fillRect(x0, y0, 57, 20, NOIR); // efface le nombre précédemment affiché
  1522. TFT480.setTextColor(BLANC, NOIR);
  1523. TFT480.setFreeFont(FM9);
  1524. TFT480.drawString(s1, x1, y0);
  1525. }
  1526.  
  1527.  
  1528.  
  1529. void affi_hauteur_RWY()
  1530. {
  1531. /**
  1532. Pour exprimer une hauteur au dessus de l'aérodrome, on défini la hauteur AAL (Above Aerodrome Level). Il
  1533. s'agit de la hauteur entre l'avion et le point de référence de l'aérodrome comme s'il était en dessous de la
  1534. position de l'appareil (même s'il n'y est pas). Cette hauteur ne suit pas le relief.
  1535. On la calculera ici en retranchant [l'altitude de l'aéroport sélectionné] à [l'altitude GPS].
  1536.  
  1537. En conséquense, il faut impérativement penser à sélectionner dans le module SD le bon aérodrome, celui d'où l'on décolle,
  1538. puis en cas de voyage, celui où l'on va se poser (ce qui renseignera son altitude) sinon l'affichage sera faux.
  1539.  
  1540. par exemple si l'on choisit "Montpellier" en étant à Clermont-Ferrand, l'erreur sera de 1089 ft
  1541.  
  1542. Les altitudes des aérodromes sont enregistées dans le fichier FG_data.h
  1543. */
  1544.  
  1545. String s1;
  1546. uint16_t x0 =365;
  1547. //---------------------------------------------------------------------------------------
  1548. //échelle verticale graduée glissante
  1549.  
  1550. uint16_t x1;
  1551. uint16_t y0;
  1552. uint16_t hauteur;
  1553. int16_t alt1;
  1554. float d5;
  1555.  
  1556. if (mode_affi_hauteur == AAL) {hauteur = hauteur_AAL;}
  1557. if (mode_affi_hauteur == ASL) {hauteur = altitude_GPS;}
  1558.  
  1559. TFT480.setFreeFont(FF1);
  1560.  
  1561.  
  1562. y0=3.2*(hauteur_AAL%10);
  1563.  
  1564. TFT480.fillRect(x0, 20, 60, 280, GRIS_AF); //efface bande verticale à droite
  1565.  
  1566.  
  1567. for(int n=0; n<9; n++)
  1568. {
  1569. d5 =0+y0+32.0*n; // pixels verticalement entre chaque trait -> 10*24 = 240px (hauteur de l'affi)
  1570. {
  1571. if (d5>=20) // marge en haut
  1572. {
  1573. TFT480.fillRect(x0, (int16_t)d5+5, 5, 2, BLANC); // petits tirets horizontaux
  1574.  
  1575. alt1 = hauteur -10*(n-5);
  1576. alt1 /= 10;
  1577. alt1 *= 10;
  1578. s1=(String) alt1;
  1579.  
  1580. if(alt1>=0)
  1581. {
  1582. TFT480.setTextColor(BLANC, GRIS_AF);
  1583. //TFT480.drawString(" ", 9, d5);
  1584. x1=x0;
  1585. if(alt1<10000){x1+=7;} // pour affichage centré
  1586. if(alt1<1000){x1+=7;}
  1587. if(alt1<100){x1+=7;}
  1588. if(alt1<10){x1+=7;}
  1589. TFT480.drawString(s1, x1, (uint16_t)d5); // Graduation (tous les 20kts)
  1590. }
  1591. }
  1592. }
  1593. }
  1594.  
  1595. //---------------------------------------------------------------------------------------
  1596. // affichage de la valeur principale
  1597.  
  1598. uint16_t x2;
  1599. uint16_t y0b = 155;
  1600. TFT480.fillRect(x0-20, y0b, 80, 25, NOIR); // efface le nombre précédemment affiché
  1601. TFT480.setTextColor(BLANC, NOIR);
  1602. TFT480.setFreeFont(FF18);
  1603.  
  1604. if ((1) && (hauteur < 60000))
  1605. {
  1606. s1=(String) hauteur;
  1607. }
  1608. else {s1="----";}
  1609. x2=x0-20;
  1610. if(hauteur<10000){x2+=10;} // pour affichage centré
  1611. if(hauteur<1000){x2+=10;}
  1612. if(hauteur<100){x2+=10;}
  1613. if(hauteur<10){x2+=10;}
  1614.  
  1615. if(hauteur<0)
  1616. {
  1617. TFT480.setTextColor(ROUGE);
  1618. x2=x0-20; // si valeur négative affichée avec signe "-"
  1619. }
  1620.  
  1621. TFT480.drawString(s1, x2, y0b);
  1622. uint16_t couleur1=GRIS;
  1623. if (mode_affi_hauteur == ASL) {couleur1=BLEU;}
  1624. if (mode_affi_hauteur == AAL) {couleur1=VERT;}
  1625.  
  1626. TFT480.drawRoundRect(x0-20, y0b-3, 75, 28, 5, couleur1); // encadrement de la valeur centrale affichée
  1627. }
  1628.  
  1629.  
  1630.  
  1631.  
  1632. void affi_distance_piste()
  1633. {
  1634. String s1;
  1635. uint16_t x0=190;
  1636. uint16_t y0=255;
  1637. float nav_nm;
  1638. // rappel: 1 mile marin (NM nautical mile) = 1852m
  1639. //ils_nm = (float)ils_dst / 1852.0;
  1640. //if (ils_nm >99) {ils_nm=0;}
  1641.  
  1642. TFT480.drawRect(x0-47, y0-15, 190, 35, GRIS_FONCE); //encadrement
  1643.  
  1644. TFT480.setTextFont(1);
  1645. TFT480.setTextColor(BLANC, NOIR);
  1646. TFT480.drawString("distance", x0, y0-12);
  1647.  
  1648. TFT480.setFreeFont(FM9);
  1649. TFT480.setTextColor(JAUNE, NOIR);
  1650. TFT480.drawString("RWY", x0-45, y0-12);
  1651.  
  1652.  
  1653. TFT480.setTextColor(BLANC, NOIR);
  1654. int nb_decimales;
  1655. if(GPS_distance_piste>99) {nb_decimales =0;} else {nb_decimales =1;}
  1656. s1 = String(GPS_distance_piste, nb_decimales);
  1657. if (data_ok == 0) {s1=" --";}
  1658. TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
  1659. TFT480.setFreeFont(FM9);
  1660. TFT480.drawString(s1, x0, y0);
  1661. TFT480.drawRoundRect(x0, y0-2, 50, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1662.  
  1663. //affi_float_test(GPS_distance_piste_new, 100, 3, VERT, NOIR);
  1664.  
  1665. TFT480.setTextColor(JAUNE, NOIR);
  1666. TFT480.drawString("NM", x0+55, y0);
  1667.  
  1668. //affi_direction_piste // direction de la piste vue de l'avion
  1669. TFT480.setTextFont(1);
  1670. TFT480.setTextColor(BLANC, NOIR);
  1671. TFT480.drawString("direction", x0+80, y0-12);
  1672.  
  1673. TFT480.setTextColor(BLANC, NOIR);
  1674. s1 = String(GPS_azimut_piste, 0); // 0 -> 0 décimales
  1675. if (data_ok == 0) {s1=" --";}
  1676. TFT480.fillRect(x0+90, y0, 52, 18, NOIR); // efface
  1677. TFT480.setFreeFont(FM9);
  1678. TFT480.drawString(s1, x0+90, y0);
  1679. TFT480.drawRoundRect(x0+90, y0-2, 40, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1680.  
  1681. TFT480.drawCircle(x0+135, y0, 2, JAUNE); // caractère 'degré'
  1682. }
  1683.  
  1684.  
  1685.  
  1686. void affi_distance_ptAA()
  1687. {
  1688. String s1;
  1689. uint16_t x0=260;
  1690. uint16_t y0=280;
  1691.  
  1692. TFT480.setTextFont(1);
  1693. TFT480.setTextColor(BLANC, NOIR);
  1694. TFT480.drawString("to ptA", x0, y0);
  1695.  
  1696. int nb_decimales =1;
  1697. if (GPS_distance_ptAA>=100) {nb_decimales=0;}
  1698. s1 = String(GPS_distance_ptAA, nb_decimales);
  1699.  
  1700. TFT480.fillRect(x0, y0+10, 45, 12, NOIR); // efface
  1701. TFT480.setFreeFont(FM9);
  1702. TFT480.setTextColor(VERT, NOIR);
  1703. TFT480.drawString(s1, x0, y0+10);
  1704.  
  1705. TFT480.drawRoundRect(x0-2, y0+8, 52, 18, 5, GRIS_FONCE);
  1706.  
  1707. TFT480.setTextColor(JAUNE, NOIR);
  1708. TFT480.drawString("NM", x0+55, y0+10);
  1709. }
  1710.  
  1711.  
  1712. void affi_distance_ptBB()
  1713. {
  1714. String s1;
  1715. uint16_t x0=260;
  1716. uint16_t y0=280;
  1717.  
  1718. TFT480.setTextFont(1);
  1719. TFT480.setTextColor(BLANC, NOIR);
  1720. TFT480.drawString("to ptB", x0, y0);
  1721.  
  1722. int nb_decimales =1;
  1723. if (GPS_distance_ptBB>=100) {nb_decimales=0;}
  1724. s1 = String(GPS_distance_ptBB, nb_decimales);
  1725.  
  1726. TFT480.fillRect(x0, y0+10, 45, 12, NOIR); // efface
  1727. TFT480.setFreeFont(FM9);
  1728. TFT480.setTextColor(VERT, NOIR);
  1729. TFT480.drawString(s1, x0, y0+10);
  1730.  
  1731. TFT480.drawRoundRect(x0-2, y0+8, 52, 18, 5, GRIS_FONCE);
  1732.  
  1733. TFT480.setTextColor(JAUNE, NOIR);
  1734. TFT480.drawString("NM", x0+55, y0+10);
  1735. }
  1736.  
  1737.  
  1738. void affi_distance_pti()
  1739. {
  1740. String s1;
  1741. uint16_t x0=260;
  1742. uint16_t y0=280;
  1743.  
  1744. TFT480.setTextFont(1);
  1745. TFT480.setTextColor(BLANC, NOIR);
  1746.  
  1747. s1 = "to Pt ";
  1748. s1 += String(num_pti);
  1749.  
  1750. TFT480.drawString(s1, x0, y0);
  1751.  
  1752. int nb_decimales =1;
  1753. if (GPS_distance_pti<100) {nb_decimales=0;}
  1754. s1 = String(GPS_distance_pti, nb_decimales);
  1755.  
  1756. TFT480.fillRect(x0, y0+10, 45, 12, NOIR); // efface
  1757. TFT480.setFreeFont(FM9);
  1758. TFT480.setTextColor(VERT, NOIR);
  1759. TFT480.drawString(s1, x0, y0+10);
  1760.  
  1761. TFT480.drawRoundRect(x0-2, y0+8, 52, 18, 5, GRIS_FONCE);
  1762.  
  1763. TFT480.setTextColor(JAUNE, NOIR);
  1764. TFT480.drawString("NM", x0+55, y0+10);
  1765. }
  1766.  
  1767.  
  1768. void affi_f_potar1()
  1769. {
  1770. uint16_t x0=145;
  1771. uint16_t y0=160;
  1772. TFT480.fillRect(x0, y0, 130, 30, GRIS_TRES_FONCE);
  1773. TFT480.drawRect(x0, y0, 130, 30, ROUGE);
  1774. TFT480.setFreeFont(FMB9);
  1775. TFT480.setTextColor(BLANC);
  1776. String s1="potar="+String(f_potar1);
  1777. TFT480.drawString(s1, x0+5, y0+6);
  1778.  
  1779. }
  1780.  
  1781.  
  1782. void affi_hauteur_SOL(int16_t H) // de l'avion / sol en dessous de lui ; dans la partie basse du PFD (dans le 'marron')
  1783. {
  1784. uint16_t x0=145;
  1785. uint16_t y0=188;
  1786. TFT480.fillRect(x0, y0, 130, 30, GRIS_TRES_FONCE);
  1787. TFT480.drawRect(x0, y0, 130, 30, ROUGE);
  1788. TFT480.setFreeFont(FMB9);
  1789. TFT480.setTextColor(BLANC);
  1790. String s1=String(H) + " ft/gnd";
  1791. TFT480.drawString(s1, x0+5, y0+6);
  1792.  
  1793. }
  1794.  
  1795. void efface_hauteur_SOL()
  1796. {
  1797. uint16_t x0=145;
  1798. uint16_t y0=188;
  1799. TFT480.fillRect(x0, y0, 130, 30, HA_SOL);
  1800. }
  1801.  
  1802.  
  1803. void efface_cadre_bas(uint16_t couleur)
  1804. {
  1805. TFT480.fillRect(70, 232, 292, 84, NOIR);
  1806. TFT480.drawRect(70, 232, 292, 84, GRIS_FONCE);
  1807. efface_sprite_trajectoire();
  1808. }
  1809.  
  1810.  
  1811.  
  1812. void affi_Airport()
  1813. {
  1814. uint16_t n;
  1815. float v1;
  1816. String s1;
  1817.  
  1818.  
  1819. TFT480.fillRect(255, 280, 108, 20, NOIR); // efface - BLEU pour test
  1820. TFT480.setTextFont(1);
  1821.  
  1822. TFT480.setTextColor(BLEU_CLAIR, NOIR);
  1823. s1= liste_bali[num_bali].ID_OACI;
  1824. TFT480.drawString(s1, 255, 280);
  1825.  
  1826. s1= (String)liste_bali[num_bali].altitude;
  1827. s1 +=" ft";
  1828. TFT480.setTextColor(VIOLET2, NOIR);
  1829. TFT480.drawString(s1, 300, 280);
  1830.  
  1831. TFT480.fillRect(270, 300, 60, 30, NOIR); // efface - GRIS pour test
  1832. s1= liste_bali[num_bali].nom;
  1833. TFT480.setTextColor(BLEU_CLAIR, NOIR);
  1834. TFT480.drawString(s1, 255, 290);
  1835. }
  1836.  
  1837.  
  1838. void affi_mode_affi_hauteur()
  1839. {
  1840. if (mode_affi_hauteur == AAL)
  1841. {
  1842. TFT480.setFreeFont(FF1);
  1843. TFT480.setTextColor(VERT, GRIS_AF); // Autolanding en cours, ok
  1844. TFT480.drawString("AAL", 290, 0);
  1845. }
  1846. if (mode_affi_hauteur == ASL)
  1847. {
  1848. TFT480.setFreeFont(FF1);
  1849. TFT480.setTextColor(BLEU_CLAIR, GRIS_AF); // Autolanding en cours, ok
  1850. TFT480.drawString("ASL", 290, 0);
  1851. }
  1852. }
  1853.  
  1854.  
  1855.  
  1856. // ===== CALCULS ===============================================================================================================
  1857.  
  1858.  
  1859. void calcul_ptAA_ptBB(float dst) // situés à dst NM de la piste, dans l'axe. (dst = 10NM en principe, sauf cas particuliers)
  1860. {
  1861.  
  1862. calculs_piste();
  1863.  
  1864. float d_lat = lat_ptA - lat_ptB;
  1865. float d_lon = lon_ptA - lon_ptB;
  1866.  
  1867. lat_ptAA = lat_centre_pst + (1852 * dst /longueur_piste) * d_lat;
  1868. lon_ptAA = lon_centre_pst + (1852 * dst /longueur_piste) * d_lon;
  1869.  
  1870. //affi_float_test(lat_ptAA, 120, 2, VERT, NOIR);
  1871. //affi_float_test(lon_ptAA, 120, 3, JAUNE, NOIR);
  1872.  
  1873. lat_ptBB = lat_centre_pst - (1852 * dst /longueur_piste) * d_lat;
  1874. lon_ptBB = lon_centre_pst - (1852 * dst /longueur_piste) * d_lon;
  1875.  
  1876. //affi_float_test(lat_ptBB, 120, 4, VERT, NOIR);
  1877. //affi_float_test(lon_ptBB, 120, 5, JAUNE, NOIR);
  1878. }
  1879.  
  1880.  
  1881.  
  1882. void calculs_piste() // lors du choix de l'Airport
  1883. {
  1884. lat_ptA = liste_bali[num_bali].lat_A;
  1885. lon_ptA = liste_bali[num_bali].lon_A;
  1886.  
  1887. lat_ptB = liste_bali[num_bali].lat_B;
  1888. lon_ptB = liste_bali[num_bali].lon_B;
  1889.  
  1890. //affi_float_test(lat_ptB, 120, 4, VERT, NOIR);
  1891. //affi_float_test(lon_ptB, 120, 5, JAUNE, NOIR);
  1892.  
  1893. longueur_piste = 1000.0* distance_AB(lat_ptA, lon_ptA, lat_ptB, lon_ptB); // en m
  1894.  
  1895. orient_pisteAB = azimut_AB(lat_ptA, lon_ptA, lat_ptB, lon_ptB);
  1896.  
  1897. //orient_pisteAB = 94.43; // fixé pour TEST (piste Béziers-Vias)
  1898. //affi_float_test(orient_pisteAB, 120, 2, JAUNE, GRIS_TRES_FONCE); // pour TEST
  1899.  
  1900. orient_pisteBA = orient_pisteAB + 180.0;
  1901. if (orient_pisteBA > 360.0) {orient_pisteBA -= 360.0;}
  1902.  
  1903. lat_centre_pst=(lat_ptA +lat_ptB)/2.0;
  1904. lon_centre_pst=(lon_ptA +lon_ptB)/2.0;
  1905. }
  1906.  
  1907.  
  1908.  
  1909.  
  1910. void calculs_GPS() // temps réel
  1911. {
  1912. // calculs de la position de l'avion / piste (distance et direction)
  1913.  
  1914. // DISTANCE (variable globale)
  1915. // voir la fonction "distance_AB()" dans le fichier "Fonctions1.h"
  1916. GPS_distance_piste = distance_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst) / 1.852; // du centre de la piste, en NM
  1917. GPS_distance_ptAA = distance_AB(lat_avion, lon_avion, lat_ptAA, lon_ptAA) / 1.852;
  1918. GPS_distance_ptBB = distance_AB(lat_avion, lon_avion, lat_ptBB, lon_ptBB) / 1.852;
  1919. GPS_distance_pti = distance_AB(lat_avion, lon_avion, lat_pti, lon_pti) / 1.852;
  1920.  
  1921.  
  1922. // DIRECTION (variable globale)
  1923. GPS_azimut_piste = azimut_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst);// latitudes et longitudes en degrés décimaux
  1924.  
  1925. GPS_azimut_ptA = azimut_AB(lat_avion, lon_avion, lat_ptA, lon_ptA);
  1926. GPS_azimut_ptB = azimut_AB(lat_avion, lon_avion, lat_ptB, lon_ptB);
  1927.  
  1928. GPS_azimut_ptAA = azimut_AB(lat_avion, lon_avion, lat_ptAA, lon_ptAA);
  1929. GPS_azimut_ptBB = azimut_AB(lat_avion, lon_avion, lat_ptBB, lon_ptBB);
  1930. GPS_azimut_pti = azimut_AB(lat_avion, lon_avion, lat_pti, lon_pti);
  1931. }
  1932.  
  1933.  
  1934. void calcul_pti(float azimut_i, float distance_i, float *latitude, float *longitude)
  1935. {
  1936. /*
  1937. calcul des coordonnées GPS d'un point quelquonque PROCHE de la piste (en vue de faire des "hyppodromes" biens maitrisés)
  1938.  
  1939. données:
  1940. -azimut et distance du point concerné par rapport au, et vu du, centre de la piste (paramètres: azimut_i et distance_i)
  1941. -coordonnées GPS des points extrémités de la piste (lus dans le fichier FG_data.h)
  1942. -coordonnées GPS du centre, et l'orientation de la piste (voir fonction "void calculs_GPS()" )
  1943. on va alors ajouter la valeur de l'azimut_i à l'orientation de la piste pour faire le calcul
  1944. */
  1945.  
  1946. // calcul de l'orientation (relevé) du point (azimut par rapport au nord)
  1947. float orientation_point = -1.0 * orient_pisteAB + azimut_i; //azimut_i étant l'angle entre la piste et la direction du point
  1948.  
  1949. // 1 minute d'angle sur un méridien => 1 NM de latitude
  1950. // 60mn d'angle (1deg) => 60 NM
  1951. // 1 NM => 1/60 de degré -> 0.0166 degrés
  1952.  
  1953. *latitude = lat_centre_pst + (distance_i /60.0 * sin(raddeg * (orientation_point - 90.0)));
  1954.  
  1955. // pour la longitude, il faut tenir compte que la longueur d'un parallèle dépend de la latitude
  1956. // (max à l'équateur, nulle au pôle) suivant une loi en cos.
  1957. *longitude= lon_centre_pst + (distance_i /(60.0 * cos(raddeg * lat_centre_pst)) * cos(raddeg * (orientation_point - 90.0)));
  1958.  
  1959. }
  1960.  
  1961.  
  1962.  
  1963.  
  1964. void find_sens_approche() // en fonction de la position réelle de l'avion
  1965. {
  1966. //détermination du sens de l'approche pour l'autoland (en vol)
  1967.  
  1968. ////float delta_1 = orient_pisteBA - GPS_azimut_piste;
  1969. ////if (delta_1<0) {delta_1+=360.0;}
  1970. ////if (delta_1>360) {delta_1-=360.0;}
  1971.  
  1972. ////if ((delta_1 >90.0) && (delta_1 <270.0)) {sens_app_effectif = sens_AB;} else {sens_app_effectif = sens_BA;}
  1973. find_END_RWY_dst();
  1974.  
  1975. if(extremite_pst=='A') {sens_app_effectif = sens_BA;}
  1976. if(extremite_pst=='B') {sens_app_effectif = sens_AB;}
  1977. }
  1978.  
  1979.  
  1980.  
  1981. void find_END_RWY_dst() //le pt le plus ELOIGNE en face de nous, en bout de piste (= A ou B )
  1982. {
  1983.  
  1984. // calcul basé sur les distances
  1985. // en vue de guider (en lacet) l'avion au roulage lors de l'atterrissage
  1986. // on visera le point le plus éloigné
  1987. // attention: lors d'un touch and go, si l'avion a dépassé le centre de la piste lors de la remise des gaz, le sens sera FAUX !
  1988.  
  1989. float lat_A=liste_bali[num_bali].lat_A;
  1990. float lon_A=liste_bali[num_bali].lon_A;
  1991. float lat_B=liste_bali[num_bali].lat_B;
  1992. float lon_B=liste_bali[num_bali].lon_B;
  1993.  
  1994. float dst_A = distance_AB(lat_avion, lon_avion, lat_A, lon_A);
  1995. float dst_B = distance_AB(lat_avion, lon_avion, lat_B, lon_B);
  1996.  
  1997. if((dst_A) > (dst_B)) {extremite_pst='A';} else {extremite_pst='B';}
  1998. }
  1999.  
  2000.  
  2001. void find_END_RWY_angl() //le pt le plus ELOIGNE en face de nous, en bout de piste (= A ou B )
  2002. {
  2003. // calcul par les angles
  2004. // en vue de guider (en lacet) l'avion au roulage lors du décollage
  2005. // on visera le point le plus éloigné
  2006.  
  2007.  
  2008. float delta = cap - orient_pisteAB;
  2009.  
  2010. if (delta < -180) {delta += 360;}
  2011. if (delta > 180) {delta -= 360;}
  2012.  
  2013. if(abs(delta) > 90) {extremite_pst='A';} else {extremite_pst='B';}
  2014.  
  2015. //affi_string_test((String)extremite_pst, 130, 4, BLANC, NOIR);
  2016. }
  2017.  
  2018. // =============================================================================================================================
  2019.  
  2020.  
  2021.  
  2022.  
  2023. void nav_to_centre_piste()
  2024. {
  2025. voyant_APP.affiche(BLANC, BLEU);
  2026. hdg1 = round(GPS_azimut_piste);
  2027. if(GPS_distance_piste < 2) // on désengage tout, il faut un appui sur touche pour décider de la suite du vol
  2028. {
  2029. raz_bit(&flags, bit_nav_to_piste);// ce qui signe la fin des appel de cette fonction
  2030. raz_bit(&flags, bit_nav_to_ptAA);
  2031. raz_bit(&flags, bit_nav_to_ptBB);
  2032. raz_bit(&flags, bit_route);
  2033. raz_bit(&flags, bit_autoland);
  2034. raz_bit(&flags, bit_atterrissage);
  2035. //raz_bit(&flags, bit_au_sol);
  2036. raz_bit(&flags, bit_decollage);
  2037.  
  2038. for (int n=0; n<4; n++)
  2039. {
  2040. TFT480.setFreeFont(FF6);
  2041. affi_message("verticale RWY", 130, 200, 200, 1000, BLEU_CLAIR, HA_SOL, 1); // ici
  2042. }
  2043.  
  2044. // rien de plus, on repasse en auto-pilotage manuel
  2045.  
  2046. set_bit(&flags, bit_FG_AP);
  2047. }
  2048. }
  2049.  
  2050.  
  2051. void nav_to_ptAA() // on passera en boucle dans cette fonction
  2052. {
  2053. msg_to_send="nav to AA";
  2054. // point situé à 12NM dans l'axe de la piste (d'un côté)
  2055.  
  2056. voyant_APP.affiche(BLANC, VIOLET1);
  2057.  
  2058. // CAP
  2059. hdg1 = round(GPS_azimut_ptAA);
  2060.  
  2061. if ((GPS_distance_ptAA < 80) && (asel1 > 100)) {asel1 = 100;}
  2062. if ((GPS_distance_ptAA < 40) && (asel1 > 60)) {asel1 = 60;}
  2063.  
  2064. uint16_t asel_mini = liste_bali[num_bali].niveau_de_vol_mini;
  2065. if ((GPS_distance_ptAA < 30) && (asel1 < asel_mini)) {asel1 ++;}
  2066. // force à garder une hauteur minimale de sécurité le cas échéant (relief...)
  2067.  
  2068. uint16_t asel_mini2 = (gnd_elv + 1600) /100;
  2069. if(asel1 < asel_mini2) {asel1 = asel_mini2;} // remonte si trop bas / sol
  2070.  
  2071. affi_distance_ptAA();
  2072.  
  2073. if(GPS_distance_ptAA < 3.0)
  2074. {
  2075. raz_bit(&flags, bit_nav_to_piste);
  2076. raz_bit(&flags, bit_nav_to_ptAA); // ce qui signe la fin des appel de cette fonction
  2077. raz_bit(&flags, bit_nav_to_ptBB);
  2078. raz_bit(&flags, bit_route);
  2079. //efface_cadre_bas(NOIR);
  2080.  
  2081. if (asel1 > 30) {asel1 = 30;}
  2082.  
  2083. TFT480.setFreeFont(FF6);
  2084. affi_message("proche ptA", 130, 200, 200, 500, BLEU_CLAIR, HA_SOL, 1); // ici
  2085.  
  2086. TFT480.setFreeFont(FF6);
  2087. affi_message("Finale", 130, 200, 200, 500, BLEU_CLAIR, HA_SOL, 1); // ici
  2088.  
  2089. efface_cadre_bas(NOIR);
  2090. find_END_RWY_dst(); // la plus éloignée (= 'A' ou = 'B')
  2091. set_bit(&flags, bit_autoland); // on passe en finale
  2092. //set_bit(&flags, bit_rudder_attero);
  2093. }
  2094.  
  2095. // vitesse
  2096.  
  2097. if ( (GPS_distance_piste < 30.0) && (target_speed>160) ) {target_speed =160;}
  2098. }
  2099.  
  2100.  
  2101.  
  2102. void nav_to_ptBB() // on passera en boucle dans cette fonction
  2103. {
  2104. msg_to_send="nav to BB";
  2105. // point situé à 12NM dans l'axe de la piste (de l'autre côté)
  2106.  
  2107. voyant_APP.affiche(BLANC, VIOLET2);
  2108.  
  2109. // CAP
  2110. hdg1 = round(GPS_azimut_ptBB);
  2111.  
  2112. if ((GPS_distance_ptBB < 80) && (asel1 > 100)) {asel1 = 100;}
  2113. if ((GPS_distance_ptBB < 40) && (asel1 > 60)) {asel1 = 60;}
  2114.  
  2115. uint16_t asel_mini = liste_bali[num_bali].niveau_de_vol_mini;
  2116. if ((GPS_distance_ptBB < 30) && (asel1 < asel_mini)) {asel1++;}
  2117. // force à garder une hauteur minimale de sécurité le cas échéant (relief...)
  2118.  
  2119. uint16_t asel_mini2 = (gnd_elv + 1600) /100;
  2120. if(asel1 < asel_mini2) {asel1 = asel_mini2;} // remonte si trop bas / sol
  2121.  
  2122. affi_distance_ptBB();
  2123.  
  2124. if(GPS_distance_ptBB < 3.0)
  2125. {
  2126. raz_bit(&flags, bit_nav_to_piste);
  2127. raz_bit(&flags, bit_nav_to_ptAA);
  2128. raz_bit(&flags, bit_nav_to_ptBB); // ce qui signe la fin des appel de cette fonction
  2129. raz_bit(&flags, bit_nav_to_pti);
  2130. raz_bit(&flags, bit_circling);
  2131. raz_bit(&flags, bit_route);
  2132.  
  2133. if (asel1 > 30) {asel1 = 30;}
  2134.  
  2135. TFT480.setFreeFont(FF6);
  2136. affi_message("proche ptB", 130, 200, 200, 500, BLEU_CLAIR, HA_SOL, 1); // ici
  2137.  
  2138. TFT480.setFreeFont(FF6);
  2139. affi_message("Finale", 130, 200, 200, 500, BLEU_CLAIR, HA_SOL, 1); // ici
  2140.  
  2141. efface_cadre_bas(NOIR);
  2142. find_END_RWY_dst(); // la plus éloignée (= 'A' ou = 'B')
  2143.  
  2144.  
  2145. set_bit(&flags, bit_autoland); // on passe en finale
  2146. //set_bit(&flags, bit_rudder_attero);
  2147. }
  2148. // vitesse
  2149.  
  2150. if ( (GPS_distance_piste < 30.0) && (target_speed>160) ) {target_speed =160;}
  2151. }
  2152.  
  2153.  
  2154. void nav_to_pti()
  2155. {
  2156. String s1;
  2157. // point quelconque
  2158. voyant_APP.affiche(BLANC, VERT_FONCE);
  2159.  
  2160. // CAP
  2161. hdg1 = round(GPS_azimut_pti);
  2162.  
  2163.  
  2164. affi_distance_pti();
  2165.  
  2166. if(GPS_distance_pti > 1.5) {inc_num_pt1_autorisee=1;}
  2167.  
  2168.  
  2169. if(GPS_distance_pti < 1.0)
  2170. {
  2171. TFT480.setFreeFont(FF6);
  2172. //s1 ="PT ";
  2173. s1 = "to PT " + String(num_pti);
  2174. msg_to_send = s1;
  2175. TFT480.setFreeFont(FF6);
  2176. affi_message(s1, 130, 200, 200, 300, VERT, HA_SOL, 1); // ici
  2177. msg_to_send=s1;
  2178.  
  2179. if (inc_num_pt1_autorisee==1)
  2180. {
  2181. num_pti ++; // pour naviguer vers le point suivant
  2182. if (num_pti >10)
  2183. {
  2184. num_pti =1;
  2185. }
  2186.  
  2187. asel1 = 30; //à priori. niveau de vol (en ft/100)
  2188.  
  2189. if (num_pti==1){asel1 = 3; flaps=3; } // 300ft -> 100m
  2190. if (num_pti==2){asel1 = 15; flaps=2; } // 1500ft -> 500m
  2191. if (num_pti==3){asel1 = 30; flaps=0; } // 3000ft -> 1000m
  2192. if (num_pti==4){asel1 = 30; flaps=0; }
  2193. if (num_pti==5){asel1 = 30; flaps=0; }
  2194. if (num_pti==6){asel1 = 30; flaps=0; }
  2195. if (num_pti==7){asel1 = 30; flaps=0; }
  2196. if (num_pti==8){asel1 = 30; flaps=0; }
  2197. if (num_pti==9){asel1 = 20; flaps=2; }
  2198. if (num_pti==10){asel1 =10; flaps=3; }
  2199.  
  2200. inc_num_pt1_autorisee =0; // pour éviter d'incrémenter plusieurs fois lorsqu'on est proche du point
  2201. }
  2202. }
  2203. }
  2204.  
  2205.  
  2206.  
  2207. void tour_de_piste()
  2208. {
  2209. msg_to_send="tour de piste";
  2210. // cheminement entre points dont la position est définie par un vecteur partant du centre de la piste (angle & distance)
  2211. // num_pti est incrémenté dans la fonction 'nav_to_pti()' lorsque le point en cours est atteint
  2212.  
  2213.  
  2214. float dst;
  2215. float alpha;
  2216. uint8_t n2=0;
  2217.  
  2218. if (extremite_pst == 'A') {n2 = num_pti;}
  2219. if (extremite_pst == 'B') {n2 = 11-num_pti;} //même trajectoire parcourue en sens inverse
  2220.  
  2221. if (n2 ==1) {alpha= 0; dst = 1.0;}
  2222. if (n2 ==2) {alpha= 0; dst = 4.0;}
  2223. if (n2 ==3) {alpha= 16; dst = 5.1;}
  2224. if (n2 ==4) {alpha= 46; dst = 4.0;}
  2225. if (n2 ==5) {alpha= 72.3; dst = 3.1;}
  2226. if (n2 ==6) {alpha= 109; dst = 3.1;}
  2227. if (n2 ==7) {alpha= 135; dst = 4.0;}
  2228. if (n2 ==8) {alpha= 163; dst = 5.1;}
  2229. if (n2 ==9) {alpha= 180; dst = 4.0;}
  2230. if (n2 ==10){alpha= 180; dst = 1.0;}
  2231.  
  2232. dst *= 1.5; // taille de la figure
  2233.  
  2234.  
  2235. if (read_bit(flags, bit_sens_circling) == 1) {alpha = 360-alpha;} // trajectoire miroir
  2236.  
  2237. calcul_pti(alpha, dst, &lat_pti, &lon_pti);
  2238.  
  2239. // variante :
  2240. //calcul_pti(30.0*num_pti, 10.0, &lat_pti, &lon_pti); // points disposés en cercle, à 10NM
  2241.  
  2242. }
  2243.  
  2244.  
  2245.  
  2246.  
  2247.  
  2248. void calcul_erreur_position() // pour savoir si l'avion se trouve exactement dans l'axe de la piste
  2249. {
  2250. float x, y;
  2251. float x1, x2;
  2252. float y1, y2;
  2253. float p;
  2254. float s;
  2255. //float erreur;
  2256.  
  2257. x=lon_avion;
  2258. y=lat_avion;
  2259.  
  2260. x1 = liste_bali[num_bali].lon_A; x2 = liste_bali[num_bali].lon_B;
  2261. y1 = liste_bali[num_bali].lat_A; y2 = liste_bali[num_bali].lat_B;
  2262.  
  2263. p = (y2-y1) / (x2-x1); // pente de la droite A-B
  2264.  
  2265. s= y1+ p * (x-x1);
  2266.  
  2267. erreur_axe = y-s;
  2268.  
  2269. //affi_float_test(erreur_axe, 110, 2, BLANC, BLEU); // pour test
  2270.  
  2271. }
  2272.  
  2273.  
  2274.  
  2275. void desengage_autoland()
  2276. {
  2277. msg_to_send= "autoland OFF";
  2278. raz_bit(&flags, bit_autoland);
  2279. efface_cadre_bas(NOIR);
  2280. //init_affi_HA();
  2281.  
  2282. voyant_L.affiche(BLANC, GRIS_FONCE);
  2283. voyant_G.affiche(BLANC, GRIS_FONCE);
  2284. /*
  2285. target_speed =180;
  2286. locks_type = "ALT";
  2287. asel1 = 30; // consigne altitude 30 -> 3000ft
  2288. climb_rate=0; // taux de montée (négatif pour descendre - sert pour attérissage automatique)
  2289. hdg1 = cap;
  2290. RAZ_chrono();
  2291. */
  2292. }
  2293.  
  2294.  
  2295. void affiche_etats_flags() // certains "voyants" en haut à gauche
  2296. {
  2297. if (read_bit(flags, bit_rudder_decol) == 1) { voyant_RD.affiche(NOIR, VERT);}
  2298. else if (read_bit(flags, bit_rudder_attero) == 1){ voyant_RD.affiche(NOIR, JAUNE); }
  2299. else { voyant_RD.affiche( BLANC, GRIS_TRES_FONCE); }
  2300.  
  2301. if (read_bit(flags, bit_nav_to_piste) == 1) {voyant_route.affiche(BLANC, BLEU);}
  2302. else if (read_bit(flags, bit_nav_to_ptAA) == 1) {voyant_route.affiche(NOIR, JAUNE);}
  2303. else if (read_bit(flags, bit_nav_to_ptBB) == 1) {voyant_route.affiche(NOIR, JAUNE);}
  2304.  
  2305. else {voyant_route.affiche(BLANC, GRIS_TRES_FONCE);}
  2306.  
  2307. if (read_bit(flags, bit_atterrissage)==1){voyant_ATT.affiche(NOIR, VERT);}
  2308. else {voyant_ATT.affiche(BLANC, GRIS_TRES_FONCE);}
  2309. }
  2310.  
  2311.  
  2312.  
  2313. void affi_ligne1_V(uint16_t x)
  2314. {
  2315. /** DOC: (source : "TFT_eSPI.h")
  2316. // The next functions can be used as a pair to copy screen blocks (or horizontal/vertical lines) to another location
  2317.  
  2318. // Read a block of pixels to a data buffer, buffer is 16 bit and the size must be at least w * h
  2319. void readRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data);
  2320.  
  2321. // Write a block of pixels to the screen which have been read by readRect()
  2322. void pushRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data);
  2323. **/
  2324.  
  2325. TFT480.pushRect(memo_x1, 0, 1, 320, data_C1); // efface la ligne en replaçant l'image
  2326. memo_x1=x;
  2327.  
  2328. TFT480.readRect(x, 0, 1, 320, data_C1); // memorisation de la ligne avant de tracer dessus
  2329. //TFT480.drawFastVLine(x, 0, 320, ROUGE);
  2330. TFT480.drawFastVLine(x, y_1, y_2-y_1, JAUNE);
  2331. }
  2332.  
  2333.  
  2334.  
  2335. void affi_ligne2_V(uint16_t x)
  2336. {
  2337. TFT480.pushRect(memo_x2, 0, 1, 320, data_C2); // efface la ligne en replaçant l'image
  2338. memo_x2=x;
  2339.  
  2340. TFT480.readRect(x, 0, 1, 320, data_C2); // memorisation de la ligne avant de tracer dessus
  2341. //TFT480.drawFastVLine(x, 0, 320, ROUGE);
  2342. TFT480.drawFastVLine(x, y_1, y_2-y_1, JAUNE);
  2343. }
  2344.  
  2345.  
  2346.  
  2347. void affi_ligne1_H(uint16_t y)
  2348. {
  2349. TFT480.pushRect(0, memo_y1, 480, 1, data_L1); // efface la ligne en replaçant l'image
  2350. memo_y1=y;
  2351.  
  2352. TFT480.readRect(0, y, 480, 1, data_L1); // memorisation de la ligne avant de tracer dessus
  2353. //TFT480.drawFastHLine(0, y, 480, ROUGE);
  2354. TFT480.drawFastHLine(x_1, y, x_2-x_1, JAUNE);
  2355. }
  2356.  
  2357.  
  2358. void affi_ligne2_H(uint16_t y)
  2359. {
  2360. TFT480.pushRect(0, memo_y2, 480, 1, data_L2); // efface la ligne en replaçant l'image
  2361. memo_y2=y;
  2362.  
  2363. TFT480.readRect(0, y, 480, 1, data_L2); // memorisation de la ligne avant de tracer dessus
  2364. //TFT480.drawFastHLine(0, y, 480, ROUGE);
  2365. TFT480.drawFastHLine(x_1, y, x_2-x_1, JAUNE);
  2366. }
  2367.  
  2368.  
  2369. // =============================================================================================================================
  2370.  
  2371.  
  2372. void prepare_decollage()
  2373. {
  2374. init_affi_HA();
  2375.  
  2376. raz_bit(&flags, bit_atterrissage);
  2377. raz_bit(&flags, bit_FG_AP);
  2378. raz_bit(&flags, bit_autoland);
  2379. raz_bit(&flags, bit_route);
  2380.  
  2381. //set_bit(&flags, bit_nav_to_piste); // par défaut
  2382.  
  2383. raz_bit(&flags, bit_nav_to_pti);
  2384. raz_bit(&flags, bit_nav_to_ptAA);
  2385. raz_bit(&flags, bit_nav_to_ptBB);
  2386. raz_bit(&flags, bit_roulage);
  2387.  
  2388. locks_type = "ALT";
  2389. asel1 = 60;
  2390. target_speed = 200;
  2391.  
  2392. find_END_RWY_angl();
  2393.  
  2394. set_bit(&flags, bit_au_sol);
  2395. set_bit(&flags, bit_rudder_decol);
  2396.  
  2397. hdg1 = cap;
  2398. RAZ_chrono();
  2399.  
  2400. set_bit(&flags, bit_decollage);
  2401. flaps = 3;
  2402. landing_light1=1;
  2403. landing_light2=1;
  2404. msg_to_send= "decollage";
  2405.  
  2406.  
  2407. }
  2408.  
  2409.  
  2410.  
  2411. void auto_rudder_deco(float correction)
  2412. {
  2413. msg_to_send="auto rudder";
  2414. // losrqu'on est bien positionné sur la piste, on doit voir l'extrémité de la piste, en face, au loin
  2415. // dans la même direction que l'orientation physique de la piste
  2416.  
  2417. float d_alpha;
  2418. float lat_i, lon_i;
  2419.  
  2420. affi_extremite(); // l'extrémité concernée est déterminée par la fonction "find_END_RWY_angl()"
  2421.  
  2422. if (extremite_pst == 'A')
  2423. {
  2424. lat_i=liste_bali[num_bali].lat_A;
  2425. lon_i=liste_bali[num_bali].lon_A;
  2426. }
  2427.  
  2428. if (extremite_pst == 'B')
  2429. {
  2430. lat_i=liste_bali[num_bali].lat_B;
  2431. lon_i=liste_bali[num_bali].lon_B;
  2432. }
  2433.  
  2434. float az1 = azimut_AB(lat_avion, lon_avion, lat_i, lon_i); // direction dans laquelle on voit le bout de la piste au loin...
  2435.  
  2436. d_alpha = az1 - cap;
  2437.  
  2438. // le débattement doit augmenter fortement lorsque la roue avant ne touche plus le sol
  2439. // et que seule la dérive a une action (aérodynamique) (vers 110 kts)
  2440.  
  2441. float facteur1 = 0.03; // 0.1->zig-zag ; agit sur l'orientation de l'avion
  2442. float facteur2 = -0.04 * vitesse; //-0.05 // agit sur la position de l'avion / axe de la piste
  2443.  
  2444. if (vitesse < 110) { facteur2 = -0.05;} // <110 ; -0.05// sinon se met à zig-zaguer grave tant que la roue avant touche le sol
  2445.  
  2446. //if (is_in(vitesse, 80, 175)) {facteur1 = 15;} // on augmente fortement l'amplitude de cette correction
  2447.  
  2448. rudder = (d_alpha * facteur1) + (correction * facteur2);
  2449. borne_in (&rudder, -0.2, 0.2);
  2450.  
  2451. if (vitesse < 20) { rudder =0;}
  2452. if (vitesse > 175) { rudder =0;} // à voir !!!
  2453. }
  2454.  
  2455.  
  2456.  
  2457.  
  2458. void decollage()
  2459. // on passera en boucle dans cette fonction ; voir aussi la fonction "prepare_decollage()"
  2460. {
  2461. asel1 = liste_bali[num_bali].niveau_de_vol_mini;
  2462.  
  2463. speedbrake =0;
  2464. reverser1 = 0;
  2465. reverser2 = 0;
  2466. throttle = -0.95;
  2467.  
  2468. brake_left =0;
  2469. brake_right =0;
  2470. landing_light1=1;
  2471. landing_light2=1;
  2472.  
  2473. trim_elevator = -0.3; // bonne valeur pour décoller
  2474. //if (hauteur_AAL >10){trim_elevator=-0.25;} // pour ne pas grimper aux arbres
  2475. //if (hauteur_AAL >15){trim_elevator=-0.2;}
  2476.  
  2477.  
  2478. // -------------------- AZIMUT -----------------------------------------------------------------
  2479.  
  2480. float op1;
  2481. float delta_AZM;
  2482.  
  2483. find_sens_approche();
  2484. affi_sens_APP(); // en haut à droite
  2485.  
  2486. if (sens_app_effectif == sens_AB)
  2487. {
  2488. op1 = orient_pisteAB;
  2489. delta_AZM = op1 -GPS_azimut_ptB;
  2490. }
  2491. if (sens_app_effectif == sens_BA)
  2492. {
  2493. op1 = orient_pisteBA;
  2494. delta_AZM = op1 -GPS_azimut_ptA;
  2495. }
  2496.  
  2497. auto_rudder_deco(delta_AZM);
  2498.  
  2499. ailerons= -roulis/10.0; // 1.2 //asservissement des ailerons en fonction de l'angle de roulis (-> ailes à plat)
  2500.  
  2501. // ----------------------------------------------------------------------------------------------
  2502.  
  2503. if (vitesse > 140){trim_elevator=-0.25;} // pour ne pas grimper aux arbres
  2504. if (vitesse > 150){trim_elevator=-0.2;}
  2505.  
  2506. affi_elevator();
  2507.  
  2508. if (hauteur_AAL > 10)
  2509. {
  2510. flaps = 2;
  2511. }
  2512.  
  2513. if ( is_in(hauteur_AAL, 12, 18) ) {msg_to_send = "engage AP";}
  2514.  
  2515. if (hauteur_AAL > 50)
  2516. {
  2517. flaps = 0;
  2518. set_bit(&flags, bit_FG_AP); // engage Autopilot de FlightGear
  2519. speed_ctrl=true;
  2520.  
  2521. TFT480.setFreeFont(FF6);
  2522. affi_message (msg_to_send, 130, 200, 200, 100, VERT, HA_SOL, 1); // ici
  2523.  
  2524. raz_bit(&flags, bit_rudder_decol);
  2525. //raz_bit(&flags, bit_au_sol);
  2526. rudder=0;
  2527. //gear_down = 0; // n'est plus géré ici, mais dans "void toutes_les_1s"
  2528. msg_to_send = "gear UP";
  2529. landing_light1=0;
  2530. landing_light2=0;
  2531. raz_bit(&flags, bit_decollage); // fin des appels de cette fonction
  2532. }
  2533. }
  2534.  
  2535.  
  2536.  
  2537. // =============================================================================================================================
  2538.  
  2539. void auto_rudder_attero(float correction) // on passera en boucle dans cette fonction
  2540. {
  2541. // losrqu'on est bien orienté sur la piste, on doit voir l'extrémité de la piste, en face, au loin
  2542. // dans la même direction que l'orientation physique de la piste
  2543.  
  2544. float d_alpha;
  2545. float lat_i, lon_i;
  2546.  
  2547. affi_extremite(); // déterninée en une seule fois lors de la fin de la phase d'autoland
  2548. // voir dans la fonction "void auto_landing()"
  2549. // ne plus la re-déterminer par la suite parce qu'une fois dépassé le centre de la piste, le résultat serait faux !
  2550.  
  2551. if (extremite_pst == 'A')
  2552. {
  2553. lat_i=liste_bali[num_bali].lat_A;
  2554. lon_i=liste_bali[num_bali].lon_A;
  2555. }
  2556.  
  2557. if (extremite_pst == 'B')
  2558. {
  2559. lat_i=liste_bali[num_bali].lat_B;
  2560. lon_i=liste_bali[num_bali].lon_B;
  2561. }
  2562.  
  2563. float az1 = azimut_AB(lat_avion, lon_avion, lat_i, lon_i); // direction dans laquelle on voit le bout de la piste au loin...
  2564.  
  2565. d_alpha = az1 - cap; // orientation de l'avion vers le bout de la piste
  2566.  
  2567. borne_in (&d_alpha, -3.0, 3.0); // -3 3
  2568.  
  2569. ////if (is_in(vitesse, 100, 140)) {rudder = d_alpha / 20.0;}
  2570. ////else if (is_in(vitesse, 80, 100)) {rudder = d_alpha / 25.0;} //30
  2571. ////else if (is_in(vitesse, 50, 80)) {rudder = d_alpha / 30.0;} //40
  2572. ////else if (is_in(vitesse, 20, 50)) {rudder = d_alpha / 50.0;} //80
  2573.  
  2574. float facteur1 = -0.26 * vitesse +65.2; // agit sur l'orientation de l'avion
  2575. float facteur2 = -0.5; // agit sur la position de l'avion / axe de la piste
  2576.  
  2577. if (vitesse < 80) { facteur2 = 0;} // sinon se met à zig-zaguer grave lorsque la roue avant touche le sol
  2578.  
  2579. // le débattement doit être important lorsque la roue avant ne touche pas le sol
  2580. // et que seule la dérive a une action (aérodynamique) (> 80 kts)
  2581.  
  2582. if (is_in(vitesse, 80, 175)) {facteur1 = 15;} // on augmente fortement l'amplitude de cette correction
  2583.  
  2584. rudder = (d_alpha / facteur1) + (correction * facteur2);
  2585.  
  2586. if (vitesse > 175) { rudder = 0;}
  2587. if (vitesse < 10) { rudder = 0;}
  2588.  
  2589. //raz_bit(&flags, bit_rudder_attero); // afin de pouvoir manoeuvrer sur les taxiways
  2590.  
  2591. // OK, garde l'axe de la piste, heu... lorsque la roue avant touche le sol...
  2592. // lorsque seul le train principal touche et la vitesse est faible et donc la gouverne de direction peu efficace... pas top !
  2593. // on pourrait jouer en différentiel sur les freins gauche-droite, mais ça complique pas mal l'affaire !
  2594. // toutefois si on freine rapidement (dans la seconde qui suit le toucher initial) la roue avant touche à son tour, et c'est OK
  2595.  
  2596. }
  2597.  
  2598.  
  2599.  
  2600.  
  2601. void auto_landing() // approche et finale
  2602. {
  2603. // on passera en boucle dans cette fonction
  2604. /**
  2605.  voir: https://en.wikipedia.org/wiki/Autoland
  2606.  
  2607.  Approche automatique
  2608.  CAPTURE l'avion et le POSE !
  2609.  
  2610.  LES CONSEILS QUI SUIVENT ne concernent que l'utilisation du simulateur de vol FlightGear connecté aux ESP32
  2611.  et le choix du Citation X comme avion.
  2612.  c'est à dire qu'ils ne doivent en aucun cas servir pour le pilotage d'un avion réel.
  2613.  
  2614.  
  2615.  -vitesse conseillée : 140kts, 160kts max
  2616.  -distance conseillée : entre 20 et 10 nautiques
  2617.  -avec une trajectoire qui recoupe l'axe de la piste, < 90°
  2618.  (si capture à moins de 10 NM, la trajectoire sera difficilement corrigée -> remise des gaz ou crash au choix !)
  2619.  -hauteur conseillée : 3000ft à 10NM (= niveau 30)
  2620.  
  2621.  -volets sortis 2 puis 3
  2622.  à priori pas d'AF si vitesse correcte
  2623.  
  2624.  -sortir le train !
  2625.  -allumer feux d'atterrissage
  2626.  
  2627.  notes: l'autopilot se désengage automatiquement (par FlightGear) sous 100ft de hauteur
  2628.  (réglable, voir la variable 'hauteur_mini_autopilot' au début de ce programme)
  2629.  
  2630. ce qui suit est actuellement devenu automatique dans les nouvelles versions :
  2631.  
  2632.  (Donc garder le manche en main pour l'arrondi et le touché final, en surveillant
  2633.  - la hauteur
  2634.  - la vitesse
  2635.  - le pitch
  2636.  - position des volets
  2637.  - éventuellement petit coup d'AF (aérofreins -> CTRL + B au clavier)
  2638.  - si piste très courte, inverseurs de poussée (au sol) + gaz (touche 'suppr')
  2639.  - toutefois si approche visiblement trop courte ou trop longue, pas d'attéro kamikaze ! -> remise des gaz !!
  2640.  - si système visuel "papi" présent, le respecter !!
  2641.  
  2642.  TOUTEFOIS :
  2643.  - on peut obtenir un posé 100% auto en anticipant un cabrage de l'avion pour faire l'arrondi dans les règles de l'art
  2644.  avec posé du train principal en premier, puis ensuite la roulette de nez. Pour cela :
  2645.  - en fin de finale, à une hauteur de 100 feet, dès le désengagement de l'autopilot de FlightGear:
  2646.  - ailes à plat !
  2647.  -gaz au mini et sortir les AF. La vitesse diminue, et l'avion se cabre un peu
  2648.  pour ne pas plonger... et il finit par poser le train principal.
  2649.  - dès que ce touché est fait, freiner légèrement -> la roue avant va alors à son tour toucher la piste, ce qui permet
  2650.  à l'auto-rudder de guider la trajectoire suivant l'axe de la piste.
  2651.  ( Tant que la roue avant ne touche pas, l'auto-rudder, qui n'agit alors qu'aérodynamiquement sur la gouverne de direction,
  2652.  n'est pas assezefficace).
  2653.  
  2654.  Le tout suivi d'un freinage, on pose avec arrêt complet sur 850m (sans faire craquer la structure...).
  2655.  Avec les inverseurs de poussée, on doit pouvoir faire bien mieux encore. Quant au porte-avion, il est normalement
  2656.  équipé d'un câble de retenue qu'on accroche avec une perche (sur un jet militaire, sans doute pas avec notre Cessna Citation X)
  2657.  
  2658. **/
  2659.  
  2660. String s1;
  2661. float alti1;
  2662. float GPS_distance_seuil_piste;
  2663.  
  2664. TFT480.setFreeFont(FSS9);
  2665.  
  2666. TFT480.setTextColor(GRIS_FONCE, NOIR);
  2667. //TFT480.fillRect(180, 0, 20, 16, JAUNE); // JAUNE pour test. efface 1/2 le bandeau d'information en haut
  2668.  
  2669. voyant_L.affiche(BLANC, GRIS_TRES_FONCE);
  2670.  
  2671. voyant_G.affiche(BLANC, GRIS_TRES_FONCE);
  2672.  
  2673. voyant_APP.affiche(BLANC, GRIS_TRES_FONCE);
  2674.  
  2675. uint8_t AZ =0; // flag azimut OK
  2676. uint8_t GL =0; // flag glide OK
  2677.  
  2678. //voyant_descente_GPS.affiche(BLANC, GRIS_TRES_FONCE);
  2679.  
  2680.  
  2681. //--------------------- (si autoland engagé, sinon on ne fait rien de plus)--------------
  2682.  
  2683. //TFT480.fillRect(HA_x0-40, HA_y0+80, 87, 15, HA_SOL); // efface "APP"
  2684.  
  2685. if (read_bit(flags, bit_autoland) == 1)
  2686. {
  2687. calculs_GPS();
  2688.  
  2689. if (GPS_distance_piste > 25.0)
  2690. {
  2691. return; // rien de plus
  2692. }
  2693. else
  2694. {
  2695. msg_to_send = "autoland ON";
  2696. //affi_float_test(liste_bali[num_bali].orient_pisteAB,110, 2, BLANC, BLEU); // pour test
  2697. //affi_float_test(GPS_azimut_piste,110, 3, BLANC, GRIS_FONCE); // pour test
  2698.  
  2699. voyant_APP.affiche(NOIR, JAUNE);
  2700.  
  2701.  
  2702. // -------------------- AZIMUT -----------------------------------------------------------------
  2703.  
  2704. float op1;
  2705. find_sens_approche();
  2706. affi_sens_APP(); // en haut à droite
  2707.  
  2708. if (sens_app_effectif == sens_AB) {op1 = orient_pisteAB;}
  2709. if (sens_app_effectif == sens_BA) {op1 = orient_pisteBA;}
  2710. //s1 = String(op1,1);
  2711. //TFT480.drawString(s1, 0, 250);
  2712.  
  2713.  
  2714. float delta_AZM = op1 -GPS_azimut_piste;
  2715.  
  2716. //affi_float_test(delta_AZM,110, 2, VERT, GRIS_FONCE); // pour test
  2717.  
  2718. delta_AZM *= 20.0;
  2719. borne_in(&delta_AZM, -35.0, 35.0);
  2720.  
  2721. //if((delta_AZM >-10.0)&&(delta_AZM <10.0))
  2722. if (is_in(delta_AZM, -10.0, 10.0)==1)
  2723. {
  2724. voyant_L.affiche(NOIR, VERT);
  2725. AZ=1;
  2726. }
  2727.  
  2728. affi_localizer(delta_AZM * -2.5);
  2729.  
  2730. hdg1 = round(op1 - delta_AZM);
  2731.  
  2732.  
  2733. //affi_float_test(hdg1,110, 5, VERT, GRIS_FONCE); // pour test
  2734.  
  2735. // -------------------- VITESSE -----------------------------------------------------------------
  2736.  
  2737. if ( (GPS_distance_piste < 20.0) && (target_speed>180) ) {target_speed =180;}
  2738. if ( (GPS_distance_piste < 10.0) && (target_speed>170) ) {target_speed =170;}
  2739. if ( (GPS_distance_piste < 5.0) && (target_speed>160) ) {target_speed =160;}
  2740. if ( (GPS_distance_piste < 2.0) && (target_speed>140) ) {target_speed =140;}
  2741. //if ( (GPS_distance_piste < 1.0) && (target_speed>130) ) {target_speed =130;}
  2742.  
  2743. if ((vitesse - target_speed) > 4 ) {speedbrake = 1.0;} else {speedbrake = 0;}
  2744.  
  2745. // -------------------- HAUTEUR -----------------------------------------------------------------
  2746.  
  2747. //voyant_descente_GPS.affiche(NOIR, JAUNE);
  2748. float longueur_piste_NM = longueur_piste / 1852.0;
  2749. GPS_distance_seuil_piste = GPS_distance_piste - (longueur_piste_NM/2.0); // + 0.1;
  2750. // Rappel : "GPS_distance_piste" est la distance au point CENTRAL de la piste
  2751.  
  2752. TFT480.setFreeFont(FM12);
  2753. TFT480.setTextColor(BLANC);
  2754. s1=String(GPS_distance_seuil_piste, 1);
  2755. s1+= " NM";
  2756. TFT480.fillRect(150, 300, 100, 25, GRIS_AF); // efface
  2757. TFT480.drawString(s1, 150, 300);
  2758.  
  2759.  
  2760. //affi_float_test(GPS_distance_seuil_piste, 110, 2, BLANC, BLEU); // pour test
  2761.  
  2762. float alti_correcte = liste_bali[num_bali].altitude + 300.0 * GPS_distance_seuil_piste;
  2763. if (alti_correcte > 3000) {alti_correcte = 3000;}
  2764.  
  2765. affi_asel(alti_correcte);
  2766.  
  2767. // soit 3000ft pour 10 nautiques -> pente 5%
  2768. //sachant que la ref de position est située au milieu de la longueur de la piste
  2769.  
  2770. //affi_float_test(alti_correcte, 110, 2, BLANC, BLEU); // pour test
  2771.  
  2772. float erreur_altitude = altitude_GPS - alti_correcte;
  2773. //affi_float_test(erreur_altitude, 110, 3, BLANC, BLEU); // pour test
  2774.  
  2775. if((erreur_altitude > -20)&& (erreur_altitude < 20))
  2776. {
  2777. //voyant_G.affiche(NOIR, VERT);
  2778. }
  2779.  
  2780. affi_index_lateral( - erreur_altitude / 3.0); // affiche les triangles roses latéraux
  2781.  
  2782. /**Rappels :
  2783. 1 NM (nautical mile ou 'nautique') = 1852 m
  2784. 1 feet = 0,3048 m
  2785. 1 NM = 1852/0.3048 = 6076.12 feet
  2786. 1 noeud (nd) = 1NM/h = 1852/3600 = 0.51444 m/s
  2787.  
  2788. début de descente (5%) vers la piste : 3000ft à 10NM, vitesse 150 nd (par exemple)
  2789. v=150*0.51444 = 77.17m/s
  2790. temps pour parcourir la distance : v=d/t
  2791. t=d/v = 10*1852m / 77.17 = 240 s (soit 4 minutes)
  2792.  
  2793. taux de descente = 3000ft/240s = 12.5 fps
  2794. **/
  2795. if ((GPS_distance_piste < 10) && (hauteur_AAL > 1500)) // && (hauteur_AAL <= 6000)
  2796. {
  2797. // initialisation de l'approche auto (palier puis descente)
  2798. //voyant_descente_GPS.affiche(NOIR, VERT);
  2799.  
  2800. locks_type = "VS"; // bascule le pilote auto de FG en mode vertical speed
  2801.  
  2802. //climb_rate = -5.0;
  2803.  
  2804. }
  2805.  
  2806. //if ((GPS_distance_piste < 30) && (hauteur_AAL < (liste_bali[num_bali].niveau_de_vol_mini * 100) ) )
  2807. if ((GPS_distance_piste < 10) && (hauteur_AAL < 8000) )
  2808. {
  2809. // descente
  2810. // correction du taux de descente (climb_rate) pour respecter la pente à 3° (=5%)
  2811. voyant_G.affiche(NOIR, VERT);
  2812. GL=1;
  2813.  
  2814. //if (erreur_altitude > 4) {climb_rate -= 5; }
  2815. //if (erreur_altitude < -4) {climb_rate += 5; }
  2816.  
  2817. climb_rate = erreur_altitude * -1.5;
  2818.  
  2819. if (climb_rate > +30) {climb_rate = +30;}
  2820. if (climb_rate < -50) {climb_rate = -50;}
  2821.  
  2822. //affi_float_test( erreur_altitude, 110, 4, NOIR, JAUNE); // pour test
  2823. }
  2824.  
  2825. // -------------------- FLAPS -----------------------------------------------------------------
  2826.  
  2827. if (GPS_distance_piste < 8)
  2828. {
  2829. msg_to_send= "land light ON";
  2830. flaps = 2;
  2831. landing_light1=1;
  2832. landing_light2=1;
  2833. }
  2834.  
  2835.  
  2836. if (GPS_distance_piste < 6)
  2837. {
  2838. msg_to_send= "gear_down";
  2839. flaps = 3; // participe grandement au freinage (l'asservissement précis des gaz maintiendra la bonne vitesse)
  2840. gear_down = 1;
  2841. }
  2842.  
  2843.  
  2844. if (GPS_distance_piste < 2)
  2845. {
  2846. msg_to_send= "flaps 4";
  2847. flaps = 4;
  2848. }
  2849.  
  2850.  
  2851. // -------------------- FINALE -----------------------------------------------------------------
  2852.  
  2853. if (hauteur_AAL < hauteur_mini_autopilot) // signe la fin des appels de cette fonction
  2854. {
  2855.  
  2856. raz_bit(&flags, bit_rudder_decol);
  2857. raz_bit(&flags, bit_nav_to_pti);
  2858. raz_bit(&flags, bit_nav_to_ptAA);
  2859. raz_bit(&flags, bit_nav_to_ptBB);
  2860. raz_bit(&flags, bit_route);
  2861.  
  2862. throttle = 1.0; // gaz au minimum
  2863. //target_speed = 120;
  2864.  
  2865. if (read_bit(flags, bit_att_short) == 1) {target_speed = 100;} else {target_speed = 120;}
  2866. //trim_elevator = 0.0;
  2867.  
  2868. ailerons=0;
  2869. raz_bit(&flags, bit_FG_AP);
  2870. desengage_autoland(); // donc on ne repassera plus dans la fonction (ici)
  2871.  
  2872. brake_left =0;
  2873. brake_right =0;
  2874.  
  2875. find_sens_approche(); //NE DOIT plus etre calculé ultérieurement! (lorsqu'on a dépassé le centre de la piste) !
  2876.  
  2877. //set_bit(&flags, bit_rudder_attero); // gouverne de lacet en mode automatique
  2878. set_bit(&flags, bit_atterrissage); // on passera dorénavant en boucle dans la fonction "atterrissage()"
  2879.  
  2880. affiche_etats_flags();
  2881. msg_to_send= "end autoland";
  2882. }
  2883.  
  2884. if( (AZ==1) && (GL==1) ) {voyant_APP.affiche(NOIR, VERT);}
  2885.  
  2886. /**
  2887. alti1 = 3.0*GPS_distance_piste + gnd_elv/100.0 -1;
  2888. if (alti1 < asel1) //empêche de (re)monter lors de la capture ILS, reste en palier le cas échéant
  2889. {
  2890. asel1 = alti1;
  2891. }
  2892. **/
  2893. }
  2894.  
  2895. // =============================================================================================================================
  2896. }
  2897. }
  2898.  
  2899.  
  2900.  
  2901. void atterrissage()
  2902. // on passera en boucle dans cette fonction
  2903. {
  2904. // premier terme = léger cabré pour l'arrondi
  2905. // deuxième terme diminue ce cabré tant que la hauteur est grande
  2906. // troisième terme = asservissement de l'angle de tangage de façon à stabiliser l'ensemble
  2907. //trim_elevator = -0.5+ (float)hauteur_AAL/1500.0 + (tangage / 20.0);
  2908. //trim_elevator = -0.3 + (tangage / 20.0);
  2909.  
  2910. calculs_piste();
  2911. calculs_GPS();
  2912.  
  2913. float H0 = hauteur_AAL;
  2914. if (H0<0) {H0 = 0;}
  2915.  
  2916. locks_type = "VS"; // pilote auto de FG en mode vertical speed
  2917.  
  2918. // -------------------- TANGAGE -----------------------------------------------------------------
  2919.  
  2920. //if (read_bit(flags, bit_att_short) == 1) {climb_rate = -5.0;} else {climb_rate = -4.0;}
  2921. climb_rate = -5.0; // -5
  2922.  
  2923. trim_elevator = -0.45 + (H0/200.0) + (tangage/20.0); // H0/300.0
  2924.  
  2925. //RAPPEL: throttle 1.0 -> ralentit; 0 -> mi-gaz ; -1.0 -> plein gaz
  2926.  
  2927. if (read_bit(flags, bit_att_short) == 1)
  2928. {
  2929. throttle = 1.0; // gaz au mini (Rappel: le mini = +1.0, le max = -1.0)
  2930. speedbrake = 1.0;
  2931. }
  2932. else
  2933. {
  2934. throttle = 0.7; // gaz presque au mini (Rappel: le mini = +1.0, le max = -1.0)
  2935. speedbrake = 0.6;
  2936. }
  2937.  
  2938.  
  2939. ailerons= -roulis/10.0; // 1.2 //asservissement des ailerons en fonction de l'angle de roulis (-> ailes à plat)
  2940.  
  2941. if(H0 < hauteur_mini_autopilot)
  2942. {
  2943.  
  2944. raz_bit(&flags, bit_FG_AP);
  2945.  
  2946. // -------------------- AZIMUT -----------------------------------------------------------------
  2947.  
  2948.  
  2949. float op1;
  2950. float delta_AZM;
  2951.  
  2952. find_sens_approche();
  2953. affi_sens_APP(); // en haut à droite
  2954.  
  2955. if (sens_app_effectif == sens_AB)
  2956. {
  2957. op1 = orient_pisteAB;
  2958. delta_AZM = op1 -GPS_azimut_ptB;
  2959. }
  2960. if (sens_app_effectif == sens_BA)
  2961. {
  2962. op1 = orient_pisteBA;
  2963. delta_AZM = op1 -GPS_azimut_ptA;
  2964. }
  2965.  
  2966. auto_rudder_attero(delta_AZM);
  2967.  
  2968. // -------------------- VITESSE ----------------------------------------------------------------
  2969.  
  2970. //if(is_in(vitesse, 120, 100) == 1) { brake_left =0.6;};
  2971.  
  2972.  
  2973.  
  2974. if(vitesse < 100)
  2975. {
  2976. //brake_left =0.5;
  2977. throttle = 1.0; // gaz au mini
  2978. if (read_bit(flags, bit_att_short) == 1)
  2979. {
  2980. brake_left =1.0;// freine à fond
  2981. reverser1 = 1;
  2982. reverser2 = 1;
  2983. throttle = -0.95;; // gaz au max pour freiner énergiquement avec les reverses
  2984. }
  2985.  
  2986.  
  2987. } // pose le train avant ce qui permet le guidage au sol en lacet
  2988.  
  2989. if(vitesse < 80)
  2990. {
  2991. speedbrake = 1.0;
  2992. brake_left =0.8;// freine
  2993. }
  2994.  
  2995. if ((vitesse < 40) && (read_bit(flags, bit_att_short) == 1) )
  2996. {
  2997. throttle = 1.0; // gaz au mini
  2998. brake_left =1.0; // freine très fortement
  2999. }
  3000.  
  3001. if(vitesse < 30)
  3002. {
  3003. reverser1 = 0;
  3004. reverser2 = 0;
  3005. throttle = 1.0; // gaz au mini
  3006. }
  3007. }
  3008.  
  3009. brake_right = brake_left;
  3010. }
  3011.  
  3012.  
  3013.  
  3014. void roulage()
  3015. // sur taxiways
  3016. // on passera en boucle dans cette fonction
  3017. {
  3018. reverser1 = 0;
  3019. reverser2 = 0;
  3020. throttle = 0.80; // 1.0 -> ralentit; 0 -> mi-gaz ; -1.0 -> plein gaz
  3021.  
  3022. msg_to_send = "Roulage";
  3023. brake_left =0;
  3024. brake_right =0;
  3025. flaps = 0;
  3026.  
  3027. if (vitesse < 15) {throttle -= 0.01;}
  3028. if (vitesse > 15) {throttle += 0.01;}
  3029.  
  3030. borne_in(&throttle, 0.5, 1.0);
  3031.  
  3032. calcul_erreur_position();
  3033. }
  3034.  
  3035. // =============================================================================================================================
  3036.  
  3037.  
  3038.  
  3039.  
  3040. void affi_localizer(float valeur_i)
  3041. {
  3042. //ILS (maintenant GPS) dans le plan horizontal; affiche l'erreur de position par rapport à l'axe de la piste
  3043.  
  3044.  
  3045. //affi_float_test(valeur_i, 110, 3, JAUNE, NOIR); // pour test
  3046.  
  3047. uint16_t y1 = HA_y0-HA_h-14;
  3048.  
  3049. uint16_t couleur1 = ROSE;
  3050.  
  3051. loc = HA_x0 + valeur_i; // sachant que HA_x0 par définition est le centre de l'horizon artificiel (et pas le bord de droite)
  3052.  
  3053. if ( loc < (HA_x0-HA_w+5)) {loc = HA_x0-HA_w+5; couleur1 = GRIS;}
  3054. if ( loc > (HA_x0+HA_w-5)) {loc= HA_x0+HA_w-5; couleur1 = GRIS;}
  3055.  
  3056.  
  3057. TFT480.fillRect(HA_x0-HA_w, y1, 2*HA_w, 9, GRIS_TRES_FONCE);
  3058. TFT480.drawLine(HA_x0, y1-5, HA_x0, y1+5, BLANC);
  3059.  
  3060. affi_indexV(loc, y1, 1, couleur1); // petit triangle rose en haut, se déplaçant horizontalement
  3061.  
  3062. memo_loc=loc;
  3063. }
  3064.  
  3065.  
  3066.  
  3067. void affi_index_lateral(uint16_t position_i)
  3068. {
  3069. // petits triangles roses de chaque côtés du PFD
  3070. // (à mi-hauteur du PFD si =0)
  3071.  
  3072. uint16_t x1 = 75;
  3073. uint16_t x2 = 332;
  3074.  
  3075. uint16_t position_V = HA_y0 - position_i;
  3076.  
  3077. TFT480.fillRect(x1, 30, 9, 2*HA_h, GRIS_TRES_FONCE); // efface
  3078. TFT480.fillRect(x2, 30, 9, 2*HA_h, GRIS_TRES_FONCE); // efface
  3079.  
  3080. TFT480.drawRect(x1, HA_y0, 12, 5, BLANC);
  3081. TFT480.drawRect(x2, HA_y0, 12, 5, BLANC);
  3082.  
  3083. uint16_t couleur1 = ROSE;
  3084. if ( position_V < (HA_y0-HA_h+5)) {position_V = HA_y0-HA_h+5; couleur1 = GRIS;}
  3085. if ( position_V > (HA_y0+HA_h-5)) {position_V = HA_y0+HA_h-5; couleur1 = GRIS;}
  3086.  
  3087. affi_indexH(x1, position_V, 1, couleur1);
  3088. affi_indexH(x2+8, position_V, -1, couleur1);
  3089. }
  3090.  
  3091.  
  3092.  
  3093. void trace_arc_gradu()
  3094. {
  3095. //arc gradué en haut au centre, indiquant la valeur de l'inclinaison
  3096.  
  3097. float angle;
  3098. //Draw_arc_elliptique(HA_x0, 120, 120, 80, 0.6, 2.6, BLANC);
  3099.  
  3100.  
  3101. for(uint8_t n=0; n<9; n++ )
  3102. {
  3103. angle =30+ n*15; // 1 tiret tous les 15 degrés
  3104. float pourcent = 0.9;
  3105. if (((n+2)%2) == 0) {pourcent = 0.8;}
  3106.  
  3107. affi_rayon1(HA_x0, HA_y0+10, 110, degTOrad(angle), pourcent, BLANC, false); // tirets de graduation
  3108. }
  3109. }
  3110.  
  3111.  
  3112.  
  3113. void rotation1()
  3114. {
  3115. // consigne de cap
  3116. // acquisition de l'encodeur pas à pas (1)
  3117. if ( millis() - TEMPO >= timer1 )
  3118. {
  3119. timer1 = millis();
  3120. bool etat = digitalRead(rot1b);
  3121. if(etat == 0) { hdg1+=1;} else { hdg1-=1;}
  3122. if (hdg1<0){hdg1=359;}
  3123.  
  3124. if (hdg1>359){hdg1=0;}
  3125. }
  3126. }
  3127.  
  3128.  
  3129.  
  3130. void rotation2()
  3131. {
  3132. // consigne d'altitude
  3133. // acquisition de l'encodeur pas à pas (2)
  3134. if ( millis() - TEMPO >= timer2 )
  3135. {
  3136. timer2 = millis();
  3137. bool etat = digitalRead(rot2b);
  3138. if(etat == 0) { asel1+=1; } else { asel1-=1; }
  3139. if (asel1<1){asel1=1;} // 100 pieds -> 30m
  3140. if (asel1>600){asel1=600;}
  3141. }
  3142. }
  3143.  
  3144.  
  3145.  
  3146. void init_SDcard()
  3147. {
  3148. String s1;
  3149.  
  3150. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  3151. TFT480.setTextColor(BLANC, NOIR);
  3152. TFT480.setFreeFont(FF1);
  3153.  
  3154. uint16_t y=0;
  3155.  
  3156. TFT480.drawString("PRIMARY FLIGHT DISPLAY", 0, y);
  3157. y+=20;
  3158.  
  3159. s1="version " + version;
  3160. TFT480.drawString(s1, 0, y);
  3161.  
  3162. y+=40;
  3163. TFT480.setTextColor(VERT, NOIR);
  3164. TFT480.drawString("Init SDcard", 0, y);
  3165. y+=20;
  3166.  
  3167.  
  3168. if(!SD.begin())
  3169. {
  3170. TFT480.drawString("Card Mount Failed", 0, y);
  3171. delay (2000);
  3172. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  3173. return;
  3174. }
  3175.  
  3176.  
  3177. uint8_t cardType = SD.cardType();
  3178.  
  3179. if(cardType == CARD_NONE)
  3180. {
  3181. TFT480.drawString("No SDcard", 0, y);
  3182. delay (2000);
  3183. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  3184. return;
  3185. }
  3186.  
  3187. flag_SDcardOk=1;
  3188.  
  3189. TFT480.drawString("SDcard Type: ", 0, y);
  3190. if(cardType == CARD_SD) {TFT480.drawString("SDSC", 150, y);}
  3191. else if(cardType == CARD_SDHC) {TFT480.drawString("SDHC", 150, y);}
  3192.  
  3193. y+=20;
  3194.  
  3195. uint32_t cardSize = SD.cardSize() / (1024 * 1024);
  3196. s1=(String)cardSize + " GB";
  3197. TFT480.drawString("SDcard size: ", 0, y);
  3198. TFT480.drawString(s1, 150, y);
  3199.  
  3200. // listDir(SD, "/", 0);
  3201.  
  3202. //Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  3203. //Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
  3204.  
  3205. delay (1000);
  3206. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  3207.  
  3208. }
  3209.  
  3210.  
  3211. void efface_sprite_trajectoire()
  3212. {
  3213. SPR_trajectoire.fillSprite(TFT_BLACK);
  3214. SPR_trajectoire.drawString("pente 5%", 170, 1 );
  3215. }
  3216.  
  3217.  
  3218.  
  3219.  
  3220. void init_sprites()
  3221. {
  3222. // sprites représentant les lettres 'N' 'S' 'E' 'O' qui seront affichées sur un cercle, inclinées donc.
  3223.  
  3224. SPR_E.setFreeFont(FF1);
  3225. SPR_E.setTextColor(JAUNE);
  3226. SPR_E.createSprite(SPR_W, SPR_H);
  3227. SPR_E.setPivot(SPR_W/2, SPR_H/2); // Set pivot relative to top left corner of Sprite
  3228. SPR_E.fillSprite(GRIS_TRES_FONCE);
  3229. SPR_E.drawString("E", 2, 1 );
  3230.  
  3231. SPR_N.setFreeFont(FF1);
  3232. SPR_N.setTextColor(JAUNE);
  3233. SPR_N.createSprite(SPR_W, SPR_H);
  3234. SPR_N.setPivot(SPR_W/2, SPR_H/2);
  3235. SPR_N.fillSprite(GRIS_TRES_FONCE);
  3236. SPR_N.drawString("N", 2, 1 );
  3237.  
  3238. SPR_O.setFreeFont(FF1);
  3239. SPR_O.setTextColor(JAUNE);
  3240. SPR_O.createSprite(SPR_W, SPR_H);
  3241. SPR_O.setPivot(SPR_W/2, SPR_H/2);
  3242. SPR_O.fillSprite(GRIS_TRES_FONCE);
  3243. SPR_O.drawString("W", 2, 1 );
  3244.  
  3245. SPR_S.setFreeFont(FF1);
  3246. SPR_S.setTextColor(JAUNE);
  3247. SPR_S.createSprite(SPR_W, SPR_H);
  3248. SPR_S.setPivot(SPR_W/2, SPR_H/2);
  3249. SPR_S.fillSprite(GRIS_TRES_FONCE);
  3250. SPR_S.drawString("S", 2, 1 );
  3251.  
  3252. SPR_trajectoire.setFreeFont(FF1);
  3253. SPR_trajectoire.setTextColor(JAUNE);
  3254. SPR_trajectoire.createSprite(292, 88);
  3255. efface_sprite_trajectoire();
  3256. }
  3257.  
  3258.  
  3259.  
  3260. void init_Leds() // pour l'affichage des données, voir la fonction "affi_data_piste()"
  3261. {
  3262. uint16_t x0 = 464;
  3263. uint16_t y0 = 0;
  3264. uint16_t xi=x0;
  3265. uint16_t yi=y0;
  3266.  
  3267.  
  3268. Led1.init(xi,yi, 10, 10);
  3269. Led1.set_couleur(ROUGE);
  3270. Led1.allume();
  3271.  
  3272. yi+=10;
  3273.  
  3274. Led2.init(xi,yi, 10, 10);
  3275. Led2.set_couleur(JAUNE);
  3276. Led2.allume();
  3277.  
  3278. yi+=10;
  3279.  
  3280. Led3.init(xi,yi, 10, 10);
  3281. Led3.set_couleur(VERT);
  3282. Led3.allume();
  3283.  
  3284. yi+=10;
  3285.  
  3286. Led4.init(xi,yi, 10, 10);
  3287. Led4.set_couleur(BLEU);
  3288. Led4.allume();
  3289.  
  3290. yi+=10;
  3291.  
  3292. Led5.init(xi,yi, 10, 10);
  3293. Led5.set_couleur(VIOLET1);
  3294. Led5.allume();
  3295.  
  3296. delay(100);
  3297.  
  3298. }
  3299.  
  3300.  
  3301.  
  3302. void int16_to_array(int16_t valeur_i)
  3303. {
  3304. // prépare la chaine de caract à zéro terminal pour l'envoi
  3305. // Remarque : 2^16 -1 = 65535 -> 5 caractères)
  3306.  
  3307. String s1= (String) valeur_i;
  3308. uint8_t len1 = s1.length();
  3309. for(int n=0; n<len1; n++)
  3310. {
  3311. var_array16[n]=s1[n];
  3312. }
  3313. var_array16[len1]=0; // zéro terminal -> chaine C
  3314. }
  3315.  
  3316.  
  3317.  
  3318. void int32_to_array(int32_t valeur_i)
  3319. {
  3320. // prépare la chaine de caract à zéro terminal pour l'envoi
  3321. // Remarque : 2^32 -1 = 4294967295 -> 10 caractères
  3322.  
  3323. String s1= (String) valeur_i;
  3324. uint8_t len1 = s1.length();
  3325. for(int n=0; n<len1; n++)
  3326. {
  3327. var_array32[n]=s1[n];
  3328. }
  3329. var_array32[len1]=0; // zéro terminal -> chaine C
  3330. }
  3331.  
  3332.  
  3333. void string_to_array(String str_i)
  3334. {
  3335. // prépare la chaine de caract à zéro terminal pour l'envoi
  3336.  
  3337. uint8_t len1 = str_i.length();
  3338. for(int n=0; n<len1; n++)
  3339. {
  3340. var_array32[n]=str_i[n];
  3341. }
  3342. var_array32[len1]=0; // zéro terminal -> chaine C
  3343. }
  3344.  
  3345.  
  3346. void annule_tout()
  3347. {
  3348. msg_to_send = "RAZ";
  3349. raz_bit(&flags, bit_decollage);
  3350. raz_bit(&flags, bit_atterrissage);
  3351. raz_bit(&flags, bit_FG_AP);
  3352. raz_bit(&flags, bit_autoland);
  3353. raz_bit(&flags, bit_route);
  3354. raz_bit(&flags, bit_nav_to_piste);
  3355. raz_bit(&flags, bit_nav_to_pti);
  3356. raz_bit(&flags, bit_circling);
  3357. raz_bit(&flags, bit_nav_to_ptAA);
  3358. raz_bit(&flags, bit_nav_to_ptBB);
  3359. raz_bit(&flags, bit_rudder_decol);
  3360. raz_bit(&flags, bit_roulage);
  3361. raz_bit(&flags, bit_att_short);
  3362.  
  3363. //gear_down = 0;
  3364. //raz_bit(&flags, bit_rudder_attero);
  3365. locks_type = "ALT";
  3366. speed_ctrl=false;
  3367. trim_elevator=0;
  3368. throttle = 1.0;
  3369. reverser1 = 0;
  3370. reverser2 = 0;
  3371. speedbrake=0;
  3372. landing_light1=0;
  3373. landing_light2=0;
  3374.  
  3375. }
  3376.  
  3377.  
  3378.  
  3379.  
  3380. void setup()
  3381. {
  3382. Serial.begin(38400); // 19200
  3383.  
  3384. locks_type ="ALT";
  3385. raz_bit(&flags, bit_FG_AP); // pas d'engagement de l'autopilot de FlightGear à ce stade
  3386. WiFi.persistent(false);
  3387. WiFi.softAP(ssid, password); // Crée un réseau WiFi en mode privé (indépendant de celui de la box internet...)
  3388. IPAddress IP = WiFi.softAPIP();
  3389.  
  3390.  
  3391. server.on("/switch", HTTP_GET, [](AsyncWebServerRequest *request) // lecture des boutons de l'ESP du module SW
  3392. {
  3393. // attention: ce code est appelé par une interruption WiFi qui intervient hors timing. Donc pas d'affichage ici !!
  3394.  
  3395. argument_recu1 = request->arg("sw1"); // réception de l'argument n°1 de la requête
  3396. switches=argument_recu1; // réception des boutons du module SW
  3397. v_switches=switches.toInt();
  3398. flag_traiter_SW=1; //positionne ce drapeau afin que le traitement se fasse dans le timming général, pas ici !
  3399.  
  3400. int16_to_array(0);
  3401.  
  3402. argument_recu2 = request->arg("pot1"); // réception de l'argument n°2 de la requête
  3403. potar1=argument_recu2; // = "0".."255"
  3404. v_potar1 = -128 + potar1.toInt(); // centre autour de 0
  3405. f_potar1 = v_potar1 / 10.0; // -> f_potar1= [-12.7 ... +12.7 ]
  3406.  
  3407. float valeur1 = v_potar1 * v_potar1; // au carré pour avoir une bonne précision aux faibles débattements,
  3408. //et une bonne réponse à fond (taxi au sol)
  3409.  
  3410. //if (abs(v_potar1) > 100) {raz_bit(&flags, bit_rudder_attero);} // afin de pouvoir manoeuvrer sur les taxiways}
  3411.  
  3412.  
  3413. if (v_potar1<0) {valeur1 = -valeur1;} // because un carré est toujours positif, or on veut conserver le signe
  3414. rudder_manuel = valeur1 / 20000.0; // 10000.0 détermine la sensibilité de la gouverne de direction (lacet)
  3415.  
  3416.  
  3417. //cet array because la fonction "request->send_P()" n'accèpte pas directement le string
  3418. //rappel :
  3419. //void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
  3420. //void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
  3421.  
  3422. request->send_P(200, "text/plain", var_array16); // envoie comme réponse au client
  3423. });
  3424.  
  3425.  
  3426. server.on("/hdg", HTTP_GET, [](AsyncWebServerRequest *request) // consigne de cap
  3427. {
  3428. // attention: ce code est appelé par une interruption WiFi qui intervient hors timing. Donc pas d'affichage ici !!
  3429.  
  3430. argument_recu1 = request->arg("a1"); // reception de l'argument n°1 de la requête
  3431. num_bali=argument_recu1.toInt();
  3432.  
  3433. argument_recu2 = request->arg("swND");
  3434. flag_traiter_SW=1; //positionne ce drapeau afin que le traitement se fasse dans le timming général, pas ici !
  3435.  
  3436. v_switches_ND = argument_recu2.toInt();
  3437. switches_ND = String(v_switches_ND);
  3438.  
  3439.  
  3440. int16_to_array(hdg1);
  3441.  
  3442. //cet array because la fonction "request->send_P()" n'accèpte pas directement le string
  3443. //rappel :
  3444. //void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
  3445. //void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
  3446.  
  3447. request->send_P(200, "text/plain", var_array16); // envoie hdg1 comme réponse au client
  3448. });
  3449.  
  3450. // réponses aux requêtes :
  3451. // VOIR la fonction "void interroge_WiFi()" dans le code du ND (l'affichage de la carte...)
  3452. // pour la réception des données qui suivent
  3453. server.on("/cap", HTTP_GET, [](AsyncWebServerRequest *request)
  3454. {
  3455. int16_to_array(cap); // prépare la chaine de caract à zéro terminal pour l'envoi
  3456. request->send_P(200, "text/plain", var_array16); // envoie réponse au client
  3457. });
  3458.  
  3459.  
  3460. server.on("/latitude", HTTP_GET, [](AsyncWebServerRequest *request) // latitude de l'avion
  3461. {
  3462. int32_t lati1 = (int32_t) (lat_avion * 10000.0);
  3463.  
  3464. int32_to_array(lati1);
  3465. request->send_P(200, "text/plain", var_array32); // envoie réponse au client
  3466. });
  3467.  
  3468.  
  3469. server.on("/longitude", HTTP_GET, [](AsyncWebServerRequest *request) // longitude de l'avion
  3470. {
  3471. int32_t longi1 = (int32_t) (lon_avion * 10000.0);
  3472.  
  3473. int32_to_array(longi1);
  3474. request->send_P(200, "text/plain", var_array32); // envoie réponse au client
  3475. });
  3476.  
  3477.  
  3478. server.on("/hauteur", HTTP_GET, [](AsyncWebServerRequest *request) // hauteur de l'avion / sol
  3479. {
  3480. int32_t haut1 = (int32_t) (hauteur_AAL * 10.0);
  3481.  
  3482. int32_to_array(haut1);
  3483. request->send_P(200, "text/plain", var_array32); // envoie réponse au client
  3484. });
  3485.  
  3486.  
  3487. server.on("/flags", HTTP_GET, [](AsyncWebServerRequest *request) // paramètres divers PFD -> ND & MCDU
  3488. {
  3489. argument_recu3 = request->arg("btMCDU"); // valeur reçue du module MCDU en tant qu'argument
  3490.  
  3491. v_bt_MCDU = argument_recu3.toInt();
  3492. flag_traiter_MCDU=1;
  3493.  
  3494. int32_to_array(flags); // valeur à envoyer
  3495. request->send_P(200, "text/plain", var_array32); // envoie réponse au client
  3496. });
  3497.  
  3498. server.on("/msg", HTTP_GET, [](AsyncWebServerRequest *request) // paramètres divers PFD -> ND & MCDU
  3499. {
  3500. string_to_array(msg_to_send); // valeur à envoyer
  3501. request->send_P(200, "text/plain", var_array32); // envoie réponse au client
  3502. msg_to_send="null";
  3503. });
  3504.  
  3505.  
  3506. server.on("/num_bali", HTTP_GET, [](AsyncWebServerRequest *request) // PFD -> MCDU
  3507. {
  3508. int32_t num1 = (int32_t) num_bali;
  3509. int32_to_array(num1);
  3510. request->send_P(200, "text/plain", var_array32); // envoie réponse au client
  3511. });
  3512.  
  3513.  
  3514. server.on("/Pos_XY", HTTP_GET, [](AsyncWebServerRequest *request) //pour recevoir les requêtes émises par le module positionneur
  3515. {
  3516. argument_recu1 = request->arg("X1"); // réception de l'argument n°1 de la requête
  3517. x_1=argument_recu1.toInt();
  3518.  
  3519. argument_recu2 = request->arg("Y1"); // réception de l'argument n°2 de la requête
  3520. y_1=argument_recu2.toInt();
  3521.  
  3522. argument_recu2 = request->arg("X2"); // réception de l'argument n°3 de la requête
  3523. x_2=argument_recu2.toInt();
  3524.  
  3525. argument_recu2 = request->arg("Y2"); // réception de l'argument n°4 de la requête
  3526. y_2=argument_recu2.toInt();
  3527.  
  3528. int32_t num1 = 0;
  3529. int32_to_array(num1);
  3530. request->send_P(200, "text/plain", var_array32); // envoie réponse (0) au client
  3531. });
  3532.  
  3533.  
  3534. server.begin();
  3535.  
  3536.  
  3537. pinMode(bouton1, INPUT);
  3538. pinMode(bouton2, INPUT);
  3539.  
  3540. //pinMode(led1, OUTPUT);
  3541.  
  3542. pinMode(rot1a, INPUT_PULLUP);
  3543. pinMode(rot1b, INPUT_PULLUP);
  3544. pinMode(rot2a, INPUT_PULLUP);
  3545. pinMode(rot2b, INPUT_PULLUP);
  3546.  
  3547. attachInterrupt(rot1a, rotation1, RISING);
  3548. attachInterrupt(rot2a, rotation2, RISING);
  3549.  
  3550. TFT480.init();
  3551. TFT480.setRotation(3); // 0..3 à voir, suivant disposition
  3552. TFT480.fillScreen(TFT_BLACK);
  3553.  
  3554. init_SDcard();
  3555.  
  3556. init_sprites();
  3557.  
  3558. delay(100);
  3559.  
  3560.  
  3561.  
  3562. TFT480.setTextColor(NOIR, BLANC);
  3563.  
  3564. //TFT480.setFreeFont(FF19);
  3565.  
  3566.  
  3567.  
  3568. //TFT480.fillRect(48, 0, 6, 100, 0xFFE0);
  3569.  
  3570. // TFT480.fillRect(0, 0, 479, 30, NOIR);
  3571. TFT480.setTextColor(BLANC, NOIR);
  3572. TFT480.setFreeFont(FF19);
  3573. String s1 = "PFD v";
  3574. s1+= version;
  3575. //TFT480.drawString(s1, 70, 3);
  3576.  
  3577. Ay_actu=120;
  3578. By_actu=120;
  3579.  
  3580. altitude_GPS =0;
  3581. vitesse =0;
  3582. roulis =0;
  3583. tangage =0;
  3584. cap=0;
  3585. vspeed=0; // vitesse verticale
  3586.  
  3587. //vor_frq=123500;
  3588.  
  3589. //vor_dst=1852*102; // 102km
  3590. //vor_actual_deg=45;
  3591. //vor_actual=45.0 * 100.0;
  3592. // affichages();
  3593.  
  3594. bouton1_etat = digitalRead(bouton1);
  3595. memo_bouton1_etat = bouton1_etat;
  3596.  
  3597. bouton2_etat = digitalRead(bouton2);
  3598. memo_bouton2_etat = bouton2_etat;
  3599. if (bouton2_etat==0)
  3600. {
  3601. mode_affi_hauteur = AAL;
  3602. affi_mode_affi_hauteur();
  3603. }
  3604. if (bouton2_etat==1)
  3605. {
  3606. mode_affi_hauteur = ASL;
  3607. affi_mode_affi_hauteur();
  3608. }
  3609.  
  3610. init_FG_bali();
  3611.  
  3612. //init_affi_autopilot();
  3613. //affi_indicateurs();
  3614.  
  3615.  
  3616. voyant_L.init(0,0,30,20);
  3617. voyant_L.caract1 ='L';
  3618. voyant_L.caract2 =' ';
  3619.  
  3620. voyant_G.init(35,0,30,20);
  3621. voyant_G.caract1 ='G';
  3622. voyant_G.caract2 =' ';
  3623.  
  3624. voyant_APP.init(70,0,30,20);
  3625. voyant_APP.caract1 ='A';
  3626. voyant_APP.caract2 ='P';
  3627.  
  3628. voyant_route.init(105,0,30,20);
  3629. voyant_route.caract1 ='G';
  3630. voyant_route.caract2 ='T';
  3631.  
  3632. voyant_RD.init(145,0,30,20);
  3633. voyant_RD.caract1 ='R';
  3634. voyant_RD.caract2 ='D';
  3635.  
  3636. voyant_ATT.init(177,0,15,20);
  3637. voyant_ATT.caract1 ='A';
  3638.  
  3639. init_Leds(); // et les affiche en bordure de l'écran en haut à droite
  3640.  
  3641. annule_tout();
  3642.  
  3643. init_affi_HA(); // affiche juste l'horizon artificiel (ciel + terre) sans les graduations ni les échelles
  3644.  
  3645. delay(100);
  3646.  
  3647. //find_END_RWY_angl();// pour le 1er décollage, on ne repassera plus ici
  3648. //prepare_decollage();
  3649. //TFT480.fillRect(0, 0, 479, 319, BLEU); // pour test parties libres
  3650.  
  3651. //msg_to_send = "RESTART"; // pour reseter le MCDU
  3652. // ^ ne marche pas ICI parce que lorsque le PFD est resété les liaisons wifi sont cassées
  3653. // et donc les clients ne reçoivent plus les messages
  3654. // toutefois on notera que lorsque l'on reprogramme le PFD, son serveur WIFI reste en fonction... et les liaisons subsistent
  3655.  
  3656. //efface_cadre_bas(NOIR); affi_approche(); while(1); // pour test
  3657.  
  3658. affi_distance_piste(); // cadre en bas
  3659. affi_Airport(); // sous le cadre en bas
  3660. }
  3661.  
  3662.  
  3663.  
  3664. uint8_t p1;
  3665.  
  3666. int32_t number = 0;
  3667.  
  3668.  
  3669. String s1;
  3670. String s2;
  3671.  
  3672.  
  3673. void acquisitions()
  3674. {
  3675. // cette fonction reçoit les données de Flightgear par la liaison USB, voir le fichier "hardware4.xml" pour le protocole
  3676.  
  3677. // Remarque : les données des autres modules (ND et SW) sont reçues par WiFi,
  3678. // voir les sous-fonctions ("server.on(...)") dans la fonction setup()
  3679.  
  3680. char buf[50];
  3681. int32_t valeur;
  3682. TFT480.setFreeFont(FM9);
  3683. TFT480.setTextColor(VERT, NOIR);
  3684.  
  3685. if(Serial.available() > 14) // 14
  3686. {
  3687. parametre="";
  3688. s1="";
  3689. char octet_in;
  3690.  
  3691. while(octet_in != '=')
  3692. {
  3693. octet_in = Serial.read();
  3694. if(octet_in != '=') {parametre += octet_in; }
  3695. }
  3696. while(octet_in != '\n')
  3697. {
  3698. octet_in = Serial.read();
  3699. if(octet_in != '\n') {s1 += octet_in; }
  3700. }
  3701.  
  3702.  
  3703. if(parametre == "joystick1" )
  3704. {
  3705. s1.toCharArray(buf, 50);
  3706. valeur = atol(buf);
  3707. joystick1 = (float)valeur / 1000.0;
  3708. data_ok |= 1; // positionne bit0
  3709. }
  3710.  
  3711.  
  3712. if(parametre == "alti" )
  3713. {
  3714. /*
  3715.  ALTITUDE GPS (et pas 'altimetre', volontairement, voir le fichier hardware4.xml)
  3716.  voir la ligne '<node>/instrumentation/gps/indicated-altitude-ft</node>'
  3717.  marre des QFE, QNH, AGL, ASFC, AMSL, STD...
  3718.  j'ai joué avec tout ça, mais finalement je décide d'utiliser le GPS,
  3719.  ce qui est contraire aux recommandations aéronautiques,
  3720.  sans doute parce que le jour où le GPS mondial viendrait à tomber en panne, c'est 275643 avions qui iraient se poser
  3721.  dans le Triangle des Bermudes.
  3722.  mais ici on est dans FlightGear, on ne risque rien !
  3723.  Et puis on a Galiléo et le système EGNOS développé à l'origine par le cnes puis sur la planète B612 à Toulouse (ESSP)...
  3724. */
  3725. s1.toCharArray(buf, 50);
  3726.  
  3727. valeur = atol(buf);
  3728. altitude_GPS_float = (float)valeur / 1000.0;
  3729. altitude_GPS = valeur/1000; // integer
  3730. hauteur_AAL = altitude_GPS - liste_bali[num_bali].altitude;
  3731.  
  3732. data_ok |= 1<<1; // positionne bit1
  3733. }
  3734.  
  3735. if(parametre == "gnd_elv" ) // hauteur de la surface du terrain situé sous l'avion
  3736. {
  3737. s1.toCharArray(buf, 50);
  3738. gnd_elv = atol(buf);
  3739. if (gnd_elv <0) {gnd_elv =0;}
  3740. data_ok |= 1<<2; // positionne bit2
  3741. }
  3742.  
  3743.  
  3744. if(parametre == "alti_agl" ) // hauteur de l'avion par rapport au terrain en dessous
  3745. // (= altitude-agl-ft c.a.d "altitude above graound level")
  3746. {
  3747. s1.toCharArray(buf, 50);
  3748. alti_agl = atol(buf);
  3749. if (alti_agl <0) {alti_agl =0;}
  3750. data_ok |= 1<<3; // positionne bit3
  3751. }
  3752.  
  3753.  
  3754.  
  3755. if(parametre == "speed" )
  3756. {
  3757. s1.toCharArray(buf, 50);
  3758. vitesse = atol(buf);
  3759. data_ok |= 1<<4; // positionne bit4
  3760. }
  3761.  
  3762. if(parametre == "pitch" )
  3763. {
  3764. //char buf[50];
  3765. s1.toCharArray(buf, 50);
  3766. tangage = atol(buf);
  3767. data_ok |= 1<<5; // positionne bit5
  3768. }
  3769.  
  3770. if(parametre == "roll" )
  3771. {
  3772. s1.toCharArray(buf, 50);
  3773. roulis = atol(buf);
  3774. data_ok |= 1<<6; // positionne bit6
  3775. }
  3776.  
  3777.  
  3778.  
  3779. if(parametre == "heading" ) // /orientation/heading-deg = cap actuel de l'avion ; ne pas confondre avec HDG bug !
  3780. {
  3781. s1.toCharArray(buf, 50);
  3782. valeur = atol(buf);
  3783. cap= (float) valeur / 100.0;
  3784. data_ok |= 1<<7; // positionne bit7
  3785. }
  3786.  
  3787.  
  3788. if(parametre == "vspeed" )
  3789. {
  3790. s1.toCharArray(buf, 50);
  3791. vspeed = atol(buf);
  3792. data_ok |= 1<<8; // positionne bit8
  3793. }
  3794.  
  3795.  
  3796. if(parametre == "latitude" )
  3797. {
  3798. s1.toCharArray(buf, 50);
  3799. valeur = atol(buf);
  3800. lat_avion = (float)valeur / 100000.0;
  3801. data_ok |= 1<<9; // positionne bit9
  3802. }
  3803.  
  3804.  
  3805. if(parametre == "longitude" )
  3806. {
  3807. s1.toCharArray(buf, 50);
  3808. valeur = atol(buf);
  3809. lon_avion = (float)valeur / 100000.0;
  3810. data_ok |= 1<<10; // positionne bit10
  3811. }
  3812.  
  3813. nb_acqui=11; // erreur non permise !!! ne pas oublier qu le compte commence à 0, et pas à 1
  3814. }
  3815.  
  3816. delay(3); // 3 important sinon ne recevra pas la totalité des données (qui se fait en plusieurs passes)
  3817.  
  3818.  
  3819. ////// pour test
  3820. ////TFT480.drawString("data= ", 90, 50);
  3821. ////s2= (String) data_ok;
  3822. ////TFT480.fillRect(140,50, 50, 15, TFT_BLACK);
  3823. ////TFT480.drawString(s2, 150, 50);
  3824.  
  3825. }
  3826.  
  3827.  
  3828.  
  3829. void data_out()
  3830. {
  3831. // à destination de FlightGear par la liaison série USB
  3832. // voir le fichier "hardware4.xml" pour le protocole et les paramètres
  3833.  
  3834. Serial.print(hdg1); // consigne de Cap -> autopilot
  3835. Serial.print(',');
  3836.  
  3837. Serial.print(asel1); // consigne d'altitude -> autopilot
  3838. Serial.print(',');
  3839.  
  3840.  
  3841. uint16_t v3 = landing_light1;
  3842. Serial.print(v3);
  3843. Serial.print(',');
  3844.  
  3845. uint16_t v4 = landing_light2;
  3846. Serial.print(v4);
  3847. Serial.print(',');
  3848.  
  3849. uint16_t v5 = target_speed;
  3850. Serial.print(v5); // écrit la consigne de vitesse (target_speed)
  3851. Serial.print(',');
  3852.  
  3853. uint16_t v6 = hauteur_mini_autopilot;
  3854. Serial.print(v6); // écrit la hauteur minimum d'engagement du pilote auto de Flightgear (par défaut = 300)
  3855. Serial.print(',');
  3856.  
  3857. float v7 = ailerons;
  3858. Serial.print(v7);
  3859. Serial.print(',');
  3860.  
  3861. float v8 = rudder; // position de la gouverne de direction (lacet)
  3862. Serial.print(v8);
  3863. Serial.print(',');
  3864.  
  3865. uint8_t v9 = view_number;
  3866. Serial.print(v9);
  3867. Serial.print(',');
  3868.  
  3869. float v10 = climb_rate; // vertical speed en fps (feet per second)
  3870. // Note : correspond au paramètre <node>/autopilot/settings/target-climb-rate-fps</node>
  3871. Serial.print(v10);
  3872. Serial.print(',');
  3873.  
  3874. Serial.print(locks_type); // "ALT" ou "VS"
  3875. Serial.print(',');
  3876. /* Note : correspond au paramètre suivant dans le fichier 'harware4.xml' :
  3877. <chunk>
  3878. <name>locks</name>
  3879. <node>/autopilot/locks/altitude</node>
  3880. <type>string</type>
  3881. </chunk>
  3882. */
  3883.  
  3884. if (read_bit(flags, bit_FG_AP) == 1) {AP_status = "AP";} else {AP_status = "";}
  3885. Serial.print(AP_status); // "" ou "AP"
  3886. Serial.print(',');
  3887.  
  3888. Serial.print(speed_ctrl); // boolean : false ou true
  3889. Serial.print(',');
  3890.  
  3891. // j'ai commenté la partie "Elevator" dans le fichier joystick_0.xml
  3892. // (usr/share/games/flightgear/Input/Joysticks/Local/joystick_0.xml)
  3893. // donc les actions de la fonction tangage sur la gouverne de profondeur passent dorénavant par ici
  3894. // ce qui implique que l'avion n'est pas pilotable si l'ESP32 du PFD n'est pas en fonction
  3895. // l'avantage est qu'il devient possible d'agir sur le tangage lors des phases de décollage et d'attérrissage auto.
  3896.  
  3897.  
  3898. /*
  3899. if ((read_bit(flags, bit_FG_AP) == 1) || (read_bit(flags, bit_rudder_decol) == 1))
  3900. {
  3901. elevator = trim_elevator; // uniquement auto
  3902. }
  3903. else
  3904. {
  3905. elevator = -1.0 * (joystick1/2.0); // on garde la main
  3906. }
  3907. */
  3908. elevator = trim_elevator; // uniquement auto
  3909.  
  3910.  
  3911. Serial.print(elevator);
  3912. Serial.print(',');
  3913.  
  3914. Serial.print(throttle);
  3915. Serial.print(',');
  3916.  
  3917. Serial.print(reverser1);
  3918. Serial.print(',');
  3919.  
  3920. Serial.print(reverser2);
  3921. Serial.print(',');
  3922.  
  3923. Serial.print(flaps);
  3924. Serial.print(',');
  3925.  
  3926. Serial.print(speedbrake);
  3927. Serial.print(',');
  3928.  
  3929. Serial.print(gear_down); // boolean : false ou true
  3930. Serial.print(',');
  3931.  
  3932. Serial.print(brake_left);
  3933. Serial.print(',');
  3934.  
  3935. Serial.print(brake_right);
  3936. Serial.print('\n');
  3937. }
  3938.  
  3939.  
  3940.  
  3941. void affi_nop()
  3942. {
  3943. for(int8_t dy=-2; dy<3; dy++)
  3944. {
  3945. TFT480.drawLine(HA_x0-HA_w, HA_y0-HA_h +dy, HA_x0 +HA_w, HA_y0 +HA_h +dy, ROUGE);
  3946. TFT480.drawLine(HA_x0-HA_w, HA_y0+HA_h +dy, HA_x0 +HA_w, HA_y0 -HA_h +dy, ROUGE);
  3947. }
  3948.  
  3949. //TFT480.fillRect(0, 0, 239, 30, NOIR);
  3950. TFT480.setTextColor(BLANC, ROUGE);
  3951. TFT480.setFreeFont(FF18);
  3952. TFT480.drawString("No Data", HA_x0-40, HA_y0+30);
  3953.  
  3954. }
  3955.  
  3956.  
  3957.  
  3958.  
  3959. void init_affi_autopilot()
  3960. {
  3961.  
  3962. TFT480.setFreeFont(FF1);
  3963. TFT480.setTextColor(JAUNE, GRIS_AF);
  3964.  
  3965. // ALT
  3966.  
  3967. //TFT480.drawString("ALT", x_autopilot, 260, 1);
  3968. //TFT480.drawRoundRect(x_autopilot-4, 255, 45, 42, 5, BLANC);
  3969. }
  3970.  
  3971.  
  3972.  
  3973. void affi_autopilot(uint8_t complet)
  3974. {
  3975. // dans le petit cercle en bas à gauche :
  3976. // affiche HDG (flèche jaune), piste (rectangle bleu), VOR (Nav1, ligne verte)
  3977.  
  3978. uint16_t x0=70; // 70
  3979. uint16_t y0=248; // 255
  3980.  
  3981. TFT480.setFreeFont(FF1);
  3982.  
  3983. TFT480.fillRect(x0, y0+2, 70, 80, NOIR); // efface
  3984.  
  3985. TFT480.drawCircle(x0+35, y0+34, 30, BLANC);
  3986.  
  3987.  
  3988. TFT480.setTextColor(BLANC, NOIR);
  3989. TFT480.drawString("N", x0+30, y0-5);
  3990.  
  3991. TFT480.setTextColor(BLANC, NOIR);
  3992. TFT480.drawString("S", x0+30, y0+60);
  3993.  
  3994. TFT480.setTextColor(BLANC, NOIR);
  3995. TFT480.drawString("E", x0+60, y0+27);
  3996. TFT480.drawString("W", x0, y0+27);
  3997.  
  3998. //uint16_t x1,y1;
  3999.  
  4000.  
  4001. // rectangle bleu, très fin -> orientation ('radial') de l'axe de la piste
  4002. float angle2 = orient_pisteAB;
  4003. affi_rectangle_incline(x0+35, y0+34, 35, 90 + angle2, BLEU_CLAIR);
  4004.  
  4005. if (complet ==1)
  4006. {
  4007. TFT480.setTextColor(JAUNE, NOIR);
  4008. TFT480.drawString("HDG", x0, y0-18);
  4009.  
  4010. String s1 =(String)hdg1;
  4011. TFT480.setTextColor(BLANC, NOIR);
  4012. TFT480.drawString(s1, x0+18, y0+35);
  4013.  
  4014. // flèche jaune = règlage HDG de l'autopilot
  4015. float angle1 = 90-hdg1;
  4016. affi_rayon2(x0+35, y0+34, 0, 30, angle1, JAUNE, 0); // tige de la flèche
  4017. affi_pointe(x0+35, y0+34, 30, angle1, 0.1, JAUNE); // pointe triangulaire en bout de flèche
  4018.  
  4019.  
  4020. }
  4021. else
  4022. {
  4023.  
  4024. if(sens_app_effectif == sens_AB)
  4025. {
  4026. affi_pointe(x0+35, y0+34, 20, 90.0 - angle2, 0.5, JAUNE);
  4027. TFT480.drawString("A->B", 240, 250);
  4028. }
  4029. if(sens_app_effectif == sens_BA)
  4030. {
  4031. affi_pointe(x0+35, y0+34, 20, 270.0 - angle2, 0.5, JAUNE);
  4032. TFT480.drawString("B->A", 240, 250);
  4033. }
  4034. }
  4035.  
  4036. }
  4037.  
  4038.  
  4039.  
  4040.  
  4041.  
  4042.  
  4043. void affi_approche() // grand rectangle en bas avec le tracé de la pente de descente (glide)
  4044. {
  4045.  
  4046. uint16_t x1=90;
  4047. uint16_t x2=140;
  4048. uint16_t x3=310;
  4049. uint16_t x4=350;
  4050.  
  4051. uint16_t y1=240;
  4052. uint16_t y2=310;
  4053.  
  4054. uint16_t x_avion;
  4055. uint16_t y_avion;
  4056.  
  4057. float xF;
  4058. float yF;
  4059. float pente;
  4060.  
  4061.  
  4062. xF = (float)x3 - GPS_distance_piste * (float)(x3-x2) / 10.0;
  4063. yF = (float)y2 - (altitude_GPS - liste_bali[num_bali].altitude) * (float)(y2-y1) / 3000.0;
  4064. x_avion = (uint16_t) xF;
  4065.  
  4066. pente = (float)(y2-y1)/(float)(x3-x2);
  4067.  
  4068. TFT480.drawLine(x1, y1, x2, y1, BLANC); // trace ligne blanche (glide : trajectoire théorique)
  4069. TFT480.drawLine(x2, y1, x3, y2, BLANC); // trace ligne blanche (glide : trajectoire théorique)
  4070. TFT480.drawLine(x3, y2, x4, y2, BLANC); // trace ligne blanche (glide : trajectoire théorique)
  4071. TFT480.setFreeFont(FM9);
  4072. TFT480.setTextColor(BLANC, NOIR);
  4073. TFT480.drawString("glide", x2+10, y2-30);
  4074. affi_autopilot(0);
  4075.  
  4076. //dessine l'avion sur la ligne, avec une trainée jaune comme trace
  4077.  
  4078.  
  4079. if (memo_y_avion >235) // pour ne pas effacer hors cadre
  4080. {
  4081. TFT480.fillCircle(memo_x_avion, memo_y_avion, 5, GRIS_TRES_FONCE); // efface
  4082. SPR_trajectoire.pushSprite(70, 232, TFT_BLACK); // TFT_BLACK -> défini la couleur de transparence
  4083.  
  4084. //TFT480.drawCircle(memo_x_avion, memo_y_avion, 10, JAUNE); // efface
  4085.  
  4086. //TFT480.drawPixel( memo_x_avion, memo_y_avion, JAUNE); // trace la trajectoire réelle en jaune
  4087. }
  4088.  
  4089. if((x_avion > x1)&&(x_avion<=x2))
  4090. {
  4091. //y_avion = y1;
  4092. TFT480.drawLine(x1, y1, x2, y1, BLANC); // trace ligne blanche (trajectoire théorique)
  4093. //TFT480.fillRect(x_avion+1, y_avion, 8, 4, BLEU); // dessine avion
  4094. if (y_avion >235)
  4095. {
  4096. TFT480.fillCircle(x_avion, y_avion, 5, BLEU);
  4097. SPR_trajectoire.drawPixel( x_avion-70, y_avion-232, JAUNE);
  4098.  
  4099. }
  4100. }
  4101.  
  4102. if((x_avion > x2)&&(x_avion<=x3))
  4103. {
  4104. y_avion = (uint16_t) yF;
  4105. TFT480.drawLine(x2, y1, x3, y2, BLANC);
  4106. if (y_avion >235)
  4107. {
  4108. TFT480.fillCircle(x_avion, y_avion, 5, BLEU);
  4109. SPR_trajectoire.drawPixel( x_avion-70, y_avion-232, JAUNE);
  4110.  
  4111. }
  4112. }
  4113.  
  4114. if((x_avion > x3)&&(x_avion<=x4))
  4115. {
  4116. y_avion = y2;
  4117. TFT480.drawLine(x3, y2, x4, y2, BLANC);
  4118. if (y_avion >235)
  4119. {
  4120. TFT480.fillCircle(x_avion, y_avion, 5, BLEU);
  4121. SPR_trajectoire.drawPixel( x_avion-70, y_avion-232, JAUNE);
  4122. }
  4123. }
  4124.  
  4125. memo_x_avion = x_avion;
  4126. memo_y_avion = y_avion;
  4127.  
  4128. //SPR_trajectoire.pushSprite(70, 232, TFT_BLACK); // TFT_BLACK -> défini la couleur de transparence
  4129.  
  4130. }
  4131.  
  4132.  
  4133.  
  4134. void affichages()
  4135. {
  4136. if (roulis < -45) {roulis = -45;}
  4137. if (roulis > 45) {roulis = 45;}
  4138.  
  4139. if (tangage < -30) {tangage = -30;}
  4140. if (tangage > 30) {tangage = 30;}
  4141.  
  4142. affi_HA();
  4143. dessine_avion();
  4144.  
  4145. affi_elevator();
  4146. affi_vitesse();
  4147. affi_hauteur_RWY();
  4148. affi_vt_verticale();
  4149. affi_acceleration();
  4150. if(locks_type != "VS") {affi_asel(asel1*100);} // attention -> résultat sur 32 bits !!!
  4151. affi_target_speed();
  4152. affi_flags();
  4153. affi_rudder();
  4154.  
  4155. if (read_bit(flags, bit_autoland)== 1)
  4156. {
  4157. affi_approche();
  4158. }
  4159. else
  4160. {
  4161. trace_arc_gradu();
  4162. affi_cap();
  4163. affi_autopilot(1); // dans le petit cercle en bas à gauche
  4164. //affi_indicateurs();
  4165. affi_switches();
  4166.  
  4167. //affi_radial_VOR();
  4168. //affi_Airport();
  4169. }
  4170. //bargraph_float_test(GPS_azimut_piste/360.0);
  4171. //bargraph_float_test(GPS_distance_piste);
  4172. }
  4173.  
  4174.  
  4175.  
  4176. void traitement_boutons()
  4177. {
  4178.  
  4179. // ===============================================================
  4180. // BOUTONS DU module SW
  4181. //----------------------------------------------------------------
  4182.  
  4183. // les valeurs sont tranmises par WiFi
  4184. // voir leur réception ici dans la fonction "setup()"
  4185.  
  4186. // cette fonction traite également les boutons des autres modules (ND, MCDU), voir plus bas
  4187.  
  4188. flag_traiter_SW =0;
  4189.  
  4190. if(v_switches != memo_v_switches)
  4191. {
  4192. memo_v_switches = v_switches;
  4193. if ((v_switches & 1)==1)
  4194. {
  4195. if (read_bit(flags, bit_autoland) == 0)
  4196. {
  4197. if (hdg1>30) {hdg1 -=30;} else {hdg1+=330;} // la barre à tribord 30° !
  4198. RAZ_chrono();
  4199. }
  4200. }
  4201.  
  4202. if ((v_switches & 2)==2)
  4203. {
  4204. if (read_bit(flags, bit_autoland) == 0)
  4205. {
  4206. if (hdg1<330) {hdg1 +=30;} else {hdg1-=330 ;} // la barre à babord 30° !
  4207. RAZ_chrono();
  4208. }
  4209. }
  4210.  
  4211.  
  4212.  
  4213. // altitude
  4214. if ((v_switches & 4)==4)
  4215. {
  4216. if (is_in(asel1, 46, 190)) {asel1 +=10;} // consigne d'altitude +1000ft
  4217. else if (is_in(asel1, 20, 45)) {asel1 +=5;}
  4218. else if (is_in(asel1, 0, 19)){asel1 += 1;}
  4219. if (asel1>200) {asel1=200;}
  4220. }
  4221.  
  4222. if ((v_switches & 8)==8)
  4223. {
  4224. if (is_in(asel1, 46, 190)){asel1 -= 10;} // consigne d'altitude -1000ft
  4225. else if (is_in(asel1, 21, 45)){asel1 -= 5;}
  4226. else if (is_in(asel1, 1, 20)) {asel1 -= 1;}
  4227. if (asel1<0) {asel1=0;}
  4228. }
  4229.  
  4230.  
  4231. // vitesse
  4232. if ((v_switches & 16)==16)
  4233. {
  4234. if (target_speed<180) {target_speed +=5;} // consigne de vitesse
  4235. else if ((target_speed>=180) && (target_speed<600)) {target_speed +=20;}
  4236. }
  4237.  
  4238. if ((v_switches & 32)==32)
  4239. {
  4240. if ((target_speed>180) && (target_speed<600)) {target_speed -=20;}
  4241. else if ((target_speed<=180) && (target_speed>90)) {target_speed -=5;}
  4242. }
  4243.  
  4244.  
  4245. if ((v_switches & 64)==64) // bouton "ILS" (à renommer !!!! -> devenu attérrissage d'urgence)
  4246. {
  4247. raz_bit(&flags, bit_decollage);
  4248. raz_bit(&flags, bit_route);
  4249. raz_bit(&flags, bit_nav_to_piste);
  4250. raz_bit(&flags, bit_nav_to_pti);
  4251. raz_bit(&flags, bit_circling);
  4252. raz_bit(&flags, bit_nav_to_ptAA);
  4253. raz_bit(&flags, bit_nav_to_ptBB);
  4254. raz_bit(&flags, bit_rudder_decol);
  4255. raz_bit(&flags, bit_roulage);
  4256.  
  4257. throttle = 1.0; // gaz au minimum
  4258. target_speed = 0;
  4259.  
  4260.  
  4261. brake_left =0;
  4262. brake_right =0;
  4263. raz_bit(&flags, bit_rudder_attero); // dans le cas présent, on conserve la dérive en mode manuel
  4264. set_bit(&flags, bit_atterrissage); // on passera dorénavant en boucle dans la fonction "atterrissage()"
  4265.  
  4266. affiche_etats_flags();
  4267. msg_to_send = "alert1";
  4268. }
  4269. /*
  4270. {
  4271. // toggle flag_autoland
  4272. if (read_bit(flags, bit_autoland) == 0)
  4273. {
  4274. if(GPS_distance_piste > 8) // sinon trop près -> crash probable !
  4275. {
  4276. if (hauteur_AAL >100) // 100 feet (30m)
  4277. {
  4278. memo_hdg1 = hdg1; // mémorise le cap avant de passer la main à l'autoland
  4279. if(asel1==0) {asel1 = 30;}
  4280.  
  4281. find_sens_approche(); // en tenant compte de la position effective de l'avion
  4282.  
  4283. if (GPS_distance_piste <= 20);
  4284. {
  4285. set_bit(&flags, bit_autoland); // engage l'approche et le posé automatiques
  4286. find_END_RWY_dst(); // celle la plus éloignes de nous. Va servir à guider la trajectoire au sol
  4287. //set_bit(&flags, bit_rudder_attero);
  4288. }
  4289.  
  4290. raz_bit(&flags, bit_rudder_decol); // puisque nous ne sommes pas en train de décoller
  4291.  
  4292. efface_cadre_bas(GRIS_TRES_FONCE);
  4293. affichages();
  4294. }
  4295. }
  4296. else
  4297. {
  4298. TFT480.setFreeFont(FF6);
  4299. affi_message ("TOO CLOSE", 140, 200, 140, 2000, BLANC, HA_SOL, 1);
  4300. }
  4301. }
  4302. else
  4303. {
  4304. if (read_bit(flags, bit_autoland) == 1) {desengage_autoland();}
  4305.  
  4306. raz_bit(&flags, bit_nav_to_piste);
  4307. raz_bit(&flags, bit_nav_to_ptAA);
  4308. raz_bit(&flags, bit_nav_to_ptBB);
  4309.  
  4310. affichages();
  4311. }
  4312.  
  4313. }
  4314. */
  4315.  
  4316. if ((v_switches & 128)==128) // bouton HDG SYNC
  4317. {
  4318. hdg1 = cap;
  4319. RAZ_chrono();
  4320. }
  4321.  
  4322. }
  4323.  
  4324.  
  4325. /**
  4326. // ===============================================================
  4327. // INVERSEUR à levier du module ND
  4328. //----------------------------------------------------------------
  4329.  
  4330. if(v_switches_ND != memo_v_switches_ND)
  4331. {
  4332. memo_v_switches_ND = v_switches_ND;
  4333. if (read_bit(v_switches_ND ,1)==1)
  4334. {
  4335. TFT480.fillCircle(465, 310, 5, BLEU_CLAIR);
  4336. landing_light1=1;
  4337. landing_light2=1;
  4338.  
  4339. }
  4340. else
  4341. {
  4342. TFT480.fillCircle(465, 310, 5, NOIR);
  4343. landing_light1=0;
  4344. landing_light2=0;
  4345. }
  4346. }
  4347. **/
  4348.  
  4349. // ===============================================================
  4350. // BOUTONS du module MCDU
  4351. //----------------------------------------------------------------
  4352.  
  4353. if(v_bt_MCDU != memo_v_bt_MCDU)
  4354. {
  4355. memo_v_bt_MCDU = v_bt_MCDU;
  4356. affi_etats_bt_MCDU();
  4357.  
  4358.  
  4359. // >>>>>>>>>>>>>>>>> ROUGE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  4360.  
  4361. if(read_bit(v_bt_MCDU, bit_bt1) == 1)
  4362. {
  4363. Led1.allume();
  4364.  
  4365. if (hauteur_AAL < 50)
  4366. {
  4367. if (GPS_distance_piste <2)
  4368. {
  4369. //inv_bit(&flags, bit_rudder_decol);// toggle manuel de ce drapeau; permet d'effectuer un décollage
  4370. // en mode manuel ou auto (concernant la gouverne de direction)
  4371.  
  4372. ////if(read_bit(flags, bit_rudder_decol) ==1)
  4373. ////{
  4374. ////find_END_RWY_angl();
  4375. ////}
  4376.  
  4377. inv_bit(&flags, bit_decollage);
  4378. affiche_etats_flags();
  4379.  
  4380. if(read_bit(flags, bit_decollage) ==1)
  4381. {
  4382. prepare_decollage();
  4383. msg_to_send="decollage";
  4384. delay(1000);
  4385. }
  4386. if(read_bit(flags, bit_decollage) ==0)
  4387. {
  4388. msg_to_send="RAZ";
  4389. annule_tout();
  4390. }
  4391.  
  4392. }
  4393. else
  4394. {
  4395. msg_to_send = " BAD RWY !";
  4396. TFT480.setFreeFont(FF6);
  4397. affi_message (msg_to_send, 140, 200, 140, 500, ROUGE, BLANC, 1); // ici
  4398. TFT480.fillRect(140, 200, 140, 22, HA_SOL); // efface
  4399. }
  4400. }
  4401. }
  4402. else {Led1.eteint();}
  4403.  
  4404.  
  4405. // >>>>>>>>>>>>>>>>> JAUNE <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  4406.  
  4407. if(read_bit(v_bt_MCDU, bit_bt2) == 1)
  4408. {
  4409. Led2.allume();
  4410.  
  4411. inv_bit(&flags, bit_FG_AP); // -> toggle Autopilot de FlightGear
  4412. if (hauteur_AAL >300) // feet
  4413. {
  4414. if (read_bit(flags, bit_FG_AP)== 1)
  4415. {
  4416. msg_to_send="engage FG-AP";
  4417. speed_ctrl = true;
  4418. }
  4419. }
  4420.  
  4421. if (read_bit(flags, bit_FG_AP)== 0)
  4422. {
  4423. msg_to_send="RAZ";
  4424. annule_tout();
  4425. }
  4426.  
  4427. }
  4428. else {Led2.eteint();}
  4429.  
  4430.  
  4431. // >>>>>>>>>>>>>>>>> VERT <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  4432.  
  4433. if(read_bit(v_bt_MCDU, bit_bt3) == 1) // -> en ROUTE
  4434. {
  4435. Led3.allume();
  4436.  
  4437. inv_bit(&flags, bit_route); // change d'état...
  4438. // et teste le résultat
  4439. if (read_bit(flags, bit_route)==1)// && (GPS_distance_piste > 1.0) )
  4440. {
  4441. // on commence à descendre le cas échéant
  4442. //if(GPS_distance_ptAA < 20.0) { if (asel1 >60) {asel1 =60;} }
  4443. //if(GPS_distance_ptAA < 15.0) { if (asel1 >30) {asel1 =30;} }
  4444. msg_to_send ="en ROUTE";
  4445. desengage_autoland();
  4446. raz_bit(&flags, bit_roulage);
  4447. }
  4448.  
  4449. /*
  4450. if (GPS_distance_piste < 1.0)
  4451. {
  4452. raz_bit(&flags, bit_route);
  4453. msg_to_send ="d<1NM !"; // à destination des autres modules (par WiFi, voir server.on("/msg"... dans le setup)
  4454. TFT480.setFreeFont(FF6);
  4455. affi_message (msg_to_send, 130, 200, 200, 500, JAUNE, HA_SOL, 1); // ici
  4456.  
  4457. }
  4458. */
  4459. }
  4460. else {Led3.eteint();}
  4461.  
  4462.  
  4463. // >>>>>>>>>>>>>>>>> BLEU <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  4464.  
  4465. if(read_bit(v_bt_MCDU, bit_bt4) == 1) // fait tourner la selection RWY -> ptA -> ptB ...
  4466. {
  4467.  
  4468. Led4.allume();
  4469.  
  4470. raz_bit(&flags, bit_route); // désenclenche le suivi de route pour éviter de zigzaguer pendant le choix
  4471. // il faudra le réarmer manuellement avec le bouton précédent ('vert')
  4472. msg_to_send ="RAZ route";
  4473.  
  4474. options_route ++; // 0 = RWW ; 1=ptA ; 2=ptB ; 3=crc1 ; 4=crc2
  4475.  
  4476. if ((options_route == 1) && (liste_bali[num_bali].sens_app_interdit == 'A')) {options_route++;}
  4477. else if ((options_route == 2) && (liste_bali[num_bali].sens_app_interdit == 'B')) {options_route++;}
  4478.  
  4479. if (options_route>4) {options_route=0;}
  4480.  
  4481. if(options_route == 0) //(RWW)
  4482. {
  4483. //msg_to_send += String(liste_bali[num_bali].ID_OACI);
  4484.  
  4485. if (GPS_distance_ptAA > 100) {asel1 = 100;} // ou 370 en croisière (37 000 ft soit 11 000m) à voir...
  4486. if (is_in(GPS_distance_ptAA, 15, 100)) {asel1 = 60;} // 6000 ft, local
  4487.  
  4488. set_bit(&flags, bit_nav_to_piste);
  4489. //msg_to_send="to RWY";
  4490. //msg_to_send += "- ";
  4491.  
  4492. raz_bit(&flags, bit_nav_to_ptAA);
  4493. raz_bit(&flags, bit_nav_to_ptBB);
  4494. raz_bit(&flags, bit_nav_to_pti);
  4495. }
  4496.  
  4497. if(options_route == 1) // ptA
  4498. {
  4499. if (GPS_distance_ptAA > 100) {asel1 = 100;} // ou 370 en croisière (37 000 ft soit 11 000m) à voir...
  4500. if (is_in(GPS_distance_ptAA, 15, 100)) {asel1 = 60;} // 6000 ft, local
  4501.  
  4502. set_bit(&flags, bit_nav_to_ptAA);
  4503.  
  4504. //msg_to_send="to AA";
  4505. //msg_to_send += " - ";
  4506.  
  4507. raz_bit(&flags, bit_nav_to_piste);
  4508. raz_bit(&flags, bit_nav_to_ptBB);
  4509. raz_bit(&flags, bit_nav_to_pti);
  4510.  
  4511. }
  4512.  
  4513. if(options_route == 2) //ptB
  4514. {
  4515. if (GPS_distance_ptBB > 100) {asel1 = 100;} // ou 370 en croisière ()37 000 ft soit 11 000m) à voir...
  4516. if (is_in(GPS_distance_ptBB, 15, 100)) {asel1 = 60;} // 6000 ft, local
  4517.  
  4518. set_bit(&flags, bit_nav_to_ptBB);
  4519.  
  4520. //msg_to_send="to BB";
  4521. //msg_to_send += " - ";
  4522.  
  4523. raz_bit(&flags, bit_nav_to_piste);
  4524. raz_bit(&flags, bit_nav_to_ptAA);
  4525. raz_bit(&flags, bit_nav_to_pti);
  4526.  
  4527. }
  4528.  
  4529. if(options_route == 3) // crc1 ( hypodromes dans un sens)
  4530. {
  4531. set_bit(&flags, bit_circling);
  4532. raz_bit(&flags, bit_sens_circling);
  4533. set_bit(&flags, bit_nav_to_pti);
  4534.  
  4535. //msg_to_send="crc1";
  4536. //msg_to_send += " - ";
  4537.  
  4538. raz_bit(&flags, bit_nav_to_piste);
  4539. raz_bit(&flags, bit_nav_to_ptAA);
  4540. raz_bit(&flags, bit_nav_to_ptBB);
  4541.  
  4542. }
  4543.  
  4544. if(options_route == 4) // crc2 ( hypodromes dans l'autre sens)
  4545. {
  4546. set_bit(&flags, bit_circling);
  4547. set_bit(&flags, bit_sens_circling);
  4548. set_bit(&flags, bit_nav_to_pti);
  4549.  
  4550. //msg_to_send="crc2";
  4551. //msg_to_send += " - ";
  4552.  
  4553. raz_bit(&flags, bit_nav_to_piste);
  4554. raz_bit(&flags, bit_nav_to_ptAA);
  4555. raz_bit(&flags, bit_nav_to_ptBB);
  4556. }
  4557.  
  4558.  
  4559. //msg_to_send += String(liste_bali[num_bali].ID_OACI);
  4560.  
  4561.  
  4562. }
  4563. else {Led4.eteint();}
  4564.  
  4565.  
  4566. // >>>>>>>>>>>>>>>>> VIOLET <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  4567.  
  4568. if(read_bit(v_bt_MCDU, bit_bt5) == 1)
  4569. {
  4570. if(hauteur_AAL<20)
  4571. {
  4572. inv_bit(&flags, bit_roulage);
  4573. if (read_bit(flags, bit_roulage) == 0)
  4574. {
  4575. throttle = 1.0; // gaz à zéro.
  4576. msg_to_send="stop";
  4577. brake_left=1;
  4578. brake_right=1;
  4579. }
  4580. else
  4581. {
  4582. annule_tout();
  4583. set_bit(&flags, bit_roulage);
  4584. msg_to_send="roulage";
  4585. brake_left=0;
  4586. brake_right=0;
  4587. }
  4588. }
  4589. else
  4590. {
  4591. raz_bit(&flags, bit_roulage); // pas question d'engager le roulage auto lorsque l'avion est en vol !!!
  4592. inv_bit(&flags, bit_att_short); // en vol permet de choisir le type d'atterrisage, long ou court.
  4593. }
  4594.  
  4595. Led5.allume();
  4596. }
  4597. else {Led5.eteint();}
  4598. }
  4599. }
  4600.  
  4601.  
  4602.  
  4603.  
  4604. void toutes_les_10s()
  4605. {
  4606.  
  4607. }
  4608.  
  4609.  
  4610.  
  4611. void toutes_les_1s()
  4612. {
  4613. nb_secondes++; // ne concerne pas le chrono de la ligne suivante
  4614.  
  4615. affiche_chrono();
  4616. inc_chrono();
  4617.  
  4618. //affi_int_test(alti_agl, 100, 2, VERT, NOIR);
  4619. if(alti_agl < 1500) {affi_hauteur_SOL(alti_agl);} else {efface_hauteur_SOL();}
  4620.  
  4621.  
  4622. if(nb_secondes>10)
  4623. {
  4624. nb_secondes=0;
  4625. toutes_les_10s();
  4626. }
  4627.  
  4628. dV =10*(vitesse - memo_vitesse);
  4629. memo_vitesse = vitesse;
  4630.  
  4631. if (vitesse > 150) // désengage auto_rudder en fin de décollage
  4632. {
  4633. raz_bit(&flags, bit_rudder_decol);// en vol la gouverne de lacet se commande au pédalier (dans ce programme, avec un pot
  4634. //raz_bit(&flags, bit_rudder_attero);
  4635. }
  4636.  
  4637. if (read_bit(flags, bit_au_sol) == 1)
  4638. {
  4639. // désengage l'autoland ILS après attéro pour éviter de re-décoller avec cet autoland engagé
  4640.  
  4641. if (read_bit(flags, bit_autoland) == 1) {desengage_autoland();}
  4642. //asel1 =30; // penser à re-règler cette valeur manuellement avant de ré-engager l'autopilot !
  4643. }
  4644.  
  4645. affiche_etats_flags();
  4646. affi_alti_agl();
  4647.  
  4648. calculs_GPS();
  4649. //affi_dst_GPS();
  4650.  
  4651. if (read_bit(flags, bit_route) == 1)
  4652. {
  4653. if (read_bit(flags, bit_nav_to_piste) == 1) { nav_to_centre_piste();}
  4654. if (read_bit(flags, bit_nav_to_ptAA) == 1) { nav_to_ptAA(); }
  4655. if (read_bit(flags, bit_nav_to_ptBB) == 1) { nav_to_ptBB(); }
  4656. if (read_bit(flags, bit_circling) == 1) {tour_de_piste();} // recalcul de la position du point pti
  4657. if (read_bit(flags, bit_nav_to_pti) == 1) { nav_to_pti(); }
  4658. }
  4659.  
  4660. /*
  4661. if ((memo_GPS_distance_piste - GPS_distance_piste) > 0 )
  4662. {
  4663. raz_bit(&flags, bit_eloignement);
  4664. //TFT480.fillCircle(300, 5, 5, VERT);
  4665.  
  4666. }
  4667. else
  4668. {
  4669. set_bit(&flags, bit_eloignement);
  4670.  
  4671. //TFT480.fillCircle(300, 5, 5, ROUGE);
  4672. }
  4673. */
  4674.  
  4675.  
  4676. memo_GPS_distance_piste = GPS_distance_piste;
  4677.  
  4678. if (GPS_distance_piste < 2)
  4679. {
  4680. raz_bit(&flags, bit_nav_to_piste); // désengage la navigation auto vers le centre de la piste
  4681. }
  4682.  
  4683.  
  4684. if(
  4685. (read_bit(flags, bit_autoland) == 0 )
  4686. && (read_bit(flags, bit_circling) == 0 )
  4687. && (read_bit(flags, bit_decollage) == 0 )
  4688. && (read_bit(flags, bit_nav_to_ptAA) == 0 )
  4689. && (read_bit(flags, bit_nav_to_ptBB) == 0 )
  4690. && (read_bit(flags, bit_nav_to_pti) == 0 )
  4691. && (read_bit(flags, bit_route) == 0 )
  4692. && (alti_agl < 200 )
  4693. ) {gear_down =1;}
  4694.  
  4695. if(alti_agl >= 200 ) {gear_down =0;}
  4696.  
  4697. auto_landing(); // ne sera effectif que si le flag autoland !=0
  4698. //toutefois la fonction est toujours appelée afin d'actualiser des affichages
  4699.  
  4700.  
  4701. //affi_distance_ptBB(); // pour test
  4702.  
  4703. //TFT480.fillRect(0, 0, 480, 320, BLEU); // pour test
  4704.  
  4705.  
  4706. }
  4707.  
  4708.  
  4709.  
  4710.  
  4711.  
  4712.  
  4713. /** ==================================================================
  4714.  variable à gérer obligatoirement */
  4715.  
  4716. //le nombre de ports GPIO libres étant atteint, on va utiiser un switch unique pour deux fonctions :
  4717. uint8_t fonction_bt1 = 1; // 0=saisie écran ; 1=changement de vue
  4718.  
  4719.  
  4720.  
  4721.  
  4722. /**======================================================================================================
  4723. LOOP
  4724. ====================================================================================================== */
  4725.  
  4726. uint16_t t=0; // temps -> rebouclera si dépassement
  4727. void loop()
  4728. {
  4729. //if (SerialBT.available()) { Serial.write(SerialBT.read()); }
  4730.  
  4731. //le bouton connecté à CET ESP32-ci est partagé entre deux fonctions, voir ci-dessus "variables à gérer obligatoirement"
  4732. bouton1_etat = digitalRead(bouton1);
  4733.  
  4734. if (bouton1_etat != memo_bouton1_etat) // vue
  4735. {
  4736. memo_bouton1_etat = bouton1_etat;
  4737. if (bouton1_etat==0)
  4738. {
  4739. TFT480.fillCircle(450, 310, 5, VERT);
  4740.  
  4741.  
  4742. if (fonction_bt1 == 0) {write_TFT_on_SDcard(); }
  4743. if (fonction_bt1 == 1)
  4744. {
  4745. if (view_number == 0) {view_number = 1;} else {view_number =0;}
  4746. }
  4747. }
  4748. if (bouton1_etat==1)
  4749. {
  4750. TFT480.fillCircle(450, 310, 5, NOIR);
  4751. if (fonction_bt1==1)
  4752. {
  4753. ;
  4754. }
  4755. }
  4756. }
  4757.  
  4758. bouton2_etat = digitalRead(bouton2); // mode affichage de la hauteur / altitude
  4759.  
  4760. if (bouton2_etat != memo_bouton2_etat)
  4761. {
  4762. memo_bouton2_etat = bouton2_etat;
  4763. if (bouton2_etat==0)
  4764. {
  4765. mode_affi_hauteur = AAL;
  4766. affi_mode_affi_hauteur();
  4767. }
  4768. if (bouton2_etat==1)
  4769. {
  4770. mode_affi_hauteur = ASL;
  4771. affi_mode_affi_hauteur();
  4772. }
  4773. }
  4774.  
  4775. if ((flag_traiter_SW ==1) || (flag_traiter_MCDU ==1))
  4776. {
  4777. // le positionnement de ce drapeau se fait dans l'interruption WiFi "server.on("/switch"..."
  4778. // définie dans le setup()
  4779. // la commande, en amont, provient de l'ESP n°3 qui gère les switches
  4780. flag_traiter_SW =0;
  4781. flag_traiter_MCDU =0;
  4782. traitement_boutons();
  4783. }
  4784.  
  4785.  
  4786. temps_ecoule = micros() - memo_micros;
  4787. if (temps_ecoule >= 1E6) // (>=) et pas strictement égal (==) parce qu'alors on rate l'instant en question
  4788. {
  4789. memo_micros = micros();
  4790. toutes_les_1s();
  4791. }
  4792.  
  4793. if (dV > acceleration) {acceleration++;}
  4794. if (dV < acceleration) {acceleration--;}
  4795.  
  4796. //---------------------------------------------------------------------------------------
  4797.  
  4798. compteur1+=1;
  4799. if (compteur1 >= 100) // tous les 100 passages dans la boucle
  4800. {
  4801. compteur1=0;
  4802. }
  4803.  
  4804. acquisitions();
  4805.  
  4806. // affi_float_test(joystick1, 100, 2, VERT, NOIR); // ok
  4807.  
  4808.  
  4809. if (num_bali != memo_num_bali)
  4810. {
  4811. memo_num_bali = num_bali;
  4812. calculs_piste();
  4813. calcul_ptAA_ptBB(liste_bali[num_bali].dst_pt_AB);
  4814. affi_Airport();
  4815. }
  4816.  
  4817.  
  4818. if (vitesse < 30) {set_bit(&flags, bit_au_sol);}
  4819. if (alti_agl > 100) {raz_bit(&flags, bit_au_sol);}
  4820.  
  4821.  
  4822. if(vitesse < 20)
  4823. {
  4824. raz_bit(&flags, bit_rudder_attero); // ce qui permet de reprendre le guidage manuel au sol en fin d'attéro
  4825. msg_to_send = "auto rudd OFF";
  4826. }
  4827.  
  4828.  
  4829. if (read_bit(flags, bit_atterrissage)==1) {atterrissage();}
  4830.  
  4831. if (read_bit(flags, bit_roulage)==1) {roulage();}
  4832.  
  4833. /*
  4834. if ((read_bit(flags, bit_rudder_decol) ==1 ))
  4835. {
  4836. calculs_GPS();
  4837. auto_rudder_deco();
  4838. }
  4839. */
  4840.  
  4841.  
  4842. /** ----------------------------------------------
  4843.  pour tester si data_ok à bien la valeur attendue, c.a.d si toutes les acquisitions sont effectuées
  4844.  ce qui est le cas lorsque le dialogue avec le serveur de protocole FG est correctement programmé
  4845.  sachant que ce ne sont pas les pièges qui manquent !
  4846.  Les 6 lignes de code qui suivent peuvent faire gagner une journée...
  4847.  Il suffit d'analyser la valeur affichée en binaire pour voir les bits qui restent à 0 -> problèmes (à priori de libellés)
  4848.  Il doit y avoir une correspondance stricte entre les noms de variables ici et celles émises par le fichier hardware4.xml
  4849. */
  4850.  
  4851. ////TFT480.setFreeFont(FM9);
  4852. ////TFT480.setTextColor(VERT, NOIR);
  4853. ////s2= (String) data_ok;
  4854. ////TFT480.fillRect(150, 220, 80, 12, TFT_BLACK);
  4855. ////TFT480.drawString(s2, 150, 220);
  4856.  
  4857. /** ---------------------------------------------- **/
  4858.  
  4859.  
  4860. if (data_ok == ((1<<nb_acqui)-1) ) // si toutes les acquisitions faites
  4861. {
  4862. TFT480.fillCircle(435, 310, 5, VERT);
  4863. if (attente_data == 1)
  4864. {
  4865. attente_data=0;
  4866. //TFT480.fillScreen(TFT_BLACK);
  4867. //init_affi_HA();
  4868. //init_affi_autopilot();
  4869. }
  4870.  
  4871. affichages();
  4872. calculs_GPS();
  4873. data_ok=0;
  4874.  
  4875. data_out(); // ** à commenter le cas échéant pour libérer le port série lors de la mise au point **
  4876. }
  4877. else
  4878. {
  4879.  
  4880. TFT480.fillCircle(435, 310, 5, ROUGE);
  4881. if(attente_data==1)
  4882. {
  4883. affi_nop();
  4884. RAZ_variables();
  4885. affichages();
  4886. delay(100);
  4887. }
  4888.  
  4889. }
  4890.  
  4891. //if ((vitesse > 120) && (altitude_GPS > 200)) {raz_bit(&flags, bit_au_sol);}
  4892.  
  4893. if ((vitesse < 50) && (hauteur_AAL < 20))
  4894. {
  4895. raz_bit(&flags, bit_FG_AP); // désengage (ici) l'autopilot de FG
  4896. affi_extremite();
  4897. }
  4898.  
  4899. ////v_test1 += 0.01;
  4900. ////if (v_test1 > 1.0) {v_test1 = -1.0;}
  4901. ////bargraph_float_test(v_test1);
  4902.  
  4903. if (read_bit(flags, bit_decollage)==1)
  4904. {
  4905. decollage();
  4906. }
  4907.  
  4908.  
  4909.  
  4910. // Les 4 lignes suivantes affichent un rectangle jaune déplaçable et redimensionnable depuis le module "PositionneurXY"
  4911. // lors de la mise au point, pour faciliter le positionnement des éléments graphiques
  4912. // les commenter par la suite
  4913.  
  4914. /*
  4915. affi_ligne1_V(x_1);
  4916. affi_ligne2_V(x_2);
  4917. affi_ligne1_H(y_1);
  4918. affi_ligne2_H(y_2);
  4919. */
  4920. //affi_f_potar1();
  4921.  
  4922. //---------------------------------------------------------------------------------------
  4923.  
  4924. }
  4925.  
  4926.  
  4927. /** ***************************************************************************************
  4928. CLASS VOYANT // affiche un nombre ou un petit texte dans un rectangle
  4929. ainsi que (en plus petit) deux valeurs supplémentaires, par ex: les valeurs mini et maxi
  4930. ********************************************************************************************/
  4931.  
  4932. // Constructeur
  4933. VOYANT::VOYANT()
  4934. {
  4935.  
  4936. }
  4937.  
  4938.  
  4939.  
  4940. void VOYANT::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
  4941. {
  4942. x0 = xi;
  4943. y0 = yi;
  4944. dx = dxi;
  4945. dy = dyi;
  4946.  
  4947. //TFT480.setTextColor(BLANC, NOIR);
  4948. }
  4949.  
  4950.  
  4951.  
  4952. void VOYANT::affiche(uint16_t couleur_texte_i, uint16_t couleur_fond_i)
  4953. {
  4954. TFT480.fillRoundRect(x0, y0, dx, dy, 3, couleur_fond_i);
  4955.  
  4956.  
  4957. if (caract2 !=' ')
  4958. {
  4959. TFT480.setTextColor(couleur_texte_i);
  4960. TFT480.setFreeFont(FM9);
  4961. TFT480.drawChar(x0+3, y0+14, caract1, couleur_texte_i, couleur_fond_i, 1);
  4962.  
  4963. TFT480.setTextColor(couleur_texte_i);
  4964. TFT480.setFreeFont(FM9);
  4965. TFT480.drawChar(x0+16, y0+14, caract2, couleur_texte_i, couleur_fond_i, 1);
  4966. }
  4967. else if (caract1 !=' ')
  4968. {
  4969. TFT480.setTextColor(couleur_texte_i);
  4970. TFT480.setFreeFont(FM9);
  4971. TFT480.drawChar(x0+8, y0+14, caract1, couleur_texte_i, couleur_fond_i, 1);
  4972. }
  4973.  
  4974. }
  4975.  
  4976.  
  4977.  
  4978. /** ***************************************************************************************
  4979. CLASS Led // affiche un petit rectangle coloré
  4980. ********************************************************************************************/
  4981.  
  4982.  
  4983. // Constructeur
  4984. Led::Led()
  4985. {
  4986.  
  4987. }
  4988.  
  4989.  
  4990. void Led::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
  4991. {
  4992. x0 = xi;
  4993. y0 = yi;
  4994. dx = dxi;
  4995. dy = dyi;
  4996. }
  4997.  
  4998.  
  4999.  
  5000. void Led::allume()
  5001. {
  5002. TFT480.fillRect(x0, y0, dx, dy, couleur);
  5003. }
  5004.  
  5005.  
  5006. void Led::eteint()
  5007. {
  5008. TFT480.fillRect(x0, y0, dx, dy, GRIS_TRES_FONCE);
  5009. //TFT480.drawRect(x0, y0, dx, dy, couleur); // bordure
  5010. }
  5011.  
  5012.  
  5013. void Led::set_couleur(uint16_t coul_i)
  5014. {
  5015. couleur = coul_i;
  5016. }
  5017.  
  5018.  
  5019.  
  5020.  
  5021. /**
  5022. -----------------------------------------------------------------------------------------------------------
  5023.  
  5024.   ci-dessous contenu du fichier (pour Linux): "/usr/share/games/protocol/hardware4.xml" qui va bien pour CE programme
  5025.   (et CETTE version)
  5026.  
  5027.   IMPORTANT : Lorsque je fais évoluer le programme principal (ci-dessus) je modifie également (le cas échéant)
  5028.   le fichier hardware4.xml (ci-dessous)
  5029.   je ne numérote pas le fichier ci-dessous.
  5030.   (en le recopiant au bon endroit, ici ce n'est qu'un commentaire non fonctionnel en tant que tel).
  5031.   en effet les variables transmises par FG doivent correspondre EXACTEMENT à ce qu'attend l'ESP32,
  5032.   voir la fonction "void acquisitions()"
  5033.  
  5034.  
  5035.   FG doit être lancé avec les paramètres suivants :
  5036.  
  5037. --generic=serial,in,2,/dev/ttyUSB0,9600,hardware4
  5038. --generic=serial,out,2,/dev/ttyUSB0,9600,hardware4
  5039.  
  5040. IMPORTANT :
  5041. -Il faut ajouter ces DEUX lignes dans un lanceur (tel que "FGo!") pour obtenir un fonctionnement bidirectionnel
  5042. -"ttyUSB0" peut être autre chose suivant la configuration de votre Linux et de l'ordinateur.
  5043.  
  5044. Remarques :
  5045.   - le nom du fichier "hardware4.xml" peut être autre chose pourvu que le paramètre de lancement corresponde exactement au nom.
  5046.   - le fichier en question n'existe pas par défaut dans le répertoire "/usr/share/games/protocol/", il faut le créer.
  5047.  
  5048.  
  5049. Astuce : pour gagner du temps au décollage, on peut lancer FG avec les options en ligne de commande suivantes (en plus de celles
  5050. vues plus haut):
  5051.  
  5052. --prop:/autopilot/locks/heading=HDG
  5053. --prop:/autopilot/locks/altitude=ALT
  5054. --prop:/autopilot/locks/speed-ctrl=true
  5055. --prop:/autopilot/settings/target-speed-kt=160
  5056.  
  5057. ce qui configure l'autopitot correctement en adéquation avec notre PFD
  5058. les deux premières lignes en particulier permettent de piloter l'avion depuis le PFD en tournant les encoreurs rotatifs du cap
  5059. et de l'altitude
  5060. elles permetttent également de contrôler l'avion en mode autopilot et landing auto ILS (guidage glide et localizer)
  5061.  
  5062. -----------------------------------------------------------------------------------------------------------
  5063.  
  5064.  
  5065. <?xml version="1.0"?>
  5066.  
  5067.  
  5068. <PropertyList>
  5069.  
  5070. <generic>
  5071.   <output>
  5072.   <binary_mode>false</binary_mode>
  5073.   <line_separator>\n</line_separator>
  5074.   <var_separator>\n</var_separator>
  5075.   <preamble></preamble>
  5076.   <postamble></postamble>
  5077.  
  5078.   <chunk>
  5079.   <name>Joystick_tangage</name>
  5080.   <node>/devices/status/joysticks/joystick/axis[1]</node>
  5081.   <type>integer</type>
  5082.   <factor>1000</factor>
  5083.   <format>joystick1=%i</format>
  5084.   </chunk>
  5085.  
  5086.   <chunk>
  5087.   <name>Altitude</name>
  5088.   <node>/instrumentation/gps/indicated-altitude-ft</node>
  5089.   <type>integer</type>
  5090.  
  5091.   <factor>1000</factor>
  5092.   <format>alti=%i</format>
  5093.   </chunk>
  5094.  
  5095.   <chunk>
  5096. <name>altitude_agl</name>
  5097. <type>integer</type>
  5098. <node>/position/altitude-agl-ft</node>
  5099. <format>alti_agl=%i</format>
  5100. </chunk>
  5101.  
  5102. <chunk>
  5103. <name>ground_elevation</name>
  5104. <type>integer</type>
  5105. <node>/position/ground-elev-ft</node>
  5106. <format>gnd_elv=%i</format>
  5107. </chunk>
  5108.  
  5109.  
  5110.   <chunk>
  5111.   <name>Speed</name>
  5112.   <node>/velocities/airspeed-kt</node>
  5113.   <type>integer</type>
  5114.   <format>speed=%i</format>
  5115.   </chunk>
  5116.  
  5117.   <chunk>
  5118.   <name>Tangage</name>
  5119.   <node>/orientation/pitch-deg</node>
  5120.   <type>integer</type>
  5121.   <format>pitch=%i</format>
  5122.   </chunk>
  5123.  
  5124.   <chunk>
  5125.   <name>Roulis</name>
  5126.   <node>/orientation/roll-deg</node>
  5127.   <type>integer</type>
  5128.   <format>roll=%i</format>
  5129.   </chunk>
  5130.  
  5131.   <chunk>
  5132.   <name>Cap</name>
  5133.   <node>/orientation/heading-deg</node>
  5134.   <type>integer</type>
  5135.   <factor>100</factor>
  5136.   <format>heading=%i</format>
  5137.   </chunk>
  5138.  
  5139.   <chunk>
  5140.   <name>Vertical_speed</name>
  5141.   <node>/velocities/vertical-speed-fps</node>
  5142.   <type>integer</type>
  5143.   <format>vspeed=%i</format>
  5144.   </chunk>
  5145.  
  5146.   <chunk>
  5147.   <name>latitude</name>
  5148.   <node>/position/latitude-deg</node>
  5149.   <type>integer</type>
  5150.   <factor>100000</factor>
  5151.   <format>latitude=%i</format>
  5152.   </chunk>
  5153.  
  5154.   <chunk>
  5155.   <name>longitude</name>
  5156.   <node>/position/longitude-deg</node>
  5157.   <type>integer</type>
  5158.   <factor>100000</factor>
  5159.   <format>longitude=%i</format>
  5160.   </chunk>
  5161.  
  5162. <chunk>
  5163. <name>Nav1_actual</name>
  5164. <type>integer</type>
  5165. <node>/instrumentation/nav/radials/actual-deg</node>
  5166. <factor>100</factor>
  5167. <format>vor_actual=%i</format>
  5168. </chunk>
  5169.  
  5170.  
  5171.   </output>
  5172.  
  5173.  
  5174.   <input>
  5175. <line_separator>\n</line_separator>
  5176.   <var_separator>,</var_separator>
  5177.  
  5178.   <chunk>
  5179.   <name>heading_bug</name>
  5180.   <node>/autopilot/settings/heading-bug-deg</node>
  5181.   <type>integer</type>
  5182.   <relative>false</relative>
  5183.   </chunk>
  5184.  
  5185.   <chunk>
  5186.   <name>asel</name>
  5187.   <node>/autopilot/settings/asel</node>
  5188.   <type>integer</type>
  5189.   <relative>false</relative>
  5190.   </chunk>
  5191.  
  5192.  
  5193. <chunk>
  5194. <name>landing-light1</name>
  5195. <node>/controls/lighting/landing-light</node>
  5196. <type>integer</type>
  5197. <relative>false</relative>
  5198. </chunk>
  5199.  
  5200. <chunk>
  5201. <name>landing-light2</name>
  5202. <node>/controls/lighting/landing-light[1]</node>
  5203. <type>integer</type>
  5204. <relative>false</relative>
  5205. </chunk>
  5206.  
  5207.  
  5208. <chunk>
  5209. <name>target_speed</name>
  5210. <node>/autopilot/settings/target-speed-kt</node>
  5211. <type>integer</type>
  5212. <relative>false</relative>
  5213. </chunk>
  5214.  
  5215.  
  5216. <chunk>
  5217. <name>minimums</name>
  5218. <node>/autopilot/settings/minimums</node>
  5219. <type>integer</type>
  5220. <relative>false</relative>
  5221. </chunk>
  5222.  
  5223.  
  5224. <chunk>
  5225. <name>ailerons</name>
  5226. <node>/controls/flight/aileron</node>
  5227. <type>float</type>
  5228. <relative>false</relative>
  5229. </chunk>
  5230.  
  5231.  
  5232. <chunk>
  5233. <name>rudder</name>
  5234. <node>/controls/flight/rudder</node>
  5235. <type>float</type>
  5236. <relative>false</relative>
  5237. </chunk>
  5238.  
  5239.  
  5240. <chunk>
  5241. <name>view_number</name>
  5242. <node>/sim/current-view/view-number</node>
  5243. <type>integer</type>
  5244. <relative>false</relative>
  5245. </chunk>
  5246.  
  5247.  
  5248. <chunk>
  5249. <name>climb_rate</name>
  5250. <node>/autopilot/settings/target-climb-rate-fps</node>
  5251. <type>float</type>
  5252. <relative>false</relative>
  5253. </chunk>
  5254.  
  5255.  
  5256. <chunk>
  5257. <name>locks</name>
  5258. <node>/autopilot/locks/altitude</node>
  5259. <type>string</type>
  5260. </chunk>
  5261.  
  5262.  
  5263. <chunk>
  5264. <name>AP_status</name>
  5265. <node>/autopilot/locks/AP-status</node>
  5266. <type>string</type>
  5267. </chunk>
  5268.  
  5269.  
  5270. <chunk>
  5271. <name>Speed_ctrl</name>
  5272. <node>/autopilot/locks/speed-ctrl</node>
  5273. <type>bool</type>
  5274. </chunk>
  5275.  
  5276.  
  5277. <chunk>
  5278. <name>elevator</name>
  5279. <node>/controls/flight/elevator</node>
  5280. <type>float</type>
  5281. <relative>false</relative>
  5282. </chunk>
  5283.  
  5284.  
  5285. <chunk>
  5286. <name>throttle</name>
  5287. <node>/controls/engines/throttle-all</node>
  5288. <type>float</type>
  5289. <relative>false</relative>
  5290. </chunk>
  5291.  
  5292.  
  5293. <chunk>
  5294. <name>reverser1</name>
  5295. <node>/controls/engines/engine/reverser</node>
  5296. <type>bool</type>
  5297. </chunk>
  5298.  
  5299.  
  5300. <chunk>
  5301. <name>reverser2</name>
  5302. <node>/controls/engines/engine[1]/reverser</node>
  5303. <type>bool</type>
  5304. </chunk>
  5305.  
  5306.  
  5307. <chunk>
  5308. <name>flaps</name>
  5309. <node>/controls/flight/flaps-select</node>
  5310. <type>integer</type>
  5311. <relative>false</relative>
  5312. </chunk>
  5313.  
  5314.  
  5315. <chunk>
  5316. <name>speedbrake</name>
  5317. <node>/controls/flight/speedbrake</node>
  5318. <type>float</type>
  5319. <relative>false</relative>
  5320. </chunk>
  5321.  
  5322.  
  5323. <chunk>
  5324. <name>gear_down</name>
  5325. <node>/controls/gear/gear-down</node>
  5326. <type>bool</type>
  5327. <relative>false</relative>
  5328. </chunk>
  5329.  
  5330.  
  5331. <chunk>
  5332. <name>brake_left</name>
  5333. <node>/controls/gear/brake-left</node>
  5334. <type>float</type>
  5335. <relative>false</relative>
  5336. </chunk>
  5337.  
  5338.  
  5339. <chunk>
  5340. <name>brake_right</name>
  5341. <node>/controls/gear/brake-right</node>
  5342. <type>float</type>
  5343. <relative>false</relative>
  5344. </chunk>
  5345.  
  5346.  
  5347.   </input>
  5348.  
  5349.  
  5350.  
  5351. </generic>
  5352.  
  5353. </PropertyList>
  5354.  
  5355.  
  5356.  
  5357. >
  5358.  
  5359.  
  5360.  
  5361. **/
  5362.  



CODE SOURCE: Liste des aérodromes en C++
  1. #ifndef FG_DATA_H
  2. #define FG_DATA_H
  3.  
  4. //
  5. // ==================================
  6. String version_datas="35.3";
  7. // ==================================
  8.  
  9.  
  10.  
  11. /*
  12. Liste des balises VOR et ILS
  13.  
  14. RAMARQUE : Ce fichier est commun aux codes sources FG_panel et FG_radio, il ne doit figurer qu'une fois sur le disque
  15. la seconde occurence nécessaire pour la compilation peut se faire par un lien symbolique (sous Linux),
  16. ou par placement judicieux dans un dossier partagé par le système Arduino (comme c'est le cas par exemple pour les lib graphiques).
  17.  
  18. Quoi qu'il en soit, la même version doit être compilée dans les deux programmes,
  19. afin de rendre les affichages cohérents entre les deux côtés, ainsi que leur utilisation.
  20.  
  21. Tout ceci dans le but d'alléger les données échangées par WiFi entre les deux montages, et donc le temps de traitement.
  22. Seul le numéro de l'aérodrome choisi sera transmis.
  23. */
  24.  
  25. /*
  26.  
  27.  
  28. todo:
  29. -Calais
  30. -Cherbourg
  31. -Gibraltar
  32. -Istres-Le-Tube
  33. -La Rochelle
  34. -Lannion
  35. -Londres
  36. -Lyon
  37. -Madrid
  38. -Nantes
  39. -Naple
  40. -Palma
  41. -Perpignan-Rivesaltes
  42. -Pise
  43. -Reykjavik
  44. -Rouen
  45. -Salon-de-Provence
  46. -St Nazaire
  47. -St-Malo
  48. -Tanger
  49. -Venise
  50. */
  51.  
  52. /**
  53. Lien : https://fr.wikipedia.org/wiki/Liste_des_codes_OACI_des_a%C3%A9roports/L
  54. **/
  55.  
  56. //ATTENTION : pour toute modif de ce fichier, il faut recompiler le source ET reprogrammer le ND ainsi que le PFD afin
  57. // qu'ils exploitent bien les même données.
  58. // Le ND envoie le n° de balise en cours au PFD (par WiFi) qui en déduit la fréquence radio
  59. // de la balise (par lecture du code de ce fichier inclus) et l'envoie à Flightgear par l'USB
  60. // donc la transmission ND->PFD c'est juste un nombre, qui permet ensuite au PFD de connaitre toutes les caractéristiques de l'aérodrome.
  61. // ce nombre est transmis sous la forme d'un argument lors d'une requête html.
  62. // Voir la requête "void httpGetHDG()" du ND, et la fonction "setup()" du PFD
  63.  
  64. /*
  65. struct FGBAL
  66. {
  67. char ID_OACI[4+1];
  68. char nom[18+1];
  69. float orient_piste; // orientation de la piste
  70. uint16_t altitude; // (feet)
  71. float latitude;
  72. float longitude;
  73. uint16_t longueur; //en m
  74. };
  75. */
  76.  
  77.  
  78. struct FGBAL
  79. {
  80. char ID_OACI[4+1];
  81. char nom[18+1];
  82. uint16_t altitude; // (en feet)
  83. // extrémités de la piste (A et B):
  84. float lat_A;
  85. float lon_A;
  86. float lat_B;
  87. float lon_B;
  88. char sens_app_interdit; // 'A', 'B' ou 'X' bout par lequel on doit aborder la piste en finale. X =indifférent, aléatoire
  89. uint16_t niveau_de_vol_mini;
  90. uint8_t dst_pt_AB; // distance du point AA (resp. BB) dont les coordonnées seront calculées lors du choix de l'aérodrome
  91.  
  92. //float orient_piste; // orientation de la piste // NON ! SERA CALCULEE
  93. //uint16_t longueur; //en m SERA CALCULEE
  94. };
  95.  
  96.  
  97. //------------------------------------------------------------------------------------------------------------------
  98. // un truc pour la route : les data sont à retrouver dans le fichier apt.dat de FlightGear
  99. // ce fichier fait 2,4 millions de lignes, toutefois il s'ouvre en 2 secondes avec Geany
  100. // (il n'en va pas de même avec un traitement de texte !!!)
  101. // chercher (par exemple) "LFMT"
  102.  
  103.  
  104. uint16_t nb_elements=0;
  105. struct FGBAL liste_bali[101]; // si > 100 aérodromes, augmenter la taille de cette variable sinon crash du programme
  106.  
  107.  
  108. char n_bal_array[4]; // 3 char + zero terminal - pour envoi par WiFi
  109.  
  110. void init_FG_bali()
  111. {
  112. uint16_t n =0;
  113.  
  114.  
  115. //KSFO San Francisco Intl
  116. // 10L 37.62872250 -122.39342127 28R 37.61351918 -122.35716907
  117. // 10R 37.62527727 -122.39074787 28L 37.61169441 -122.35837447
  118. // 01R 37.60620279 -122.38114713 19L 37.62781186 -122.36681630
  119. // 01L 37.60897310 -122.38223043 19R 37.62719258 -122.37015433
  120.  
  121. strcpy(liste_bali[n].ID_OACI, "KSFO");
  122. strcpy(liste_bali[n].nom, "San Francisco");
  123. liste_bali[n].altitude = 0;
  124. liste_bali[n].lat_A = 37.62872250;
  125. liste_bali[n].lon_A = -122.39342127;
  126. liste_bali[n].lat_B = 37.61351918;
  127. liste_bali[n].lon_B = -122.35716907;
  128. liste_bali[n].sens_app_interdit ='X';
  129. liste_bali[n].niveau_de_vol_mini = 40; // il y a des sommets à 100m dans les parages...
  130. liste_bali[n].dst_pt_AB = 15; // NM
  131. n++;
  132.  
  133. // 14 1 0 LEBL Barcelona - El Prat
  134. // 02 41.28775900 002.08483610 20 41.30938568 002.09470900
  135. // 07R 41.28231220 002.07434999 25L 41.29222053 002.10328100
  136. // 07L 41.29324514 002.06727496 25R 41.30572604 002.10372899
  137. strcpy(liste_bali[n].ID_OACI, "LEBL");
  138. strcpy(liste_bali[n].nom, "BARCELONA");
  139. liste_bali[n].altitude = 13;
  140. liste_bali[n].lat_A = 41.28231220; // 07R , celle // au rivage au bord de l'eau
  141. liste_bali[n].lon_A = 2.07434999;
  142. liste_bali[n].lat_B = 41.29222053;
  143. liste_bali[n].lon_B = 2.10328100;
  144. liste_bali[n].sens_app_interdit ='X';
  145. liste_bali[n].niveau_de_vol_mini = 30;
  146. liste_bali[n].dst_pt_AB = 15; // NM
  147. n++;
  148.  
  149.  
  150. // LFRB Brest
  151. // 07R 48.44338700 -004.43837200 25L 48.45236700 -004.39867000
  152.  
  153. strcpy(liste_bali[n].ID_OACI, "LFRB");
  154. strcpy(liste_bali[n].nom, "BREST");
  155. liste_bali[n].altitude = 325;
  156. liste_bali[n].lat_A = 48.44338700;
  157. liste_bali[n].lon_A = -4.43837200;
  158. liste_bali[n].lat_B = 48.45236700;
  159. liste_bali[n].lon_B = -4.39867000;
  160. liste_bali[n].sens_app_interdit ='X';
  161. liste_bali[n].niveau_de_vol_mini = 30;
  162. liste_bali[n].dst_pt_AB = 15; // NM
  163. n++;
  164.  
  165.  
  166.  
  167.  
  168. // LESO San_Sebastian
  169. // 04 43.35035700 -001.79739800 22 43.36268300 -001.78382400
  170. strcpy(liste_bali[n].ID_OACI, "LESO");
  171. strcpy(liste_bali[n].nom, "SAN SEBASTIAN");
  172. liste_bali[n].altitude = 15;
  173. liste_bali[n].lat_A = 43.35035700;
  174. liste_bali[n].lon_A = -1.79739800;
  175. liste_bali[n].lat_B = 43.36268300;
  176. liste_bali[n].lon_B = -1.78382400;
  177. liste_bali[n].sens_app_interdit ='X';
  178. liste_bali[n].niveau_de_vol_mini = 40;
  179. liste_bali[n].dst_pt_AB = 15; // NM
  180. n++;
  181.  
  182.  
  183. // LFBD Bordeaux Merignac
  184. // 05 44.81921870 -000.72896149 23 44.83874314 -000.70095469
  185. // 11 44.83162304 -000.72924702 29 44.82546392 -000.69985752
  186. strcpy(liste_bali[n].ID_OACI, "LFBD");
  187. strcpy(liste_bali[n].nom, "BORDEAUX MERIGNAC");
  188. liste_bali[n].altitude = 166;
  189. liste_bali[n].lat_A = 44.81921870;
  190. liste_bali[n].lon_A = -0.72896149;
  191. liste_bali[n].lat_B = 44.83874314;
  192. liste_bali[n].lon_B = -0.70095469;
  193. liste_bali[n].sens_app_interdit ='B';
  194. liste_bali[n].niveau_de_vol_mini = 30;
  195. liste_bali[n].dst_pt_AB = 15; // NM
  196. n++;
  197.  
  198.  
  199. // LFBO Toulouse Blagnac
  200. // 14R 43.64411400 001.34593100 32L 43.61898100 001.37209700
  201. // 14L 43.63736400 001.35762200 32R 43.61564400 001.38022500
  202. strcpy(liste_bali[n].ID_OACI, "LFBO");
  203. strcpy(liste_bali[n].nom, "TOULOUSE BLAGNAC");
  204. liste_bali[n].altitude = 499; // feet
  205. liste_bali[n].lat_A = 43.64411400;
  206. liste_bali[n].lon_A = 1.34593100;
  207. liste_bali[n].lat_B = 43.61898100 ;
  208. liste_bali[n].lon_B = 1.37209700;
  209. liste_bali[n].sens_app_interdit ='B';
  210. liste_bali[n].niveau_de_vol_mini = 30;
  211. liste_bali[n].dst_pt_AB = 15; // NM
  212. n++;
  213.  
  214.  
  215. // LFBT Tarbes Ossun Lourdes
  216. // 43.16598600 -000.01275000 20 43.19130000 000.00006900
  217. // remarque : la piste est à cheval sur le Méridien d'Origine...
  218. strcpy(liste_bali[n].ID_OACI, "LFBT");
  219. strcpy(liste_bali[n].nom, "TARBES");
  220. liste_bali[n].altitude = 1186; // feet
  221. liste_bali[n].lat_A = 43.16598600;
  222. liste_bali[n].lon_A = -0.01275000;
  223. liste_bali[n].lat_B = 43.19130000;
  224. liste_bali[n].lon_B = 0.00006900;
  225. liste_bali[n].sens_app_interdit ='A';
  226. liste_bali[n].niveau_de_vol_mini = 60;
  227. liste_bali[n].dst_pt_AB = 15; // NM
  228. n++;
  229.  
  230.  
  231. // LFCK Mazamet
  232. // 14 43.56266100 002.28210300 32 43.54983900 002.29626100
  233. // attention: sommet (Pic de Nore) alt 1200m (3900 feet !) à 10NM au SE, donc dans le circuit d'approche
  234. // donc décollages et approches par le nord si possible
  235. strcpy(liste_bali[n].ID_OACI, "LFCK");
  236. strcpy(liste_bali[n].nom, "CASTRES MAZAMET");
  237. liste_bali[n].altitude = 741;
  238. liste_bali[n].lat_A = 43.56266100;
  239. liste_bali[n].lon_A = 2.28210300;
  240. liste_bali[n].lat_B = 43.54983900;
  241. liste_bali[n].lon_B = 2.29626100;
  242. liste_bali[n].sens_app_interdit ='B';
  243. liste_bali[n].niveau_de_vol_mini = 40;
  244. liste_bali[n].dst_pt_AB = 15; // NM
  245. n++;
  246.  
  247.  
  248. // LFKB Bastia Poretta
  249. // 16 42.56333900 009.47916800 34 42.54167700 009.48825000
  250. strcpy(liste_bali[n].ID_OACI, "LFKB");
  251. strcpy(liste_bali[n].nom, "BASTIA PORETTA");
  252. liste_bali[n].altitude = 26;
  253. liste_bali[n].lat_A = 42.56333900;
  254. liste_bali[n].lon_A = 9.47916800;
  255. liste_bali[n].lat_B = 42.54167700;
  256. liste_bali[n].lon_B = 9.48825000;
  257. liste_bali[n].sens_app_interdit ='X';
  258. liste_bali[n].niveau_de_vol_mini = 50;
  259. liste_bali[n].dst_pt_AB = 15; // NM
  260. n++;
  261.  
  262.  
  263. // LFKC St Catherine
  264. // 18 42.54117000 008.79282700 36 42.52035400 008.79302300
  265. strcpy(liste_bali[n].ID_OACI, "LFKC");
  266. strcpy(liste_bali[n].nom, "CALVI"); // St.Catherine
  267. liste_bali[n].altitude = 209;
  268. liste_bali[n].lat_A = 42.54117000;
  269. liste_bali[n].lon_A = 8.79282700;
  270. liste_bali[n].lat_B = 42.52035400;
  271. liste_bali[n].lon_B = 8.79302300;
  272. liste_bali[n].sens_app_interdit ='B';
  273. liste_bali[n].niveau_de_vol_mini = 40;
  274. liste_bali[n].dst_pt_AB = 15; // NM
  275. n++;
  276.  
  277.  
  278. // LFKF Figari Sud Corse
  279. // 05 41.49452700 009.08600400 23 41.50960100 009.10791600
  280. strcpy(liste_bali[n].ID_OACI, "LFKF");
  281. strcpy(liste_bali[n].nom, "FIGARI SUD CORSE");
  282. liste_bali[n].altitude = 87;
  283. liste_bali[n].lat_A = 41.49452700;
  284. liste_bali[n].lon_A = 9.08600400;
  285. liste_bali[n].lat_B = 41.50960100;
  286. liste_bali[n].lon_B = 9.10791600;
  287. liste_bali[n].sens_app_interdit ='B';
  288. liste_bali[n].niveau_de_vol_mini = 40;
  289. liste_bali[n].dst_pt_AB = 15; // NM
  290. n++;
  291.  
  292.  
  293. // LFKJ Napoleon Bonaparte
  294. // 02 41.91186937 008.79496744 20 41.93149762 008.80723663
  295. // 10 41.92444700 008.79174100 28 41.92372900 008.80440100
  296. strcpy(liste_bali[n].ID_OACI, "LFKJ");
  297. strcpy(liste_bali[n].nom, "AJACCIO"); // "NAPOLEON BONAPARTE"
  298. liste_bali[n].altitude = 17;
  299. liste_bali[n].lat_A = 41.91186937;
  300. liste_bali[n].lon_A = 8.79496744;
  301. liste_bali[n].lat_B = 41.93149762;
  302. liste_bali[n].lon_B = 8.80723663;
  303. liste_bali[n].sens_app_interdit ='B';
  304. liste_bali[n].niveau_de_vol_mini = 40;
  305. liste_bali[n].dst_pt_AB = 15; // NM
  306. n++;
  307.  
  308.  
  309. // LFLB Aix-Les-Bains
  310. // 18 45.64504575 005.87971068 36 45.63012950 005.88093496
  311. // 18L 45.63803914 005.88121204 36R 45.63172344 005.88173284
  312. strcpy(liste_bali[n].ID_OACI, "LFLB");
  313. strcpy(liste_bali[n].nom, "CHAMBERY - Aix-L-B"); // Aix-Les-Bains
  314. liste_bali[n].altitude = 778;
  315. liste_bali[n].lat_A = 45.64504575;
  316. liste_bali[n].lon_A = 5.87971068;
  317. liste_bali[n].lat_B = 45.63012950;
  318. liste_bali[n].lon_B = 5.88093496;
  319. liste_bali[n].sens_app_interdit ='B'; // par moi ! (pas en réel bien sûr)
  320. liste_bali[n].niveau_de_vol_mini = 50;
  321. liste_bali[n].dst_pt_AB = 15; // NM sachant qu'une distance < 10NM avec un niveau de départ > 30 conduit à une descente musclée ! >> 5%
  322. // dans le cas de cet aérodrome, c'est ça ou slalomer au dessus du lac entre les montagnes...
  323. //(dans le brouillard, c'est encore mieux !)
  324. n++;
  325.  
  326.  
  327. // LFLC Clermont-Ferrand_Auvergne
  328. // 08 45.78482100 003.15074800 26 45.78860300 003.18918600
  329.  
  330. strcpy(liste_bali[n].ID_OACI, "LFLC");
  331. strcpy(liste_bali[n].nom, "CLERMONT FERRAND");
  332. liste_bali[n].altitude = 1089;
  333. liste_bali[n].lat_A = 45.78482100;
  334. liste_bali[n].lon_A = 3.15074800;
  335. liste_bali[n].lat_B = 45.78860300;
  336. liste_bali[n].lon_B = 3.18918600;
  337. liste_bali[n].sens_app_interdit ='A';
  338. liste_bali[n].niveau_de_vol_mini = 50;
  339. liste_bali[n].dst_pt_AB = 15; // NM
  340. n++;
  341.  
  342.  
  343. // LFLP Annecy Haute-Sovoie Mont Blanc Meythet
  344. // 04 45.92345900 006.09231400 22 45.93480300 006.10529200
  345. // 4C 45.92552100 006.09699400 22C 45.93149800 006.10380500
  346. strcpy(liste_bali[n].ID_OACI, "LFLP");
  347. strcpy(liste_bali[n].nom, "ANNECY"); // "ANNECY MEYTHET" Altitude 1521m
  348. liste_bali[n].altitude = 1521;
  349. liste_bali[n].lat_A = 45.92345900;
  350. liste_bali[n].lon_A = 6.09231400;
  351. liste_bali[n].lat_B = 45.93480300;
  352. liste_bali[n].lon_B = 6.10529200;
  353. liste_bali[n].sens_app_interdit ='X';
  354. liste_bali[n].niveau_de_vol_mini = 50;
  355. liste_bali[n].dst_pt_AB = 15; // NM
  356. n++;
  357.  
  358.  
  359. // LFLL Lyon Saint Exupery
  360. // 18R 45.74659384 005.08596244 36L 45.71068847 005.09033087
  361. strcpy(liste_bali[n].ID_OACI, "LFLL");
  362. strcpy(liste_bali[n].nom, "Lyon Saint Exupery");
  363. liste_bali[n].altitude = 820; // ft
  364. liste_bali[n].lat_A = 45.74659384;
  365. liste_bali[n].lon_A = 5.08596244;
  366. liste_bali[n].lat_B = 45.71068847;
  367. liste_bali[n].lon_B = 5.09033087;
  368. liste_bali[n].sens_app_interdit ='X';
  369. liste_bali[n].niveau_de_vol_mini = 40;
  370. liste_bali[n].dst_pt_AB = 15; // NM
  371. n++;
  372.  
  373.  
  374. // LFLY Lyon Bron
  375. // 16 45.73501400 004.94093300 34 45.71932800 004.94761400
  376. strcpy(liste_bali[n].ID_OACI, "LFLY");
  377. strcpy(liste_bali[n].nom, "Lyon BRON");
  378. liste_bali[n].altitude = 659; // ft
  379. liste_bali[n].lat_A = 45.73501400;
  380. liste_bali[n].lon_A = 4.94093300;
  381. liste_bali[n].lat_B = 45.71932800;
  382. liste_bali[n].lon_B = 4.94761400;
  383. liste_bali[n].sens_app_interdit ='X';
  384. liste_bali[n].niveau_de_vol_mini = 50;
  385. liste_bali[n].dst_pt_AB = 15; // NM
  386. n++;
  387.  
  388. // LFMI Istres Le Tube
  389. // 15 43.53777100 004.91330200 0.00 1207.92 3 1 0 1 33 43.50778900 004.93462600 0.00 114.00 3 0 0 1
  390. // 15R 43.52330300 004.91811600 0.00 0.00 0 0 0 0 33L 43.51610700 004.92323400 0.00 0.00 1 0 0 0
  391. strcpy(liste_bali[n].ID_OACI, "LFMI");
  392. strcpy(liste_bali[n].nom, "Istres Le Tube");
  393. liste_bali[n].altitude = 81;
  394. liste_bali[n].lat_A = 43.53777100;
  395. liste_bali[n].lon_A = 4.91330200;
  396. liste_bali[n].lat_B = 43.50778900;
  397. liste_bali[n].lon_B = 4.93462600;
  398. liste_bali[n].sens_app_interdit ='X';
  399. liste_bali[n].niveau_de_vol_mini = 30;
  400. liste_bali[n].dst_pt_AB = 15; // NM
  401. n++;
  402.  
  403.  
  404. // LFML Marseille Provence
  405. // 13L 43.44912530 005.19729576 31R 43.42727470 005.22845736
  406. // 13R 43.44130300 005.20302200 31L 43.42586400 005.22435300
  407. strcpy(liste_bali[n].ID_OACI, "LFML");
  408. strcpy(liste_bali[n].nom, "MARSEILLE PROVENCE");
  409. liste_bali[n].altitude = 23; // au centre de la piste, qui est fort en pente !
  410. liste_bali[n].lat_A = 43.44912530;
  411. liste_bali[n].lon_A = 5.19729576 ;
  412. liste_bali[n].lat_B = 43.42727470;
  413. liste_bali[n].lon_B = 5.22845736;
  414. liste_bali[n].sens_app_interdit ='B';
  415. liste_bali[n].niveau_de_vol_mini = 40;
  416. liste_bali[n].dst_pt_AB = 15; // NM
  417. n++;
  418.  
  419.  
  420. // LFMN Nice/Cote d'Azur
  421. // 04R 43.64658511 007.20258040 22L 43.66554901 007.22843657
  422. // 04L 43.65171156 007.20395740 22R 43.66807997 007.22647422
  423. strcpy(liste_bali[n].ID_OACI, "LFMN");
  424. strcpy(liste_bali[n].nom, "NICE COTE D'AZUR");
  425. liste_bali[n].altitude = 10;
  426. liste_bali[n].lat_A = 43.64658511;
  427. liste_bali[n].lon_A = 7.20258040;
  428. liste_bali[n].lat_B = 43.66554901;
  429. liste_bali[n].lon_B = 007.22843657;
  430. liste_bali[n].sens_app_interdit ='B';
  431. liste_bali[n].niveau_de_vol_mini = 30; // accès côté ouest, (relief côté est)
  432. liste_bali[n].dst_pt_AB = 15; // NM
  433. n++;
  434.  
  435.  
  436. //13L 43.58614900 003.95574600 31R 43.57281900 003.98222800
  437. strcpy(liste_bali[n].ID_OACI, "LFMT");
  438. strcpy(liste_bali[n].nom, "MONTPELLIER");
  439. liste_bali[n].altitude = 17; // feet
  440. liste_bali[n].lat_A = 43.58614900;
  441. liste_bali[n].lon_A = 3.95574600;
  442. liste_bali[n].lat_B = 43.57281900;
  443. liste_bali[n].lon_B = 3.98222800;
  444. liste_bali[n].sens_app_interdit ='X';
  445. liste_bali[n].niveau_de_vol_mini = 30;
  446. liste_bali[n].dst_pt_AB = 15; // NM
  447. n++;
  448.  
  449.  
  450. // LFMU Beziers Vias
  451. // 10 43.32402100 003.34238700
  452. // 28 43.32282300 003.36475900
  453. strcpy(liste_bali[n].ID_OACI, "LFMU");
  454. strcpy(liste_bali[n].nom, "BEZIERS VIAS");
  455. liste_bali[n].altitude = 52;
  456.  
  457. /*
  458. // les valeurs ci-dessous sont celles du fichier d'origine de Flightgear (apt.dat)
  459. liste_bali[n].lat_A = 43.32402100; // 43.32402100
  460. liste_bali[n].lon_A = 3.34238700; // 3.34238700
  461.  
  462. liste_bali[n].lat_B = 43.32282300; // 43.32282300
  463. liste_bali[n].lon_B = 3.36475900; // 3.36475900
  464. */
  465.  
  466. // les valeurs ci-dessous sont celles mesurées avec l'UFO posé au sol en bout de piste
  467. liste_bali[n].lat_A = 43.32402254; // 43.32402100
  468. liste_bali[n].lon_A = 3.342375755; // 3.34238700
  469.  
  470. liste_bali[n].lat_B = 43.32282329; // 43.32282300
  471. liste_bali[n].lon_B = 3.364738854; // 3.36475900
  472.  
  473.  
  474. liste_bali[n].sens_app_interdit ='X';
  475. liste_bali[n].niveau_de_vol_mini = 30;
  476. liste_bali[n].dst_pt_AB = 15; // NM
  477. n++;
  478.  
  479.  
  480. // LFTW Garons Navy
  481. // 18 43.76929100 004.41555600 100.89 0.00 3 1 0 1
  482. // 36 43.74559700 004.41722000 100.89 0.00 3 0 0 1
  483. strcpy(liste_bali[n].ID_OACI, "LFTW");
  484. strcpy(liste_bali[n].nom, "NIMES");
  485. liste_bali[n].altitude = 309;
  486. liste_bali[n].lat_A = 43.76929100;
  487. liste_bali[n].lon_A = 4.41555600;
  488. liste_bali[n].lat_B = 43.74559700;
  489. liste_bali[n].lon_B = 4.41722000;
  490. liste_bali[n].sens_app_interdit ='X';
  491. liste_bali[n].niveau_de_vol_mini = 30;
  492. liste_bali[n].dst_pt_AB = 15; // NM
  493. n++;
  494.  
  495.  
  496. // LFMV Caumont
  497. // 16 43.91080605 004.90321905 34 43.90662331 004.90428974 -> longueur 400m !!!
  498. // 17 43.91556441 004.89936384 35 43.89895841 004.90384384
  499. // 17R 43.91292125 004.89919522 35L 43.90673757 004.90086420
  500. strcpy(liste_bali[n].ID_OACI, "LFMV");
  501. strcpy(liste_bali[n].nom, "AVIGNON Caumont");
  502. liste_bali[n].altitude = 124;
  503. liste_bali[n].lat_A = 43.91556441;
  504. liste_bali[n].lon_A = 4.89936384;
  505. liste_bali[n].lat_B = 43.89895841;
  506. liste_bali[n].lon_B = 4.90384384;
  507. liste_bali[n].sens_app_interdit ='X';
  508. liste_bali[n].niveau_de_vol_mini = 30;
  509. liste_bali[n].dst_pt_AB = 15; // NM
  510. n++;
  511.  
  512.  
  513. // LFPG Paris Charles De Gaulle
  514. // 08L 48.99566392 002.55215505 26R 48.99875734 002.61060285
  515. // 09L 49.02473614 002.52489020 27R 49.02667786 002.56169447
  516. // 08R 48.99292932 002.56581580 26L 48.99486268 002.60243818
  517. // 09R 49.02064475 002.51305527 27L 49.02366525 002.57030334
  518. strcpy(liste_bali[n].ID_OACI, "LFPG");
  519. strcpy(liste_bali[n].nom, "PARIS Ch-De-Gaulle");
  520. liste_bali[n].altitude = 392;
  521. liste_bali[n].lat_A = 48.99566392;
  522. liste_bali[n].lon_A = 2.55215505;
  523. liste_bali[n].lat_B = 48.99875734;
  524. liste_bali[n].lon_B = 2.61060285;
  525. liste_bali[n].sens_app_interdit ='X';
  526. liste_bali[n].niveau_de_vol_mini = 30;
  527. liste_bali[n].dst_pt_AB = 15; // NM
  528. n++;
  529.  
  530.  
  531. // LSGG Geneva
  532. // 05 46.22579381 006.09093933 23 46.25039752 006.12706146
  533. // 05L 46.23481646 006.09953181 23R 46.24001038 006.10715805
  534.  
  535. strcpy(liste_bali[n].ID_OACI, "LSGG");
  536. strcpy(liste_bali[n].nom, "GENEVE");
  537. liste_bali[n].altitude = 1374;
  538. liste_bali[n].lat_A = 46.22579381;
  539. liste_bali[n].lon_A = 6.09093933;
  540. liste_bali[n].lat_B = 46.25039752;
  541. liste_bali[n].lon_B = 6.12706146;
  542. liste_bali[n].sens_app_interdit ='X';
  543. liste_bali[n].niveau_de_vol_mini = 145;
  544. liste_bali[n].dst_pt_AB = 15; // NM
  545. n++;
  546.  
  547.  
  548. // LFNP Nizas
  549. // 11 43.50372800 003.41673200 29 43.50577800 003.40853400
  550.  
  551. strcpy(liste_bali[n].ID_OACI, "LFNP");
  552. strcpy(liste_bali[n].nom, "Nizas");
  553. liste_bali[n].altitude = 308; // = 94m
  554. liste_bali[n].lat_A = 43.50372800;
  555. liste_bali[n].lon_A = 3.41673200;
  556. liste_bali[n].lat_B = 43.50577800;
  557. liste_bali[n].lon_B = 3.40853400;
  558. liste_bali[n].sens_app_interdit ='X';
  559. liste_bali[n].niveau_de_vol_mini = 15;
  560. liste_bali[n].dst_pt_AB = 15; // NM
  561. n++;
  562.  
  563. /*
  564. strcpy(liste_bali[n].ID_OACI, "TEST");
  565. strcpy(liste_bali[n].nom, "T1");
  566. liste_bali[n].altitude = 0;
  567. liste_bali[n].lat_A = 0;
  568. liste_bali[n].lon_A = 0;
  569. liste_bali[n].lat_B = 0;
  570. liste_bali[n].lon_B = 0;
  571. liste_bali[n].sens_app_interdit ='X';
  572. n++;
  573. */
  574.  
  575. nb_elements =n;
  576.  
  577. }
  578.  
  579.  
  580. #endif
  581.  



CODE SOURCE : Fonctions partagées, en C++
  1. //
  2. // ==================================
  3. String version1="35.3";
  4. // ==================================
  5.  
  6.  
  7. #ifndef FONCTIONS1_H
  8. #define FONCTIONS1_H
  9.  
  10. #define _pi 3.141592653
  11. float raddeg =_pi/180.0;
  12.  
  13.  
  14. // COULEURS RGB565
  15. // Outil de sélection -> http://www.barth-dev.de/online/rgb565-color-picker/
  16.  
  17. #define NOIR 0x0000
  18. #define MARRON 0x9240
  19. #define ROUGE 0xF800
  20. #define ROSE 0xFBDD
  21. #define ORANGE 0xFBC0
  22. #define JAUNE 0xFFE0
  23. #define JAUNE_PALE 0xF7F4
  24. #define VERT 0x07E0
  25. #define VERT_FONCE 0x02E2
  26. #define PAMPA 0xBED6
  27. #define OLIVE 0x05A3
  28. #define CYAN 0x07FF
  29. #define BLEU_CLAIR 0x455F
  30. #define AZUR 0x1BF9
  31. #define BLEU 0x001F
  32. #define BLEU_FONCE 0x0005
  33. #define MAGENTA 0xF81F
  34. #define VIOLET1 0x781A
  35. #define VIOLET2 0xECBE
  36. #define GRIS_TRES_CLAIR 0xDEFB
  37. #define GRIS_CLAIR 0xA534
  38. #define GRIS 0x8410
  39. #define GRIS_FONCE 0x5ACB
  40. #define GRIS_TRES_FONCE 0x2124
  41.  
  42. #define GRIS_AF 0x51C5 // 0x3985
  43. #define BLANC 0xFFFF
  44.  
  45. // AVIONIQUE : Horizon Artificiel (HA):
  46.  
  47. #define HA_CIEL 0x33FE
  48. #define HA_SOL 0xAA81 //0xDB60
  49.  
  50.  
  51. // Width and height of sprite
  52. #define SPR_W 25
  53. #define SPR_H 16
  54.  
  55. char var_array16[6]; // 5 char + zero terminal - pour envoi par WiFi (because 2^16 -1 = 65535 -> 5 caractères)
  56. char var_array32[10];// 10 char + zero terminal - pour envoi par WiFi (because 2^32 -1 = 4294967295 -> 10 caractères)
  57.  
  58.  
  59. uint16_t flags=0; // 16 valeurs booléennes dont les bits sont précisés ci-dessous
  60. // attention : pas de notations du type 1<<3 (dans les #define) qui ne sont pas, dans ce cas, précalculées par le compilateur
  61. // mais recopiées telle quelle dans le code
  62. // ce qui entrainerait des erreurs (à l'exécution) par la suite!
  63. // à la rigueur, des notations binaires...
  64.  
  65.  
  66. // valeurs des bits pour chaque attribution :
  67.  
  68. # define bit_au_sol 1 // bit0
  69. # define bit_decollage 2
  70. # define bit_FG_AP 4
  71. # define bit_autoland 8 // procedure d'approche
  72. # define bit_atterrissage 16 // finale et touché des roues
  73. # define bit_rudder_decol 32
  74. # define bit_rudder_attero 64
  75. # define bit_route 128
  76. # define bit_nav_to_piste 256
  77. # define bit_nav_to_pti 512
  78. # define bit_nav_to_ptAA 1024
  79. # define bit_nav_to_ptBB 2048
  80. # define bit_att_short 4096 // atterrissage court (avec reverse, etc..) pour pistes très courtes
  81. # define bit_roulage 8192
  82. # define bit_circling 16384 // tour de piste
  83. # define bit_sens_circling 32768 // sens du tour de piste
  84.  
  85. // pour les boutons
  86.  
  87. # define bit_bt1 1
  88. # define bit_bt2 2
  89. # define bit_bt3 4
  90. # define bit_bt4 8
  91. # define bit_bt5 16
  92.  
  93.  
  94. /* ******************************************************************************** */
  95.  
  96.  
  97. void set_bit(uint16_t *mot_i, uint16_t val_bit) // val_bit étant la valeur du bit (le poids) c.a.d =1 ou 2 ou 4 ou 8...
  98. {
  99. *mot_i |= val_bit;
  100. }
  101.  
  102.  
  103. void raz_bit(uint16_t *mot_i, uint16_t val_bit) // val_bit étant la valeur du bit (le poids) c.a.d =1 ou 2 ou 4 ou 8...
  104. {
  105. *mot_i &= ~val_bit;
  106. }
  107.  
  108.  
  109. void inv_bit(uint16_t *mot_i, uint16_t val_bit) // val_bit étant la valeur du bit (le poids) c.a.d =1 ou 2 ou 4 ou 8...
  110. {
  111. *mot_i ^= val_bit;
  112. }
  113.  
  114.  
  115. uint8_t read_bit(uint16_t mot_i, uint16_t val_bit) // val_bit étant la valeur du bit (le poids) c.a.d =1 ou 2 ou 4 ou 8...
  116. {
  117. if ((mot_i & val_bit) == 0 ) {return 0;} else {return 1;}
  118. }
  119.  
  120.  
  121.  
  122. uint8_t is_in(float valeur_i, float min, float max)
  123. {
  124. if ((valeur_i >= min) && (valeur_i <= max)) {return 1;} else {return 0;}
  125. }
  126.  
  127.  
  128. void borne_in(float *valeur_i, float min, float max)
  129. {
  130. if (*valeur_i < min) {*valeur_i = min;}
  131. if (*valeur_i > max) {*valeur_i = max;}
  132. }
  133.  
  134.  
  135.  
  136.  
  137. float distance_AB(float lat_A, float lon_A, float lat_B, float lon_B)
  138. {
  139. float R = 6373.0; // en km : rayon de la Terre
  140.  
  141. //conversions en radians
  142. lat_A *= raddeg; lat_B *= raddeg;
  143. lon_A *= raddeg; lon_B *= raddeg;
  144.  
  145. float dlat = lat_A - lat_B;
  146. float dlon = lon_A - lon_B;
  147.  
  148. float a = pow(sin(dlat / 2.0),2) + cos(lat_A) * cos(lat_B) * pow(sin(dlon / 2.0),2);
  149. float c = 2.0 * atan2(sqrt(a), sqrt(1 - a));
  150.  
  151. float d= R * c;
  152. return d; // en km
  153. }
  154.  
  155.  
  156. /*
  157.  calcul angle entre deux points WGS84
  158.  voir https://fr.wikipedia.org/wiki/Trigonom%C3%A9trie_sph%C3%A9rique
  159.  latitudes et longitudes en degrés décimaux (ex: 43.47684)
  160.  nonbre négatif si longitude Ouest
  161. */
  162. float azimut_AB(float lat_A, float lon_A, float lat_B, float lon_B) //retour float en degrés
  163. {
  164. //conversions en radians
  165. lat_A *= raddeg; lat_B *= raddeg;
  166. lon_A *= raddeg; lon_B *= raddeg;
  167.  
  168. float delta_lon = lon_B - lon_A;
  169.  
  170. float y = sin(delta_lon) * cos(lat_B);
  171. float x = cos(lat_A)*sin(lat_B) - sin(lat_A)*cos(lat_B)*cos(delta_lon);
  172. float resultat = atan2(y, x)/raddeg;
  173. if (resultat<0) {resultat += 360.0;}
  174. return resultat; //retour en degrés
  175. }
  176.  
  177.  
  178.  
  179. float arc_cos_sin(float cosAlpha, float sinAlpha)
  180. {
  181. //cettte fonction retourne l'angle en fontion du couple [cos, sin] ce qui lève l'ambiguite sur 4 quadrants
  182. if ( sinAlpha >= 0 ) {return acos(cosAlpha);} else {return -acos(cosAlpha);}
  183. }
  184.  
  185.  
  186.  
  187. void bargraph_H_float(float v, uint16_t x0, uint16_t y0, uint16_t couleur) // [-1.0 .. +1.0]
  188. {
  189. // horizontal
  190. TFT480.fillRect(x0-100, y0, 200, 3, GRIS_TRES_FONCE);
  191.  
  192. borne_in(&v, -1, 1);
  193.  
  194. int16_t dx = (int16_t) (100 * v);
  195.  
  196. if(dx <0){TFT480.fillRect(x0+dx, y0, -dx, 3, couleur);}
  197. if(dx >0){TFT480.fillRect(x0, y0, dx, 3, couleur);}
  198.  
  199. }
  200.  
  201.  
  202. void bargraph_V_float(float v, uint16_t x0, uint16_t y0, uint16_t couleur) // [-1.0 .. +1.0]
  203. {
  204. // vertical
  205. TFT480.fillRect(x0, y0-100, 3, 200, GRIS_TRES_FONCE);
  206.  
  207. borne_in(&v, -1, 1);
  208.  
  209. int16_t dy = (int16_t) (100 * v);
  210.  
  211. if(dy <0){TFT480.fillRect(x0, y0+dy, 3, -dy, couleur);}
  212. if(dy >0){TFT480.fillRect(x0, y0, 3, dy, couleur);}
  213.  
  214. }
  215.  
  216.  
  217. void affi_indexH(uint16_t x, uint16_t y, int8_t sens, uint16_t couleur)
  218. {
  219. // petite pointe de flèche horizontale
  220. // sens = +1 ou -1 pour orienter la pointe vers la droite ou vers la gauche
  221.  
  222. TFT480.fillTriangle(x, y-4, x, y+4, x+8*sens, y, couleur);
  223. }
  224.  
  225.  
  226. void affi_indexV(uint16_t x, uint16_t y, int8_t sens, uint16_t couleur)
  227. {
  228. // petite pointe de flèche verticale
  229. // sens = +1 ou -1 pour orienter la pointe vers le haut ou vers le bas
  230.  
  231. TFT480.fillTriangle(x-4, y, x+4, y, x, y+8*sens, couleur);
  232. }
  233.  
  234. void affi_int_en_binaire(uint8_t v, uint16_t x0,uint16_t y0)
  235. {
  236. TFT480.fillRect(x0-72, y0, 80, 12, GRIS_FONCE); // efface le nombre précédemment affiché
  237. char s1;
  238. for(int i=0; i<8; i++)
  239. {
  240. if (( ((v >> i) & 1) != 0 ) == true) {s1='1';} else {s1='0';}
  241. TFT480.drawChar(s1, x0-10*i, y0); // affiche de droite à gauche les bits 0..7
  242. }
  243. }
  244.  
  245.  
  246.  
  247. void affi_int_test(uint32_t v, uint16_t x0, uint8_t num, uint16_t txt_couleur, uint16_t back_couleur) // num = numéro de l'emplacement (en hauteur)
  248. {
  249.  
  250. TFT480.setFreeFont(FM9);
  251. TFT480.setTextColor(txt_couleur);
  252. String s1=String(v);
  253. TFT480.fillRect(x0, 10+18*num, 200, 18, back_couleur); //efface
  254. TFT480.drawString(s1, x0, 10+18*num);
  255. }
  256.  
  257.  
  258. void affi_float_test(float v, uint16_t x0, uint8_t num, uint16_t txt_couleur, uint16_t back_couleur) // num = numéro de l'emplacement (en hauteur)
  259. {
  260.  
  261. TFT480.setFreeFont(FM9);
  262. TFT480.setTextColor(txt_couleur);
  263. String s1=String(v, 6);
  264. TFT480.fillRect(x0, 10+18*num, 200, 18, back_couleur); //efface
  265. TFT480.drawString(s1, x0, 10+18*num);
  266. }
  267.  
  268.  
  269. void affi_string_test(String s, uint16_t x0, uint8_t num, uint16_t txt_couleur, uint16_t back_couleur) // num = numéro de l'emplacement (en hauteur)
  270. {
  271.  
  272. TFT480.setFreeFont(FM9);
  273. TFT480.setTextColor(txt_couleur);
  274. TFT480.fillRect(x0, 10+18*num, 200, 18, back_couleur); //efface
  275. TFT480.drawString(s, x0, 10+18*num);
  276. }
  277.  
  278.  
  279. void affi_message(String s, uint16_t x, uint16_t y,uint16_t dx, uint16_t duree, uint16_t txt_couleur, uint16_t back_couleur, uint8_t efface)
  280. {
  281. //TFT480.setFreeFont(FF6);
  282. TFT480.setTextColor(txt_couleur, back_couleur);
  283. //TFT480.fillRect(x0, 10+18*num, 200, 18, back_couleur); //efface
  284. TFT480.drawString(s, x, y);
  285.  
  286. if (efface == 1)
  287. {
  288. delay(duree);
  289. TFT480.fillRect(x, y, dx, 22, back_couleur); //efface
  290. }
  291. }
  292.  
  293.  
  294.  
  295.  
  296.  
  297. #endif
  298.  

Vous pouvez constater que j'ai fait un effort pour commenter en détail le code source.

IMPORTANT :

sous Linux :
Afin de les rendre accessibles, il y a lieu de créer un lien symbolique des fichiers .h présents dans le dossier '/documents/utility/' dans chaque répertoire des programmes qui les utilisent (PFD et ND).

Il ne faut, en effet, pas dupliquer physiquement ces fichiers dans les répertoires, au risque de se retrouver, lors de mises à jour, avec des versions différentes, source de bugs difficiles à débusquer. Le PFD et le ND doivent utiliser la même version de ces fichiers partagés.

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,10,/dev/ttyUSB0,38400,hardware4
--generic=serial,out,10,/dev/ttyUSB0,38400,hardware4


La vitesse de la liaison série (ici 38400 bauds) doit correspondre exactement à celle programmée dans l'ESP32, voir le code en C++
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)
  • etc... (voir le code C++ ; j'en rajoute régulièrement !)
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...)
(voir le code C++ ; j'en rajoute aussi régulièrement !)

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 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.

9 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

10 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.

11 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.

12 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.

13 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 :

14 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 !

15 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.

16 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.

17 Fonction Autoland (Atterrissage automatique)

27 juin 2021 : version 9.0
Je viens d'ajouter une fonction "Autoland" 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 (enfin presque parce que Flightgear désenclenche automatiquement l'autopilot à l'approche du sol, donc l'arrondi et le posé voivent se faire manuellement, et si le joystick est mal trimé il faut s'attendre à une grosse surprise !)

Ç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.

18 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.

19 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 ??

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

CODE SOURCE en C++
  1. //
  2. // ==================================
  3. String version="35.3";
  4. // ==================================
  5.  
  6. /*---------------------------------------------------------------------------------------
  7. ND.ino - Navigation Display pour ESP32 - pour Afficheur TFT480 480x320
  8.  
  9. par Silicium628
  10. Ce logiciel est libre et gratuit sous licence GNU GPL
  11.  
  12. Pour les #includes issus de l'univers Arduino (que je ne fournis pas), il faut voir au cas par cas.
  13. (drivers d'affichage en particulier)
  14.  
  15. ---------------------------------------------------------------------------------------
  16. De petites images à placer sur la SDcard centrées sur les aérodromes proviennent de OpenStreetMap
  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. CONCERNANT L'AFFICHAGE TFT : connexion :
  30.  
  31. ( Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT_eSPI/ )
  32.  
  33. les lignes qui suivent ne sont q'un commentaire pour vous indiquer la config à utiliser
  34. placée ici, elle ne sont pas fonctionnelles
  35. Il FAUT modifier le fichier User_Setup.h installé par le système Arduino dans ~/Arduino/libraries/TFT_eSPI/
  36.  
  37. // ESP32 pins used for the parallel interface TFT
  38. #define TFT_CS 27 // Chip select control pin
  39. #define TFT_DC 14 // Data Command control pin - must use a pin in the range 0-31
  40. #define TFT_RST 26 // Reset pin
  41.  
  42. #define TFT_WR 12 // Write strobe control pin - must use a pin in the range 0-31
  43. #define TFT_RD 13
  44.  
  45. #define TFT_D0 16 // Must use pins in the range 0-31 for the data bus
  46. #define TFT_D1 4 // so a single register write sets/clears all bits
  47. #define TFT_D2 2 // 23
  48. #define TFT_D3 22
  49. #define TFT_D4 21
  50. #define TFT_D5 15 // 19
  51. #define TFT_D6 25 // 18
  52. #define TFT_D7 17
  53. ==========================================================================================================
  54. **/
  55.  
  56. /*
  57. Notes :
  58.  
  59. - ce Navigation Display reçoit toutes les données par WiFi, émises par l'ESP du PFD. Il n'est donc pas nécessaire de le connecter
  60. sur un port USB, il peut être simplement alimenté par un adaptateur 5V, ou sur l'alim 5V du PFD (en amont du régulateur 3V3 )
  61. si toutefois le port USB0 (et son câble) qui alimentent le PFD peuvent fournir suffisamment de courant.
  62. */
  63.  
  64.  
  65. // le numéro de version doit être identique à celui du PFD (Primary Flight Display)
  66.  
  67. // affichage carte + zoom
  68. // selection des carte par encodeur rotatif (ce qui met à jour les fréquences radio et les paramètres de la piste, et ILS)
  69. // affiche avion sur la carte
  70. // affiche faisceau ILS sur la carte
  71. // affiche cercles 10 , 20, 40, 80, 160, 320NM sur la carte
  72. // v14.0 les fichiers principaux ont été renommés par soucis de clarté
  73. // v15.6 ajout de cartes .bmp (à copier sur la SDcard)
  74. // v19.0 choix aérodrome dans une liste. Affichage d'une photo de l'aérodrome en plus de la carte
  75.  
  76. // todo : mieux centrer certaines cartes (j'ai ajouté une mire dans le .zip)
  77.  
  78.  
  79. #include <stdint.h>
  80. #include <TFT_eSPI.h> // Hardware-specific library
  81.  
  82. #include "Free_Fonts.h"
  83.  
  84. #include "FS.h"
  85. #include "SD.h"
  86. #include "SPI.h"
  87.  
  88. #include "ND.h"
  89. #include "FG_data.h"
  90. #include "Fonctions1.h"
  91.  
  92. #include <WiFi.h>
  93. #include <HTTPClient.h> // nous nous connecterons en tant que client au serveur PFD.
  94. // Remarque: les fonctions WiFi consomment 50% de la mémoire flash (de programme)
  95.  
  96. const char* ssid = "PFD_srv"; // doit être identique à celui du serveur du PFD
  97. const char* password = "72r4TsJ28"; // doit être identique à celui du serveur du PFD
  98.  
  99.  
  100. // addresse IP + chemins URL du PFD:
  101. const char* srvName_hdg = "http://192.168.4.1/hdg";
  102. const char* srvName_cap = "http://192.168.4.1/cap";
  103. const char* srvName_ils_nm = "http://192.168.4.1/ILSnm";
  104. const char* srvName_ils_actual = "http://192.168.4.1/ILSactual";
  105. ////const char* srvName_sw = "http://192.168.4.1/switch";
  106. const char* srvName_latitude = "http://192.168.4.1/latitude";
  107. const char* srvName_longitude = "http://192.168.4.1/longitude";
  108. const char* srvName_hauteur = "http://192.168.4.1/hauteur";
  109. const char* srvName_flags = "http://192.168.4.1/flags";
  110.  
  111.  
  112.  
  113. // données reçues par WiFi depuis le serveur du PFD
  114. String recp_hdg; // heading bug - HDG = consigne de cap
  115. String recp_cap; // cap actuel (orientation/Nord du nez de l'avion) - sens horaire
  116. String recp_latitude;
  117. String recp_longitude;
  118. String recp_hauteur;
  119. String recp_flags;
  120.  
  121. Led Led1;
  122. Led Led2;
  123.  
  124. TFT_eSprite SPR_VOR = TFT_eSprite(&TFT480);
  125. TFT_eSprite SPR_avion = TFT_eSprite(&TFT480); // le petit avion sur la carte
  126.  
  127. uint16_t memo_img[20*20];
  128.  
  129. TFT_eSprite SPR_E = TFT_eSprite(&SPR_VOR); // Déclare Sprite object "SPR_11" with pointer to "TFT480" object
  130. TFT_eSprite SPR_N = TFT_eSprite(&SPR_VOR);
  131. TFT_eSprite SPR_O = TFT_eSprite(&SPR_VOR);
  132. TFT_eSprite SPR_S = TFT_eSprite(&SPR_VOR);
  133.  
  134. TFT_eSprite SPR_3 = TFT_eSprite(&SPR_VOR);
  135. TFT_eSprite SPR_6 = TFT_eSprite(&SPR_VOR);
  136. TFT_eSprite SPR_12 = TFT_eSprite(&SPR_VOR);
  137. TFT_eSprite SPR_15 = TFT_eSprite(&SPR_VOR);
  138. TFT_eSprite SPR_21 = TFT_eSprite(&SPR_VOR);
  139. TFT_eSprite SPR_24 = TFT_eSprite(&SPR_VOR);
  140. TFT_eSprite SPR_30 = TFT_eSprite(&SPR_VOR);
  141. TFT_eSprite SPR_33 = TFT_eSprite(&SPR_VOR);
  142.  
  143. Cadre cadre_top1;
  144. Cadre cadre_top1b;
  145. Cadre cadre_top2;
  146.  
  147. Cadre cadre_00;
  148. Cadre cadre_01;
  149.  
  150. Cadre cadre_10;
  151. Cadre cadre_11;
  152.  
  153. Cadre cadre_20;
  154. Cadre cadre_21;
  155.  
  156. Cadre cadre_cap1;
  157. Cadre cadre_hdg1;
  158.  
  159. Cadre cadre_AZ_piste;
  160. Cadre cadre_distance_piste;
  161.  
  162. Cadre cadre_bas1; // dans la partie droite
  163. Cadre cadre_bas2;
  164.  
  165. Cadre cadre_position; // affiche la position de l'avion (Lat/Long WGS84)
  166.  
  167. Cadre cadre_info; // messages d'info au bas de la partie gauche
  168.  
  169.  
  170. uint8_t saisir_ecran=0;
  171.  
  172. int16_t num_bali=9; // peut être négatif
  173. uint32_t memo_micros = 0;
  174. uint32_t temps_ecoule;
  175. uint16_t nb_100ms=0;
  176. uint16_t nb_secondes=0;
  177.  
  178. float cap;
  179. float memo_cap;
  180.  
  181. int16_t hdg1 = 150; // consigne cap
  182. int16_t memo1_hdg1; // pour le changement manuel
  183. int16_t memo2_hdg1; // pour l'autoland
  184.  
  185. uint8_t SDcardOk=0;
  186. uint8_t gs_ok;
  187.  
  188. uint8_t refresh_ND =1;
  189. uint8_t refresh_Encoche =1;
  190. uint8_t refresh_carte =1;
  191.  
  192. // les deux valeurs suivantes sont chosies pour afficher LFMT au lancement du programme. On peut choisir autre chose...
  193. uint16_t decal=6; // pour la liste des aérodromes
  194. uint16_t num_ligne =14; // pour la liste des aérodromes
  195.  
  196. float longueur_piste;
  197. float orient_piste;
  198.  
  199. float lat_centre_pst;
  200. float lon_centre_pst;
  201.  
  202. uint16_t x_avion; // position (en px) du petit avion qui se déplace sur la carte
  203. uint16_t y_avion;
  204. uint16_t memo_x_avion;
  205. uint16_t memo_y_avion;
  206.  
  207. float lon_avion;
  208. float lat_avion;
  209. float hauteur_AAL;
  210.  
  211. float GPS_distance_piste; // en NM
  212. float GPS_azimut_piste;
  213.  
  214. uint16_t memo_flags; // servira à détecter et agir rapidement lors de la modif de la valeur
  215.  
  216. int8_t zoom; // pour la carte; = numéro de la carte .bmp
  217. int8_t memo_zoom;
  218.  
  219. float px_par_km;
  220. float px_par_NM;
  221.  
  222. float plan_ref_x; // centre de la piste
  223. float plan_ref_y;
  224. float plan_echelle;
  225.  
  226.  
  227. uint16_t SW0=1;
  228. const int GPIO_SW0 = 36;
  229. bool SW0_etat;
  230. bool memo_SW0_etat;
  231.  
  232.  
  233. uint16_t bouton0=1; // bouton poussoir smd
  234. const int GPIO_bouton0 = 34;
  235. bool bouton0_etat;
  236. bool memo_bouton0_etat;
  237.  
  238. uint16_t bouton1=2; // bouton poussoir smd
  239. const int GPIO_bouton1 = 35;
  240. bool bouton1_etat;
  241. bool memo_bouton1_etat;
  242.  
  243. uint16_t bouton2=4; // bouton poussoir smd
  244. const int GPIO_bouton2 = 32;
  245. bool bouton2_etat;
  246. bool memo_bouton2_etat;
  247.  
  248. uint16_t bouton3=8; // bouton poussoir smd
  249. const int GPIO_bouton3 = 33;
  250. bool bouton3_etat;
  251. bool memo_bouton3_etat;
  252.  
  253.  
  254.  
  255.  
  256. // encodeurs rotatif pas à pas
  257.  
  258. /* remplacé par 2 boutons poussoirs
  259. const int rot1a = 32; // GPIO32 -> câbler une R au +3V3
  260. const int rot1b = 33; // GPIO33 -> câbler une R au +3V3
  261.  
  262. /* remplacé par 2 boutons poussoirs
  263. const int rot2a = 35; // GPIO35 -> câbler une R au +3V3
  264. const int rot2b = 34; // GPIO34 -> câbler une R au +3V3
  265. */
  266.  
  267. //const int led1 = 25; // GPIO15
  268.  
  269. #define TEMPO 20 // tempo anti-rebond pour l'acquisition des encodeurs rotatifs
  270. volatile uint32_t timer1 = 0;
  271. volatile uint32_t timer2 = 0;
  272.  
  273. uint16_t compteur1;
  274. uint8_t premier_passage=1;
  275. uint8_t zoom_auto=0;
  276.  
  277. uint32_t bmp_offset = 0;
  278.  
  279. String switches="null"; // boutons connectés au 3eme ESP32, reçus par WiFi
  280.  
  281.  
  282. #define MODE_ND 0
  283. #define MODE_LISTE 1
  284. #define MODE_PLAN 2
  285. #define MODE_CARTE_FR 3
  286.  
  287. uint8_t mode_affi=MODE_LISTE;
  288.  
  289.  
  290.  
  291.  
  292. void RAZ_variables()
  293. {
  294. hdg1=0; // consigne cap
  295. }
  296.  
  297.  
  298. void httpGetHDG()
  299. {
  300. String s1;
  301. String s2;
  302.  
  303. HTTPClient http;
  304. char c1[4];
  305.  
  306. s1= "?a1="; // a1 (nom arbitraire..) pour désigner l'argument envoyé dans l'URL de la requête vers le serveur (le PFD)
  307. s1+= num_bali;
  308.  
  309. s1+= "&swND="; // swND pour désigner l'argument envoyé dans l'URL de la requête
  310. s2= switches;
  311. s1+= s2;
  312.  
  313. http.begin(srvName_hdg+s1); // rappel : srvName_hdg = "http://192.168.4.1/switch"
  314. int httpResponseCode = http.GET();
  315. if (httpResponseCode>0) { recp_hdg = http.getString();}
  316. http.end();
  317. }
  318.  
  319.  
  320.  
  321. void httpGetCap()
  322. {
  323. HTTPClient http;
  324. http.begin(srvName_cap);
  325. int httpResponseCode = http.GET();
  326. if (httpResponseCode>0) { recp_cap = http.getString(); }
  327. http.end();
  328. }
  329.  
  330.  
  331. void httpGetLatitude() // Latitude de l'avion
  332. {
  333. HTTPClient http;
  334. http.begin(srvName_latitude);
  335. int httpResponseCode = http.GET();
  336. if (httpResponseCode>0) { recp_latitude = http.getString(); }
  337. http.end();
  338. }
  339.  
  340.  
  341. void httpGetLongitude() // Longitude de l'avion
  342. {
  343. HTTPClient http;
  344. http.begin(srvName_longitude);
  345. int httpResponseCode = http.GET();
  346. if (httpResponseCode>0) { recp_longitude = http.getString(); }
  347. http.end();
  348. }
  349.  
  350.  
  351. void httpGetHauteur() // Hauteur de l'avion / sol
  352. {
  353. HTTPClient http;
  354. http.begin(srvName_hauteur);
  355. int httpResponseCode = http.GET();
  356. if (httpResponseCode>0) { recp_hauteur = http.getString(); }
  357. http.end();
  358. }
  359.  
  360.  
  361.  
  362. void httpGetParams()
  363. {
  364. HTTPClient http;
  365. http.begin(srvName_flags);
  366. int httpResponseCode = http.GET();
  367. if (httpResponseCode>0) { recp_flags = http.getString(); }
  368. http.end();
  369. }
  370.  
  371. ////void httpGet_SW() // depuis le SW: c.a.d l'ESP32 n°3
  372. ////{
  373. ////HTTPClient http;
  374. ////http.begin(srvName_sw);
  375. ////int httpResponseCode = http.GET();
  376. ////if (httpResponseCode>0) { switches = http.getString(); }
  377. ////http.end();
  378. ////}
  379.  
  380.  
  381.  
  382. /** ***********************************************************************************
  383. IMAGE.bmp
  384. ***************************************************************************************/
  385.  
  386.  
  387. /**
  388. Rappel et décryptage de la fonction Color_To_565 : (elle se trouve dans le fichier LCDWIKI_KBV.cpp)
  389.  
  390. //Pass 8-bit (each) R,G,B, get back 16-bit packed color
  391.  
  392. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  393. {
  394. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  395. }
  396.  
  397. 0xF8 = 11111000
  398. 0xFC = 11111100
  399.  
  400. (r & 0xF8) -> 5 bit de gauche de r (on ignore donc les 3 bits de poids faible)
  401. (g & 0xFC) -> 6 bit de gauche de g (on ignore donc les 2 bits de poids faible)
  402. (b & 0xF8) -> 5 bit de gauche de b (on ignore donc les 3 bits de poids faible)
  403.  
  404. rrrrr---
  405. gggggg--
  406. bbbbb---
  407.  
  408. après les décalages on obtient les 16 bits suivants:
  409.  
  410. rrrrr---========
  411.   gggggg--===
  412.   ===bbbbb
  413.  
  414. soit après le ou :
  415.  
  416. rrrrrggggggbbbbb
  417.  
  418. calcul de la Fonction inverse :
  419. RGB565_to_888
  420. **/
  421.  
  422. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  423. {
  424. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  425. }
  426.  
  427.  
  428. void RGB565_to_888(uint16_t color565, uint8_t *R, uint8_t *G, uint8_t *B)
  429. {
  430. *R=(color565 & 0xFFFFF800) >> 8;
  431. *G=(color565 & 0x7E0) >> 3;
  432. *B=(color565 & 0x1F) << 3 ;
  433. }
  434.  
  435.  
  436.  
  437.  
  438. uint16_t bmp_width;
  439. uint16_t bmp_heigh;
  440.  
  441.  
  442. uint16_t read_16(File fp)
  443. {
  444. uint8_t low;
  445. uint16_t high;
  446. low = fp.read();
  447. high = fp.read();
  448. return (high<<8)|low;
  449. }
  450.  
  451.  
  452.  
  453. uint32_t read_32(File fp)
  454. {
  455. uint16_t low;
  456. uint32_t high;
  457. low = read_16(fp);
  458. high = read_16(fp);
  459. return (high<<8)|low;
  460. }
  461.  
  462.  
  463.  
  464. void write_16(uint16_t v16, File fp)
  465. {
  466. uint8_t low, high;
  467.  
  468. low = v16 & 0xFF;
  469. high= v16 >>8;
  470.  
  471. fp.write(low);
  472. fp.write(high);
  473. }
  474.  
  475.  
  476. void write_32(uint32_t v32, File fp)
  477. {
  478. uint16_t low, high;
  479.  
  480. low = v32 & 0xFFFF;
  481. high= v32 >>16;
  482.  
  483. write_16(low, fp);
  484. write_16(high, fp);
  485. }
  486.  
  487.  
  488.  
  489. bool teste_bmp_header(File fp)
  490. {
  491. if(read_16(fp) != 0x4D42) { return false; } // (2 bytes) The header field used to identify the BMP
  492. read_32(fp); // (4 bytes) get bmp size (nombre total d'octets)
  493. read_32(fp); // (4 bytes) get creator information
  494. bmp_offset = read_32(fp); // (4 bytes) get offset information
  495. read_32(fp);//get DIB information
  496.  
  497. // ici on a lu 16 octets
  498. bmp_width = read_32(fp); //(4 bytes) get width and heigh information
  499. bmp_heigh = read_32(fp); //(4 bytes)
  500.  
  501. // ici on a lu 24 octets
  502. //if(read_16(fp)!= 1) {return false;}
  503. read_16(fp);
  504. //if(read_32(fp)!= 0) {return false;}
  505. return true;
  506. }
  507.  
  508.  
  509.  
  510. uint8_t LumCtr(uint8_t vi, float lum, float ctr)
  511. {
  512. float v2;
  513. uint8_t result;
  514. v2 = ((float)vi - lum) * ctr;
  515. if (v2<0) {v2=0;}
  516. if (v2>255) {v2=255;}
  517. result = (uint8_t) v2;
  518. return result;
  519. }
  520.  
  521.  
  522.  
  523. void draw_bmp(uint16_t x0, uint16_t y0, uint8_t rot, float lum, float contraste, File* fp)
  524. {
  525.  
  526. //sram = freeRam(); Serial.print("03-freeRam="); Serial.println(sram);
  527. uint16_t i,j,k,p,m=0;
  528. uint16_t y1;
  529. uint8_t bmp_data[2*3]={0};
  530. uint16_t bmp_color[2];
  531.  
  532. fp->seek(bmp_offset);
  533. for(i=0; i<bmp_heigh; i++)
  534. {
  535. for(j=0; j<(bmp_width/2); j++)
  536. {
  537. m=0;
  538. fp->read(bmp_data,2*3);
  539. for(k=0; k<2; k++)
  540. {
  541. bmp_color[k]= Color_To_565(LumCtr(bmp_data[m+2],lum, contraste), LumCtr(bmp_data[m+1],lum, contraste), LumCtr(bmp_data[m+0],lum, contraste));
  542. m+=3;
  543. }
  544. for(p=0; p<2; p++)
  545. {
  546. if (rot==0)
  547. {
  548. y1=y0;
  549. TFT480.drawPixel(x0+i, y0+j*2+p, bmp_color[p]);
  550. }
  551. if (rot==1)
  552. {
  553. y1=160-y0;
  554. TFT480.drawPixel(x0+j*2+p,320-(y1+i), bmp_color[p]);
  555. }
  556. }
  557. }
  558. }
  559. }
  560.  
  561.  
  562.  
  563.  
  564. void affi_img(uint16_t x0, uint16_t y0, float lum, float contraste, const char* filename1)
  565. {
  566.  
  567. File bmp_file;
  568. TFT480.setFreeFont(FF1);
  569. TFT480.setTextColor(ORANGE, NOIR);
  570. bmp_file = SD.open(filename1);
  571. if(!bmp_file)
  572. {
  573.  
  574. bmp_file.close();
  575. return;
  576. }
  577. if(!teste_bmp_header(bmp_file))
  578. {
  579. bmp_file.close();
  580. return;
  581. }
  582.  
  583. draw_bmp(x0, y0, 1, lum, contraste, &bmp_file);
  584.  
  585. bmp_file.close();
  586. // delay(1000);
  587.  
  588. }
  589.  
  590.  
  591.  
  592. /** -----------------------------------------------------------------------------------
  593. CAPTURE D'ECRAN vers SDcard
  594. /** ----------------------------------------------------------------------------------- */
  595.  
  596. void write_TFT480_on_SDcard() // enregistre le fichier .bmp
  597. {
  598. if (SDcardOk==1)
  599. {
  600. ////TFT480.drawString("capture bmp !", 100, 100);
  601. ////delay(1000);
  602.  
  603. String s1;
  604. uint16_t ys=200;
  605. TFT480.setFreeFont(FF1);
  606. TFT480.setTextColor(JAUNE, NOIR);
  607.  
  608. uint16_t x, y;
  609. uint16_t color565;
  610. uint16_t bmp_color;
  611. uint8_t R, G, B;
  612.  
  613. if( ! SD.exists("/bmp/capture2.bmp"))
  614. {
  615. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  616. TFT480.setTextColor(ROUGE, NOIR);
  617. TFT480.drawString("NO /bmp/capture2.bmp !", 100, ys);
  618. delay(300);
  619. TFT480.fillRect(100, ys, 220, 20, NOIR); // efface
  620. return;
  621. }
  622.  
  623.  
  624. File File1 = SD.open("/bmp/capture2.bmp", FILE_WRITE); // ouverture du fichier binaire (vierge) en écriture
  625. if (File1)
  626. {
  627. /*
  628. Les images en couleurs réelles BMP888 utilisent 24 bits par pixel:
  629. Il faut 3 octets pour coder chaque pixel, en respectant l'ordre de l'alternance bleu, vert et rouge.
  630. */
  631. uint16_t bmp_offset = 138;
  632. File1.seek(bmp_offset);
  633.  
  634.  
  635. TFT480.setTextColor(VERT, NOIR);
  636.  
  637. for (y=320; y>0; y--)
  638. {
  639. for (x=0; x<480; x++)
  640. {
  641. color565=TFT480.readPixel(x, y);
  642.  
  643. RGB565_to_888(color565, &R, &G, &B);
  644.  
  645. File1.write(B); //G
  646. File1.write(G); //R
  647. File1.write(R); //B
  648. }
  649.  
  650. s1=(String) (y/10);
  651. TFT480.fillRect(2, 300, 20, 20, NOIR);
  652. TFT480.drawString(s1, 2, 300);// affi compte à rebour
  653. }
  654.  
  655. File1.close(); // referme le fichier
  656.  
  657. TFT480.fillRect(2, 300, 20, 20, NOIR); // efface le compte à rebour
  658. }
  659. }
  660. }
  661.  
  662. /** ----------------------------------------------------------------------------------- */
  663.  
  664.  
  665.  
  666.  
  667. void Draw_arc_elliptique(uint16_t x0, uint16_t y0, int16_t dx, int16_t dy, float angle1, float angle2, uint16_t couleur)
  668. {
  669. /*
  670. REMARQUES :
  671. -cette fonction permet également de dessiner un arc de cercle (si dx=dy), voire le cercle complet
  672. - dx et dy sont du type int (et pas uint) et peuvent êtres négafifs, ou nuls.
  673. -alpha1 et alpha2 sont les angles (en radians) des caps des extrémités de l'arc
  674. */
  675.  
  676. // angle1 et angle2 en degrés décimaux
  677.  
  678.  
  679. float alpha1 =angle1/57.3; // (57.3 ~ 180/pi)
  680. float alpha2 =angle2/57.3;
  681. uint16_t n;
  682. float i;
  683. float x,y;
  684.  
  685. i=alpha1;
  686. while(i<alpha2)
  687. {
  688. x=x0+dx*cos(i);
  689. y=y0+dy*cos(i+M_PI/2.0);
  690. TFT480.drawPixel(x,y, couleur);
  691. i+=0.01; // radians
  692. }
  693. }
  694.  
  695.  
  696. void affi_rayon1(uint16_t x0, uint16_t y0, uint16_t rayon, float angle_i, float pourcent, uint16_t couleur_i, bool gras)
  697. {
  698. // trace une portion de rayon de cercle depuis 100%...à pourcent du rayon du cercle
  699. // angle_i en degrés décimaux - sens trigo
  700.  
  701. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  702. float x1, x2;
  703. float y1, y2;
  704.  
  705. x1=x0+rayon* cos(angle);
  706. y1=y0-rayon* sin(angle);
  707.  
  708. x2=x0+pourcent*rayon* cos(angle);
  709. y2=y0-pourcent*rayon* sin(angle);
  710.  
  711. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  712.  
  713. /*
  714. if (gras)
  715. {
  716. TFT480.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  717. TFT480.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  718. }
  719. */
  720. }
  721.  
  722.  
  723.  
  724. void affi_rayon2(uint16_t x0, uint16_t y0, float r1, float r2, float angle_i, uint16_t couleur_i, uint8_t gras)
  725. {
  726. // trace une portion de rayon de cercle entre les distances r1 et r2 du centre
  727. // angle_i en degrés décimaux - sens trigo (= anti-horaire)
  728. // origine d'angle -> trigo (à droite)
  729.  
  730. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  731. int16_t x1, x2;
  732. int16_t y1, y2;
  733.  
  734.  
  735. x1=x0+int16_t(r1* cos(angle));
  736. y1=y0-int16_t(r1* sin(angle));
  737.  
  738. x2=x0+int16_t(r2* cos(angle));
  739. y2=y0-int16_t(r2* sin(angle));
  740.  
  741. //en fait si les extrémités dépassent de la taille de l'afficheur, ça se passe bien !
  742. //if ((x1>0) && (x2>0) && (y1>0) && (y2>0) && (x1<480) && (x2<480) && (y1<320) && (y2<320) )
  743. {
  744. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  745.  
  746. if (gras==1)
  747. {
  748. TFT480.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  749. TFT480.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  750. }
  751. }
  752. }
  753.  
  754.  
  755.  
  756. void affi_char_r_alpha(uint16_t x0, uint16_t y0, float r1, float angle_i, char c1, uint16_t couleur_i)
  757. {
  758. // affiche un caractère à un emplacement situé à une distance r1 du centre et à la distance r1
  759. // angle_i en degrés décimaux - sens trigo (= anti-horaire)
  760. // origine d'angle -> trigo (à droite)
  761. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  762. int16_t x1;
  763. int16_t y1;
  764.  
  765. x1=x0+int16_t(r1* cos(angle));
  766. y1=y0-int16_t(r1* sin(angle));
  767.  
  768. TFT480.setFreeFont(FF1);
  769. TFT480.setTextColor(couleur_i, BLANC);
  770. String s1 =String(c1);
  771. TFT480.drawString(s1, x1-4, y1-4);
  772. }
  773.  
  774.  
  775.  
  776. void affi_SPR_rayon2(uint16_t x0, uint16_t y0, float r1, float r2, float angle_i, uint16_t couleur_i, bool gras)
  777. {
  778. // trace une portion de rayon de cercle entre les distances r1 et r2 du centre
  779. // angle_i en degrés décimaux - sens trigo
  780.  
  781. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  782. int16_t x1, x2;
  783. int16_t y1, y2;
  784.  
  785. x1=x0+int16_t(r1* cos(angle));
  786. y1=y0-int16_t(r1* sin(angle));
  787.  
  788. x2=x0+int16_t(r2* cos(angle));
  789. y2=y0-int16_t(r2* sin(angle));
  790.  
  791. if ((x1>0) && (x2>0) && (y1>0) && (y2>0) && (x1<480) && (x2<480) && (y1<320) && (y2<320) )
  792. {
  793. SPR_VOR.drawLine(x1, y1, x2, y2, couleur_i);
  794.  
  795. if (gras)
  796. {
  797. SPR_VOR.drawLine(x1, y1-1, x2, y2-1, couleur_i);
  798. SPR_VOR.drawLine(x1, y1+1, x2, y2+1, couleur_i);
  799. }
  800. }
  801. }
  802.  
  803.  
  804.  
  805. void affi_tiret_H(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
  806. {
  807. // trace un tiret perpendiculaire à un rayon de cercle de rayon r
  808. // angle_i en degrés décimaux - sens trigo
  809.  
  810. float angle =angle_i/57.3; // (57.3 ~ 180/pi)
  811. float x1, x2;
  812. float y1, y2;
  813.  
  814. x1=x0+(r)* cos(angle-1);
  815. y1=y0-(r)* sin(angle-1);
  816.  
  817. x2=x0+(r)* cos(angle+1);
  818. y2=y0-(r)* sin(angle+1);
  819.  
  820. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  821. }
  822.  
  823.  
  824.  
  825. void affi_pointe(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
  826. {
  827. // trace une pointe de flèche sur un cercle de rayon r
  828. // angle_i en degrés décimaux - sens trigo
  829.  
  830. float angle = angle_i /57.3; // (57.3 ~ 180/pi)
  831. int16_t x1, x2, x3;
  832. int16_t y1, y2, y3;
  833.  
  834. x1=x0+r* cos(angle); // pointe
  835. y1=y0-r* sin(angle); // pointe
  836.  
  837. x2=x0+(r-7)* cos(angle-0.1); // base A
  838. y2=y0-(r-7)* sin(angle-0.1); // base A
  839.  
  840. x3=x0+(r-7)* cos(angle+0.1); // base B
  841. y3=y0-(r-7)* sin(angle+0.1); // base B
  842.  
  843. TFT480.fillTriangle(x1, y1, x2, y2, x3, y3, couleur_i);
  844. }
  845.  
  846.  
  847.  
  848. void affi_SPR_encoche(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
  849. {
  850. // trace une encoche (deux 'demi'-triangles) sur un cercle de rayon r
  851. // angle_i en degrés décimaux - sens trigo
  852.  
  853. float angle = angle_i /57.3; // (57.3 ~ 180/pi)
  854. int16_t x1, x2, x3;
  855. int16_t y1, y2, y3;
  856.  
  857.  
  858. // partie gauche
  859. x1=x0+r* cos(angle+0.08); // pointe
  860. y1=y0-r* sin(angle+0.08); // pointe
  861.  
  862. x2=x0+(r-10)* cos(angle+0.08); // base A
  863. y2=y0-(r-10)* sin(angle+0.08); // base A
  864.  
  865. x3=x0+(r-10)* cos(angle); // base B
  866. y3=y0-(r-10)* sin(angle); // base B
  867.  
  868. SPR_VOR.fillTriangle(x1, y1, x2, y2, x3, y3, couleur_i);
  869.  
  870. // partie droite
  871. x1=x0+r* cos(angle-0.08); // pointe
  872. y1=y0-r* sin(angle-0.08); // pointe
  873.  
  874. x2=x0+(r-10)* cos(angle-0.08); // base A
  875. y2=y0-(r-10)* sin(angle-0.08); // base A
  876.  
  877. x3=x0+(r-10)* cos(angle); // base B
  878. y3=y0-(r-10)* sin(angle); // base B
  879.  
  880. SPR_VOR.fillTriangle(x1, y1, x2, y2, x3, y3, couleur_i);
  881. }
  882.  
  883.  
  884.  
  885. void affi_rectangle_incline(uint16_t x0, uint16_t y0, uint16_t r, float angle_i, uint16_t couleur_i)
  886. {
  887. //rectangle inscrit dans le cerce de rayon r
  888. // angle_i en degrés décimaux - sens trigo
  889.  
  890. float angle =(angle_i-3.5)/57.3;
  891. int16_t x1, x2, x3, x4;
  892. int16_t y1, y2, y3, y4;
  893. float d_alpha=0.05; // détermine la largeur du rectangle
  894.  
  895. // point 1
  896. x1=x0+r*cos(angle-d_alpha);
  897. y1=y0+r*sin(angle-d_alpha);
  898. // point 2
  899. x2=x0+r*cos(angle+d_alpha);
  900. y2=y0+r*sin(angle+d_alpha);
  901. // point 3
  902. x3=x0+r*cos(M_PI + angle-d_alpha);
  903. y3=y0+r*sin(M_PI + angle-d_alpha);
  904. // point 4
  905. x4=x0+r*cos(M_PI + angle+d_alpha);
  906. y4=y0+r*sin(M_PI + angle+d_alpha);
  907.  
  908. TFT480.drawLine(x1, y1, x2, y2, couleur_i);
  909. TFT480.drawLine(x2, y2, x3, y3, couleur_i);
  910. TFT480.drawLine(x3, y3, x4, y4, couleur_i);
  911. TFT480.drawLine(x4, y4, x1, y1, couleur_i);
  912.  
  913. }
  914.  
  915.  
  916.  
  917.  
  918.  
  919. float degTOrad(float angle)
  920. {
  921. return (angle * M_PI / 180.0);
  922. }
  923.  
  924.  
  925.  
  926.  
  927. void affi_dst_piste() // distance GPS
  928. {
  929. if (mode_affi == MODE_PLAN) {return;}
  930. //TFT480.setTextFont(2);
  931. cadre_distance_piste.efface();
  932. int nb_decimales =2;
  933. if (GPS_distance_piste>=10){nb_decimales=1;}
  934. if (GPS_distance_piste>=100){nb_decimales=0;}
  935. cadre_distance_piste.affiche_float(GPS_distance_piste, nb_decimales);
  936. //TFT480.setTextFont(1);
  937. //TFT480.setTextColor(BLANC);
  938. //TFT480.drawString("GPS", cadre_distance_piste.x0, cadre_distance_piste.y0-15);
  939. TFT480.setTextFont(1);
  940. TFT480.setTextColor(VERT);
  941. TFT480.drawString("NM", cadre_distance_piste.x0+60, cadre_distance_piste.y0+12);
  942.  
  943. // ZOOM AUTO
  944. if (read_bit(flags, bit_autoland) == 1) // c.a.d si le mode autoland de l'ESP32 PFD est engagé
  945. // ce qui évite le déclenchement de cet automatisme au décollage !
  946. {
  947. memo_zoom=zoom;
  948. if((GPS_distance_piste <16.0)&& (zoom>5)) {zoom=5;}
  949. if((GPS_distance_piste <8.0) && (zoom>4)) {zoom=4;}
  950. if((GPS_distance_piste <4.0) && (zoom>3)) {zoom=3;}
  951. if((GPS_distance_piste <2.0) && (zoom>2)) {zoom=2;}
  952. if((GPS_distance_piste <1.0) && (zoom>1)) {zoom=1;}
  953. if((GPS_distance_piste <0.5) && (zoom>0)) {zoom=0;}
  954.  
  955.  
  956. if (zoom!=memo_zoom)
  957. {
  958. affi_carte_locale(235,0,1);
  959. affi_ND(cap);
  960. }
  961.  
  962. ////if((GPS_distance_piste <0.5) && (hauteur_AAL<10.0))
  963. ////{
  964. ////mode_affi=MODE_PLAN;
  965. ////init_sprite_avion(ROUGE);
  966. ////affi_plan_aerodrome();
  967. ////}
  968.  
  969. }
  970. }
  971.  
  972.  
  973.  
  974. void affi_direction_piste() // direction de la piste vue de l'avion (affiche au dessus du grand cercle)
  975. {
  976. if (mode_affi == MODE_PLAN) {return;}
  977. String s;
  978. uint16_t x0=133;
  979. uint16_t y0=270;
  980.  
  981. int16_t alpha1 = GPS_azimut_piste;
  982.  
  983. cadre_AZ_piste.efface();
  984. cadre_AZ_piste.affiche_int(alpha1,' ');
  985. TFT480.drawCircle(215, 5, 2, BLANC); // caractère 'degré'
  986. }
  987.  
  988.  
  989. void affi_dst_GPS() //distance de la piste vue de l'avion
  990. {
  991.  
  992. if (mode_affi == MODE_PLAN) {return;}
  993. String s1;
  994. uint16_t x0=70;
  995. uint16_t y0=250;
  996.  
  997. TFT480.setFreeFont(FM9);
  998. TFT480.setTextColor(JAUNE, NOIR);
  999. TFT480.drawString("RWY", x0-45, y0);
  1000.  
  1001. TFT480.setTextColor(BLANC, NOIR);
  1002. int nb_decimales;
  1003. if(GPS_distance_piste>99) {nb_decimales =0;} else {nb_decimales =1;}
  1004. s1 = String(GPS_distance_piste, nb_decimales);
  1005. if (GPS_distance_piste > 2000) {s1=" --";}
  1006.  
  1007. TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
  1008. TFT480.setFreeFont(FM9);
  1009. TFT480.drawString(s1, x0, y0);
  1010. TFT480.drawRoundRect(x0, y0-2, 50, 18, 5, GRIS_FONCE); // encadrement de la valeur affichée
  1011.  
  1012. TFT480.setTextColor(JAUNE, NOIR);
  1013. TFT480.drawString("NM", x0+55, y0);
  1014.  
  1015.  
  1016. }
  1017.  
  1018.  
  1019.  
  1020.  
  1021. void init_SDcard()
  1022. {
  1023.  
  1024. uint16_t y=0;
  1025.  
  1026. TFT480.setTextColor(VERT, NOIR);
  1027. TFT480.drawString("Init SDcard", 0, y);
  1028. y+=20;
  1029.  
  1030. if(!SD.begin())
  1031. {
  1032. TFT480.drawString("Card Mount Failed", 0, y);
  1033. delay (2000);
  1034. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  1035. return;
  1036. }
  1037.  
  1038.  
  1039. uint8_t cardType = SD.cardType();
  1040.  
  1041. if(cardType == CARD_NONE)
  1042. {
  1043. TFT480.drawString("No SDcard", 0, y);
  1044. delay (2000);
  1045. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  1046. return;
  1047. }
  1048.  
  1049. SDcardOk=1;
  1050.  
  1051. TFT480.drawString("SDcard Type: ", 0, y);
  1052. if(cardType == CARD_SD) {TFT480.drawString("SDSC", 150, y);}
  1053. else if(cardType == CARD_SDHC) {TFT480.drawString("SDHC", 150, y);}
  1054.  
  1055. y+=20;
  1056.  
  1057. uint32_t cardSize = SD.cardSize() / (1024 * 1024);
  1058. String s1=(String)cardSize + " GB";
  1059. TFT480.drawString("SDcard size: ", 0, y);
  1060. TFT480.drawString(s1, 150, y);
  1061.  
  1062. //Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  1063. //Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
  1064.  
  1065. delay (500);
  1066. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  1067. }
  1068.  
  1069.  
  1070.  
  1071. void dessine_avion(uint16_t x, uint16_t y) // celui, fixe, au centre du grand cercle
  1072. {
  1073.  
  1074. TFT480.drawRoundRect(x+2, y-6, 6, 25, 2, BLANC); // fuselage
  1075. TFT480.drawRoundRect(x-10, y-1, 32, 8, 2, BLANC); // aile
  1076. TFT480.drawRoundRect(x-2, y+17, 15, 6, 2, BLANC); // stab
  1077.  
  1078. TFT480.fillRoundRect(x+3, y-5, 8, 23, 2, BLEU); // fuselage
  1079. TFT480.fillRoundRect(x-9, y, 30, 6, 2, BLEU); // aile
  1080. TFT480.fillRoundRect(x-1, y+18, 13, 4, 2, BLEU); // stab
  1081. }
  1082.  
  1083.  
  1084.  
  1085. void dessine_point_sur_carte(float lat_i, float long_i, int16_t couleur)
  1086. {
  1087. // calcul de la position du point/ piste
  1088.  
  1089. float distance_piste = distance_AB(lat_i, long_i, lat_centre_pst, lon_centre_pst) / 1.852; //du centre de la piste, en NM
  1090. float azimut_piste = azimut_AB(lat_i, long_i, lat_centre_pst, lon_centre_pst); // latitudes et longitudes en degrés décimaux
  1091.  
  1092. float dx = distance_piste * cos(raddeg * (azimut_piste - 90.0));
  1093. float dy = distance_piste * sin(raddeg * (azimut_piste - 90.0));
  1094.  
  1095. //if(GPS_distance_piste < 1.0){mode_affi = MODE_PLAN;}
  1096.  
  1097. int32_t dx_px = (dx * px_par_NM);
  1098. int32_t dy_px = (dy * px_par_NM);
  1099.  
  1100. int16_t x_pt = 357 - dx_px;
  1101. int16_t y_pt = 75 - dy_px;
  1102.  
  1103. if ((x_pt > 245) && (y_pt > 10) && (y_pt < 170))
  1104. {
  1105. TFT480.fillCircle(x_pt, y_pt+2, 2, couleur);
  1106. //TFT480.drawPixel(x_pt, y_pt, couleur);
  1107. }
  1108. }
  1109.  
  1110.  
  1111.  
  1112.  
  1113. void affi_avion_sur_carte(float alpha) // dessine le sprite du petit sur la carte, mobile, avec l'orientation alpha
  1114. {
  1115. //TFT480.drawRect(x-5, y-3, 15, 12, NOIR); // pour test visu délimitation de la zone
  1116.  
  1117. if ((x_avion !=0) && (memo_x_avion !=0) && (y_avion !=0) && (memo_y_avion !=0))
  1118. {
  1119. TFT480.pushRect(memo_x_avion-10, memo_y_avion-10, 20, 20, memo_img); // print (petit) fond de carte
  1120. }
  1121.  
  1122. if ((x_avion > 245) && (y_avion > 10) && (y_avion < 170))
  1123. {
  1124. TFT480.readRect(x_avion-10, y_avion-10, 20, 20, memo_img); // enregistre en mémoire RAM
  1125.  
  1126. //affiche le sprite de l'avion avec la bonne orientation
  1127.  
  1128. TFT480.setPivot(x_avion, y_avion);
  1129.  
  1130. SPR_avion.pushRotated(alpha, TFT_BLACK); // noir définit comme transparent
  1131.  
  1132. memo_x_avion = x_avion;
  1133. memo_y_avion = y_avion;
  1134. }
  1135. }
  1136.  
  1137.  
  1138.  
  1139. void affi_avion_sur_plan(float alpha) // dessine le sprite du petit sur le plan des pistes, mobile, avec l'orientation alpha
  1140. {
  1141. //TFT480.drawRect(x-5, y-3, 15, 12, NOIR); // pour test visu délimitation de la zone
  1142.  
  1143. if (memo_x_avion !=0)
  1144. {
  1145. TFT480.pushRect(memo_x_avion-10, memo_y_avion-10, 20, 20, memo_img); // print (petit) fond de carte
  1146. }
  1147.  
  1148. if ((x_avion > 10) && (y_avion > 10) && (y_avion < 350))
  1149. {
  1150. TFT480.readRect(x_avion-10, y_avion-10, 20, 20, memo_img); // enregistre en mémoire RAM
  1151.  
  1152. //affiche le sprite de l'avion avec la bonne orientation
  1153.  
  1154. TFT480.setPivot(x_avion, y_avion);
  1155.  
  1156. SPR_avion.pushRotated(alpha, TFT_BLACK); // noir définit comme transparent
  1157.  
  1158. memo_x_avion = x_avion;
  1159. memo_y_avion = y_avion;
  1160. }
  1161. }
  1162.  
  1163.  
  1164.  
  1165. void calculs_GPS()
  1166. {
  1167. float lat_A = liste_bali[num_bali].lat_A;
  1168. float lon_A = liste_bali[num_bali].lon_A;
  1169.  
  1170. float lat_B = liste_bali[num_bali].lat_B;
  1171. float lon_B = liste_bali[num_bali].lon_B;
  1172.  
  1173. longueur_piste = 1000.0* distance_AB(lat_A, lon_A, lat_B, lon_B); // en m
  1174. orient_piste = azimut_AB(lat_A, lon_A, lat_B, lon_B); // en degrés
  1175.  
  1176. lat_centre_pst=(lat_A +lat_B)/2.0;
  1177. lon_centre_pst=(lon_A +lon_B)/2.0;
  1178.  
  1179. // DISTANCE (variable globale)
  1180. // voir la fonction "distance_AB()" dans le fichier "Fonctions1.h"
  1181. GPS_distance_piste = distance_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst) / 1.852; // du centre de la piste, en NM
  1182.  
  1183. // DIRECTION (variable globale)
  1184. GPS_azimut_piste = azimut_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst); // latitudes et longitudes en degrés décimaux
  1185. }
  1186.  
  1187.  
  1188.  
  1189.  
  1190. void calcul_point_i(float azimut_i, float distance_i, float *latitude, float *longitude)
  1191. {
  1192. /*
  1193. calcul des coordonnées GPS d'un point quelquonque PROCHE de la piste (en vue de faire des "hyppodromes" biens maitrisés)
  1194.  
  1195. données:
  1196. -azimut et distance du point concerné par rapport au, et vu du, centre de la piste (paramètres: azimut_i et distance_i)
  1197. -coordonnées GPS des points extrémités de la piste (lus dans le fichier FG_data.h)
  1198. -coordonnées GPS du centre, et l'orientation de la piste (voir fonction "void calculs_GPS()" )
  1199. on va alors ajouter la valeur de l'azimut_i à l'orientation de la piste pour faire le calcul
  1200. */
  1201.  
  1202. // calcul de l'orientation (relevé) du point (azimut par rapport au nord)
  1203. float orientation_point = -1.0 * orient_piste + azimut_i; // azimut_i étant l'angle entre la piste et la direction du point
  1204.  
  1205. // 1 minute d'angle sur un méridien => 1 NM de latitude
  1206. // 60mn d'angle (1deg) => 60 NM
  1207. // 1 NM => 1/60 de degré -> 0.0166 degrés
  1208.  
  1209. *latitude = lat_centre_pst + (distance_i /60.0 * sin(raddeg * (orientation_point - 90.0)));
  1210.  
  1211. // pour la longitude, il faut tenir compte que la longueur d'un parallèle dépend de la latitude
  1212. // (max à l'équateur, nulle au pôle) suivant une loi en cos.
  1213. *longitude= lon_centre_pst + (distance_i /(60.0 * cos(raddeg * lat_centre_pst)) * cos(raddeg * (orientation_point - 90.0)));
  1214.  
  1215. }
  1216.  
  1217.  
  1218.  
  1219. void calcul_pos_avion_sur_carte() // en px
  1220. {
  1221.  
  1222. // calcul de la position de l'avion / piste
  1223.  
  1224. float dx = GPS_distance_piste * cos(raddeg * (GPS_azimut_piste - 90.0));
  1225. float dy = GPS_distance_piste * sin(raddeg * (GPS_azimut_piste - 90.0));
  1226.  
  1227. //if(GPS_distance_piste < 1.0){mode_affi = MODE_PLAN;}
  1228.  
  1229. int32_t dx_px = (dx * px_par_NM);
  1230. int32_t dy_px = (dy * px_par_NM);
  1231.  
  1232. x_avion = 357 - dx_px;
  1233. y_avion = 75 - dy_px;
  1234. }
  1235.  
  1236.  
  1237.  
  1238.  
  1239. void calcul_pos_avion_sur_plan() // (sur le plan de l'aérodrome)
  1240. {
  1241. String s1;
  1242.  
  1243. // calcul de la position de l'avion / piste
  1244.  
  1245. float dx = GPS_distance_piste * cos(raddeg * (GPS_azimut_piste - 90.0));
  1246. float dy = GPS_distance_piste * sin(raddeg * (GPS_azimut_piste - 90.0));
  1247.  
  1248.  
  1249. float dx_px = (dx * plan_echelle);
  1250. float dy_px = (dy * plan_echelle);
  1251.  
  1252.  
  1253. x_avion = uint16_t(plan_ref_x - dx_px);
  1254. y_avion = uint16_t(plan_ref_y - dy_px);
  1255. }
  1256.  
  1257.  
  1258. void init_sprite_avion(uint16_t couleur)
  1259. {
  1260. //--------------------------------------------------------------
  1261. // dessin du petit avion dans son sprite pour affichage sur la carte (orientaion nulle ici)
  1262.  
  1263. //SPR_avion.fillRect(0,0,19,19, VERT); // pour test visuel du cadrage du dessin dans le sprite
  1264. SPR_avion.fillRoundRect(7+1, 8-3, 3, 12, 2, couleur); // fuselage
  1265. SPR_avion.fillRoundRect(7-5, 8, 15, 3, 2, couleur); // aile
  1266. SPR_avion.fillRoundRect(7-1, 8+8, 7, 2, 2, couleur); // stab
  1267.  
  1268. }
  1269.  
  1270.  
  1271.  
  1272. void init_sprites()
  1273. {
  1274. // sprites représentant les lettres 'N' 'S' 'E' 'O' qui seront affichées sur un cercle, inclinées donc.
  1275.  
  1276.  
  1277. SPR_E.setFreeFont(FF5); SPR_E.setTextColor(JAUNE);
  1278. SPR_E.createSprite(SPR_W, SPR_H);
  1279. SPR_E.setPivot(SPR_W/2, SPR_H/2); // Set pivot relative to top left corner of Sprite
  1280. SPR_E.fillSprite(NOIR);
  1281. SPR_E.drawString("E", 2, 2 );
  1282.  
  1283. SPR_N.setFreeFont(FF5); SPR_N.setTextColor(JAUNE);
  1284. SPR_N.createSprite(SPR_W, SPR_H);
  1285. SPR_N.setPivot(SPR_W/2, SPR_H/2);
  1286. SPR_N.fillSprite(NOIR);
  1287. SPR_N.drawString("N", 2, 2 );
  1288.  
  1289. SPR_O.setFreeFont(FF5); SPR_O.setTextColor(JAUNE);
  1290. SPR_O.createSprite(SPR_W, SPR_H);
  1291. SPR_O.setPivot(SPR_W/2, SPR_H/2);
  1292. SPR_O.fillSprite(NOIR);
  1293. SPR_O.drawString("W", 2, 2 );
  1294.  
  1295. SPR_S.setFreeFont(FF5); SPR_S.setTextColor(JAUNE);
  1296. SPR_S.createSprite(SPR_W, SPR_H);
  1297. SPR_S.setPivot(SPR_W/2, SPR_H/2);
  1298. SPR_S.fillSprite(NOIR);
  1299. SPR_S.drawString("S", 2, 2 );
  1300.  
  1301. // sprites représentant les nombres '3' '6' '12' '15' '21' '24' '30' '33'
  1302.  
  1303. SPR_3.setFreeFont(FF5); SPR_3.setTextColor(BLANC);
  1304. SPR_3.createSprite(SPR_W, SPR_H);
  1305. SPR_3.setPivot(SPR_W/2, SPR_H/2); // Set pivot relative to top left corner of Sprite
  1306. SPR_3.fillSprite(NOIR);
  1307. SPR_3.drawString("3", 2, 2 );
  1308.  
  1309. SPR_6.setFreeFont(FF5); SPR_6.setTextColor(BLANC);
  1310. SPR_6.createSprite(SPR_W, SPR_H);
  1311. SPR_6.setPivot(SPR_W/2, SPR_H/2);
  1312. SPR_6.fillSprite(NOIR);
  1313. SPR_6.drawString("6", 2, 2 );
  1314.  
  1315. SPR_12.setFreeFont(FF5); SPR_12.setTextColor(BLANC);
  1316. SPR_12.createSprite(SPR_W, SPR_H);
  1317. SPR_12.setPivot(SPR_W/2, SPR_H/2);
  1318. SPR_12.fillSprite(NOIR);
  1319. SPR_12.drawString("12", 2, 2 );
  1320.  
  1321. SPR_15.setFreeFont(FF5); SPR_15.setTextColor(BLANC);
  1322. SPR_15.createSprite(SPR_W, SPR_H);
  1323. SPR_15.setPivot(SPR_W/2, SPR_H/2);
  1324. SPR_15.fillSprite(NOIR);
  1325. SPR_15.drawString("15", 2, 2 );
  1326.  
  1327. SPR_21.setFreeFont(FF5); SPR_21.setTextColor(BLANC);
  1328. SPR_21.createSprite(SPR_W, SPR_H);
  1329. SPR_21.setPivot(SPR_W/2, SPR_H/2); // Set pivot relative to top left corner of Sprite
  1330. SPR_21.fillSprite(NOIR);
  1331. SPR_21.drawString("21", 2, 2 );
  1332.  
  1333. SPR_24.setFreeFont(FF5); SPR_24.setTextColor(BLANC);
  1334. SPR_24.createSprite(SPR_W, SPR_H);
  1335. SPR_24.setPivot(SPR_W/2, SPR_H/2);
  1336. SPR_24.fillSprite(NOIR);
  1337. SPR_24.drawString("24", 2, 2 );
  1338.  
  1339. SPR_30.setFreeFont(FF5); SPR_30.setTextColor(BLANC);
  1340. SPR_30.createSprite(SPR_W, SPR_H);
  1341. SPR_30.setPivot(SPR_W/2, SPR_H/2);
  1342. SPR_30.fillSprite(NOIR);
  1343. SPR_30.drawString("30", 2, 2 );
  1344.  
  1345. SPR_33.setFreeFont(FF5); SPR_33.setTextColor(BLANC);
  1346. SPR_33.createSprite(SPR_W, SPR_H);
  1347. SPR_33.setPivot(SPR_W/2, SPR_H/2);
  1348. SPR_33.fillSprite(NOIR);
  1349. SPR_33.drawString("33", 2, 2 );
  1350.  
  1351. SPR_VOR.setFreeFont(FF5); SPR_33.setTextColor(BLANC);
  1352. SPR_VOR.createSprite(180, 180);
  1353. SPR_VOR.setPivot(90, 90);
  1354. SPR_VOR.fillSprite(NOIR); // pour test en rotation --> BLEU
  1355.  
  1356. //SPR_avion.setFreeFont(FF5); SPR_33.setTextColor(BLANC);
  1357. SPR_avion.createSprite(20, 20);
  1358. SPR_avion.setPivot(10, 10);
  1359. //SPR_avion.fillSprite(NOIR); // pour test en rotation --> BLEU
  1360.  
  1361. //--------------------------------------------------------------
  1362. // DESSIN DU COMPAS gradué
  1363. //--------------------------------------------------------------
  1364. // dessine les tirets de graduation du compas sur le sprite SPR_VOR
  1365.  
  1366. uint16_t angle1 = 0;
  1367. uint8_t a;
  1368. uint16_t R =90;
  1369. for(uint8_t n=0; n<72; n+=2 )
  1370. {
  1371. angle1 = n*5; // 1 tiret tous les 15 degrés
  1372. a= 3;
  1373. //if ((n%2) == 0){a=10;}
  1374. if ((n%6) == 0){a=12;}
  1375. affi_SPR_rayon2(90, 90, R-a, R, angle1, BLANC, false);
  1376. }
  1377. //--------------------------------------------------------------
  1378. // écrit les nombres (sprites définis ci-dessus) sur le sprite SPR_VOR
  1379.  
  1380. uint16_t x_spr;
  1381. uint16_t y_spr;
  1382.  
  1383.  
  1384. x_spr = 90 +70*cos(degTOrad(-60));
  1385. y_spr = 90 -70*sin(degTOrad(-60));
  1386. SPR_VOR.setPivot(x_spr, y_spr);
  1387. SPR_15.pushRotated(&SPR_VOR, 150); // sur adresse relative du SPR_VOR
  1388.  
  1389.  
  1390. x_spr = 90 +70*cos(degTOrad(-30));
  1391. y_spr = 90 -70*sin(degTOrad(-30));
  1392. SPR_VOR.setPivot(x_spr, y_spr);
  1393. SPR_12.pushRotated(&SPR_VOR, 120);
  1394.  
  1395.  
  1396. x_spr = 90 +70*cos(degTOrad(0-5));
  1397. y_spr = 90 -70*sin(degTOrad(0-5));
  1398. SPR_VOR.setPivot(x_spr, y_spr);
  1399. SPR_E.pushRotated(&SPR_VOR, 90); // Plot rotated Sprite 'SPR_E' dans le sprite 'SPR_VOR'
  1400.  
  1401.  
  1402. x_spr = 90 +70*cos(degTOrad(30-5));
  1403. y_spr = 90 -70*sin(degTOrad(30-5));
  1404. SPR_VOR.setPivot(x_spr, y_spr);
  1405. SPR_6.pushRotated(&SPR_VOR, 60);
  1406.  
  1407.  
  1408. x_spr = 90 +70*cos(degTOrad(60-5));
  1409. y_spr = 90 -70*sin(degTOrad(60-5));
  1410. SPR_VOR.setPivot(x_spr, y_spr);
  1411. SPR_3.pushRotated(&SPR_VOR, 30);
  1412.  
  1413. x_spr = 90 +70*cos(degTOrad(90-5));
  1414. y_spr = 90 -70*sin(degTOrad(90-5));
  1415. SPR_VOR.setPivot(x_spr, y_spr);
  1416. SPR_N.pushRotated(&SPR_VOR, 0);
  1417.  
  1418. x_spr = 90 +70*cos(degTOrad(120));
  1419. y_spr = 90 -70*sin(degTOrad(120));
  1420. SPR_VOR.setPivot(x_spr, y_spr);
  1421. SPR_33.pushRotated(&SPR_VOR, -30);
  1422.  
  1423. x_spr = 90 +70*cos(degTOrad(150));
  1424. y_spr = 90 -70*sin(degTOrad(150));
  1425. SPR_VOR.setPivot(x_spr, y_spr);
  1426. SPR_30.pushRotated(&SPR_VOR, -60);
  1427.  
  1428. x_spr = 90 +70*cos(degTOrad(180-5));
  1429. y_spr = 90 -70*sin(degTOrad(180-5));
  1430. SPR_VOR.setPivot(x_spr, y_spr);
  1431. SPR_O.pushRotated(&SPR_VOR, -90);
  1432.  
  1433. x_spr = 90 +70*cos(degTOrad(210));
  1434. y_spr = 90 -70*sin(degTOrad(210));
  1435. SPR_VOR.setPivot(x_spr, y_spr);
  1436. SPR_24.pushRotated(&SPR_VOR, 240);
  1437.  
  1438. x_spr = 90 +70*cos(degTOrad(240));
  1439. y_spr = 90 -70*sin(degTOrad(240));
  1440. SPR_VOR.setPivot(x_spr, y_spr);
  1441. SPR_21.pushRotated(&SPR_VOR, 210);
  1442.  
  1443.  
  1444. x_spr = 90 +70*cos(degTOrad(-90-5));
  1445. y_spr = 90 -70*sin(degTOrad(-90-5));
  1446. SPR_VOR.setPivot(x_spr, y_spr);
  1447. SPR_S.pushRotated(&SPR_VOR, 180);
  1448.  
  1449.  
  1450.  
  1451. }
  1452.  
  1453.  
  1454. /*
  1455. void init_affi_vor()
  1456. {
  1457.  
  1458. uint16_t x0 = 120;
  1459. uint16_t y0 = 120;
  1460. uint16_t w = 240;
  1461. uint16_t h = 240;
  1462.  
  1463. uint16_t R =100;
  1464. uint16_t R2 =70;
  1465.  
  1466. //TFT480.fillRect(x0-w/2, y0-h/2, w, h, NOIR);
  1467. TFT480.drawRoundRect(0, 0, 230, 240, 5, GRIS_FONCE);
  1468.  
  1469. uint16_t y1 = y0+12;
  1470.  
  1471. ////TFT480.fillTriangle(
  1472. ////x0, y1+18-h/2,
  1473. ////x0-5, y1+12-h/2,
  1474. ////x0+5, y1+12-h/2,
  1475. ////BLANC);
  1476.  
  1477. //TFT480.fillCircle(x0, y0, R, NOIR);
  1478. //TFT480.fillCircle(x0, y1, 5, GRIS_FONCE);
  1479. //TFT480.drawCircle(x0, y1, R, GRIS_FONCE);
  1480.  
  1481. dessine_avion(104, 125);
  1482.  
  1483. }
  1484. */
  1485.  
  1486. void affi_index_cap() // petite pointe de flèche fixe au sommet du grand cercle
  1487. {
  1488. uint16_t x0 = 110;
  1489. uint16_t y0 = 120+5;
  1490. uint16_t h = 180;
  1491. TFT480.fillTriangle(
  1492. x0, y0+6-h/2,
  1493. x0-5, y0-h/2,
  1494. x0+5, y0-h/2,
  1495. BLANC);
  1496. TFT480.setFreeFont(FM9);
  1497. TFT480.setTextColor(BLANC, NOIR);
  1498. //TFT480.drawString("HDG", x0-60, y0-30-h/2);
  1499.  
  1500. }
  1501.  
  1502.  
  1503.  
  1504. void affi_encoche_hdg(uint16_t alpha_i, uint16_t couleur) // encoche qui se déplace sur le grand sprite
  1505. {
  1506.  
  1507. init_sprites();
  1508. init_sprite_avion(BLEU);
  1509. affi_SPR_encoche(90, 90, 90, -alpha_i +90, couleur);
  1510.  
  1511. //affi_rayon2(x0, y0, 85, 20, -alpha_i, couleur, false);
  1512. }
  1513.  
  1514.  
  1515. void affi_liste() // des aérodromes
  1516. {
  1517. uint16_t x0=0;
  1518. uint16_t x1=10;
  1519. uint16_t y0=0;
  1520. uint16_t w = 480;
  1521. uint16_t h = 320;
  1522.  
  1523. //uint16_t num_ligne =0;
  1524.  
  1525. String s1;
  1526.  
  1527. TFT480.fillScreen(NOIR);
  1528. TFT480.drawRoundRect(x0, y0, 235, h, 3, BLANC);
  1529.  
  1530. TFT480.setTextFont(2);
  1531.  
  1532. uint16_t y=10;
  1533.  
  1534. int n;
  1535. uint16_t n_min;
  1536. uint16_t n_max;
  1537.  
  1538. if(decal>0){affi_indexV(225, 12, -1, JAUNE);}
  1539.  
  1540. n_min = decal;
  1541. n_max = n_min + 17;
  1542. if (n_max>nb_elements) {n_max = nb_elements;}
  1543.  
  1544. for (n=n_min; n<n_max; n++)
  1545. {
  1546. num_bali=n;
  1547.  
  1548. s1=(String)(n+1);
  1549. if(n<9){s1=" "+s1;}
  1550. TFT480.setTextColor(BLEU, NOIR);
  1551. TFT480.drawString(s1, x1, y);
  1552.  
  1553. s1= liste_bali[num_bali].ID_OACI;
  1554. TFT480.setTextColor(VERT, NOIR);
  1555. TFT480.drawString(s1, x1+22, y);
  1556. s1= liste_bali[num_bali].nom;
  1557. TFT480.setTextColor(BLANC, NOIR);
  1558. TFT480.drawString(s1, x1+65, y);
  1559. y+=18;
  1560. }
  1561.  
  1562. if(n_max < nb_elements){affi_indexV(225, 300, 1, JAUNE);}
  1563. }
  1564.  
  1565.  
  1566. void incremente_ligne()
  1567. {
  1568. uint16_t x0=0;
  1569. uint16_t x1=10;
  1570. uint16_t y0=0;
  1571. uint16_t w = 480;
  1572. uint16_t h = 320;
  1573.  
  1574. TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, NOIR); // efface le cadre
  1575. num_ligne++;
  1576. if ((num_ligne+decal)>=nb_elements)
  1577. {
  1578. num_ligne = nb_elements - decal -1; // on ne descend pas d'avantage
  1579. }
  1580. else if (num_ligne>=16)
  1581. {
  1582. num_ligne=15;
  1583. decal++;
  1584. affi_liste();
  1585. }
  1586. //affi_int_test(num_ligne, 300, 2, CYAN, NOIR);
  1587. //affi_int_test(decal, 300, 3, VERT, NOIR);
  1588.  
  1589. num_bali = num_ligne + decal;
  1590. TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, JAUNE); // encadre la ligne choisie
  1591.  
  1592. }
  1593.  
  1594.  
  1595. void decremente_ligne()
  1596. {
  1597. uint16_t x0=0;
  1598. uint16_t x1=10;
  1599. uint16_t y0=0;
  1600. uint16_t w = 480;
  1601. uint16_t h = 320;
  1602.  
  1603. TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, NOIR);
  1604. if (num_ligne>0)
  1605. {
  1606. num_ligne--;
  1607. }
  1608. else
  1609. {
  1610. if(decal>0)
  1611. {
  1612. decal--;
  1613. num_ligne=0;
  1614. affi_liste();
  1615. }
  1616. }
  1617. //affi_int_test(num_ligne, 300, 2, CYAN, NOIR);
  1618. //affi_int_test(decal, 300, 3, VERT, NOIR);
  1619.  
  1620. num_bali = num_ligne + decal;
  1621. TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, JAUNE);
  1622. }
  1623.  
  1624.  
  1625. void init_mode_ND()
  1626. {
  1627. TFT480.fillScreen(NOIR);
  1628. mode_affi=MODE_ND;
  1629. init_sprite_avion(BLEU);
  1630. memo_cap=cap;
  1631. dessine_pannel_frq();
  1632. refresh_ND=1;
  1633. affi_carte_locale(235,0,1);
  1634. affi_ND(cap);
  1635. }
  1636.  
  1637.  
  1638. void choix_aerodrome() // choix d'un aérodrome dans la liste
  1639. {
  1640.  
  1641. uint16_t x0=0;
  1642. uint16_t x1=10;
  1643. uint16_t y0=0;
  1644. uint16_t w = 480;
  1645. uint16_t h = 320;
  1646.  
  1647. uint16_t compte=0;
  1648.  
  1649. TFT480.fillScreen(NOIR);
  1650. TFT480.drawRoundRect(x0, y0, 235, h, 3, BLANC);
  1651.  
  1652. TFT480.setTextFont(2);
  1653.  
  1654. //decal=0;
  1655.  
  1656. affi_liste();
  1657.  
  1658. //num_ligne=9;
  1659. num_bali = num_ligne + decal;
  1660. TFT480.drawRect(x0+2, y0+9+num_ligne*18, 215, 18, JAUNE);
  1661.  
  1662. uint8_t sortir=0;
  1663. uint8_t maj_carte=1;
  1664. uint16_t compteur2=0;
  1665.  
  1666.  
  1667. while (sortir==0)
  1668. {
  1669. bouton0_etat = digitalRead(GPIO_bouton0);
  1670. if (bouton0_etat != memo_bouton0_etat)
  1671. {
  1672. memo_bouton0_etat = bouton0_etat;
  1673. if (bouton0_etat==0) // ============ DESCEND DANS LA LISTE ====================
  1674. {
  1675. Led2.allume();
  1676. incremente_ligne(); // si appui bref
  1677. while(bouton0_etat == 0) // puis on détecte un éventuel appui long
  1678. {
  1679. bouton0_etat = digitalRead(GPIO_bouton0);
  1680. compte++;
  1681. if (compte>=50) // appui long détecté
  1682. {
  1683. compte=0;
  1684. bouton0_etat = digitalRead(GPIO_bouton0);
  1685. while(bouton0_etat == 0) // tant que le bouton reste appuyé, on incrémente rapidement les pas
  1686. {
  1687. bouton0_etat = digitalRead(GPIO_bouton0);
  1688. compteur2=0;
  1689. incremente_ligne(); // si appuis répétitifs
  1690. //incremente_ligne(); // incrémente les lignes 2 à 2
  1691. delay(100);
  1692. }
  1693. }
  1694. delay(20);
  1695. }
  1696. maj_carte=1;
  1697. }
  1698. else {Led2.eteint();}
  1699. }
  1700.  
  1701.  
  1702. bouton1_etat = digitalRead(GPIO_bouton1);
  1703. if (bouton1_etat != memo_bouton1_etat)
  1704. {
  1705. memo_bouton1_etat = bouton1_etat;
  1706. if (bouton1_etat==0) // ============ MONTE DANS LA LISTE ====================
  1707. {
  1708. Led1.allume();
  1709. decremente_ligne();
  1710. while(bouton1_etat == 0) // puis on détecte un éventuel appui long (500ms)
  1711. {
  1712. bouton1_etat = digitalRead(GPIO_bouton1);
  1713. compte++;
  1714. if (compte>=50) // appui long détecté
  1715. {
  1716. compte=0;
  1717. bouton1_etat = digitalRead(bouton1);
  1718. while(bouton1_etat == 0) // tant que le bouton reste appuyé, on incrémente rapidement les pas
  1719. {
  1720. bouton1_etat = digitalRead(GPIO_bouton1);
  1721. compteur2=0;
  1722. decremente_ligne();
  1723. //decremente_ligne(); // 2 lignes
  1724. delay(100);
  1725. }
  1726. }
  1727. delay(20);
  1728. }
  1729. maj_carte=1;
  1730. }
  1731. else {Led1.eteint();}
  1732. }
  1733. compteur2++;
  1734.  
  1735. if (compteur2 >= 20)
  1736. {
  1737. compteur2=0;
  1738. {
  1739. if(maj_carte==1)
  1740. {
  1741. zoom=2;
  1742. affi_carte_locale(238,0,0);
  1743. maj_carte=0;
  1744. affi_photo(238,160);
  1745. }
  1746. }
  1747. }
  1748.  
  1749.  
  1750. delay(10);
  1751.  
  1752.  
  1753. bouton3_etat = digitalRead(GPIO_bouton3);
  1754. if (bouton3_etat != memo_bouton3_etat)
  1755. {
  1756. memo_bouton3_etat = bouton3_etat;
  1757. if (bouton3_etat==0)
  1758. {
  1759. if (saisir_ecran==1) {write_TFT480_on_SDcard(); } /* ====== saisie écran ====== */
  1760. else {sortir=1;}
  1761. }
  1762. }
  1763. }
  1764.  
  1765. init_variables();
  1766. calculs_GPS();
  1767.  
  1768. read_data_plan_sur_SDcard();
  1769.  
  1770. init_mode_ND();
  1771. }
  1772.  
  1773.  
  1774.  
  1775. void affi_ND(float alpha_i) // Navigation Display (le grand cercle à gauche avec les différents affichages dessus)
  1776. {
  1777. if (mode_affi != MODE_ND) {return;}
  1778.  
  1779. uint16_t w = 180;
  1780. uint16_t h = 180;
  1781.  
  1782. uint16_t R =100;
  1783. uint16_t R2 =70;
  1784.  
  1785. float angle1, angle2;
  1786.  
  1787. uint16_t x_spr;
  1788. uint16_t y_spr;
  1789.  
  1790. angle2 = -alpha_i;
  1791.  
  1792. TFT480.setPivot(110, 130);
  1793. SPR_VOR.setPivot(90, 90);
  1794.  
  1795. SPR_VOR.pushRotated(angle2); // affiche le grand cercle sous forme d'un (grand) sprite
  1796. dessine_avion(104, 125);
  1797.  
  1798. TFT480.drawRoundRect(0, 0, 230, 240, 5, GRIS_FONCE);
  1799.  
  1800. affi_index_cap(); // petite pointe de flèche (fixe, orientée vers le bas) au sommet du grand cercle
  1801.  
  1802. cadre_cap1.efface();
  1803. cadre_cap1.affiche_int(cap,' ');
  1804. TFT480.drawCircle(136, 5, 2, BLANC); // caractère 'degré'
  1805.  
  1806. // rectangle bleu visualisant l'orientation de la piste
  1807. //float angle3 = 90.0+ils_orientation; // en degrés décimaux
  1808. float angle3 = 90.0 + orient_piste;
  1809. affi_rectangle_incline(110, 130, 60, angle3 + angle2 +3, BLEU_CLAIR);
  1810.  
  1811.  
  1812. // flêche -> direction(/Nord) de la piste, vue de l'avion
  1813.  
  1814. // DIRECTION
  1815.  
  1816. GPS_azimut_piste = azimut_AB(lat_avion, lon_avion, lat_centre_pst, lon_centre_pst); // latitudes et longitudes en degrés décimaux
  1817.  
  1818. int16_t alpha2 = GPS_azimut_piste;
  1819. if (alpha2>360) {alpha2 -= 360;}
  1820.  
  1821. affi_rayon2(110, 130, 70, 20, -angle2 -alpha2 +90, GRIS_CLAIR, 1); // vers Nav1 radial = VOR
  1822. //affi_rayon2(110, 130, 70, 20, -angle2 -vor_radial +90, GRIS_FONCE, 0); // azimut de l'avion vu du VOR
  1823.  
  1824. affi_pointe(110, 130, 70, -angle2 -alpha2 +90, GRIS_CLAIR); // pointe dans la direction de l'aérodrome (piste)
  1825.  
  1826. // HDG
  1827. affi_rayon2(110, 130, 80, 20, -angle2 -hdg1 +90, OLIVE, 1); // HDG
  1828. }
  1829.  
  1830.  
  1831.  
  1832. void dessine_pannel_frq() // pour l'affichage des données, voir la fonction "affi_data_piste()"
  1833. {
  1834. if (mode_affi == MODE_PLAN) {return;}
  1835.  
  1836. uint16_t x0 = 235;
  1837. uint16_t y0 = 172;
  1838. uint16_t xi, yi;
  1839.  
  1840. xi = x0+5;
  1841. yi = y0+5;
  1842.  
  1843. TFT480.drawRoundRect(x0, y0, 245, 146, 5, GRIS);
  1844.  
  1845. yi = y0+5;
  1846.  
  1847. // -------------------------------------
  1848. // entête
  1849.  
  1850. cadre_top1.init(xi,yi,60,22);
  1851. cadre_top1.couleur_contour = GRIS_FONCE;
  1852. cadre_top1.couleur_texte = BLEU_CLAIR;
  1853. cadre_top1.traceContour();
  1854.  
  1855. TFT480.setTextFont(1);; TFT480.setTextColor(BLANC, NOIR);
  1856. TFT480.drawString("ALTITUDE:", xi+75, yi+7);
  1857.  
  1858. cadre_top1b.init(xi+135,yi,85,22);
  1859. cadre_top1b.couleur_contour = GRIS_FONCE;
  1860. cadre_top1b.couleur_texte = VIOLET2;
  1861. cadre_top1b.traceContour();
  1862.  
  1863. yi+=25;
  1864.  
  1865. cadre_top2.init(xi,yi,220,22);
  1866. cadre_top2.couleur_contour = GRIS_FONCE;
  1867. cadre_top2.couleur_texte = BLEU_CLAIR;
  1868. cadre_top2.traceContour();
  1869.  
  1870. yi+=25;
  1871.  
  1872. // affichage LATI - LONG
  1873.  
  1874. xi=x0+5;
  1875. cadre_position.init(xi,yi,220,15);
  1876. cadre_position.couleur_contour = GRIS_FONCE;
  1877. cadre_position.couleur_texte = VIOLET2;
  1878. cadre_position.traceContour();
  1879. yi+=18;
  1880.  
  1881.  
  1882.  
  1883. // -------------------------------------
  1884. // colonne 0
  1885.  
  1886. yi=y0+75;
  1887.  
  1888. TFT480.setFreeFont(FM9); TFT480.setTextColor(GRIS_CLAIR, NOIR);
  1889. TFT480.drawString("RWY", xi+5, yi+5);
  1890.  
  1891. TFT480.drawCircle(xi+110, yi+5, 2, BLEU_CLAIR); // caractère 'degré'
  1892. TFT480.drawCircle(xi+190, yi+5, 2, BLEU_CLAIR); // caractère 'degré'
  1893.  
  1894. cadre_00.init(xi+45,yi,80,22);
  1895. cadre_00.couleur_contour = GRIS_FONCE;
  1896. cadre_00.couleur_texte = BLEU_CLAIR;
  1897. cadre_00.traceContour();
  1898. yi+=25;
  1899.  
  1900. TFT480.setFreeFont(FM9);
  1901. TFT480.setTextColor(GRIS_CLAIR, NOIR);
  1902. TFT480.drawString("longueur", xi+5, yi+5);
  1903.  
  1904. cadre_01.init(xi+100,yi,80,22);
  1905. cadre_01.couleur_contour = GRIS_FONCE;
  1906. cadre_01.couleur_texte = VERT;
  1907. cadre_01.traceContour();
  1908.  
  1909.  
  1910. // -------------------------------------
  1911. // colonne 1
  1912.  
  1913. xi+=85;
  1914. yi=y0+75;
  1915.  
  1916. cadre_10.init(xi+45,yi,80,22);
  1917. cadre_10.couleur_contour = GRIS_FONCE;
  1918. cadre_10.couleur_texte = BLEU_CLAIR;
  1919. cadre_10.traceContour();
  1920. yi+=25;
  1921.  
  1922. ////cadre_11.init(xi+45,yi,80,22);
  1923. ////cadre_11.couleur_contour = GRIS_FONCE;
  1924. ////cadre_11.couleur_texte = VERT;
  1925. ////cadre_11.traceContour();
  1926. ////yi+=25;
  1927.  
  1928.  
  1929. // -------------------------------------
  1930. // colonne 2
  1931.  
  1932. xi+=55;
  1933. yi=y0+75;
  1934.  
  1935. ////cadre_20.init(xi+45,yi,40,22);
  1936. ////cadre_20.couleur_contour = GRIS_FONCE;
  1937. ////cadre_20.couleur_texte = GRIS_CLAIR;
  1938. ////cadre_20.traceContour();
  1939. yi+=25;
  1940.  
  1941.  
  1942. ////cadre_21.init(xi+45,yi,40,22);
  1943. ////cadre_21.couleur_contour = GRIS_FONCE;
  1944. ////cadre_21.couleur_texte = BLEU_CLAIR;
  1945. ////cadre_21.traceContour();
  1946. yi+=25;
  1947.  
  1948.  
  1949.  
  1950. // -------------------------------------
  1951. // pied
  1952.  
  1953. xi=x0+5;
  1954. yi=y0+125;
  1955.  
  1956. cadre_bas1.init(xi,yi,105,15);
  1957. cadre_bas1.couleur_contour = GRIS_FONCE;
  1958. cadre_bas1.couleur_texte = JAUNE;
  1959. cadre_bas1.traceContour();
  1960.  
  1961. xi+=110;
  1962. cadre_bas2.init(xi,yi,110,15);
  1963. cadre_bas2.couleur_contour = GRIS_FONCE;
  1964. cadre_bas2.couleur_texte = VERT;
  1965. cadre_bas2.traceContour();
  1966.  
  1967. // -------------------------------------
  1968. //juste sous la carte
  1969.  
  1970. ////cadre_distance_piste.init(235,150,60,18);
  1971. ////cadre_distance_piste.couleur_contour = GRIS;
  1972. ////cadre_distance_piste.couleur_texte = VERT;
  1973. ////cadre_distance_piste.traceContour();
  1974.  
  1975. // -------------------------------------
  1976. // 3 cadres au dessus du grand cercle :
  1977.  
  1978. cadre_hdg1.init(10,3,40,22);
  1979. cadre_hdg1.couleur_contour = GRIS;
  1980. cadre_hdg1.couleur_texte = OLIVE;
  1981. cadre_hdg1.traceContour();
  1982.  
  1983. TFT480.setTextFont(1);
  1984. TFT480.setTextColor(BLANC, NOIR);
  1985. TFT480.drawString("HDG", 14, 26);
  1986.  
  1987. cadre_cap1.init(90,3,40,22);
  1988. cadre_cap1.couleur_contour = GRIS;
  1989. cadre_cap1.couleur_texte = BLANC;
  1990. cadre_cap1.traceContour();
  1991. TFT480.drawString("CAP", 94, 26);
  1992.  
  1993.  
  1994. cadre_AZ_piste.init(170,3,40,22);
  1995. cadre_AZ_piste.couleur_contour = GRIS;
  1996. cadre_AZ_piste.couleur_texte = JAUNE;
  1997. cadre_AZ_piste.traceContour();
  1998. TFT480.drawString("Dir PST", 174, 26);
  1999.  
  2000.  
  2001. // -------------------------------------
  2002. // cadre INFO en bas à gauche de l'afficheur
  2003. cadre_info.init(5,273,225,44);
  2004. cadre_info.couleur_contour = GRIS;
  2005. cadre_info.couleur_texte = BLEU_CLAIR;
  2006. cadre_info.traceContour();
  2007. }
  2008.  
  2009.  
  2010.  
  2011. void efface_carte(uint16_t x0, uint16_t y0)
  2012. {
  2013.  
  2014. TFT480.fillRoundRect(x0, y0, 245, 170, 5, GRIS_TRES_FONCE);
  2015. affi_X(x0+120, y0+80);
  2016. }
  2017.  
  2018.  
  2019. /**
  2020.  ECHELLES des cartes OpenStreetMap
  2021.  
  2022. pour les petites cartes 240x160 px:
  2023. 0.bmp 500m->70px = 70/0.5 soit 140 px/km
  2024. 1.bmp 1km->70px = 70/1 soit 70 px/km
  2025. 2.bmp 2km->70px = 70/2 soit 35 px/km
  2026. 3.bmp 5km->87px = 87/5 soit 17.5 px/km
  2027. 4.bmp 10km->87px = 87/10 soit 8.75 px/km
  2028. 5.bmp 20km->87px = 87/20 soit 4.37 px/km
  2029. 6.bmp 30km->66px = 65/30 soit 2.19 px/km
  2030.  
  2031. On voit que la progression est /2 depuis 140
  2032.  
  2033. **/
  2034.  
  2035.  
  2036. void affi_X(uint16_t x, uint16_t y)
  2037. {
  2038. TFT480.drawLine(x-20, y-20, x+20, y+20, JAUNE);
  2039. TFT480.drawLine(x-20, y+20, x+20, y-20, JAUNE);
  2040.  
  2041. }
  2042.  
  2043.  
  2044. void affi_photo(uint16_t x0, uint16_t y0)
  2045. {
  2046. String s1;
  2047. char var_array[30];
  2048. uint8_t numero=1;
  2049.  
  2050. s1="/bmp/";
  2051. s1 += liste_bali[num_bali].ID_OACI;
  2052. s1 += "/240x160/img";
  2053. s1 += String(numero);
  2054. s1+= ".bmp";
  2055. s1+='\0';
  2056. //Serial.println(s1);
  2057.  
  2058. for(int i=0; i<30; i++) {var_array[i] = s1[i];}
  2059. var_array[29]=0; // zéro terminal -> string
  2060.  
  2061. float contraste=1.4; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
  2062. float lum = 100; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage
  2063.  
  2064. TFT480.fillRect(x0, y0, 240, 160, GRIS_TRES_FONCE);
  2065. affi_X(x0+120, y0+80);
  2066.  
  2067. affi_img(x0,y0, 0, 1.0, var_array); // affichage sans correction de lum ni contraste ; pour test différence...
  2068.  
  2069. TFT480.fillRect(x0, y0-2, 240, 3, NOIR); // trait horizontal
  2070. }
  2071.  
  2072.  
  2073. void read_data_plan_sur_SDcard()
  2074. {
  2075. // récupération des coordonnées du centre de la piste et de l'échelle du plan des installations
  2076. // ce qui servira à afficher l'avion correctement sur le plan
  2077. File file1;
  2078. String filename1;
  2079. String s1, s2;
  2080. uint8_t c1;
  2081. uint16_t p1;
  2082.  
  2083. float x0, y0, ech;
  2084.  
  2085. filename1="/bmp/";
  2086. filename1 += liste_bali[num_bali].ID_OACI;
  2087. filename1 += "/480x320/plan1.dat";
  2088. filename1+='\0';
  2089.  
  2090. s1="";
  2091. file1 = SD.open(filename1, FILE_READ); //ouverture du fichier en lecture
  2092. if (file1)
  2093. {
  2094. while (file1.available() > 0)
  2095. {
  2096. c1 = file1.read();
  2097. s1 += char (c1);
  2098. }
  2099.  
  2100. // centre de la piste
  2101. p1 = s1.indexOf("x0=");
  2102. s2=s1.substring(p1+3, p1+8);
  2103. plan_ref_x=s2.toFloat(); // variable globale
  2104.  
  2105. p1 = s1.indexOf("y0=");
  2106. s2=s1.substring(p1+3, p1+8);
  2107. plan_ref_y=s2.toFloat(); // variable globale
  2108.  
  2109. p1 = s1.indexOf("ech=");
  2110. s2=s1.substring(p1+4, p1+9);
  2111. plan_echelle=s2.toFloat(); // variable globale
  2112.  
  2113. }
  2114.  
  2115. file1.close();
  2116.  
  2117. }
  2118.  
  2119.  
  2120.  
  2121.  
  2122. void affi_plan_aerodrome()
  2123. {
  2124. String s1,s2;
  2125. char var_array[30];
  2126. uint8_t numero=1;
  2127.  
  2128. s1="/bmp/";
  2129. s1 += liste_bali[num_bali].ID_OACI;
  2130. s1 += "/480x320/plan1.bmp";
  2131. s1+='\0';
  2132. //Serial.println(s1);
  2133.  
  2134. for(int i=0; i<30; i++) {var_array[i] = s1[i];}
  2135. var_array[29]=0; // zéro terminal -> string
  2136.  
  2137. float contraste=1.4; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
  2138. float lum = 100; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage
  2139.  
  2140. TFT480.fillRect(0, 0, 479, 319, GRIS_TRES_FONCE);
  2141. TFT480.setFreeFont(FF1);
  2142. TFT480.setTextColor(VERT, GRIS_TRES_FONCE);
  2143. TFT480.drawString("Plan", 200, 150);
  2144.  
  2145. s2 = liste_bali[num_bali].ID_OACI;
  2146. TFT480.drawString(s2, 200, 170);
  2147.  
  2148. //efface_carte(x0,y0);
  2149. affi_img(0,160, lum, contraste, var_array);
  2150. }
  2151.  
  2152.  
  2153.  
  2154. void affi_flags()
  2155. {
  2156. // la variable 'flags' est reçue du module PFD (où elle est configurée)
  2157. TFT480.setFreeFont(FF1);
  2158.  
  2159. cadre_info.efface();
  2160.  
  2161. if (read_bit(flags, bit_autoland) == 1) { cadre_info.affiche_string("Approche ILS","Auto-land"); }
  2162. if (read_bit(flags, bit_rudder_decol) == 1) { cadre_info.affiche_string("Rudder auto","(for takeoff)"); }
  2163. if (read_bit(flags, bit_rudder_attero) == 1){ cadre_info.affiche_string("Rudder auto","(for Landing)"); }
  2164. if (read_bit(flags, bit_nav_to_piste) == 1){ cadre_info.affiche_string("Nav to RWY","Rappelez verticale"); }
  2165. if (read_bit(flags, bit_nav_to_pti) == 1){ cadre_info.affiche_string("Nav to pti",""); }
  2166. if (read_bit(flags, bit_nav_to_ptAA) == 1){ cadre_info.affiche_string("Nav to A","Approche directe"); }
  2167. if (read_bit(flags, bit_nav_to_ptBB) == 1){ cadre_info.affiche_string("Nav to B","Approche directe"); }
  2168.  
  2169. }
  2170.  
  2171.  
  2172.  
  2173.  
  2174.  
  2175. void affi_carte_France()
  2176. {
  2177.  
  2178. //uint16_t y0 = 0;
  2179. String s1;
  2180. char var_array[25];
  2181.  
  2182. s1="/bmp/FRANCE/img1.bmp";
  2183.  
  2184. for(int i=0; i<24; i++) {var_array[i] = s1[i];}
  2185. var_array[24]=0; // zéro terminal -> string
  2186. float contraste=0.7; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
  2187. float lum = 50; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage
  2188.  
  2189. affi_img(0,160, lum, contraste, var_array);
  2190.  
  2191. }
  2192.  
  2193.  
  2194.  
  2195. void affi_carte_locale(uint16_t x0, uint16_t y0, uint8_t ajouts) // ajouts = cadre, avion, ILS, cercles...
  2196. {
  2197. uint16_t x_centre = 357;
  2198. uint16_t y_centre = 80;
  2199. //uint16_t y0 = 0;
  2200. String s1;
  2201. char var_array[25];
  2202.  
  2203. s1="/bmp/";
  2204. s1 += liste_bali[num_bali].ID_OACI;
  2205. s1 += "/240x160/";
  2206. s1 += String(zoom);
  2207. s1+= ".bmp";
  2208. s1+='\0';
  2209. //Serial.println(s1);
  2210.  
  2211. for(int i=0; i<24; i++) {var_array[i] = s1[i];}
  2212. var_array[24]=0; // zéro terminal -> string
  2213.  
  2214. float contraste=1.4; // 1.4 à ajuster en fonction de l'afficheur et de la qualité de son rétroéclairage
  2215. float lum = 100; // 100 ; 180 pour photos - une augmentation de cette valeur assombrit l'affichage
  2216.  
  2217. //affi_img(237,156, 0, 1.0, var_array); // affichage sans correction de lum ni contraste ; pour test différence...
  2218. efface_carte(x0,y0);
  2219. affi_img(x0,y0, lum, contraste, var_array); // affichage corrigé
  2220.  
  2221. //calcul de l'échelle compte tenu des valeurs mesurées, voir commentaire au-dessus de cette fonction
  2222.  
  2223. px_par_km = 140.0 / (1 << zoom); // -> variable globale
  2224. px_par_NM = px_par_km * 1.852; // -> variable globale
  2225.  
  2226.  
  2227. if(ajouts==1)
  2228. {
  2229. //TFT480.drawRoundRect(x0, y0, 245, 170, 5, GRIS);
  2230.  
  2231. TFT480.setFreeFont(FM9);
  2232. TFT480.setTextColor(BLANC);
  2233. s1=(String) (zoom);
  2234. //s1+= ".bmp";
  2235. TFT480.fillRect(x0+220, y0, 20, 14, GRIS_AF); //efface
  2236. TFT480.drawString(s1, x0+220, y0); // affiche le numéro de la carte
  2237.  
  2238.  
  2239. // trace 'faisceau localizer' (même si pas d'ILS, quand même utile sur la carte pour une approche VFR)
  2240. float alpha1 = 90.0-orient_piste; // angle direct
  2241. if (alpha1<0) {alpha1 += 360.0;}
  2242.  
  2243. float alpha2 = alpha1 + 180.0;
  2244. if (alpha2>360) {alpha2 -= 360.0;} // angle opposé
  2245.  
  2246. affi_rayon2(x_centre, y_centre, 2, 150, alpha1-3.0, NOIR, 0);
  2247. affi_rayon2(x_centre, y_centre, 2, 150, alpha1+3.0, NOIR, 0);
  2248.  
  2249. affi_rayon2(x_centre, y_centre, 2, 150, alpha2-3.0, NOIR, 0);
  2250. affi_rayon2(x_centre, y_centre, 2, 150, alpha2+3.0, NOIR, 0);
  2251.  
  2252. refresh_carte=0;
  2253.  
  2254. affi_char_r_alpha(x_centre, y_centre, 60, alpha1, 'B', NOIR);
  2255. affi_char_r_alpha(x_centre, y_centre, 60, alpha2, 'A', NOIR);
  2256.  
  2257.  
  2258.  
  2259.  
  2260. // cercle 10NM
  2261. TFT480.setTextFont(1);
  2262. TFT480.setTextColor(NOIR);
  2263. if (zoom>4)
  2264. {
  2265. uint16_t r = 10*(uint16_t)px_par_NM;
  2266. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2267. TFT480.drawString("10",x_centre+3+0.7*r,75+0.7*r);
  2268. }
  2269.  
  2270. // cercle 20NM
  2271. if (zoom>5)
  2272. {
  2273. uint16_t r = 20.0*px_par_NM;
  2274. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2275. TFT480.drawString("20",x_centre+3+0.7*r,75+0.7*r);
  2276. }
  2277.  
  2278. // cercle 30NM
  2279. if (zoom>6)
  2280. {
  2281. uint16_t r = 40.0*px_par_NM;
  2282. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2283. TFT480.drawString("40",x_centre+3+0.7*r,75+0.7*r);
  2284. }
  2285.  
  2286. // cercle 80NM
  2287. if (zoom>7)
  2288. {
  2289. uint16_t r = 80.0*px_par_NM;
  2290. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2291. TFT480.drawString("80",x_centre+3+0.7*r,75+0.7*r);
  2292. }
  2293.  
  2294. // cercle 160NM
  2295. if (zoom>8)
  2296. {
  2297. uint16_t r = 160.0*px_par_NM;
  2298. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2299. TFT480.drawString("160",x_centre+3+0.7*r,75+0.7*r);
  2300. }
  2301.  
  2302. // cercle 320NM
  2303. if (zoom>9)
  2304. {
  2305. uint16_t r = 320.0*px_par_NM;
  2306. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2307. TFT480.drawString("320",x_centre+3+0.7*r,75+0.7*r);
  2308. }
  2309.  
  2310. // cercle 640NM
  2311. if (zoom>10)
  2312. {
  2313. uint16_t r = 640.0*px_par_NM;
  2314. TFT480.drawCircle(x_centre, y_centre, r, NOIR);
  2315. TFT480.drawString("640",x_centre+3+0.7*r,75+0.7*r);
  2316. }
  2317.  
  2318.  
  2319. affi_avion_sur_carte(cap);
  2320.  
  2321.  
  2322. // POUR TEST
  2323. dessine_point_sur_carte(lat_centre_pst, lon_centre_pst, BLEU); // ok
  2324.  
  2325.  
  2326. float lat_A=liste_bali[num_bali].lat_A;
  2327. float lon_A=liste_bali[num_bali].lon_A;
  2328. dessine_point_sur_carte(lat_A, lon_A, BLEU); // ok
  2329.  
  2330. float lat_B=liste_bali[num_bali].lat_B;
  2331. float lon_B=liste_bali[num_bali].lon_B;
  2332. dessine_point_sur_carte(lat_B, lon_B, BLEU); // ok
  2333.  
  2334.  
  2335. float lat_1, lon_1;
  2336.  
  2337. /*
  2338. for (int i=0; i<=6; i++)
  2339. {
  2340. calcul_point_i(30.0*i, 10.0, &lat_1, &lon_1); // en 1/2 cercle, à 10NM du centre de la piste
  2341. dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2342. }
  2343. */
  2344.  
  2345. float e = 1.5;
  2346. calcul_point_i(0, 1.0*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2347. calcul_point_i(0, 4.0*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2348. calcul_point_i(16, 5.1*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2349. calcul_point_i(46, 4.0*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2350. calcul_point_i(72.3, 3.1*e, &lat_1, &lon_1);dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2351. calcul_point_i(109, 3.1*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2352. calcul_point_i(135, 4.0*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2353. calcul_point_i(163, 5.1*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2354. calcul_point_i(180, 4.0*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2355. calcul_point_i(180, 1.0*e, &lat_1, &lon_1); dessine_point_sur_carte(lat_1, lon_1, ROUGE);
  2356.  
  2357.  
  2358. if (read_bit(flags, bit_autoland) == 1)
  2359. {
  2360. TFT480.setFreeFont(FF1);
  2361. TFT480.setTextColor(ORANGE, NOIR);
  2362. cadre_bas1.affiche_string("zoom auto","");
  2363. }
  2364. }
  2365. }
  2366.  
  2367.  
  2368.  
  2369. void affi_position(float lat_i, float long_i, uint16_t x_i, uint16_t y_i)
  2370. {
  2371. String s1= String(abs(lat_i),4);
  2372. if(lat_i>0){s1+=" N";} else {s1+=" S";}
  2373. s1+=" / ";
  2374. s1+= String(abs(long_i),4);
  2375. if(long_i>0){s1+=" E";} else {s1+=" O";}
  2376. TFT480.setTextFont(1);
  2377. TFT480.drawString(s1, x_i, y_i);
  2378. }
  2379.  
  2380.  
  2381.  
  2382. void affi_data_piste() // dans la partie droite ; pour l'init des cadres, voir la fonction "dessine_pannel_frq()"
  2383. {
  2384.  
  2385. if (mode_affi == MODE_PLAN) {return;}
  2386. String s1,s2;
  2387. float v1;
  2388. // -------------------------------------
  2389. // entête
  2390.  
  2391. s1= liste_bali[num_bali].ID_OACI;
  2392. TFT480.setFreeFont(FM9);
  2393. cadre_top1.affiche_string(s1,"");
  2394.  
  2395. s1= (String)liste_bali[num_bali].altitude;
  2396. s1 +=" ft";
  2397. TFT480.setFreeFont(FM9);
  2398. cadre_top1b.affiche_string(s1,"");
  2399.  
  2400. s1=(String)(num_bali +1);
  2401. s1+=" ";
  2402. s1+= liste_bali[num_bali].nom;
  2403. if ((s1.length())>19) {TFT480.setTextFont(2);} else {TFT480.setFreeFont(FM9);}
  2404. cadre_top2.affiche_string(s1,"");
  2405.  
  2406.  
  2407. float alpha1 = orient_piste; // angle direct
  2408. if (alpha1<0) {alpha1 += 360.0;}
  2409.  
  2410. float alpha2 = alpha1 + 180.0;
  2411. if (alpha2>360) {alpha2 -= 360.0;} // angle opposé
  2412.  
  2413. cadre_00.affiche_float(alpha1, 1);
  2414. cadre_10.affiche_float(alpha2, 1);
  2415.  
  2416.  
  2417. uint16_t longueur1= (uint16_t)longueur_piste; // en m
  2418. cadre_01.affiche_int(longueur1, 'm');
  2419.  
  2420. //cadre_info.efface();
  2421. //cadre_info.couleur_texte = GRIS_CLAIR;
  2422.  
  2423. // affichage de la position géographique de l'aérodrome
  2424. TFT480.setTextColor(VIOLET2, NOIR);
  2425. affi_position(lat_centre_pst , lon_centre_pst, cadre_position.x0 +5, cadre_position.y0 +5);
  2426.  
  2427. refresh_ND=0;
  2428. }
  2429.  
  2430.  
  2431.  
  2432. /*
  2433. void affi_switches()
  2434. {
  2435. TFT480.setTextFont(1);
  2436. TFT480.fillRect(420, 302, 30, 10, NOIR); // efface le nombre précédemment affiché
  2437. TFT480.setTextColor(GRIS);
  2438. TFT480.drawString(switches, 420, 302);
  2439. }
  2440. */
  2441.  
  2442.  
  2443. void init_WiFi()
  2444. {
  2445. WiFi.persistent(false);
  2446. WiFi.begin(ssid, password);
  2447. delay(2000);
  2448. }
  2449.  
  2450.  
  2451. void init_boutons()
  2452. {
  2453. pinMode(GPIO_SW0, INPUT);
  2454.  
  2455. //pinMode(led1, OUTPUT);
  2456.  
  2457. //pinMode(rot1a, INPUT_PULLUP);
  2458. //pinMode(rot1b, INPUT_PULLUP);
  2459.  
  2460. pinMode(GPIO_bouton0, INPUT_PULLUP);
  2461. pinMode(GPIO_bouton1, INPUT_PULLUP);
  2462. pinMode(GPIO_bouton2, INPUT_PULLUP);
  2463. pinMode(GPIO_bouton3, INPUT_PULLUP);
  2464.  
  2465. }
  2466.  
  2467.  
  2468. void init_afficheur()
  2469. {
  2470. TFT480.init();
  2471. TFT480.setRotation(3); // 0..3 à voir, suivant disposition de l'afficheur
  2472.  
  2473. TFT480.fillScreen(NOIR);
  2474. TFT480.setTextColor(NOIR, BLANC);
  2475. }
  2476.  
  2477.  
  2478. void init_variables()
  2479. {
  2480. cap=0;
  2481.  
  2482. zoom=1;
  2483. SW0_etat = digitalRead(SW0);
  2484. memo_SW0_etat = SW0_etat;
  2485. x_avion=0;
  2486. y_avion=0;
  2487. memo_x_avion=0;
  2488. memo_y_avion=0;
  2489. }
  2490.  
  2491.  
  2492. void init_Leds() // pour l'affichage des données, voir la fonction "affi_data_piste()"
  2493. {
  2494. uint16_t x0 = 470;
  2495. uint16_t y0 = 116;
  2496.  
  2497. uint16_t yi = y0;
  2498.  
  2499. Led1.init(x0,yi, 20, 20);
  2500. Led1.set_couleur(BLEU);
  2501. Led1.eteint();
  2502.  
  2503. yi+=80;
  2504.  
  2505. Led2.init(x0,yi, 20, 20);
  2506. Led2.set_couleur(ROUGE);
  2507. Led2.eteint();
  2508. }
  2509.  
  2510.  
  2511. void trace_point_FR(float lat_i, float lon_i, uint8_t diam, uint16_t couleur)
  2512. {
  2513. float x;
  2514. float y;
  2515. // sur l'image .bmp 360x480px de la France entière
  2516.  
  2517. // calibration :
  2518. TFT480.fillCircle(73, 44, 2, BLANC);// 50°N -4° ->REF
  2519. TFT480.fillCircle(73, 258, 2, BLANC);// 44°N -4°
  2520. TFT480.fillCircle(369, 44, 2, BLANC);// 50°N +8°
  2521. TFT480.fillCircle(369, 258, 2, BLANC);// 44°N +8°
  2522.  
  2523.  
  2524. /**
  2525. lat=4° -> x=73px
  2526. lat=8° -> x=369px
  2527. ech_x = dx/dlon = (369-73)/(8+4)= 296/12 = 24.66
  2528.  
  2529. lon=44 -> y=258px
  2530. lon=50 -> y=44px
  2531. ech_y= dy/dlat = (258-44)/(50-44) = 214/6 = 35.66
  2532.  
  2533. il vient:
  2534. x=73+(lon-4)*74
  2535. y=44+(lat-50)*35.66
  2536.  
  2537. **/
  2538.  
  2539. x=73.0+(lon_i+4)*24.66; // ok
  2540. y=44.0+(50.0-lat_i)*35.66; //ok
  2541.  
  2542. if (diam>1) {TFT480.fillCircle((uint16_t)x, (uint16_t)y, diam, couleur);}
  2543. else {TFT480.drawPixel((uint16_t)x, (uint16_t)y, couleur);}
  2544. }
  2545.  
  2546.  
  2547.  
  2548. void affi_1_airport(float lat_i, float lon_i) // sur carte France
  2549. {
  2550. trace_point_FR(lat_i, lon_i, 4, JAUNE);
  2551. }
  2552.  
  2553.  
  2554.  
  2555. void affi_avion_sur_carte_FR() // sur carte France
  2556. {
  2557. trace_point_FR(lat_avion, lon_avion, 1, BLANC);
  2558. }
  2559.  
  2560.  
  2561.  
  2562.  
  2563. void affi_all_airports() // sur carte France
  2564. {
  2565. float lat_i;
  2566. float lon_i;
  2567.  
  2568. for(uint16_t i=0 ; i<nb_elements ; i++)
  2569. {
  2570. lat_i = liste_bali[i].lat_A;
  2571. lon_i = liste_bali[i].lon_A;
  2572. trace_point_FR(lat_i, lon_i, 2, ROUGE);
  2573. }
  2574.  
  2575. affi_1_airport(liste_bali[num_bali].lat_A, liste_bali[num_bali].lon_A); // celui sélectionné
  2576. affi_avion_sur_carte_FR(); // position actuelle de l'avion
  2577. }
  2578.  
  2579.  
  2580.  
  2581.  
  2582. void affi_navi()
  2583. {
  2584. zoom=1;
  2585. affi_ND(cap);
  2586. memo_cap=cap;
  2587.  
  2588. affi_carte_locale(235,0,1);
  2589. dessine_pannel_frq();
  2590. refresh_carte=0;
  2591. }
  2592.  
  2593.  
  2594.  
  2595. void setup()
  2596. {
  2597. Serial.begin(19200);
  2598.  
  2599. init_variables();
  2600.  
  2601. init_WiFi();
  2602. init_boutons();
  2603. init_afficheur();
  2604. init_SDcard();
  2605.  
  2606. init_FG_bali();
  2607. init_sprites();
  2608. init_sprite_avion(BLEU);
  2609. //init_affi_vor();
  2610. /*
  2611. affi_carte_France();
  2612. //trace_point_FR(46.0, 2.0); // sur carte France
  2613. affi_all_airports();
  2614. delay(2000);
  2615. //while(1);
  2616. */
  2617.  
  2618. TFT480.setTextColor(BLANC, NOIR);
  2619. TFT480.setFreeFont(FF19);
  2620. String s1 = "ND v";
  2621. s1+= version;
  2622.  
  2623. init_Leds();
  2624.  
  2625. affi_navi();
  2626. }
  2627.  
  2628.  
  2629.  
  2630.  
  2631. void affichages()
  2632. {
  2633. affi_dst_GPS();
  2634. affi_direction_piste();
  2635. affi_dst_piste();
  2636.  
  2637. if(cap != memo_cap)
  2638. {
  2639. affi_ND(cap);
  2640.  
  2641. refresh_ND=1; // pour raffraichir l'affichage du panneau de fréquences à droite
  2642. memo_cap = cap;
  2643. }
  2644. //affi_float_test(145.62 , 0, BLEU);
  2645. //affi_float_test(25.89 , 1 , GRIS_FONCE);
  2646. }
  2647.  
  2648.  
  2649.  
  2650. void teste_boutons() // boutons reliés à l'ESP n°2 (ND), pas ceux de l'ESP du SW
  2651. {
  2652. // REMARQUE : les boutons sont aussi testés localement dans la fonction "choix_aerodrome()"
  2653.  
  2654.  
  2655. bouton2_etat = digitalRead(GPIO_bouton2); // ZOOM -
  2656. //Serial.print(bouton0_etat);
  2657.  
  2658. if (bouton2_etat != memo_bouton2_etat)
  2659. {
  2660. memo_bouton2_etat = bouton2_etat;
  2661. if (bouton2_etat==0)
  2662. {
  2663. if (zoom == 0)
  2664. {
  2665. mode_affi=MODE_ND;
  2666. init_mode_ND();
  2667. }
  2668. zoom++;
  2669. if (zoom<0){zoom=0;}
  2670. if (zoom > 11){zoom=11;}
  2671. //refresh_carte=1;
  2672. affi_carte_locale(235,0,1);
  2673.  
  2674. }
  2675. affi_ND(cap);
  2676. }
  2677.  
  2678. bouton3_etat = digitalRead(GPIO_bouton3); // ZOOM +
  2679. //Serial.println(bouton1_etat);
  2680.  
  2681. if (bouton3_etat != memo_bouton3_etat)
  2682. {
  2683. memo_bouton3_etat = bouton3_etat;
  2684. if (bouton3_etat==0)
  2685. {
  2686. zoom--;
  2687. if (zoom< 0){zoom = 0;}
  2688. if (zoom > 11){zoom=11;}
  2689.  
  2690. //Serial.print("zoom="); Serial.println(zoom);
  2691.  
  2692. if (zoom == 0) // rappel: il n'y a pas de carte 0; le 1er numéro est 1.bmp, voir dans le dossier bmp...
  2693. {
  2694. mode_affi=MODE_PLAN;
  2695. init_sprite_avion(ROUGE);
  2696. affi_plan_aerodrome();
  2697. }
  2698. else
  2699. {
  2700. mode_affi=MODE_ND;
  2701. affi_carte_locale(235,0,1);
  2702. }
  2703. affi_ND(cap);
  2704. }
  2705. }
  2706.  
  2707.  
  2708. bouton0_etat = digitalRead(GPIO_bouton0);
  2709. //Serial.print(bouton0_etat);
  2710.  
  2711. if (bouton0_etat != memo_bouton0_etat) // le bouton qui sert aussi à descendre dans la liste, en mode liste
  2712. {
  2713. memo_bouton0_etat = bouton0_etat;
  2714. if (bouton0_etat==0)
  2715. {
  2716. mode_affi=MODE_LISTE;
  2717. choix_aerodrome();
  2718. }
  2719. }
  2720.  
  2721.  
  2722. bouton1_etat = digitalRead(GPIO_bouton1);
  2723.  
  2724. if (bouton1_etat != memo_bouton1_etat) // le bouton qui sert aussi à monter dans la liste, en mode liste
  2725. {
  2726. memo_bouton1_etat = bouton1_etat;
  2727. if (bouton1_etat==0)
  2728. {
  2729. mode_affi=MODE_LISTE;
  2730. choix_aerodrome();
  2731. }
  2732. }
  2733. }
  2734.  
  2735.  
  2736.  
  2737.  
  2738. void toutes_les_10s()
  2739. {
  2740. dessine_pannel_frq(); // donc le cadre s'abime lors de la rotation du sprite à gauche
  2741. }
  2742.  
  2743.  
  2744.  
  2745.  
  2746.  
  2747. void toutes_les_1s()
  2748. {
  2749.  
  2750. nb_secondes++;
  2751.  
  2752. if (refresh_carte==1)
  2753. {
  2754. if(mode_affi != MODE_PLAN)
  2755. {
  2756. affi_ND(cap);
  2757. affi_carte_locale(235,0,1);
  2758. }
  2759. }
  2760.  
  2761. if((nb_secondes>9) == 0)
  2762. {
  2763. nb_secondes=0;
  2764. toutes_les_10s();
  2765. }
  2766.  
  2767.  
  2768. }
  2769.  
  2770.  
  2771.  
  2772. void interroge_WiFi()
  2773. {
  2774. // voir les "server.on()" dans la fonction "setup()" du code du PFD pour la source des données
  2775. recp_hdg = "";
  2776. if(WiFi.status()== WL_CONNECTED )
  2777. {
  2778. httpGetHDG(); // envoie requete au serveur
  2779.  
  2780. TFT480.setTextColor(VERT, NOIR);
  2781. hdg1=recp_hdg.toInt();
  2782.  
  2783.  
  2784. if(hdg1 != memo1_hdg1)
  2785. {
  2786. if (mode_affi == MODE_ND)
  2787. {
  2788. refresh_Encoche=1;
  2789. cadre_hdg1.efface();
  2790. cadre_hdg1.affiche_int(hdg1,' ');
  2791. TFT480.drawCircle(52, 5, 2, BLANC); // caractère 'degré'
  2792. }
  2793. }
  2794.  
  2795. httpGetCap();
  2796. cap=recp_cap.toInt();
  2797.  
  2798. if(cap != memo_cap)
  2799. {
  2800. if (mode_affi == MODE_ND)
  2801. {
  2802. affi_ND(cap);
  2803. refresh_Encoche=1;
  2804. }
  2805. }
  2806.  
  2807. httpGetLatitude();
  2808. lat_avion=recp_latitude.toFloat() / 10E3;
  2809.  
  2810. httpGetLongitude();
  2811. lon_avion=recp_longitude.toFloat() / 10E3;
  2812.  
  2813. httpGetHauteur();
  2814. hauteur_AAL = recp_hauteur.toFloat()/10;
  2815.  
  2816. httpGetParams();
  2817. flags = recp_flags.toInt(); // les significations des bits sont définies dans le fichier "Fonctions1.h"
  2818.  
  2819. //affi_int_test(flags, 300, 1, VERT, NOIR);
  2820.  
  2821. }
  2822. else if (premier_passage == 1)
  2823. {
  2824. RAZ_variables();
  2825. affi_ND(cap);
  2826. premier_passage=0;
  2827. }
  2828.  
  2829. }
  2830.  
  2831.  
  2832.  
  2833.  
  2834. void toutes_les_100ms()
  2835. {
  2836. calculs_GPS();
  2837.  
  2838. nb_100ms++;
  2839. if(nb_100ms>10)
  2840. {
  2841. nb_100ms=0;
  2842. toutes_les_1s();
  2843. }
  2844.  
  2845. if (mode_affi != MODE_PLAN)
  2846. {
  2847.  
  2848. if (refresh_ND==1)
  2849. {
  2850. affi_data_piste();
  2851. affi_ND(cap);
  2852.  
  2853. refresh_ND=0;
  2854. //refresh_Encoche=1;
  2855. }
  2856.  
  2857.  
  2858. if (refresh_Encoche==1)
  2859. {
  2860. affi_encoche_hdg(memo1_hdg1, NOIR); // efface
  2861. affi_encoche_hdg(hdg1, OLIVE);
  2862. affi_ND(cap);
  2863.  
  2864. memo1_hdg1=hdg1;
  2865. refresh_Encoche=0;
  2866. }
  2867.  
  2868. calcul_pos_avion_sur_carte();
  2869. affi_avion_sur_carte(cap);
  2870.  
  2871. TFT480.setTextColor(VIOLET2, NOIR);
  2872. affi_position(lat_avion, lon_avion, 55, 230);
  2873. }
  2874.  
  2875. if (mode_affi == MODE_PLAN)
  2876. {
  2877. calcul_pos_avion_sur_plan();
  2878. affi_avion_sur_plan(cap);
  2879. }
  2880. }
  2881.  
  2882.  
  2883.  
  2884.  
  2885. /** ==================================================================
  2886.  variables à gérer obligatoirement */
  2887. //le nombre de ports GPIO libres étant atteint, on va utiiser un switch unique pour deux fonctions :
  2888. uint8_t fonction_bt0 = 1; // 0=saisie écran ; 1=feux d'atterrissage
  2889.  
  2890. /** ================================================================== */
  2891.  
  2892. uint16_t t=0; // temps -> rebouclera si dépassement
  2893. void loop()
  2894. {
  2895.  
  2896. SW0_etat = digitalRead(GPIO_SW0);
  2897. if (SW0_etat==0)
  2898. {
  2899. mode_affi= MODE_CARTE_FR;
  2900. affi_carte_France();
  2901. affi_all_airports();
  2902. while(digitalRead(GPIO_SW0)==0)
  2903. {
  2904. interroge_WiFi();
  2905. affi_avion_sur_carte_FR(); // et boucle
  2906. }
  2907. mode_affi = MODE_ND;
  2908. TFT480.fillScreen(NOIR);
  2909. TFT480.setTextColor(NOIR, BLANC);
  2910. affi_navi();
  2911. init_mode_ND();
  2912. }
  2913.  
  2914. teste_boutons();
  2915.  
  2916. temps_ecoule = micros() - memo_micros;
  2917.  
  2918. if (temps_ecoule >= 1E5) // (>=) et pas strictement égal (==) parce qu'alors on rate l'instant en question
  2919. {
  2920. memo_micros = micros();
  2921. toutes_les_100ms();
  2922. }
  2923.  
  2924.  
  2925. //Serial.print("mode_affi="); Serial.println(mode_affi);
  2926.  
  2927. if (mode_affi == MODE_ND)
  2928. {
  2929. compteur1+=1;
  2930. if (compteur1 >= 100) // tous les 1000 passages dans la boucle
  2931. {
  2932. compteur1=0;
  2933.  
  2934. }
  2935.  
  2936. interroge_WiFi(); // et envoie les arguments
  2937.  
  2938. affichages();
  2939.  
  2940. delay(30);
  2941. }
  2942.  
  2943. if (mode_affi == MODE_LISTE)
  2944. {
  2945. choix_aerodrome();
  2946. }
  2947.  
  2948. if (mode_affi == MODE_PLAN)
  2949. {
  2950. interroge_WiFi();
  2951. }
  2952.  
  2953. if (flags != memo_flags)
  2954. {
  2955. memo_flags = flags;
  2956. affi_flags();
  2957. }
  2958.  
  2959.  
  2960. /** ----------------------------------------------
  2961.  pour tester si data_ok à bien la valeur attendue, c.a.d si toutes les acquisitions sont effectuées
  2962.  ce qui est le cas lorsque le dialogue avec le serveur de protocole FG est correctement programmé
  2963.  sachant que ce ne sont pas les pièges qui manquent !
  2964. */
  2965. ////TFT480.setFreeFont(FM9);
  2966. ////TFT480.setTextColor(VERT, NOIR);
  2967. ////TFT480.drawString("d_sum:", 0, 0);
  2968. ////s2= (String) data_ok;
  2969. ////TFT480.fillRect(70, 0, 50, 30, NOIR);
  2970. ////TFT480.drawString(s2, 70, 0);
  2971.  
  2972. /** ---------------------------------------------- **/
  2973.  
  2974. //---------------------------------------------------------------------------------------
  2975.  
  2976. }
  2977.  
  2978.  
  2979. /** ***************************************************************************************
  2980. CLASS Cadre // affiche un nombre ou un petit texte dans un rectangle
  2981. ********************************************************************************************/
  2982.  
  2983. // Constructeur
  2984. Cadre::Cadre()
  2985. {
  2986.  
  2987. }
  2988.  
  2989.  
  2990. void Cadre::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
  2991. {
  2992. x0 = xi;
  2993. y0 = yi;
  2994. dx = dxi;
  2995. dy = dyi;
  2996.  
  2997. TFT480.setTextColor(BLANC, NOIR);
  2998. }
  2999.  
  3000.  
  3001.  
  3002. void Cadre::traceContour()
  3003. {
  3004. TFT480.drawRoundRect(x0, y0, dx, dy, 3, couleur_contour);
  3005. }
  3006.  
  3007.  
  3008.  
  3009. void Cadre::efface()
  3010. {
  3011. TFT480.fillRect(x0, y0, dx, dy, NOIR);
  3012. traceContour();
  3013. }
  3014.  
  3015.  
  3016.  
  3017.  
  3018. void Cadre::affiche_int(uint32_t valeur, char lettre)
  3019. {
  3020. String s1;
  3021. efface();
  3022. TFT480.setFreeFont(FM9);
  3023. TFT480.setTextColor(couleur_texte, NOIR);
  3024. s1 = String(valeur);
  3025. //TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
  3026. TFT480.drawString(s1, x0+5, y0+5);
  3027. s1 =String(lettre);
  3028. TFT480.drawString(s1, x0+50, y0+5);
  3029. }
  3030.  
  3031.  
  3032.  
  3033. void Cadre::affiche_float(float valeur, int nb_dec)
  3034. {
  3035. efface();
  3036. TFT480.setFreeFont(FM9);
  3037. TFT480.setTextColor(couleur_texte, NOIR);
  3038. String s1 = String( valeur, nb_dec);
  3039. //TFT480.fillRect(x0, y0, 52, 18, NOIR); // efface
  3040. TFT480.drawString(s1, x0+5, y0+5);
  3041. }
  3042.  
  3043.  
  3044.  
  3045. void Cadre::affiche_string(String s1, String s2)
  3046. {
  3047. // Pour cette méthode, la police de caract doit être choisie au préalable, extérieurement
  3048. efface();
  3049. TFT480.setTextColor(couleur_texte, NOIR);
  3050. TFT480.drawString(s1, x0+5, y0+5);
  3051. if(s2 !="") {TFT480.drawString(s2, x0+5, y0+20);}
  3052. }
  3053.  
  3054.  
  3055.  
  3056. /** ***************************************************************************************
  3057. CLASS Led // affiche un petit rectangle coloré
  3058. ********************************************************************************************/
  3059.  
  3060. // Constructeur
  3061. Led::Led()
  3062. {
  3063.  
  3064. }
  3065.  
  3066.  
  3067. void Led::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
  3068. {
  3069. x0 = xi;
  3070. y0 = yi;
  3071. dx = dxi;
  3072. dy = dyi;
  3073. }
  3074.  
  3075.  
  3076.  
  3077. void Led::allume()
  3078. {
  3079. TFT480.fillRect(x0, y0, dx, dy, couleur);
  3080. }
  3081.  
  3082.  
  3083. void Led::eteint()
  3084. {
  3085. TFT480.fillRect(x0, y0, dx, dy, GRIS_TRES_FONCE);
  3086. TFT480.drawRect(x0, y0, dx, dy, couleur); // bordure
  3087. }
  3088.  
  3089.  
  3090. void Led::set_couleur(uint16_t coul_i)
  3091. {
  3092. couleur = coul_i;
  3093. }
  3094.  

21 Planche de bord

23 juillet 2021 :
Je suis en train de regrouper les deux instruments sur une petite planche de bord (en PLA). Vu que tout se fait maintenant par ordinateur, j'ai commencé par dessiner la chose avec LibreOffice Draw. En parallèle j'ai confectionné des supports en PLA jaune avec l'imprimante 3D Ender3 (fichiers sources ci-dessous dans le dossier "3D"). La plaque est déjà percée il ne reste plus qu'à la découper avec la bonne forme, et à fixer les éléments. Donc une photo de l'ensemble terminé ne devrait pas tarder...

Dans un vrai poste de pilotage, l'écran du PFD est doublé pour le copilote, mais je ne vais pas pousser le réalisme jusque là. On pourrait toutefois en ajouter un pour afficher la carte et/ou le gestionnaire de route.

22 La voici la planche de bord

En PLA blanc.
Découpée avec une CNC Alfawise C10 Pro.

19 septembre 2021 :
J'ai habillé cette face avant avec du vinyle gris foncé.

23 Utilisation

Le Navigation Display reçoit toutes ses données par WiFi, depuis l'ESP du PFD. Il n'est donc pas nécessaire de le connecter sur un port USB, il peut être simplement alimenté par un adaptateur 5V, ou sur l'alim 5V du PFD (en amont du régulateur 3V3 si toutefois le port USB0 (et son câble) qui alimentent le PFD peuvent fournir suffisamment de courant).

Avant de décoller :
  • Dés-enclencher l'autoland du PFD.
  • Choisir un aérodrome dans le ND (avec l'encodeur rotatif), ce qui renseignera l'ensemble des deux instruments (par la liaison WiFi entre le PFD et le ND) ET le programme Flightgear (par la liaison USB entre l'ordinateur et le PFD) concernant les fréquences radio du VOR le plus proche et de l'ILS, ainsi que les cartes à afficher (avec choix du zoom par le second encodeur rotatif du ND). Je rajouterai des aérodromes petit à petit...
  • Régler le QNH afin que l'afficheur de l'altitude indique 0 avion au sol. Pour ce réglage, utiliser la molette de la souris sur le petit bouton indiqué sur la figure ci-contre, dans Flightgear.
  • Régler l'HDG suivant l'axe de la piste (SYNC HDG) afin d'éviter une surprise lors de l'activation de l'Autopilot.
  • Régler la consigne d'altitude de l'Autopilot (ASEL) avec l'encodeur rotatif (3000ft par exemple). Toutes ces valeurs ne seront prises en compte que lors de l'activation de l'autopilot.
  • Sortir les flaps (10°, pas à fond donc).
  • Desserrer le frein de parking.
  • Mettre les gaz.
  • Lâcher les freins.
  • La trajectoire au sol se contrôle au palonnier
  • Apprendre à piloter, il reste vingt secondes...
  • Dès le décollage effectué, rentrer le train.
  • Lorsque la vitesse et le taux de montés sont satisfaisants, rentrer les flaps.
  • Stabiliser les paramètres de vols et placer l'avion en palier au moment d'enclencher l'Autopilot, pour éviter des efforts sur la structure de l'appareil.
  • On peut dès lors piloter par action sur l'Autopilot de Flightgear depuis nos ESP.

voir ce lien à propos du QNH : - https://wiki.flightgear.org/Fr/Altitude

Remarque : si on modifie la météo, entre le coeur d'une haute pression et le coeur d'une dépression, on fait varier l'altitude affichée de +/- 500 ft ce qui est très loin d'être négligeable ! (mais est tout à fait réel).
donc avant de décoller il faut absolument régler le QNH, de même en approche d'un aérodrome pour atterrissage, le contrôleur ATC indique le QNH (local) - https://fr.wikipedia.org/wiki/Phras%C3%A9ologie_de_l%27aviation


Dans FG on peut connaître la pression atmosphérique locale d'un aérodrome, si l'on est en mode météo réelle, en consultant le METAR. Note : au dessus de 3000ft en France, il faut en principe voler en pression standard (1013 hpa / niv mer) afin que tous les aéronefs aient localement la même échelle affichée. Au dessous de 3000ft, au contraire, c'est la hauteur réelle qui prévaut, il faut alors connaître la pression atmosphérique. Pour basculer en mode pression standard, il y a un bouton "STD" prévu, juste à côté, à gauche de celui indiqué par la flèche. Si on clique dessus en étant au sol, on voit 1013 s"afficher dans FG, et simultanément l'indicateur d'altitude de notre PFD partir dans les choux, dans une plage de +/- 500ft. Il convient alors de refaire le réglage avec le bouton rotatif de droite.

Je vous dis tout ça pour vous éviter une prise de tête dès le départ...

24 Un troisième ESP32 gérant des boutons

Je rajoute un troisième ESP32, sans écran LCD cette fois ce qui libère tous les ports d'E/S et permet ainsi de connecter des boutons (quatre pour l'instant, mais on en rajoutera, cette fois ce ne sont plus les pins GPIO qui manquent).

La liaison avec le serveur du PFD se fait en tant que client WiFi, les données (état des boutons et même peut-être d'encodeurs rotatifs voire de potentiomètres) sont envoyé vers le serveur sous forme de paramètres dans les requêtes.

Je peux d'ores et déjà vous dire que ça fonctionne parfaitement. Pour l'instant 2 boutons poussoirs servent à incrémenter l'HDG
- la consigne de cap du pilote automatique) par pas de +/- 30° (ce qui évite de devoir tourner comme un fou l'encodeur rotatif...) Virer à angle droit ? 3 clics ! Demi-tour ? 6 clics.
- et la consigne "a_sel" d'altitude par pas de 1000 pieds.
Je vais bien entendu publier le code, le schéma et tutti quanti prochainement.

En attendant voici une photo réelle (pas en incrustation) du panel PFD + ND devant l'écran 24"; avec le Citation X en approche de Calvi.

Le "soleil" en haut à gauche n'est autre que le reflet de la lampe à LED qui éclaire le panel. Ben oui, en fait c'est un métier la photographie d'objets ! Beaucoup utilisé dans le domaine de la publicité, mais pas que...

25 Le panel des boutons

20 août 2021 :
Le panel des boutons est opérationnel, il communique avec le PFD, par WiFi comme prévu. Les données d'appui des boutons sont codées en binaire sur un mots de 16 bits transmis au PFD par argument dans une requête html.
L'ESP32 est disposé côté CU, écarté des pistes par l'emploi d'un connecteur femelle au pas 2.54mm

Reste à lui confectionner une face avant (à l'imprimante 3D) et un petit boîtier pour protéger l’électronique.

Pour l'instant seuls quatre boutons ont une fonction, deux pour régler la consigne de cap par sauts de +/-30° et deux pour la consigne d'altitude par sauts de +/- 1000 pieds ce qui rend le réglage (en vol) du pilote automatique bien plus pratique. (Les réglages fins restent opérationnels avec les encodeurs rotatifs pas à pas du PFD)

Les autres, au nombre de quatre également, alignés en colonne sur la droite du panel, n'ont pas de fonction définie pour l'instant bien qu'ils soient pris en compte par le logiciel et transmis au PFD. Mais ce ne sont pas les idées qui manquent, bien que les fonctions de base comme sortie du train d'atterrissage ou des flaps sont, pour ma part, déjà commandées par des boutons sur le joystick principal.

(j'utilise aussi un pédalier pour la gouverne de lacet, confectionné à partir d'un "stepper" de chez Déca..lon, méchamment bricolé à la scie à métaux, avec ajout d'un potentiomètre relié à un second joystick trafiqué en conséquence) :


26 Les boutons sont dans la boite

22 août 2021 :
J'ai donc imprimé en 3D, avec du filament PLA, ce boîtier dont le fond fait office de face avant. J'ai indiqué plus haut les fonctions des quatre boutons de gauche. Je prévois maintenant d'attribuer les deux suivants au réglage de la consigne de vitesse du pilote auto. Je parle tout le temps de "consigne", c'est un terme qu'on emploie en électronique pour les asservissements de vitesse ou de position. En aéronautique on utilise d'autres mots, je pense aux abréviations plus spécifiques et précises telles que AP, APP, CLB, FD, ASEL, FMS, G/S, HDG, HUD, LNAV, LOC, PDU, SPD, VALT, VGP, VNAV, VS...

Oui mais si je parle avec dans cette langue propre à l'avionique sur ce site qui s'adresse à tous et aussi plus précisément aux électroniciens et programmeurs en informatique, je pense que ça ne va pas le faire.

Pour revenir à notre boite à boutons, il manque maintenant une sérigraphie sur la face avant pour indiquer la fonctions de chaque bouton. Et pour une sérigraphie sur du PLA blanc j'avoue que je ne sais pas trop comment faire... J'ai vu qu'on peut graver au laser à condition de peindre en noir au préalable la partie à imprimer. Ce qui m'inquiète, c'est de trouver le colorant qui sera totalement nettoyable après gravure. Je dois faire des essais. Sinon, on peut écrire directement avec un indélébile (éventuellement guidé par une CNC).

27 Code source du panel des boutons (SW)

CODE SOURCE en C++
  1. //
  2. // ==================================
  3. String version="35.3";
  4. // ==================================
  5.  
  6.  
  7. /*---------------------------------------------------------------------------------------
  8. SW.ino - Gestion de boutons (switches) pour ESP32
  9. numéro de version doit être identique à celui du PFD (Primary Flight Display)
  10.  
  11. par Silicium628
  12. Ce logiciel est libre et gratuit sous licence GNU GPL
  13.  
  14. Pour les #includes issus de l'univers Arduino (que je ne fournis pas), il faut voir au cas par cas.
  15. ---------------------------------------------------------------------------------------
  16.  
  17. Notes :
  18.  
  19. - ce SW envoi toutes les données par WiFi. Il n'est donc pas nécessaire de le connecter
  20. sur un port USB, il peut être simplement alimenté par un adaptateur 5V.
  21. */
  22.  
  23. #include <stdint.h>
  24.  
  25. #include "SPI.h"
  26.  
  27.  
  28. #include <WiFi.h>
  29. #include <HTTPClient.h> // Remarque: les fonctions WiFi consomment 50% de la mémoire flash (de programme)
  30.  
  31. const char* ssid = "PFD_srv"; // nous nous connectons en tant que client au même serveur (le PFD) que le ND.
  32. const char* password = "72r4TsJ28"; // doit être identique à celui du serveur du PFD
  33.  
  34.  
  35. //IP address with URL path
  36. const char* srvName_sw = "http://192.168.4.1/switch";
  37.  
  38.  
  39. String recp_1;
  40.  
  41. char var_array[5]; // 4 char + zero terminal - pour envoi par WiFi
  42.  
  43. uint16_t v_switches=0; // états des boutons
  44.  
  45. uint32_t memo_micros = 0;
  46. uint32_t temps_ecoule;
  47. uint16_t nb_100ms=0;
  48. uint16_t nb_secondes=0;
  49.  
  50.  
  51. //-------------------------------------------------------------------------------------------
  52. // j'attribue les ports dans l'ordre des pins de la platine ESP32, voir schéma
  53. // Le SW (panel des boutons) peut être positionné soit à l'horizontale soit à la verticale.
  54. // Il faut cofigurer ci-dessous les pins des boutons en conséqence.
  55. // j'indique en colonne dans les commentaires les différents paramètrages utilisés au fil des versions...
  56.  
  57. uint16_t bouton0=1; // (2^0) -> positionnera 1 bit dans le mot "v_switches"
  58. const int GPIO_bouton0 = 27; // 14 ; 13 pour la version horizontale du SW
  59. bool bouton0_etat;
  60. bool memo_bouton0_etat;
  61.  
  62. uint16_t bouton1=2; // (2^1)
  63. const int GPIO_bouton1 = 14; // 27 ; 12 pour la version horizontale du SW
  64. bool bouton1_etat;
  65. bool memo_bouton1_etat;
  66.  
  67. uint16_t bouton2=4; // (2^2)
  68. const int GPIO_bouton2 = 13; // 12 ; 14 pour la version horizontale du SW
  69. bool bouton2_etat;
  70. bool memo_bouton2_etat;
  71.  
  72. uint16_t bouton3=8; // (2^3)
  73. const int GPIO_bouton3 = 12; // 13 ; 27 pour la version horizontale du SW
  74. bool bouton3_etat;
  75. bool memo_bouton3_etat;
  76.  
  77. uint16_t bouton4=64; // 16 (2^4)
  78. const int GPIO_bouton4 = 25; // 26 pour la version horizontale du SW
  79. bool bouton4_etat;
  80. bool memo_bouton4_etat;
  81.  
  82. uint16_t bouton5=128; // 32 (2^5)
  83. const int GPIO_bouton5 = 26; //25 pour la version horizontale du SW
  84. bool bouton5_etat;
  85. bool memo_bouton5_etat;
  86.  
  87. uint16_t bouton6=16; // 64 (2^6)
  88. const int GPIO_bouton6 = 33; // 33
  89. bool bouton6_etat;
  90. bool memo_bouton6_etat;
  91.  
  92. uint16_t bouton7=32; // 128 (2^7)
  93. const int GPIO_bouton7= 32; // 32
  94. bool bouton7_etat;
  95. bool memo_bouton7_etat;
  96.  
  97. /*
  98. uint16_t bouton8=256; // (2^7)
  99. const int GPIO_bouton8= 35; // 32
  100. bool bouton8_etat;
  101. bool memo_bouton8_etat;
  102. */
  103.  
  104.  
  105.  
  106.  
  107. uint16_t potar1;
  108. uint16_t memo_potar1;
  109. const int GPIO_potar1= 35;
  110.  
  111.  
  112.  
  113. //-------------------------------------------------------------------------------------------
  114.  
  115.  
  116. //const int led1 = 25;
  117.  
  118.  
  119. #define TEMPO 20 // tempo anti-rebond pour l'acquisition des encodeurs rotatifs
  120. volatile uint32_t timer1 = 0;
  121. volatile uint32_t timer2 = 0;
  122.  
  123. uint16_t compteur1;
  124. uint8_t premier_passage=1;
  125.  
  126.  
  127.  
  128.  
  129. void httpGet1()
  130. {
  131. String s1;
  132. String s2;
  133. String s3;
  134. String s4;
  135. /**
  136.  envoie requete au serveur, avec position des switches en argument.
  137.  il s'agit donc ici d'un envoi de données de ce SW vers le PFD. (voir la fonction "setup()" du PFD)
  138.  dans une adresse (URL) html, le 1er argument se place à droite après un signe '?'
  139.  les arguments suivants (éventuels) se placent à la suite, précédés cette fois par le signe '&'
  140.  sur le web, la méthode n'est pas sécurisée à priori, mais ici il s'agit d'un réseau WiFi privé à courte portée
  141.  et les données ne sont ni confidentielles ni critiques :
  142.  (pas d'action sur un objet réel, chauffage, voiture, drône, Ariane5, bombe atomique etc...)
  143. **/
  144. HTTPClient http;
  145. char c1[4];
  146.  
  147. s1= "?sw1="; // sw1 (nom arbitraire..) pour désigner l'argument n°1 envoyé dans l'URL de la requête
  148. s2= String(v_switches);
  149. s1+= s2;
  150.  
  151. s1+= "&pot1="; // pot1 (nom arbitraire..) pour désigner l'argument n°3 envoyé dans l'URL de la requête
  152. s4= String(potar1);
  153. s1+= s4; // valeur à transmettre
  154.  
  155.  
  156. http.begin(srvName_sw+s1);
  157. int httpResponseCode = http.GET();
  158. if (httpResponseCode>0) { recp_1 = http.getString();}
  159. http.end();
  160. }
  161.  
  162.  
  163.  
  164. void int_to_array(uint16_t valeur_i)
  165. {
  166. // prépare la chaine de caract à zéro terminal pour l'envoi
  167. String s1= (String) valeur_i;
  168. var_array[0]=s1[0];
  169. var_array[1]=s1[1];
  170. var_array[2]=s1[2];
  171. var_array[3]=s1[3];
  172. var_array[4]=0; // zéro terminal -> chaine C
  173. }
  174.  
  175.  
  176.  
  177. void setup()
  178. {
  179. Serial.begin(19200);
  180. delay(300);
  181. // Serial.println("");
  182. // Serial.println("Ici le SW");
  183.  
  184. WiFi.persistent(false);
  185. WiFi.begin(ssid, password);
  186.  
  187.  
  188. delay(100);
  189.  
  190.  
  191. pinMode(GPIO_bouton0, INPUT_PULLUP);
  192. pinMode(GPIO_bouton1, INPUT_PULLUP);
  193. pinMode(GPIO_bouton2, INPUT_PULLUP);
  194. pinMode(GPIO_bouton3, INPUT_PULLUP);
  195. pinMode(GPIO_bouton4, INPUT_PULLUP);
  196. pinMode(GPIO_bouton5, INPUT_PULLUP);
  197. pinMode(GPIO_bouton6, INPUT_PULLUP);
  198. pinMode(GPIO_bouton7, INPUT_PULLUP);
  199.  
  200. // pinMode(GPIO_bouton8, INPUT);
  201.  
  202. pinMode(GPIO_potar1, INPUT);
  203.  
  204.  
  205. //pinMode(led1, OUTPUT);
  206.  
  207. delay(100);
  208.  
  209.  
  210. bouton0_etat = digitalRead(bouton0);
  211. memo_bouton0_etat = bouton0_etat;
  212.  
  213. bouton1_etat = digitalRead(bouton1);
  214. memo_bouton1_etat = bouton1_etat;
  215.  
  216. bouton2_etat = digitalRead(bouton2);
  217. memo_bouton2_etat = bouton2_etat;
  218.  
  219. bouton3_etat = digitalRead(bouton3);
  220. memo_bouton3_etat = bouton3_etat;
  221.  
  222. bouton4_etat = digitalRead(bouton4);
  223. memo_bouton4_etat = bouton4_etat;
  224.  
  225. bouton5_etat = digitalRead(bouton5);
  226. memo_bouton5_etat = bouton5_etat;
  227.  
  228. bouton6_etat = digitalRead(bouton6);
  229. memo_bouton6_etat = bouton6_etat;
  230.  
  231. bouton7_etat = digitalRead(bouton7);
  232. memo_bouton7_etat = bouton7_etat;
  233.  
  234. }
  235.  
  236.  
  237. uint16_t L=0;
  238. uint16_t C=0;
  239. uint16_t x, y;
  240. uint16_t i1 = 0;
  241.  
  242. uint8_t p1;
  243.  
  244. int32_t number = 0;
  245.  
  246. String parametre;
  247. String s1;
  248. String s2;
  249.  
  250.  
  251.  
  252. void requete_WiFi()
  253. {
  254. // voir les "server.on()" dans la fonction "setup()" du code du PFD pour la source des données
  255. recp_1 = "";
  256. if(WiFi.status()== WL_CONNECTED )
  257. {
  258. httpGet1(); // envoie requete au serveur, avec position des switch en argument.
  259. // il s'agit donc ici d'un envoi de données de ce SW vers le PFD.
  260. // les arguments dans les requetes permettent en effet à un client d'envoyer des data au serveur.
  261. }
  262. }
  263.  
  264.  
  265.  
  266.  
  267. void toutes_les_10s()
  268. {
  269. }
  270.  
  271.  
  272.  
  273. void toutes_les_1s()
  274. {
  275. nb_secondes++;
  276.  
  277. if(nb_secondes>10)
  278. {
  279. nb_secondes=0;
  280. toutes_les_10s();
  281. }
  282.  
  283.  
  284. }
  285.  
  286.  
  287.  
  288. void toutes_les_100ms()
  289. {
  290. nb_100ms++;
  291.  
  292. if(nb_100ms>10)
  293. {
  294. nb_100ms=0;
  295. toutes_les_1s();
  296. }
  297.  
  298. }
  299.  
  300.  
  301.  
  302. void teste_boutons()
  303. {
  304. bouton0_etat = digitalRead(GPIO_bouton0);
  305. if (bouton0_etat != memo_bouton0_etat)
  306. {
  307. memo_bouton0_etat = bouton0_etat;
  308. if (bouton0_etat==1) { v_switches &= ~bouton0;} else { v_switches |= bouton0;}
  309. requete_WiFi();
  310. }
  311.  
  312. bouton1_etat = digitalRead(GPIO_bouton1);
  313. if (bouton1_etat != memo_bouton1_etat)
  314. {
  315. memo_bouton1_etat = bouton1_etat;
  316. if (bouton1_etat==1){ v_switches &= ~bouton1;} else { v_switches |= bouton1;}
  317. requete_WiFi();
  318. }
  319.  
  320. bouton2_etat = digitalRead(GPIO_bouton2);
  321. if (bouton2_etat != memo_bouton2_etat)
  322. {
  323. memo_bouton2_etat = bouton2_etat;
  324. if (bouton2_etat==1) { v_switches &= ~bouton2;} else { v_switches |= bouton2;}
  325. requete_WiFi();
  326. }
  327.  
  328. bouton3_etat = digitalRead(GPIO_bouton3);
  329. if (bouton3_etat != memo_bouton3_etat)
  330. {
  331. memo_bouton3_etat = bouton3_etat;
  332. if (bouton3_etat==1) { v_switches &= ~bouton3;} else { v_switches |= bouton3;}
  333. requete_WiFi();
  334. }
  335.  
  336. bouton4_etat = digitalRead(GPIO_bouton4);
  337. if (bouton4_etat != memo_bouton4_etat)
  338. {
  339. memo_bouton4_etat = bouton4_etat;
  340. if (bouton4_etat==1) { v_switches &= ~bouton4;} else { v_switches |= bouton4;}
  341. requete_WiFi();
  342. }
  343.  
  344. bouton5_etat = digitalRead(GPIO_bouton5);
  345. if (bouton5_etat != memo_bouton5_etat)
  346. {
  347. memo_bouton5_etat = bouton5_etat;
  348. if (bouton5_etat==1) { v_switches &= ~bouton5;} else { v_switches |= bouton5;}
  349. requete_WiFi();
  350. }
  351.  
  352. bouton6_etat = digitalRead(GPIO_bouton6);
  353. if (bouton6_etat != memo_bouton6_etat)
  354. {
  355. memo_bouton6_etat = bouton6_etat;
  356. if (bouton6_etat==1) { v_switches &= ~bouton6;} else { v_switches |= bouton6;}
  357. requete_WiFi();
  358. }
  359.  
  360. bouton7_etat = digitalRead(GPIO_bouton7);
  361. if (bouton7_etat != memo_bouton7_etat)
  362. {
  363. memo_bouton7_etat = bouton7_etat;
  364. if (bouton7_etat==1) { v_switches &= ~bouton7;} else { v_switches |= bouton7;}
  365. requete_WiFi();
  366. }
  367.  
  368. /*
  369. bouton8_etat = digitalRead(GPIO_bouton8);
  370. Serial.println(bouton8_etat);
  371. if (bouton8_etat != memo_bouton8_etat)
  372. {
  373. memo_bouton8_etat = bouton8_etat;
  374. if (bouton8_etat==1) { v_switches &= ~bouton8;} else { v_switches |= bouton8;}
  375. requete_WiFi();
  376. }
  377. */
  378.  
  379. potar1 = analogRead(GPIO_potar1)/16; // 0..255
  380. potar1 += 14; // en fonction de MON potentiomètre, qui du fait de son encoche sur l'axe, ne permet pas le règlage de son bouton
  381. if (abs(potar1 - memo_potar1) >1) // et pas '!=' pour éviter de faire des requetes intempestives lorsque le read hésite entre deux valeurs
  382. {
  383. memo_potar1 = potar1;
  384. //Serial.println(potar1);
  385. requete_WiFi();
  386.  
  387. }
  388.  
  389. }
  390.  
  391.  
  392.  
  393. uint16_t t=0; // temps -> rebouclera si dépassement
  394. void loop()
  395. {
  396.  
  397. teste_boutons();
  398.  
  399. temps_ecoule = micros() - memo_micros;
  400.  
  401. if (temps_ecoule >= 1E5) // (>=) et pas strictement égal (==) parce qu'alors on rate l'instant en question
  402. {
  403. memo_micros = micros();
  404. toutes_les_100ms();
  405. }
  406.  
  407.  
  408.  
  409. compteur1+=1;
  410. if (compteur1 >= 100) // tous les 1000 passages dans la boucle
  411. {
  412. compteur1=0;
  413. }
  414.  
  415.  
  416. delay(30);
  417. }
  418.  
  419.  
  420.  
  421.  
  422.  
  423.  
  424.  

28 Sérigraphie

5 septembre 2021 :
J'ai imprimé des labels sur le panel des boutons (SW) avec une graveuse laser (NEJE Master 2S). La surface à graver étant en PLA, blanc de surcroît, le résultat ne pouvait guère être très bon. (Il faut au préalable peindre la surface avec un marqueur noir, puis la nettoyer après la gravure, ce qui la laisse une peu sale... Quant au contraste obtenu, c'est le minimum syndical ! Le mot 'AUTOLAND' tout fin et tout net... a été rajouté sur la photo avec Gimp !)

Mais je ne suis pas inquiet, j'ai commandé des plaques fines en alu anodisé coloré qui sont faites pour être gravées au laser. Et je vais voir aussi du côté d'une découpeuse vinyle, il semblerait qu'on peut créer des logos et des textes très fins.

Quant au soft il continue à évoluer :
- ajout de nouvelles petites cartes géographiques (OSM) pour le ND.
- désormais le basculement (toggle) de l'autoland se fait par un bouton sur le module SW (voir photo), ce qui libère l’interrupteur à bascule en haut à gauche du PFD.

9 septembre 2021 :
  • L’interrupteur à bascule en haut à gauche du PFD sert maintenant à la commande des feux d’atterrissage.
  • Le dernier bouton du panel des boutons, celui, rouge, à gauche en bas, se voit attribué à la fonction "HDG SYNC" , c'est à dire qu'il règle la consigne de cap du pilote automatique sur le cap actuel. Ça évite de tourner des boutons, c'est précis, rapide et efficace.
17 septembre 2021 :
La découpeuse vinyle "Silhouette Portrait 3" m'a permis d'obtenir le résultat visible sur l'image de droite. J'ai choisi une police de caractères (sous Inkscape) un peu grosse afin d'éviter des problèmes de découpe. Du coup on les voit bien ! Ça laisse entrevoir la réalisation de tout une planche de bord réaliste... Aie, faudrait pas que je dise des choses comme ça, dans un élan d'enthousiasme, après je me sens obligé de le faire !

29 Un pas de plus vers une planche de bord

27 septembre 2021 :
J'ai finalement accolé les trois modules, le PFD, le ND et le SW, solidement fixés ensemble et rajouté des équerres à 45° (en vert, imprimées en 3D) permettant de poser le tout sur le bureau. Pour le coup le panel des boutons à droite se retrouve disposé verticalement, j'ai donc refait la sérigraphie à la découpeuse vinyle et modifié le code source en conséquence (les fonctions identiques ne sont plus placées au même endroit, mais je n'ai pas modifié le circuit imprimé, ouf !)

30 Nouvelle version, nouvelles fonctions...

22 octobre 2021 :
version="17.0"
1) J'ai tourné l'ensemble à 180°, ce qui permet d'actionner les boutons du module SW de la main gauche, sans cacher les affichages, tout en pilotant de la main droite avec le joystick. L'opération a nécessité de tourner les affichages dans le code source des modules, ce qui se fait très simplement (dans le setup, voir les lignes :
TFT480.init();
TFT480.setRotation(3);

Au passage il a fallu refaire la sérigraphie vinyle du module SW.

2) Ajout d'un bouton poussoir sur le dessus du SW (permute entre vue interne / externe)

3) Ajout d'un potentiomètre sur le module SW -> commande de lacet (gouverne de direction de l'empennage arrière), du coup suppression du pédalier et du second joystick qui le prenait en charge. La raison ? Mon petit lapin Skippy vit en totale liberté dans la maison, et je craignais qu'il aille un jour se faufiler sous les pédales pendant le vol. D'une manière générale il n'y a rien de dangereux pour lui à moins de 80cm du sol. Il a 9 ans et n'a jamais eu de problème, ni causé de dégâts.

4) Ajout d'un second switch (inverseur unipolaire miniature à levier), le premier à droite allume les feux atterrissage, le second au centre commute l'affichage de l'indicateur d'altitude du PFD entre les modes ASL (Above Sea Level) et AAL (Above Aerodrome Level), la référence étant désormais celle du GPS, donc plus de soucis avec les QFE et QNH.

5) Modifications dans le fichier FG_data.h (ajout de l'altitude des pistes).

6) Modifications dans le fichier hardware4.xml.

D'autre part j'ai ajouté les lignes suivantes :

dans le fichier ~/FlightGear/Aircraft/CitationX/citationX-set.xml

ce qui permet d'engager (et stopper) l'autopilot de l'avion (celui de Flightgear) avec la touche 'q' du clavier de l'ordi, sachant qu'il est nécessaire d'engager cet autopilot pour pouvoir piloter l'avion depuis notre panel, avec les encodeurs rotatifs HDG et ALTI et aussi avec notre fonction d'atterrissage automatique guidée pas l'ILS de l'aérodrome (localizer et glide) par appui sur le bouton poussoir "ILS" de notre module SD lorsque l'avion se trouve dans le faisceau de l'ILS (à peu près dans l'axe de la piste entre 20 et 10 NM et à 3000ft de hauteur).

31 Ecrans de la version 17.3

Quoi de neuf sur le PFD ?
-Le voyant L indique en rouge que l'avion est en dehors du faisceau du 'localizer'
-le voyant G en vert indique que l'avion se trouve sur la bonne pente de descente à 5% (3°) matérialisée par la nappe du 'glide' du système ILS.
-AAL en vert en haut indique que l'altitude affichée est une hauteur par rapport à la piste (AAL = Above Aerodrome Level).
-les petits chiffres en haut à droite répertorient la position de différent switches en temps réel (permet de s'assurer que les liaisons WiFi entre les module sont OK).
-'AUTOLAND' en vert au milieu indique que notre fonction d'atterrissage auto est enclenchée (suite à appui sur le bouton ILS du module SW). -l'indication '17 ft' en bas, c'est l'altitude de la piste sélectionnée (ici LFMT)
-

Quoi de neuf sur le ND ?
Ici l'avion en bleu sur la carte c'est nous, qui entrons dans le faisceau ILS (un peu tard, nous aurions du le faire plutôt (et plus tôt!) à la distance de 20 NM et pas 6.3NM, mais le but était d'obtenir cette capture d'écran pas de se poser).
-Ici aussi l'altitude de la piste est maintenant indiquée.
-'LOC GS' en jaune indique le type d'équipement ILS dont est doté l'aérodrome sélectionné. Certains n'ont pas de 'glide' ou pas de 'localizer' voir aucun des deux, juste une balise NDB et un système visuel "PAPI"
L'indication de ces données est d'ailleurs reprise en clair dans le cadre en bas à gauche. -Quant au compas principal, il est orienté par rapport à l'avion, le rectangle bleu c'est l'orientation de la piste (129 - 309 °), la flèche grise c'est la direction dans laquelle on voit le VOR (trajet direct, par une radiale), la ligne verte terminée par une encoche c'est la consigne pour le HDG (la direction dans laquelle l'avion va automatiquement se placer, raison pour laquelle le PFD indique que nous nous inclinons à droite de 5°, nous tournons donc (automatiquement) vers la droite pour rejoindre cette direction).

32 Version 18.0

Dans cette nouvelle version la sélection de l'aérodrome se fait dans une liste à côté de laquelle s'affiche la petite carte et en plus une photo de la piste. Le tout sur le module ND dont les autres fonctions demeurent inchangées dès le choix effectué. A cette occasion j'ai remplacé les deux encodeurs pas-à-pas rotatifs par quatre boutons poussoirs parce qu'il s'avère que les encodeurs en question fonctionnent très bien lorsqu'ils sont neuf (neuf dans le sens peu servi, pas ...9 !). Du coup le code source a changé ainsi bien entendu que le schéma.

PHOTO COPYRIGHT:
La photo est une minuscule copie d'écran de l'original dont la taille est 800x600px
cet original (magnifique) se trouve sur la page suivante:
https://vfr-pilote.fr/aerodrome/lfkb/
Un copyright figure au bas de la page:
Copyright © 2021 VFR-pilote

33 Schéma de la version 18.0 du module ND

.

34 Version 19.0

15 décembre 2021 :
Dans cette nouvelle version le positionnement du petit avion sur la carte du ND ne se fait plus en fonction du radial et de la distance des balises de l'ILS, elle est maintenant directement reçue (en USB) depuis FlightGear, par l'intermédiaire du module PFD (en WiFi). L'avantage c'est que cela fonctionne même en l'absence d'équipement ILS de l'aéroport (comme c'est par exemple le cas, jusqu'à plus ample information, pour San SEBASTIAN).

Mais cela m'a obligé :
  • de faire sortir les données de Longitude et de Latitude sur la liaison USB par FlightGear, (par lecture dans l'arbre des propriétés internes), donc -> modif du fichier 'hardware4.xml'
  • De recevoir ces données par le PFD et de les exposer sur le serveur WiFi
  • De consulter ensuite ces données sur le module ND
  • puis de convertir cette position Log/Lat (WGS84) en coordonnées géographiques planes (par projection Lambert II étendu) utilisables sur la carte . Cette projection n'est toutefois exploitable que pour la couverture de la France métropolitaine... Il restera à trouver une solution pour l'ensemble de la planète.

35 Version 21.0

12 janvier 2022 :
Meilleurs vœux à vous toutes et tous, et bon vols !

Nous en sommes à la version 21.0.1 qui utilise les paramètres GPS (fournis par Flightgear) pour guider l'avion en azimut et hauteur lors de l'approche aux instruments de sorte qu'on peut maintenant se poser automatiquement sur tous les aérodromes dès lors qu'ils figurent dans le fichier FG_data.h (j'en ajoute régulièrement). Le PFD doit avoir accès, outre la position géographique de l'avion et son altitude fournis en temps réel par Flightgear pendant le vol (par la liaison USB), aux caractéristiques (Latitude, Longitude, orientation et altitude) de la piste visée.

Je donne la priorité aux aérodromes entourés de jolis paysages, (Alpes, Pyrénées, Corse...) et/ou les infrastructures sont présentes dans FG.
J'entends par "jolis paysages" ceux qui sont effectivement dessinés sur mesure dans Flightgear et donc "jolis" vus d'avion. Je sais bien, pour avoir parcouru (physiquement) le pays dans tous les sens, que tous les paysages sont jolis en France.

36 version 24.0

24 avril 2022 :
Plusieurs versions ont vu le jour depuis le début de l'année, apportant des modifications mineures.
Cette fois je me suis attaqué au calcul des distances. Je vous avais dit que j'avais laissé tomber la détermination de la position de l'avion à partir des balises VOR et des signaux ILS au profit des coordonnées GPS fournis par FlightGear. Mais il y avait un problème de justesse dans le calcul des angles (par exemple pour déterminer la direction dans laquelle se trouve une piste) : J'avais basé ce calcul par une triangulation à plat sur des points de la carte à partir des coordonnées LAMBERT (x,y) elles même calculées à partir des coordonnées GPS (système WGS84). Or il s'avère qu'il est beaucoup plus simple de calculer ces angles directement à partir des coordonnées sphériques WGS84 en appliquant la "formule des cosinus" de la trigonométrie sphérique. Et le résultat est beaucoup plus précis et non limité à une zone LAMBERT.

En pratique ça change quoi ? ben que l'avion ne se pose plus à côté de la piste, mais bien dessus !
Et ne vous inquiétez pas: si la Russie (voire l'OTAN) s'attaquent aux satellites GPS, le GPS de FlightGear, lui, continuera à fonctionner au fond de notre tunnel.

Voilà, bon vols à toutes et à tous.

Ecran de la version 24.1

Les fréquences radio et les données relatives aux signaux de guidage Nav1 et Nav2 ont été remplacés par l'affichage de la situation de la piste (RWY = Runway, distance et direction de la piste vue de l'avion, ces informations provenant d'un calcul en temps réel à partir des coordonnées GPS de la piste et celles de l'avion).

37 Version 25.0 - Plan des installations

Cette nouvelle version affiche sur le Navigation Display (afficheur de droite), lorsqu'on zoom à fond sur la carte, le plan des installations, comme sur l'image ci-contre (avec en plus notre avion en surimpression) et surtout comme dans les vrais avions ! Il y a eu dans l'histoire de nombreuses "incursions sur piste" donnant lieu à des accidents (Tenerife en 1977 pour citer un cas extrême) suite à la traversée d'une piste en service. Avec cet affichage, on sait au moins où on se trouve.

Cette fonction n'est pour l'instant disponible que pour LFKJ et LFMT, le temps pour moi de saisir les petites images correspondantes et de les calibrer. Voir dans le sous-dossier "bmp/LFKJ/480x320" destiné à figurer sur la SDcard du ND. Vous y trouverez les fichiers plan1.bmp et plan1.dat. Ce dernier est un fichier texte qui est lu par l'ESP32. Il ne comprend que trois lignes :
x0=250.0
y0=165.5
ech=206.8


Une feuille de calcul(1) (LibreOffice calc) permet de calculer facilement les valeurs pour plan1.dat en fonction des coordonnées des extrémités de la piste qui sont à lire (avec Gimp) sur le plan1.bmp de l'aérodrome. Le plan en question est obtenu par copie d'écran (avec Shutter en mode bmp)
la taille de l'image bmp doit être 480x320px de façon à correspondre à l'afficheur TFT
x0et y0 (centre de la piste en px) servent de référence de position pour l'affichage de l'avion sur le plan
ech = echelle -> en px/NM

(1) voir plus bas dans la section "Documents et code source complet" -> lien "code_source.zip" (vers fichier "documents.zip" puis -> /ND/bmp/calculs/calcul coordonnées piste.ods


38 version 28.0

06 juin 2022 :
Beaucoup de changements dans cette version, côté programmation : Le fichier FG_data.h qui regroupe les caractéristiques des airports ne spécifie plus les coordonnées du centre de la piste et son orientation, mais celles des extrémités de la piste, ce qui permet de retrouver le centre et l'orientation par calcul par l'ESP32, mais surtout qui facilite le guidage en lacet lors du roulage au sol (au décollage et à l’atterrissage). Précédemment je rencontrais des problèmes lors du franchissement de centre de la piste (ce qui inversait les orientations). Dorénavant l'avion met le cap directement sur l'extrémité opposée de la piste. Je ne vous raconte pas combien la mise au point de ces asservissements et délicate et chronophage parce qu'il faut exécuter de nombreux décollages et atterrissages pour tester le moindre changement des paramètres. En effet le comportement de l'avion, en particulier la sensibilité lorsqu'on agit sur la gouverne de direction ET la roue avant dépend fortement de la vitesse mais aussi de l'attitude de l'avion (attitude, pas altitude !) suivant que la roue de nez touche ou non la piste. Les embardées sont bien entendu inadmissibles, pas plus que les oscillations autour de l'axe de la piste. Ce comportement est commun à tout asservissement mais avec d'avantage de paramètres ici. Bref c'est super intéressant à peaufiner.

Bien sûr cette modification du principe de base a eu comme conséquence de multiples modifs dans tous les autres programmes, PFD, ND, SW...

39 Un nouveau module ! le MCDU

Multipurpose Control and Display Unit

Ce MCDU comprend un afficheur et cinq boutons poussoirs. Il permet en quelques clics :
  • de décoller automatiquement gère les gaz, la consigne d'altitude, les flaps, le tangage, puis engage l'autopilot de FG
  • gestion de la route (goto vers une piste ou un point d'approche) puis engage l'autoland
  • approche (gère altitude, vitesse, pente et taux de descente, cap, trajectoire, )
  • pose l'avion sur la piste sélectionnée sur le ND (gère les AF et l'arrondi)
  • freine
Ah oui.. et nous on fait quoi ? Ben on boit un café !

40 Détail du MCDU

Dans la partie gauche : le listing des action effectuées
Au centre : des étiquettes qui s'allument en fonction de l'état du vol et des options choisies.
Sur la droite l'afficheur 'allume' des 'leds' lors d'appui sur le bouton correspondant. Les traits inclinés indiquent la fonction de chaque bouton.

Concernant la route :
  • RWY (runway) permet de naviguer vers l'aérodrome choisi (le centre de la piste)
  • les points ptAA et ptBB sont des balises(virtuelles) d'approche et de passage en finale, situées à 10NM de la piste, de part et d'autre. Un vignette rouge indique un sens d'approche interdit (à cause du relief ou de la proximité d'une ville ou zone interdite de survol).
  • La fonction roulage permet de déplacer l'avion au sol sur les taxiway (et sur la piste) à une vitesse automatique = 10 kts

41 Code source du MCDU

CODE SOURCE en C++
  1.  
  2. //
  3. // ==================================
  4. String version="35.3";
  5. // ==================================
  6.  
  7. #include <stdint.h>
  8.  
  9. #include <TFT_eSPI.h> // Hardware-specific library
  10. #include "Free_Fonts.h"
  11. #include "SPI.h"
  12.  
  13. #include "MCDU.h"
  14. #include "FG_data.h"
  15. #include "Fonctions1.h"
  16.  
  17. #include <WiFi.h>
  18. #include <HTTPClient.h> // Remarque: les fonctions WiFi consomment 50% de la mémoire flash (de programme)
  19.  
  20. const char* ssid = "PFD_srv"; // nous nous connectons en tant que client au même serveur (le PFD) que le ND.
  21. const char* password = "72r4TsJ28"; // doit être identique à celui du serveur du PFD
  22.  
  23. const char* srvName_flags = "http://192.168.4.1/flags";
  24. const char* srvName_num_bali = "http://192.168.4.1/num_bali";
  25. const char* srvName_msg = "http://192.168.4.1/msg";
  26.  
  27. String recp_flags; // qui sont émis par le serveur (PFD) en réponse à la requette
  28.  
  29. String recp_msg;
  30. String memo_msg;
  31.  
  32. String recp_num_bali;
  33. String memo_num_bali;
  34.  
  35.  
  36. Etiquette Etiq_1;
  37. Etiquette Etiq_2;
  38. Etiquette Etiq_3;
  39. Etiquette Etiq_4;
  40. Etiquette Etiq_5;
  41. Etiquette Etiq_6;
  42. Etiquette Etiq_7;
  43. Etiquette Etiq_7b;
  44. Etiquette Etiq_8;
  45. Etiquette Etiq_9;
  46. Etiquette Etiq_10;
  47. Etiquette Etiq_10b;
  48. Etiquette Etiq_10c;
  49. Etiquette Etiq_11;
  50. Etiquette Etiq_12;
  51. Etiquette Etiq_13;
  52. Etiquette Etiq_14;
  53. Etiquette Etiq_15;
  54. Etiquette Etiq_16;
  55.  
  56. Led Led1;
  57. Led Led2;
  58. Led Led3;
  59. Led Led4;
  60. Led Led5;
  61.  
  62.  
  63.  
  64. //-------------------------------------------------------------------------------------------
  65. // pour la 'carte universelle'
  66. // voir schéma de la platine ESP32
  67. // numérotation dans l'ordre de la disposition des pins du connecteur des boutons, en partant de GND
  68.  
  69.  
  70. uint16_t bouton1=1; // (2^0) -> positionnera 1 bit dans le mot
  71. const int GPIO_bouton1 = 33;
  72. bool bouton1_etat;
  73. bool memo_bouton1_etat;
  74.  
  75. uint16_t bouton2=2; // (2^1)
  76. const int GPIO_bouton2 = 32;
  77. bool bouton2_etat;
  78. bool memo_bouton2_etat;
  79.  
  80. uint16_t bouton3=4; // (2^2)
  81. const int GPIO_bouton3 = 34;
  82. bool bouton3_etat;
  83. bool memo_bouton3_etat;
  84.  
  85. uint16_t bouton4=8; // (2^3)
  86. const int GPIO_bouton4 = 36;
  87. bool bouton4_etat;
  88. bool memo_bouton4_etat;
  89.  
  90. uint16_t bouton5=16; // 16 (2^4)
  91. const int GPIO_bouton5 = 39;
  92. bool bouton5_etat;
  93. bool memo_bouton5_etat;
  94.  
  95. uint16_t bouton6=32; // 32 (2^5)
  96. const int GPIO_bouton6 = 35;
  97. bool bouton6_etat;
  98. bool memo_bouton6_etat;
  99. //-------------------------------------------------------------------------------------------
  100.  
  101. uint16_t boutons =0; // mot dont chaque bit correspond à un bouton connecté à ce module MCDU
  102.  
  103. uint8_t SDcardOk=0;
  104.  
  105. int16_t num_bali=0;
  106. uint16_t flags_i;
  107. uint16_t memo_flags_i;
  108.  
  109.  
  110. uint32_t memo_micros=0;
  111. uint32_t temps_ecoule;
  112. uint8_t num_ligne_msg=0;
  113.  
  114. uint16_t couleur_ligne;
  115.  
  116. uint32_t bmp_offset=0;
  117.  
  118. uint16_t data_rect1[300*50]; // pixels d'un rectangle
  119.  
  120.  
  121. void init_afficheur()
  122. {
  123. TFT480.init();
  124. TFT480.setRotation(1); // 0..3 à voir, suivant disposition de l'afficheur
  125.  
  126. TFT480.fillScreen(NOIR);
  127. TFT480.setTextColor(NOIR, BLANC);
  128. }
  129.  
  130.  
  131. void init_WiFi()
  132. {
  133. WiFi.persistent(false);
  134. WiFi.begin(ssid, password);
  135. delay(2000);
  136. }
  137.  
  138.  
  139. void httpGetParams() // paramètres divers depuis le PFD & envoi état des états des boutons en argument de la requette
  140. {
  141. String s1;
  142. String s2;
  143.  
  144. HTTPClient http;
  145.  
  146. s1= "?btMCDU="; // sw1 (nom arbitraire..) pour désigner l'argument n°1 envoyé dans l'URL de la requête
  147. s2= String(boutons);
  148. s1+= s2;
  149.  
  150. http.begin(srvName_flags+s1);
  151. int httpResponseCode = http.GET();
  152. if (httpResponseCode>0) { recp_flags = http.getString();}
  153. http.end();
  154. }
  155.  
  156.  
  157.  
  158. void httpGet_msg()
  159. {
  160. // envoie requete au serveur
  161.  
  162. HTTPClient http;
  163.  
  164. http.begin(srvName_msg);
  165. int httpResponseCode = http.GET();
  166. if (httpResponseCode>0) { recp_msg = http.getString();}
  167. http.end();
  168. }
  169.  
  170.  
  171.  
  172. void httpGet_num_bali()
  173. {
  174. // envoie requete au serveur
  175.  
  176. HTTPClient http;
  177.  
  178. http.begin(srvName_num_bali);
  179. int httpResponseCode = http.GET();
  180. if (httpResponseCode>0) { recp_num_bali = http.getString();}
  181. http.end();
  182. }
  183.  
  184.  
  185.  
  186. void interroge_WiFi()
  187. {
  188. // voir les "server.on()" dans la fonction "setup()" du code du PFD pour la source des données
  189.  
  190. if(WiFi.status()== WL_CONNECTED )
  191. {
  192. httpGetParams();
  193. flags_i = recp_flags.toInt();
  194. }
  195.  
  196. }
  197.  
  198.  
  199. void requete_WiFi_2()
  200. {
  201. // voir les "server.on()" dans la fonction "setup()" du code du PFD pour la source des données
  202. // plus précisément la ligne -> server.on("/msg", ....
  203. recp_msg = "";
  204. if(WiFi.status()== WL_CONNECTED )
  205. {
  206. httpGet_msg(); // envoie requete au serveur, avec position des switch en argument.
  207. }
  208. }
  209.  
  210.  
  211. void requete_WiFi_3()
  212. {
  213. // voir les "server.on()" dans la fonction "setup()" du code du PFD pour la source des données
  214. recp_num_bali= "";
  215. if(WiFi.status()== WL_CONNECTED )
  216. {
  217. httpGet_num_bali(); // envoie requete au serveur, avec position des switch en argument.
  218. }
  219. }
  220.  
  221.  
  222.  
  223. void affi_flags() // nombre jaune en haut
  224. {
  225. TFT480.setTextFont(1);
  226. TFT480.setTextColor(JAUNE);
  227.  
  228. affi_int_en_binaire(flags_i, 400, 2);
  229. }
  230.  
  231.  
  232. void affi_etat_boutons() // nombre vert en haut à gauche
  233. {
  234. uint16_t x0=10;
  235. uint16_t y0=23;
  236.  
  237. interroge_WiFi(); // et envoie les arguments le cas échéant
  238.  
  239. TFT480.fillRect(x0, y0, 30, 12, GRIS_TRES_FONCE); // efface le nombre précédemment affiché
  240. //TFT480.setTextFont(1);
  241. TFT480.setFreeFont(FF1);
  242. TFT480.setTextColor(VERT);
  243. String s1 = String(boutons);
  244. TFT480.drawString(s1, x0, y0);
  245. }
  246.  
  247.  
  248.  
  249.  
  250. void init_etiquettes() // pour l'affichage des données, voir la fonction "affi_data_piste()"
  251. {
  252. uint16_t x0 = 120;
  253. uint16_t y0 = 5;
  254. uint16_t xi;
  255. uint16_t yi;
  256.  
  257. xi=x0;
  258. yi=y0;
  259.  
  260.  
  261. TFT480.drawRoundRect(xi-10, yi+10, 300, 65, 3, GRIS); // panel décollage
  262.  
  263. TFT480.setFreeFont(FF5);
  264.  
  265. Etiq_1.init(xi,yi,110,24);
  266. Etiq_1.st="DECOLLAGE";
  267. Etiq_1.set_couleurs(GRIS, GRIS_TRES_FONCE);
  268.  
  269. yi+=30;
  270.  
  271.  
  272. TFT480.setFreeFont(FF1);
  273.  
  274. TFT480.setTextColor(BLANC, NOIR);
  275. TFT480.drawString("Rudder:", xi+5, yi+5);
  276.  
  277. TFT480.setFreeFont(FF5);
  278.  
  279. Etiq_2.init(xi+100,yi,80,24);
  280. Etiq_2.st="Manuel";
  281. Etiq_2.set_couleurs(GRIS, GRIS_TRES_FONCE);
  282.  
  283. Etiq_3.init(xi+200,yi,80,24);
  284. Etiq_3.st="Auto";
  285. Etiq_3.set_couleurs(GRIS, GRIS_TRES_FONCE);
  286.  
  287. yi+=50;
  288.  
  289. ////Etiq_4.init(xi,yi,180,24);
  290. ////Etiq_4.st="EN VOL";
  291. ////Etiq_4.set_couleurs(GRIS, GRIS_TRES_FONCE);
  292. //yi+=30;
  293.  
  294. Etiq_4.init(xi+110,yi,180,24);
  295. Etiq_4.st="AutoPilot de FG";
  296. Etiq_4.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  297.  
  298. yi+=40;
  299.  
  300. TFT480.setFreeFont(FF1);
  301.  
  302. TFT480.setTextColor(BLANC, NOIR);
  303. TFT480.drawString("AutoPilot ESP:", xi, yi+5);
  304.  
  305. TFT480.setFreeFont(FF5);
  306.  
  307. Etiq_5.init(xi+160,yi,55,24);
  308. Etiq_5.st="Manu";
  309. Etiq_5.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  310.  
  311. Etiq_6.init(xi+225,yi,65,24);
  312. Etiq_6.st="Route";
  313. Etiq_6.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  314.  
  315.  
  316. yi+=30;
  317.  
  318. TFT480.drawRoundRect(xi-10, yi+10, 305, 55, 3, GRIS);
  319.  
  320. Etiq_7.init(xi,yi,75,24);
  321. Etiq_7.st="ROUTE";
  322. Etiq_7.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  323.  
  324. TFT480.drawRoundRect(xi-10, yi+10, 305, 55, 3, GRIS);
  325.  
  326. TFT480.setFreeFont(FF1);
  327.  
  328. Etiq_7b.init(xi+ 90,yi,200,24);
  329. Etiq_7b.st="----"; // OACI + Nom
  330. Etiq_7b.set_couleurs(BLEU_CLAIR, NOIR);
  331.  
  332.  
  333. yi+=30;
  334.  
  335. TFT480.setFreeFont(FF1);
  336. TFT480.setTextColor(BLANC, NOIR);
  337. TFT480.drawString("to", xi, yi+5);
  338.  
  339. TFT480.setFreeFont(FF5);
  340.  
  341. TFT480.setFreeFont(FF5);
  342.  
  343. Etiq_8.init(xi+28,yi,46,24);
  344. Etiq_8.st="RWY";
  345. Etiq_8.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  346.  
  347. Etiq_9.init(xi+81,yi,46,24);
  348. Etiq_9.st="ptA";
  349. Etiq_9.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  350.  
  351. Etiq_10.init(xi+133,yi,46,24);
  352. Etiq_10.st="ptB";
  353. Etiq_10.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  354.  
  355. Etiq_10b.init(xi+184,yi,50,24);
  356. Etiq_10b.st="crc1"; // Tours de piste (circling) dans un sens
  357. Etiq_10b.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  358.  
  359. Etiq_10c.init(xi+240,yi,50,24);
  360. Etiq_10c.st="crc2"; // // Tours de piste dans l'autre sens
  361. Etiq_10c.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  362.  
  363.  
  364. yi+=55;
  365.  
  366. //TFT480.drawRoundRect(xi-10, yi+10, 300, 55, 3, GRIS);
  367.  
  368. Etiq_11.init(xi,yi,100,24);
  369. Etiq_11.st=" FINALE";
  370. Etiq_11.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  371.  
  372.  
  373.  
  374. Etiq_12.init(xi+105,yi,100,24);
  375. Etiq_12.st=" LANDING";
  376. Etiq_12.set_couleurs(GRIS, GRIS_TRES_FONCE);
  377.  
  378. Etiq_14.init(xi+210,yi-13,80,24);
  379. Etiq_14.st=" LONG";
  380. Etiq_14.set_couleurs(GRIS, GRIS_TRES_FONCE);
  381.  
  382. Etiq_15.init(xi+210,yi+13,80,24);
  383. Etiq_15.st=" SHORT";
  384. Etiq_15.set_couleurs(GRIS, GRIS_TRES_FONCE);
  385.  
  386.  
  387. yi+=50;
  388.  
  389. Etiq_16.init(xi+60,yi,100,24);
  390. Etiq_16.st=" AU SOL";
  391. Etiq_16.set_couleurs(GRIS, GRIS_TRES_FONCE);
  392.  
  393. Etiq_13.init(xi+190,yi,100,24);
  394. Etiq_13.st=" ROULAGE";
  395. Etiq_13.set_couleurs(GRIS, GRIS_TRES_FONCE);
  396.  
  397. }
  398.  
  399.  
  400.  
  401. void init_SDcard()
  402. {
  403. String s1;
  404.  
  405. uint16_t y=0;
  406. TFT480.setTextColor(VERT, NOIR);
  407. TFT480.drawString("Init SDcard", 0, y);
  408. y+=20;
  409.  
  410. if(!SD.begin())
  411. {
  412. TFT480.drawString("Card Mount Failed", 0, y);
  413. delay (2000);
  414. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  415. return;
  416. }
  417.  
  418.  
  419. uint8_t cardType = SD.cardType();
  420.  
  421. if(cardType == CARD_NONE)
  422. {
  423. TFT480.drawString("No SDcard", 0, y);
  424. delay (2000);
  425. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  426. return;
  427. }
  428.  
  429. SDcardOk=1;
  430.  
  431. TFT480.drawString("SDcard Type: ", 0, y);
  432. if(cardType == CARD_SD) {TFT480.drawString("SDSC", 150, y);}
  433. else if(cardType == CARD_SDHC) {TFT480.drawString("SDHC", 150, y);}
  434.  
  435. y+=20;
  436.  
  437. uint32_t cardSize = SD.cardSize() / (1024 * 1024);
  438. s1=(String)cardSize + " GB";
  439. TFT480.drawString("SDcard size: ", 0, y);
  440. TFT480.drawString(s1, 150, y);
  441.  
  442.  
  443. //Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  444. //Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
  445. }
  446.  
  447.  
  448. /** ***********************************************************************************
  449. IMAGE.bmp
  450. ***************************************************************************************/
  451.  
  452.  
  453. /**
  454. Rappel et décryptage de la fonction Color_To_565 : (elle se trouve dans le fichier LCDWIKI_KBV.cpp)
  455.  
  456. //Pass 8-bit (each) R,G,B, get back 16-bit packed color
  457.  
  458. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  459. {
  460. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  461. }
  462.  
  463. 0xF8 = 11111000
  464. 0xFC = 11111100
  465.  
  466. (r & 0xF8) -> 5 bit de gauche de r (on ignore donc les 3 bits de poids faible)
  467. (g & 0xFC) -> 6 bit de gauche de g (on ignore donc les 2 bits de poids faible)
  468. (b & 0xF8) -> 5 bit de gauche de b (on ignore donc les 3 bits de poids faible)
  469.  
  470. rrrrr---
  471. gggggg--
  472. bbbbb---
  473.  
  474. après les décalages on obtient les 16 bits suivants:
  475.  
  476. rrrrr---========
  477.   gggggg--===
  478.   ===bbbbb
  479.  
  480. soit après le ou :
  481.  
  482. rrrrrggggggbbbbb
  483.  
  484. calcul de la Fonction inverse :
  485. RGB565_to_888
  486. **/
  487.  
  488. uint16_t Color_To_565(uint8_t r, uint8_t g, uint8_t b)
  489. {
  490. return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
  491. }
  492.  
  493.  
  494. void RGB565_to_888(uint16_t color565, uint8_t *R, uint8_t *G, uint8_t *B)
  495. {
  496. *R=(color565 & 0xFFFFF800) >> 8;
  497. *G=(color565 & 0x7E0) >> 3;
  498. *B=(color565 & 0x1F) << 3 ;
  499. }
  500.  
  501.  
  502.  
  503.  
  504. uint16_t bmp_width;
  505. uint16_t bmp_heigh;
  506.  
  507.  
  508. uint16_t read_16(File fp)
  509. {
  510. uint8_t low;
  511. uint16_t high;
  512. low = fp.read();
  513. high = fp.read();
  514. return (high<<8)|low;
  515. }
  516.  
  517.  
  518.  
  519. uint32_t read_32(File fp)
  520. {
  521. uint16_t low;
  522. uint32_t high;
  523. low = read_16(fp);
  524. high = read_16(fp);
  525. return (high<<8)|low;
  526. }
  527.  
  528.  
  529.  
  530. void write_16(uint16_t v16, File fp)
  531. {
  532. uint8_t low, high;
  533.  
  534. low = v16 & 0xFF;
  535. high= v16 >>8;
  536.  
  537. fp.write(low);
  538. fp.write(high);
  539. }
  540.  
  541.  
  542. void write_32(uint32_t v32, File fp)
  543. {
  544. uint16_t low, high;
  545.  
  546. low = v32 & 0xFFFF;
  547. high= v32 >>16;
  548.  
  549. write_16(low, fp);
  550. write_16(high, fp);
  551. }
  552.  
  553.  
  554.  
  555. bool teste_bmp_header(File fp)
  556. {
  557. if(read_16(fp) != 0x4D42) { return false; } // (2 bytes) The header field used to identify the BMP
  558. read_32(fp); // (4 bytes) get bmp size (nombre total d'octets)
  559. read_32(fp); // (4 bytes) get creator information
  560. bmp_offset = read_32(fp); // (4 bytes) get offset information
  561. read_32(fp);//get DIB information
  562.  
  563. // ici on a lu 16 octets
  564. bmp_width = read_32(fp); //(4 bytes) get width and heigh information
  565. bmp_heigh = read_32(fp); //(4 bytes)
  566.  
  567. // ici on a lu 24 octets
  568. //if(read_16(fp)!= 1) {return false;}
  569. read_16(fp);
  570. //if(read_32(fp)!= 0) {return false;}
  571. return true;
  572. }
  573.  
  574.  
  575.  
  576. uint8_t LumCtr(uint8_t vi, float lum, float ctr)
  577. {
  578. float v2;
  579. uint8_t result;
  580. v2 = ((float)vi - lum) * ctr;
  581. if (v2<0) {v2=0;}
  582. if (v2>255) {v2=255;}
  583. result = (uint8_t) v2;
  584. return result;
  585. }
  586.  
  587.  
  588.  
  589. void draw_bmp(uint16_t x0, uint16_t y0, uint8_t rot, float lum, float contraste, File* fp)
  590. {
  591.  
  592. //sram = freeRam(); Serial.print("03-freeRam="); Serial.println(sram);
  593. uint16_t i,j,k,p,m=0;
  594. uint16_t y1;
  595. uint8_t bmp_data[2*3]={0};
  596. uint16_t bmp_color[2];
  597.  
  598. fp->seek(bmp_offset);
  599. for(i=0; i<bmp_heigh; i++)
  600. {
  601. for(j=0; j<(bmp_width/2); j++)
  602. {
  603. m=0;
  604. fp->read(bmp_data,2*3);
  605. for(k=0; k<2; k++)
  606. {
  607. bmp_color[k]= Color_To_565(LumCtr(bmp_data[m+2],lum, contraste), LumCtr(bmp_data[m+1],lum, contraste), LumCtr(bmp_data[m+0],lum, contraste));
  608. m+=3;
  609. }
  610. for(p=0; p<2; p++)
  611. {
  612. if (rot==0)
  613. {
  614. y1=y0;
  615. TFT480.drawPixel(x0+i, y0+j*2+p, bmp_color[p]);
  616. }
  617. if (rot==1)
  618. {
  619. y1=160-y0;
  620. TFT480.drawPixel(x0+j*2+p,320-(y1+i), bmp_color[p]);
  621. }
  622. }
  623. }
  624. }
  625. }
  626.  
  627.  
  628.  
  629.  
  630. void affi_img(uint16_t x0, uint16_t y0, float lum, float contraste, const char* filename1)
  631. {
  632.  
  633. File bmp_file;
  634. TFT480.setFreeFont(FF1);
  635. TFT480.setTextColor(ORANGE, NOIR);
  636. bmp_file = SD.open(filename1);
  637. if(!bmp_file)
  638. {
  639.  
  640. bmp_file.close();
  641. return;
  642. }
  643. if(!teste_bmp_header(bmp_file))
  644. {
  645. bmp_file.close();
  646. return;
  647. }
  648.  
  649. draw_bmp(x0, y0, 1, lum, contraste, &bmp_file);
  650.  
  651. bmp_file.close();
  652. // delay(1000);
  653.  
  654. }
  655.  
  656.  
  657.  
  658.  
  659.  
  660. /** -----------------------------------------------------------------------------------
  661. CAPTURE D'ECRAN vers SDcard
  662. /** ----------------------------------------------------------------------------------- */
  663.  
  664. void write_TFT480_on_SDcard() // enregistre le fichier .bmp
  665. {
  666. if (SDcardOk==1)
  667. {
  668. ////TFT480.drawString("capture bmp !", 100, 100);
  669. ////delay(1000);
  670.  
  671. String s1;
  672. uint16_t ys=200;
  673. TFT480.setFreeFont(FF1);
  674. TFT480.setTextColor(JAUNE, NOIR);
  675.  
  676. uint16_t x, y;
  677. uint16_t color565;
  678. uint16_t bmp_color;
  679. uint8_t R, G, B;
  680.  
  681. if( ! SD.exists("/bmp/capture2.bmp"))
  682. {
  683. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  684. TFT480.setTextColor(ROUGE, NOIR);
  685. TFT480.drawString("NO /bmp/capture2.bmp !", 100, ys);
  686. delay(300);
  687. TFT480.fillRect(100, ys, 220, 20, NOIR); // efface
  688. return;
  689. }
  690.  
  691.  
  692. File File1 = SD.open("/bmp/capture2.bmp", FILE_WRITE); // ouverture du fichier binaire (vierge) en écriture
  693. if (File1)
  694. {
  695. /*
  696. Les images en couleurs réelles BMP888 utilisent 24 bits par pixel:
  697. Il faut 3 octets pour coder chaque pixel, en respectant l'ordre de l'alternance bleu, vert et rouge.
  698. */
  699. uint16_t bmp_offset = 138;
  700. File1.seek(bmp_offset);
  701.  
  702.  
  703. TFT480.setTextColor(VERT, NOIR);
  704.  
  705. for (y=320; y>0; y--)
  706. {
  707. for (x=0; x<480; x++)
  708. {
  709. color565=TFT480.readPixel(x, y);
  710.  
  711. RGB565_to_888(color565, &R, &G, &B);
  712.  
  713. File1.write(B); //G
  714. File1.write(G); //R
  715. File1.write(R); //B
  716. }
  717.  
  718. s1=(String) (y/10);
  719. TFT480.fillRect(2, 300, 20, 20, NOIR);
  720. TFT480.drawString(s1, 2, 300);// affi compte à rebour
  721. }
  722.  
  723. File1.close(); // referme le fichier
  724.  
  725. TFT480.fillRect(2, 300, 20, 20, NOIR); // efface le compte à rebour
  726. }
  727. }
  728. }
  729.  
  730. /** ----------------------------------------------------------------------------------- */
  731.  
  732.  
  733. void affiche_traits_violets(uint16_t x0, uint16_t yi)
  734. {
  735. TFT480.fillRect(410, 230, 40, 80, GRIS_TRES_FONCE); // efface
  736.  
  737. if (read_bit(flags_i, bit_au_sol) == 0)
  738. {
  739. // pour la partie haute
  740. TFT480.drawLine(x0-50, yi-25, x0-20, yi-25, VIOLET1); affi_indexH(x0-45, yi-25, -1, VIOLET1);
  741. TFT480.drawLine(x0-20, yi-25, x0-20, yi+10, VIOLET1);
  742. TFT480.drawLine(x0-20, yi+10, x0+10, yi+10, VIOLET1);
  743. }
  744. else
  745. {
  746. // pour la partie basse (vers étiquette [ROULAGE])
  747. TFT480.drawLine(x0-50, yi+25, x0-20, yi+25, VIOLET1); affi_indexH(x0-45, yi+25, -1, VIOLET1);
  748. TFT480.drawLine(x0-20, yi+10, x0-20, yi+25, VIOLET1);
  749. TFT480.drawLine(x0-20, yi+10, x0+10, yi+10, VIOLET1);
  750. }
  751. }
  752.  
  753.  
  754.  
  755. void init_Leds() // pour l'affichage des données, voir la fonction "affi_data_piste()"
  756. {
  757. uint16_t x0 = 460;
  758. uint16_t y0 = 5;
  759. uint16_t xi;
  760. uint16_t yi;
  761.  
  762. TFT480.setFreeFont(FF1);
  763. TFT480.setTextColor(BLANC, NOIR);
  764.  
  765. TFT480.drawRoundRect(x0, y0, 160, 305, 3, GRIS);
  766.  
  767. xi=x0+10;
  768. yi=y0+16;
  769.  
  770. TFT480.drawLine(x0-50, yi+25, x0-20, yi+25, ROUGE); affi_indexH(x0-45, yi+25, -1, ROUGE);
  771. TFT480.drawLine(x0-20, yi+25, x0-20, yi+10, ROUGE);
  772. TFT480.drawLine(x0-20, yi+10, x0+10, yi+10, ROUGE);
  773.  
  774.  
  775.  
  776. Led1.init(xi,yi, 20, 20);
  777. Led1.set_couleur(ROUGE);
  778. Led1.eteint();
  779.  
  780. yi+=64;
  781.  
  782. TFT480.drawLine(x0-50, yi+10, x0+10, yi+10, JAUNE); affi_indexH(x0-45, yi+10, -1, JAUNE);
  783.  
  784. TFT480.setFreeFont(FF1);
  785.  
  786. Led2.init(xi,yi, 20, 20);
  787. Led2.set_couleur(JAUNE);
  788. Led2.eteint();
  789.  
  790. yi+=64;
  791.  
  792. TFT480.drawLine(x0-50, yi-10, x0-20, yi-10, VERT); affi_indexH(x0-45, yi-10, -1, VERT);
  793. TFT480.drawLine(x0-20, yi-10, x0-20, yi+10, VERT);
  794. TFT480.drawLine(x0-20, yi+10, x0+10, yi+10, VERT);
  795.  
  796.  
  797. Led3.init(xi,yi, 20, 20);
  798. Led3.set_couleur(VERT);
  799. Led3.eteint();
  800.  
  801. yi+=64;
  802.  
  803. TFT480.drawLine(x0-50, yi-20, x0-20, yi-20, BLEU); affi_indexH(x0-45, yi-20, -1, BLEU);
  804. TFT480.drawLine(x0-20, yi-20, x0-20, yi+10, BLEU);
  805. TFT480.drawLine(x0-20, yi+10, x0+10, yi+10, BLEU);
  806.  
  807. Led4.init(xi,yi, 20, 20);
  808. Led4.set_couleur(BLEU);
  809. Led4.eteint();
  810.  
  811. yi+=64;
  812.  
  813. affiche_traits_violets(x0, yi);
  814.  
  815. Led5.init(xi,yi, 20, 20);
  816. Led5.set_couleur(VIOLET1);
  817. Led5.eteint();
  818. }
  819.  
  820.  
  821.  
  822.  
  823.  
  824. void setup()
  825. {
  826. Serial.begin(19200);
  827.  
  828. pinMode(GPIO_bouton1, INPUT_PULLUP);
  829. pinMode(GPIO_bouton1, INPUT_PULLUP);
  830. pinMode(GPIO_bouton2, INPUT_PULLUP);
  831. pinMode(GPIO_bouton3, INPUT_PULLUP);
  832. pinMode(GPIO_bouton4, INPUT_PULLUP);
  833. pinMode(GPIO_bouton5, INPUT_PULLUP);
  834.  
  835. init_afficheur();
  836. init_WiFi();
  837.  
  838.  
  839. TFT480.fillRect(0, 0, 480, 320, NOIR); // efface
  840. TFT480.setTextColor(BLANC, NOIR);
  841. TFT480.setFreeFont(FF1);
  842.  
  843. uint16_t y=0;
  844. TFT480.drawString("MCDU", 0, y);
  845. y+=20;
  846. String s1="version " + version;
  847. TFT480.drawString(s1, 0, y);
  848.  
  849.  
  850. init_SDcard();
  851.  
  852. delay (2000);
  853. TFT480.fillRect(0, 0, 480, 320, GRIS_TRES_FONCE); // efface
  854.  
  855. TFT480.fillRect(5, 2, 100, 310, NOIR);
  856. TFT480.drawRoundRect(5, 2, 100, 310, 3, BLANC);
  857.  
  858. init_FG_bali();
  859. init_etiquettes();
  860. init_Leds();
  861.  
  862. }
  863.  
  864.  
  865.  
  866. void eteint_toutes_etiquettes()
  867. {
  868. Etiq_1.set_couleurs(GRIS, GRIS_TRES_FONCE); // DECOLLAGE
  869. Etiq_2.set_couleurs(GRIS, GRIS_TRES_FONCE);
  870. Etiq_3.set_couleurs(GRIS, GRIS_TRES_FONCE); // [Auto]
  871. Etiq_4.set_couleurs(GRIS, GRIS_TRES_FONCE); // [Autopilot de FG]
  872. Etiq_5.set_couleurs(GRIS, GRIS_TRES_FONCE); // [Manu]
  873. Etiq_6.set_couleurs(GRIS, GRIS_TRES_FONCE); // [Route]
  874. Etiq_7.set_couleurs(GRIS, GRIS_TRES_FONCE); // [ROUTE]
  875. Etiq_7b.set_couleurs(BLEU_CLAIR, NOIR);
  876. Etiq_8.set_couleurs(GRIS, GRIS_TRES_FONCE); // [RWY]
  877. Etiq_9.set_couleurs(GRIS, GRIS_TRES_FONCE); // [ptA]
  878. Etiq_10.set_couleurs(GRIS, GRIS_TRES_FONCE);// [ptB]
  879. Etiq_10b.set_couleurs(GRIS, GRIS_TRES_FONCE); // [crc1]
  880. Etiq_10c.set_couleurs(GRIS, GRIS_TRES_FONCE); // [crc2]
  881. Etiq_11.set_couleurs(GRIS, GRIS_TRES_FONCE); // [FINALE]
  882. Etiq_12.set_couleurs(GRIS, GRIS_TRES_FONCE); // [LANDING]
  883. Etiq_13.set_couleurs(GRIS, GRIS_TRES_FONCE); // [ROULAGE]
  884. Etiq_14.set_couleurs(GRIS, GRIS_TRES_FONCE); // [LANDING LONG]
  885. Etiq_15.set_couleurs(GRIS, GRIS_TRES_FONCE); // [LANDING SHORT]
  886. Etiq_16.set_couleurs(GRIS, GRIS_TRES_FONCE); // AU SOL
  887. }
  888.  
  889.  
  890.  
  891.  
  892. void teste_flags_i() // reçus du PFD ; -> éteint & allume les étiquettes; fonction appelée uniquement si un flag a changé
  893. {
  894.  
  895. TFT480.setFreeFont(FF5);
  896.  
  897. eteint_toutes_etiquettes();
  898.  
  899. TFT480.setFreeFont(FF5);
  900.  
  901. if (read_bit(flags_i, bit_decollage) ==1) { Etiq_1.set_couleurs(VERT, GRIS_TRES_FONCE); } // [DECOLLAGE]
  902. if( read_bit(flags_i, bit_rudder_decol) ==1) { Etiq_3.set_couleurs(NOIR, VERT); }// [Auto]
  903.  
  904. if (read_bit(flags_i, bit_FG_AP) ==1) { Etiq_4.set_couleurs(VERT, GRIS_TRES_FONCE); }// [Autopilot de FG]
  905. else {Etiq_4.set_couleurs(GRIS, GRIS_TRES_FONCE);}
  906.  
  907. if (read_bit(flags_i, bit_route) ==1)
  908. {
  909. Etiq_6.set_couleurs(NOIR, VERT); // [Route]
  910. Etiq_7.set_couleurs(VERT, GRIS_TRES_FONCE); // [ROUTE]
  911. }
  912. else { Etiq_5.set_couleurs(NOIR, VERT); } // [Manu]
  913.  
  914. if (read_bit(flags_i, bit_nav_to_piste) ==1) { Etiq_8.set_couleurs(NOIR, VERT); } // [RWY]
  915. if (read_bit(flags_i, bit_nav_to_ptAA)==1) { Etiq_9.set_couleurs(NOIR, VERT); }// [ptA]
  916. if (read_bit(flags_i, bit_nav_to_ptBB)==1) { Etiq_10.set_couleurs(NOIR, VERT); } // [ptB]
  917. if (read_bit(flags_i, bit_nav_to_pti)==1)
  918. {
  919. if (read_bit(flags_i, bit_circling)==1)
  920. {
  921. if (read_bit(flags_i, bit_sens_circling)==0) { Etiq_10b.set_couleurs(NOIR, VERT);} // [crc1] tour de piste sens 0
  922. else { Etiq_10c.set_couleurs(NOIR, VERT);} // [crc2] tour de piste sens 1
  923. }
  924. }
  925.  
  926. if (read_bit(flags_i, bit_autoland) ==1) { Etiq_11.set_couleurs(NOIR, VERT);} // allume [FINALE]
  927. if (read_bit(flags_i, bit_atterrissage) ==1) { Etiq_12.set_couleurs(NOIR, VERT); }// [LANDING]
  928. if (read_bit(flags_i, bit_roulage) ==1) { Etiq_13.set_couleurs(NOIR, VERT); }// [ROULAGE]
  929.  
  930. if (read_bit(flags_i, bit_au_sol) ==0)
  931. {
  932. if (read_bit(flags_i, bit_att_short) ==1) { Etiq_15.set_couleurs(NOIR, VERT); } // [LANDING SHORT]
  933. if (read_bit(flags_i, bit_att_short) ==0) { Etiq_14.set_couleurs(NOIR, VERT); } // [LANDING LONG]
  934. }
  935. if (read_bit(flags_i, bit_au_sol) ==1) { Etiq_16.set_couleurs(NOIR, VERT); } // [AU SOL]
  936. }
  937.  
  938.  
  939.  
  940. void efface_listing()
  941. {
  942. TFT480.fillRect(8, 40, 95, 265, NOIR);// efface la liste
  943. num_ligne_msg=0;
  944.  
  945. /*
  946. TFT480.setFreeFont(FM12);
  947. TFT480.setTextColor(ROUGE);
  948.  
  949. TFT480.drawString("RAZ", 30, 10);
  950. delay(1000);
  951. TFT480.fillRect(8, 5, 95, 265, NOIR);// efface
  952. */
  953. }
  954.  
  955.  
  956.  
  957. void affi_alerte(String str1)
  958. {
  959. uint16_t x0=90;
  960. uint16_t y0=100;
  961.  
  962. for (uint8_t n=0; n<5; n++)
  963. {
  964. TFT480.readRect(x0, y0, 300, 50, data_rect1); // memorisation
  965.  
  966. TFT480.fillRect(x0, y0, 300, 50, BLANC); // efface
  967. TFT480.drawRoundRect(x0, y0, 300, 50, 3, VERT);
  968.  
  969. TFT480.setFreeFont(FMB12);
  970. TFT480.setTextColor(ROUGE);
  971. TFT480.drawString(str1, x0+20, y0+10);
  972.  
  973. delay(500);
  974. TFT480.fillRect(x0, y0, 300, 50, GRIS_TRES_FONCE); // efface
  975.  
  976. TFT480.pushRect(x0, y0, 300, 50, data_rect1);
  977.  
  978. delay(1000);
  979. }
  980.  
  981. TFT480.setFreeFont(FF5);
  982.  
  983. }
  984.  
  985.  
  986.  
  987. void teste_boutons() // boutons locaux
  988. {
  989. // l'int16_t "boutons" sera envoyé au PFD en argument de la requette "httpGetParams()"
  990. // son traitement sera fait par le module PFD, pas ici.
  991. // voir la partie "BOUTONS du module MCDU" dans la fonction "void traitement_boutons()" dans le code du PFD
  992.  
  993. bouton1_etat = digitalRead(GPIO_bouton1); // ROUGE
  994. if (bouton1_etat != memo_bouton1_etat)
  995. {
  996. efface_listing();
  997. memo_bouton1_etat = bouton1_etat;
  998. if (bouton1_etat==1)
  999. {
  1000. Led1.eteint();
  1001. raz_bit(&boutons, bit_bt1);
  1002. }
  1003. else
  1004. {
  1005. Led1.allume();
  1006.  
  1007. TFT480.setFreeFont(FF1);
  1008. TFT480.setTextColor(JAUNE);
  1009.  
  1010. TFT480.drawString("Desserez frein de parking", 120, 60);
  1011. delay(1000);
  1012. TFT480.fillRect(120, 60, 280, 16, GRIS_TRES_FONCE); // efface
  1013.  
  1014. set_bit(&boutons, bit_bt1);
  1015. }
  1016. affi_etat_boutons();
  1017. }
  1018.  
  1019. bouton2_etat = digitalRead(GPIO_bouton2); // JAUNE
  1020. if (bouton2_etat != memo_bouton2_etat)
  1021. {
  1022. memo_bouton2_etat = bouton2_etat;
  1023. if (bouton2_etat==1) {Led2.eteint(); raz_bit(&boutons, bit_bt2);}
  1024. else {Led2.allume(); set_bit(&boutons, bit_bt2);}
  1025. affi_etat_boutons();
  1026. }
  1027.  
  1028. bouton3_etat = digitalRead(GPIO_bouton3); // VERT
  1029. if (bouton3_etat != memo_bouton3_etat)
  1030. {
  1031. memo_bouton3_etat = bouton3_etat;
  1032. if (bouton3_etat==1) {Led3.eteint(); raz_bit(&boutons, bit_bt3);}
  1033. else {Led3.allume(); set_bit(&boutons, bit_bt3);}
  1034. affi_etat_boutons();
  1035. }
  1036.  
  1037. bouton4_etat = digitalRead(GPIO_bouton4); // BLEU
  1038. if (bouton4_etat != memo_bouton4_etat)
  1039. {
  1040. memo_bouton4_etat = bouton4_etat;
  1041. if (bouton4_etat==1) {Led4.eteint(); raz_bit(&boutons, bit_bt4);}
  1042. else {Led4.allume(); set_bit(&boutons, bit_bt4);}
  1043. affi_etat_boutons();
  1044. }
  1045.  
  1046. bouton5_etat = digitalRead(GPIO_bouton5); // VIOLET
  1047. if (bouton5_etat != memo_bouton5_etat)
  1048. {
  1049.  
  1050. memo_bouton5_etat = bouton5_etat;
  1051. if (bouton5_etat==1)
  1052. {
  1053. Led5.eteint();
  1054. raz_bit(&boutons, bit_bt5);
  1055. }
  1056. else
  1057. {
  1058. Led5.allume();
  1059. set_bit(&boutons, bit_bt5);
  1060. //write_TFT480_on_SDcard();
  1061. }
  1062. affi_etat_boutons();
  1063.  
  1064.  
  1065.  
  1066. }
  1067. }
  1068.  
  1069.  
  1070. void affi_ligne_listing()
  1071. {
  1072. if ((recp_msg !="") && (recp_msg !="null") && (recp_msg !="RAZ") )
  1073. {
  1074. TFT480.setTextFont(1);
  1075. if(num_ligne_msg==0)
  1076. {
  1077. efface_listing();
  1078. }
  1079. String s1 = String(num_ligne_msg);
  1080. affi_message(s1, 10, 40+9*num_ligne_msg, 97, 0, JAUNE, NOIR, 0);
  1081.  
  1082. s1= recp_msg;
  1083. if ((num_ligne_msg %2) == 0) {couleur_ligne = BLEU_FONCE;} else{couleur_ligne = GRIS_FONCE;}
  1084.  
  1085. TFT480.fillRect(25, 40+9*num_ligne_msg, 75, 9, couleur_ligne); // fond de la ligne
  1086. TFT480.setTextFont(1);
  1087. affi_message(s1, 25, 40+9*num_ligne_msg, 87, 0, BLANC, couleur_ligne, 0);
  1088.  
  1089. num_ligne_msg ++;
  1090. if(num_ligne_msg>27){num_ligne_msg=0;}
  1091. }
  1092.  
  1093.  
  1094. if(recp_msg == "alert1")
  1095. {
  1096. affi_alerte("EMERGENCY landing");
  1097. }
  1098.  
  1099.  
  1100. if(recp_msg == "RAZ")
  1101. {
  1102. //delay(500);
  1103. efface_listing();
  1104. }
  1105.  
  1106. }
  1107.  
  1108.  
  1109.  
  1110.  
  1111. void toutes_les_1s() // pour ne pas trop solliciter le serveur
  1112. {
  1113. interroge_WiFi(); // et envoie les arguments le cas échéant
  1114. affi_flags();
  1115. affi_etat_boutons();
  1116.  
  1117. if ((liste_bali[num_bali].sens_app_interdit) == 'A')
  1118. {
  1119. Etiq_9.set_couleurs(BLANC, ROUGE);// sens interdit
  1120. //Etiq_10.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  1121.  
  1122. }
  1123.  
  1124. if ((liste_bali[num_bali].sens_app_interdit) == 'B')
  1125. {
  1126. Etiq_10.set_couleurs(BLANC, ROUGE);// sens interdit
  1127. //Etiq_9.set_couleurs(GRIS_CLAIR, GRIS_TRES_FONCE);
  1128. }
  1129.  
  1130. requete_WiFi_2();
  1131. if(recp_msg != memo_msg)
  1132. {
  1133. memo_msg = recp_msg;
  1134. affi_ligne_listing();
  1135. }
  1136.  
  1137.  
  1138. String s1;
  1139.  
  1140. requete_WiFi_3();
  1141. if(recp_num_bali != memo_num_bali)
  1142. {
  1143. memo_num_bali = recp_num_bali;
  1144.  
  1145. num_bali = recp_num_bali.toInt();
  1146.  
  1147. s1= (String)liste_bali[num_bali].ID_OACI;
  1148. s1 += " ";
  1149. s1 += (String)liste_bali[num_bali].nom;
  1150.  
  1151. s1 = s1.substring(0,17);
  1152.  
  1153. TFT480.setTextFont(2);
  1154. Etiq_7b.st=s1;
  1155. Etiq_7b.set_couleurs(BLEU_CLAIR, NOIR); // et affiche le texte
  1156.  
  1157. //Etiq_8.set_couleurs(GRIS, GRIS_TRES_FONCE);
  1158. //Etiq_9.set_couleurs(GRIS, GRIS_TRES_FONCE);
  1159. //Etiq_10.set_couleurs(GRIS, GRIS_TRES_FONCE);
  1160.  
  1161. }
  1162.  
  1163. //inv_bit(&flags_i, bit_au_sol);
  1164. //init_Leds();
  1165. //affiche_traits_violets(460, 276);
  1166.  
  1167. }
  1168.  
  1169.  
  1170.  
  1171.  
  1172. void loop()
  1173. {
  1174.  
  1175. temps_ecoule = micros() - memo_micros;
  1176. if (temps_ecoule >= 1E6) // (>=) et pas strictement égal (==) parce qu'alors on rate l'instant en question
  1177. {
  1178. memo_micros = micros();
  1179. toutes_les_1s();
  1180. }
  1181.  
  1182. teste_boutons(); // local
  1183.  
  1184. if(flags_i != memo_flags_i)
  1185. {
  1186. memo_flags_i = flags_i;
  1187. teste_flags_i(); // reçus du PFD
  1188. affiche_traits_violets(460, 276);
  1189. }
  1190.  
  1191. delay(30);
  1192. }
  1193.  
  1194.  
  1195.  
  1196.  
  1197. /** ***************************************************************************************
  1198. CLASS Etiquette // affiche sous forme d'une étiquette colorée
  1199. ********************************************************************************************/
  1200.  
  1201. // Constructeur
  1202. Etiquette::Etiquette()
  1203. {
  1204.  
  1205. }
  1206.  
  1207.  
  1208. void Etiquette::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
  1209. {
  1210. x0 = xi;
  1211. y0 = yi;
  1212. dx = dxi;
  1213. dy = dyi;
  1214.  
  1215. //TFT480.setTextColor(BLANC, NOIR);
  1216. couleur_contour = BLANC; // par défaut
  1217. }
  1218.  
  1219.  
  1220.  
  1221. void Etiquette::traceContour()
  1222. {
  1223. TFT480.drawRoundRect(x0, y0, dx, dy, 3, couleur_contour);
  1224. }
  1225.  
  1226.  
  1227. void Etiquette::efface()
  1228. {
  1229. TFT480.fillRoundRect(x0, y0, dx, dy, 3, couleur_fond);
  1230. traceContour();
  1231.  
  1232. }
  1233.  
  1234.  
  1235.  
  1236. void Etiquette::set_couleurs(uint16_t c_txt, uint16_t c_fond) // et affiche le texte
  1237. {
  1238. couleur_texte = c_txt;
  1239. couleur_fond = c_fond;
  1240. affi_string();
  1241. }
  1242.  
  1243.  
  1244.  
  1245.  
  1246. void Etiquette::affi_string()
  1247. {
  1248. // Pour cette méthode, la police de caract doit être choisie au préalable, extérieurement
  1249. efface();
  1250. // TFT480.setFreeFont(FF5); // à parametrer expérieurement avant l'appel
  1251. TFT480.setTextColor(couleur_texte, couleur_fond);
  1252. TFT480.drawString(st, x0+5, y0+5);
  1253. }
  1254.  
  1255.  
  1256. /** ***************************************************************************************
  1257. CLASS Led // affiche un petit rectangle coloré
  1258. ********************************************************************************************/
  1259.  
  1260.  
  1261. // Constructeur
  1262. Led::Led()
  1263. {
  1264.  
  1265. }
  1266.  
  1267.  
  1268. void Led::init(uint16_t xi, uint16_t yi, uint16_t dxi, uint16_t dyi)
  1269. {
  1270. x0 = xi;
  1271. y0 = yi;
  1272. dx = dxi;
  1273. dy = dyi;
  1274. }
  1275.  
  1276.  
  1277.  
  1278. void Led::allume()
  1279. {
  1280. TFT480.fillRect(x0, y0, dx, dy, couleur);
  1281. }
  1282.  
  1283.  
  1284. void Led::eteint()
  1285. {
  1286. TFT480.fillRect(x0, y0, dx, dy, GRIS_TRES_FONCE);
  1287. TFT480.drawRect(x0, y0, dx, dy, couleur); // bordure
  1288. }
  1289.  
  1290.  
  1291. void Led::set_couleur(uint16_t coul_i)
  1292. {
  1293. couleur = coul_i;
  1294. }
  1295.  
  1296.  
  1297.  
  1298.  
  1299.  
  1300.  

42 Le fichier d'entête MCDU.h

CODE SOURCE en C++
  1. #ifndef MCDU_H
  2. #define MCDU_H
  3.  
  4. //
  5. // ==================================
  6. String version_MCDU_h="35.3";
  7. // ==================================
  8.  
  9.  
  10. #include <stdint.h>
  11. #include <TFT_eSPI.h>
  12.  
  13. #include "SPI.h"
  14. #include "SD.h"
  15.  
  16.  
  17.  
  18. TFT_eSPI TFT480 = TFT_eSPI(); // Configurer le fichier User_Setup.h de la bibliothèque TFT480_eSPI au préalable
  19.  
  20.  
  21. /** ***********************************************************************************
  22. CLASS Etiquette // affiche un nombre ou un petit texte dans un rectangle
  23. ***************************************************************************************/
  24.  
  25.  
  26. class Etiquette
  27. {
  28. public:
  29.  
  30. uint16_t x0; //position
  31. uint16_t y0;
  32.  
  33. uint16_t dx; //dimension
  34. uint16_t dy;
  35.  
  36. String st;
  37.  
  38. //couleurs
  39. uint16_t couleur_fond;
  40. uint16_t couleur_contour;
  41. uint16_t couleur_texte;
  42.  
  43. Etiquette();
  44.  
  45. void init(uint16_t x, uint16_t y, uint16_t dx, uint16_t dy);
  46. void traceContour();
  47. void efface();
  48. void set_couleurs(uint16_t c_txt, uint16_t c_fond);
  49. void affi_string();
  50.  
  51.  
  52. private:
  53.  
  54.  
  55. };
  56.  
  57.  
  58. /** ***********************************************************************************
  59. CLASS Led // affiche un petit rectangle rectangle coloré
  60. ***************************************************************************************/
  61.  
  62. class Led
  63. {
  64. public:
  65.  
  66. uint16_t x0; //position
  67. uint16_t y0;
  68.  
  69. uint16_t dx; //dimension
  70. uint16_t dy;
  71.  
  72.  
  73. //couleurs
  74. uint16_t couleur;
  75.  
  76. Led();
  77.  
  78. void init(uint16_t x, uint16_t y, uint16_t dx, uint16_t dy);
  79. void set_couleur(uint16_t coul_i);
  80. void allume();
  81. void eteint();
  82.  
  83.  
  84. private:
  85.  
  86.  
  87. };
  88.  
  89.  
  90.  
  91. #endif
  92.  

43 Remplacer OpenStreetMap par OpenTopoMap dans l'interface Phi

J'aime bien voler dans des endroits montagneux, aux alentours d'Annecy, Chambéry, Tarbes, dans l'arrière pays niçois ou en Corse pour la beauté des paysages. Toutefois les montagnes et les avions ne font pas très bon ménage ! Aussi je me suis dit que les cartes de opentopomap seraient adaptées à la situation. L'interface Phi permet de visualiser la position de l'avion sur les cartes OpenStreetMap par défaut mais OpenTopoMap n'est pas proposé dans les menu (en tous cas pas dans ma config).

Je savais que le code concernant l'interface phi se trouvait dans le dossier "/usr/share/games/flightgear/Phi/". J'ai donc fait une recherche de fichiers contenant le texte "openstreetmap" dans ce dossier, et j'ai trouvé le fichier "map.js" dans le dossier "/usr/share/games/flightgear/Phi/widgets/". Et je savais que la syntaxe de l'URL d'accès aux cartes est identique pour OpenStreetMas et OpenTopomap... Donc rien de plus simple, logiquement, pour obtenir le résultat recherché.

Voici la marche à suivre :

1- Faire une copie de sécurité du fichier "/usr/share/games/flightgear/Phi/widgets/maps.js".
2- Ouvrir le-dit fichier.
3- Remplacer toutes les (5) occurrences "openstreetmap" par "opentopomap" (en conservant la casse d'origine le cas échéant). 4- Enregistrer le fichier.

Et voilà, ça marche !
Et il est facile de conserver les deux options en modifiant le javascript dans le fichier map.js Voici le fichier ainsi modifié: (cherchez "baseLayers" dans ce fichier pour voir les détails de la modif)

44 Documents et code source complet :

Remarques :

Lorsque je modifie les fichiers .ino, j'incrémente leur numéro (interne, pas le nom de fichier qui reste identique pour des raisons de simplicité de publication sur ce site). Il n'en est pas de même si je modifie les autres types de fichiers (.h, images .bmp, etc...) qui ne sont pas numérotés du tout. Or je les modifie souvent !

MAIS dans le fichier 'code_source.zip' tout est toujours à jour et cohérent.


45 Utiliser Geany comme éditeur

Commencer par faire fonctionner la programmation avec l'EDI Arduino, Puis, dans cet EDI, cocher "utiliser un éditeur externe" dans Préférences/Paramètres.

Dès lors, le fichier de code source préalablement créé par l'EDI Arduino peut être travaillé en externe par Geany (ou tout autre éditeur), puis sauvegardé par cet éditeur externe. On constate alors que l'affichage dans l'EDI Arduino est automatiquement mis à jour. Et cet EDI Arduino nous sert alors "juste" à compiler le code et à le downloader sur la carte ESP32.

Toutefois il convient de garder ".ino" comme extension pour le fichier source principal ainsi que les fonctions "Setup()" et "Loop()" afin de conserver la compatibilité avec le système Arduino, son compilateur ainsi que sa gestion des library et des options de compilation des cartes (nombreuses et bien utiles), même si en définitive nous n'utilisons pas de carte Arduino proprement dite.

46 Voici un exemple de l'édition du code source avec Geany

Pour info, j'écris le code avec Geany qui permet :
  • Coloration syntaxique entièrement personnalisable
  • liste des fonctions
  • onglets
  • outils de recherche d’occurrence
  • outils de recherche de fonctions
  • arborescence des fichiers
  • marques pages
  • se rendre à un numéro de ligne (par exemple une ligne que le compilateur Arduino ne digère pas...)
et j'en oublie.
et bien sûr en tout-écran sur un moniteur 24", avec un second écran (24" également) à côté pour afficher un terminal série (CuteCom), et l'EdI Arduino pour compiler. Pour les essais : FlightGear sur l'écran principal et la carte (géographique) dans un navigateur (Firefox) sur l'écran secondaire.

47 Vidéo - Décollage LFMT


48 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".

49 -

Liens...

Cliquez sur une image ci-dessous pour visualiser l'album. Certaines sont commentées

7759