Radio FM - ESP32 + écran TFT 240x240

Récepteur FM ; module TEA5767 + ESP32 + TFT couleur. Et application de la transformée de Fourier rapide.

1 Description

Nous avions déjà conçu un tel récepteur, avec une puce TEA5767 et un microcontrôleur ATmega8 soudés manuellement.

Les choses ont évolué depuis, ce qui permet de nous affranchir de la réalisation du circuit imprimé et de la soudure des composants cms. Et la puissance sans comparaison de l'ESP32 ainsi que l'évolution des TFT nous permet un affichage en couleurs de haute définition.

Le vu-mètre, par exemple, est virtuel, affiché sur l'écran TFT 240x240px.

2 Le schéma

3 Le code source du programme

CODE SOURCE en C++
  1. /* -------------------------------------------------------------------------
  2. RADIO FM - pour ESP32
  3.  
  4. par Silicium628
  5.  
  6. --------------------------------------------------------
  7. CONCERNANT L'AFFICHAGE TFT :
  8.  
  9. Pensez à configurer le fichier User_Setup.h de la bibliothèque ~/Arduino/libraries/TFT_eSPI/ au préalable
  10. voici les paramètres OK pour cette réalisation :
  11.  
  12. #define ST7789_DRIVER // Full configuration option, define additional parameters below for this display
  13. #define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red
  14. #define TFT_WIDTH 240 // ST7789 240 x 240 and 240 x 320
  15. #define TFT_HEIGHT 240 // ST7789 240 x 240
  16.  
  17. #define TFT_MISO 19
  18. #define TFT_MOSI 23
  19. #define TFT_SCLK 18
  20. #define TFT_CS 15 // Chip select control pin
  21. #define TFT_DC 2 // Data Command control pin
  22. #define TFT_RST 4 // Reset pin (could connect to RST pin)
  23.  
  24. explications ici : https://www.youtube.com/watch?v=HoZhgNcJjNA
  25.  
  26.  Remerciements à :
  27.  - big12boy 2017 - Licence: GNU GPL
  28.  
  29. --------------------------------------------------------
  30.  CONCERNANT LA TRANSFORMEE DE FOURIER :
  31.  
  32.   FFT par Silicium628
  33.  
  34. logiciel libre, OPEN SOURCE
  35.  
  36.  pour l'explication des calculs voir mes pages :
  37.  
  38. http://www.silicium628.fr/article_i.php?id=123 -> La transformée de Fourier Etude analytique
  39. http://www.silicium628.fr/article_i.php?id=126 -> Transformée de Fourier rapide
  40. http://www.silicium628.fr/article_i.php?id=31 -> Transformée de Fourier sur ATmega32 et Arduino Mega2560
  41.  
  42. ------------------------------------------------------------------------- */
  43.  
  44. String version="2.0";
  45.  
  46.  
  47. #include <SPI.h>
  48. #include <TEA5767.h>
  49. #include <TFT_eSPI.h> // Hardware-specific library
  50. #include "complexe628.h"
  51. #include "Free_Fonts.h"
  52.  
  53. TEA5767 radio = TEA5767();
  54. TFT_eSPI tft = TFT_eSPI(); // Configurer le fichier User_Setup.h de la bibliothèque TFT_eSPI au préalable
  55.  
  56. typedef int16_t complexe_f[2]; // entier signé pour calculs (rapides) en virgule fixe
  57.  
  58. Complexe ech[480]; // (480 = nb_ech echantillons)
  59. Complexe tab_X[480]; // nb_ech valeurs traitées
  60. Complexe tab_W[240]; // nb_ech/2
  61.  
  62. uint nb_etapes=8;
  63. uint nb_ech = pow (2,nb_etapes); // nombre d'échantillons = 2 puissance(nb_etapes)
  64.  
  65. struct boite
  66. {
  67. uint16_t x;
  68. uint16_t y;
  69. uint16_t w;
  70. uint16_t h;
  71. };
  72.  
  73. boite boite_FFT;
  74.  
  75.  
  76. #define TFT_GREY 0x5AEB
  77.  
  78. float ltx = 0; // Saved x coord of bottom of aiguille
  79. uint16_t osx = 120, osy = 120; // Saved x & y coords
  80.  
  81.  
  82. const int bouton1 = 12; // GPIO12
  83. const int bouton2 = 14; // GPIO14
  84. const int bouton3 = 27; // GPIO27
  85.  
  86. //const int led1 = 13; // GPIO13
  87.  
  88. bool bouton1_etat;
  89. bool bouton2_etat;
  90. bool bouton3_etat;
  91.  
  92. bool memo_bouton1_etat;
  93. bool memo_bouton2_etat;
  94. bool memo_bouton3_etat;
  95.  
  96. uint8_t mode_boutons;
  97. uint8_t mode_affi;
  98. uint8_t memo_mode_affi;
  99.  
  100. uint8_t num_station;
  101. uint8_t nb_stations;
  102.  
  103. uint16_t frq_Int;
  104. float frq_out; // ex: 89.4 (MHz)
  105.  
  106.  
  107. uint16_t compteur1=0;
  108.  
  109.  
  110. struct station
  111. {
  112. uint16_t frq;
  113. char nom[20+1];
  114. };
  115.  
  116.  
  117. struct station liste_stations[20]; // 0..19
  118.  
  119.  
  120. void init_stations()
  121. {
  122. nb_stations = 12;
  123.  
  124. strcpy(liste_stations[0].nom , "France Inter");
  125. liste_stations[0].frq = 894;
  126.  
  127. strcpy(liste_stations[1].nom , "France Musique");
  128. liste_stations[1].frq = 929;
  129.  
  130. strcpy(liste_stations[2].nom , "RFM");
  131. liste_stations[2].frq = 956;
  132.  
  133. strcpy(liste_stations[3].nom , "France Culture");
  134. liste_stations[3].frq = 978;
  135.  
  136. strcpy(liste_stations[4].nom , "Radio France Herault");
  137. liste_stations[4].frq = 1011;
  138.  
  139. strcpy(liste_stations[5].nom , "Fun Radio");
  140. liste_stations[5].frq = 918;
  141.  
  142. strcpy(liste_stations[6].nom , "Radio Clapas");
  143. liste_stations[6].frq = 935;
  144.  
  145. strcpy(liste_stations[7].nom , "Cherie FM");
  146. liste_stations[7].frq = 969;
  147.  
  148. strcpy(liste_stations[8].nom , "FIP");
  149. liste_stations[8].frq = 997;
  150.  
  151. strcpy(liste_stations[9].nom , "Nostalgie");
  152. liste_stations[9].frq = 1039;
  153.  
  154. strcpy(liste_stations[10].nom , "France Info");
  155. liste_stations[10].frq = 1051;
  156.  
  157. strcpy(liste_stations[11].nom , "Radio Classique");
  158. liste_stations[11].frq = 1073;
  159. }
  160.  
  161.  
  162.  
  163. void affiche_Signal_Level()
  164. {
  165. short level = radio.getSignalLevel(); //Get Signal Level
  166.  
  167. plotAiguille(100*level/15);
  168. }
  169.  
  170. // ================== FONCTIONS FFT ==============================
  171.  
  172.  
  173. void effacer_trace()
  174. {
  175. tft.fillRect(boite_FFT.x+1, boite_FFT.y+1, boite_FFT.w-2, boite_FFT.h-2, TFT_BLACK);
  176. }
  177.  
  178.  
  179.  
  180. void RAZ_tableau_echantillons()
  181. {
  182. uint n;
  183. for(n=0; n < nb_ech; n++)
  184. {
  185. ech[n].a = 0; // partie reelle
  186. ech[n].b = 0; // partie imaginaire
  187. }
  188. }
  189.  
  190.  
  191. uint16_t bit_reversal(uint16_t num, uint8_t nb_bits)
  192. {
  193. uint r = 0, i, s;
  194. if ( num > (1<< nb_bits)) { return 0; }
  195.  
  196. for (i=0; i<nb_bits; i++)
  197. {
  198. s = (num & (1 << i));
  199. if(s) { r |= (1 << ((nb_bits - 1) - i)); }
  200. }
  201. return r;
  202. }
  203.  
  204.  
  205. void bit_reverse_tableau_X()
  206. {
  207.  
  208. // recopie les échantillons en les classant dans l'ordre 'bit reverse'
  209. uint n,r;
  210.  
  211. for(n=0; n < nb_ech; n++) // nb d'échantillons
  212. {
  213. r=bit_reversal(n,nb_etapes);
  214. tab_X[n] = ech[r];
  215. }
  216. }
  217.  
  218.  
  219. void acquisition()
  220. {
  221. // source = signal analogique en entrée du CAN.
  222. // destination -> tableau des échantillons complexes
  223. float x;
  224.  
  225. for(int n=0; n<nb_ech; n++)
  226. {
  227. x= analogRead(25)/30.0; // GPIO 25; le /30.0 détermine la sensibilité (atténuateur sur l'amplitude)
  228. ech[n].a = x; // partie reelle
  229. ech[n].b = 0; // partie imaginaire
  230. }
  231. }
  232.  
  233.  
  234.  
  235. /**
  236.  pour l'explication des calculs suivants voir mes pages :
  237.  
  238. http://www.silicium628.fr/article_i.php?id=123 -> La transformée de Fourier Etude analytique
  239. http://www.silicium628.fr/article_i.php?id=126 -> Transformée de Fourier rapide
  240. http://www.silicium628.fr/article_i.php?id=31 -> Transformée de Fourier sur ATmega32 et Arduino Mega2560
  241.  
  242. **/
  243.  
  244.  
  245. void calcul_tableau_W()
  246. {
  247. // calcul et memorisation dans un tableau des twiddle factors
  248.  
  249. uint n;
  250. float x;
  251.  
  252. for(n=0; n<(nb_ech/2-1); n++)
  253. {
  254. x=2.0*M_PI * n / nb_ech;
  255.  
  256. tab_W[n].a = cos(x); // partie reelle
  257. tab_W[n].b = -sin(x); // partie imaginaire
  258. }
  259. }
  260.  
  261.  
  262.  
  263. void calcul_fourier()
  264. {
  265. Complexe produit; // voir la classe "Complexe" : complexe.h et complexe.ccp
  266.  
  267. uint etape, e1, e2, li, w, ci;
  268. uint li_max=nb_ech;
  269.  
  270. e2=1;
  271.  
  272. for (etape=1; etape<=nb_etapes; etape++)
  273. {
  274. e1=e2; //(e1 evite d'effectuer des divisions e2/2 plus bas)
  275. e2=e2+e2;
  276.  
  277. for (li=0; li<li_max; li+=1)
  278. {
  279. ci=li & (e2-1); // ET bit à bit
  280. if (ci>(e1-1))
  281. {
  282. w=li_max/e2*(li & (e1 -1)); // ET bit à bit calcul du numéro du facteur de tripatouillages W
  283.  
  284. produit = tab_W[w] * tab_X[li]; // le twiddle factor est lu en memoire; le produit est une mutiplication de nb complexes. Voir "complexe.cpp"
  285.  
  286. tab_X[li]=tab_X[li-e1]-produit; // concerne la ligne basse du croisillon; soustraction complexe; Voir "complexe.cpp"
  287. tab_X[li-e1]=tab_X[li-e1] + produit; // concerne la ligne haute du croisillon; addition complexe; Voir "complexe.cpp"
  288. }
  289. }
  290. }
  291. }
  292.  
  293.  
  294. // couleurs RGB24 :
  295.  
  296. //couleur1= 255; // bleu
  297. //couleur1= 255<<1; // bleu-clair
  298. //couleur1= 255<<2; // bleu très clair
  299. //couleur1= 255<<3; // blanc bleuté
  300. //couleur1= 255<<4; // cyan
  301. //couleur1= 255<<5; // vert-vert-jaune
  302. //couleur1= 255<<6; // vert-jaune
  303. //couleur1= 255<<7; // vert-jaune
  304. //couleur1= 255<<8; // jaune
  305. //couleur1= 255<<9; // jaune orangé
  306. //couleur1= 255<<10; // orange
  307. //couleur1= 255<<11; // rouge
  308.  
  309.  
  310. void affi_FFT()
  311. {
  312. float TF;
  313. uint8_t offset_y = boite_FFT.y + boite_FFT.h -1;
  314. float echelle_x = 8.0;
  315. int n;
  316. float x, y, memo_x, memo_y;
  317. float z= 0.5;
  318.  
  319. uint32_t couleur1;
  320.  
  321. x=0;
  322. y=offset_y;
  323. uint16_t n_max = nb_ech/2;
  324. for(n=1; n< n_max; n++)
  325. {
  326. memo_x = x;
  327. memo_y = y;
  328. x = boite_FFT.x + echelle_x * n;
  329.  
  330. if (x<(boite_FFT.w-10))
  331. {
  332. TF = z * sqrt( tab_X[n].a * tab_X[n].a + tab_X[n].b * tab_X[n].b ); // racine de A²+B²
  333.  
  334. if(TF>100) {TF=100;}
  335.  
  336. y = offset_y - TF;
  337.  
  338. //tft.drawLine(memo_x ,memo_y, x, y, TFT_YELLOW); // courbe continue
  339. //tft.drawLine(x+1 ,offset_y, x+1, y, TFT_YELLOW);// barres verticales
  340. //tft.drawFastVLine(x, y, TF, TFT_YELLOW); // barres verticales, tarcé rapide
  341.  
  342. int decalage = 12 - (int16_t)x/21.0;
  343. if (decalage < 0) {decalage = 0;}
  344.  
  345. couleur1= (255 << decalage);
  346.  
  347. tft.fillRect(x+1, y, 6, TF, couleur1); // barres verticales larges
  348.  
  349. }
  350. }
  351. }
  352.  
  353. // ===============================================================
  354.  
  355.  
  356. void setup(void)
  357. {
  358. pinMode(bouton1, INPUT_PULLUP);
  359. pinMode(bouton2, INPUT_PULLUP);
  360. pinMode(bouton3, INPUT_PULLUP);
  361.  
  362. //pinMode(led1, OUTPUT);
  363.  
  364. tft.init();
  365. tft.setRotation(3); // 0..3 à vous de voir, suivant disposition de l'afficheur
  366. // Serial.begin(57600);
  367. tft.fillScreen(TFT_BLACK);
  368.  
  369. //Setup I2C
  370. Wire.begin();
  371.  
  372. dessine_VuMetre();
  373. //dessine_gamme_FM();
  374.  
  375. mode_affi=0;
  376. mode_boutons=0;
  377.  
  378. init_stations();
  379. num_station = 0;
  380.  
  381. if (mode_boutons==0) {frq_Int = liste_stations[num_station].frq;}
  382. if (mode_boutons==1) {frq_Int=880;}
  383.  
  384. frq_out = (float)frq_Int / 10;
  385.  
  386. MAJ_frq();
  387. affiche_numero_station(num_station);
  388.  
  389. delay(20);
  390. affiche_Signal_Level();
  391.  
  392. bouton1_etat = digitalRead(bouton1);
  393. memo_bouton1_etat = bouton1_etat;
  394.  
  395. bouton2_etat = digitalRead(bouton2);
  396. memo_bouton2_etat = bouton2_etat;
  397.  
  398. bouton3_etat = digitalRead(bouton3);
  399. memo_bouton3_etat = bouton3_etat;
  400.  
  401.  
  402.  
  403. calcul_tableau_W(); // (twiddle factors)
  404.  
  405. boite_FFT.x = 0;
  406. boite_FFT.y = 0;
  407. boite_FFT.w = 239;
  408. boite_FFT.h = 120;
  409.  
  410. tft.drawRect(boite_FFT.x, boite_FFT.y, boite_FFT.w, boite_FFT.h, TFT_CYAN);
  411.  
  412.  
  413. //updateTime = millis(); // Next update time
  414. }
  415.  
  416.  
  417. void affiche_numero_station(uint8_t n_i)
  418. {
  419. if (mode_boutons==0)
  420. {
  421. tft.fillRect(0, 138, 25, 25, TFT_BLUE);
  422. String s1= (String) (n_i + 1);
  423. tft.setTextColor(TFT_WHITE);
  424. tft.drawString(s1, 5, 145, 2);
  425. }
  426. if (mode_boutons==1)
  427. {
  428. tft.fillRect(0, 138, 25, 25, TFT_GREY);
  429. }
  430. }
  431.  
  432.  
  433.  
  434. void efface_nom_station()
  435. {
  436. tft.fillRect(0, 180, 239, 30, TFT_BLACK);
  437. }
  438.  
  439.  
  440. void affiche_nom_station(uint8_t n)
  441. {
  442. tft.setTextColor(TFT_YELLOW);
  443. String s3= liste_stations[n].nom;
  444. tft.drawString(s3, 0, 180, 4);
  445. }
  446.  
  447.  
  448.  
  449. void affiche_frq_out()
  450. {
  451.  
  452. tft.fillRect(25, 135, 239, 30, TFT_BLACK); //TFT_BLACK
  453.  
  454. String s2= (String) frq_out;
  455. s2+=" MHz";
  456.  
  457. if (mode_boutons==0) {tft.setTextColor(TFT_CYAN);}
  458. if (mode_boutons==1) {tft.setTextColor(TFT_GREEN);}
  459.  
  460.  
  461. tft.setFreeFont(FF19);
  462. tft.drawString(s2, 35, 135, GFXFF);
  463.  
  464. tft.fillRect(0, 180, 239, 30, TFT_BLACK);
  465. tft.setTextColor(TFT_YELLOW);
  466.  
  467. }
  468.  
  469.  
  470. void MAJ_frq()
  471. {
  472. radio.setFrequency(frq_out);
  473. affiche_frq_out();
  474. affiche_nom_station(num_station);
  475. dessine_gamme_FM();
  476. delay(20);
  477. affiche_Signal_Level();
  478. }
  479.  
  480.  
  481. void inc_frq_int()
  482. {
  483. if ( frq_Int < 1080) {frq_Int += 1;}
  484. if ( frq_Int > 1080) {frq_Int = 1080; }
  485. }
  486.  
  487.  
  488.  
  489. void dec_frq_int()
  490. {
  491. if ( frq_Int > 880) {frq_Int -= 1;}
  492. }
  493.  
  494.  
  495.  
  496. uint8_t detect_num_station(uint16_t frq_Int_i)
  497. {
  498. for (int i=0; i<nb_stations; i++)
  499. {
  500. if (frq_Int_i == liste_stations[i].frq) return i;
  501. }
  502. return 255;
  503. }
  504.  
  505.  
  506. void loop()
  507. {
  508. memo_mode_affi = mode_affi;
  509. uint16_t compte=0;
  510. bouton1_etat = digitalRead(bouton1);
  511. bouton2_etat = digitalRead(bouton2);
  512.  
  513. if (mode_boutons == 0)
  514. {
  515.  
  516. if (bouton1_etat != memo_bouton1_etat)
  517. {
  518. memo_bouton1_etat = bouton1_etat;
  519.  
  520. if (bouton1_etat == 0)
  521. {
  522. mode_affi=0;
  523. if ( num_station < nb_stations) {num_station += 1;}
  524. if ( num_station > (nb_stations-1)) {num_station = nb_stations-1; }
  525. frq_Int = liste_stations[num_station].frq;
  526. frq_out = (float)frq_Int / 10;
  527. }
  528. affiche_numero_station(num_station);
  529. MAJ_frq();
  530. }
  531.  
  532. if (bouton2_etat != memo_bouton2_etat)
  533. {
  534. memo_bouton2_etat = bouton2_etat;
  535.  
  536. if (bouton2_etat == 0)
  537. {
  538. mode_affi=0;
  539. if ( num_station > 0) {num_station -= 1;}
  540. frq_Int = liste_stations[num_station].frq;
  541. frq_out = (float)frq_Int / 10;
  542. }
  543. affiche_numero_station(num_station);
  544. MAJ_frq();
  545. }
  546.  
  547. }
  548.  
  549.  
  550. if (mode_boutons == 1)
  551. {
  552. bouton1_etat = digitalRead(bouton1);
  553. bouton2_etat = digitalRead(bouton2);
  554. if (bouton1_etat != memo_bouton1_etat)
  555. {
  556. memo_bouton1_etat = bouton1_etat;
  557.  
  558. if (bouton1_etat == 0) // suite à un appui bref, on avance de 1 pas
  559. {
  560. mode_affi=0;
  561. inc_frq_int();
  562. frq_out = (float)frq_Int / 10;
  563. radio.setFrequency(frq_out);
  564. affiche_frq_out();
  565. dessine_gamme_FM();
  566. affiche_Signal_Level();
  567. }
  568. //MAJ_frq();
  569.  
  570. bouton1_etat = digitalRead(bouton1);
  571. while(bouton1_etat == 0) // pui on détecte un éventuel appui long (500ms)
  572. {
  573. bouton1_etat = digitalRead(bouton1);
  574. compte++;
  575. if (compte>=50) // l'appui long est advenu
  576. {
  577. compte=0;
  578. bouton1_etat = digitalRead(bouton1);
  579. while(bouton1_etat == 0) // tant que le bouton reste appuyé, on incrémente rapidement les pas
  580. {
  581. bouton1_etat = digitalRead(bouton1);
  582. inc_frq_int();
  583. frq_out = (float)frq_Int / 10;
  584. radio.setFrequency(frq_out);
  585. affiche_frq_out();
  586. dessine_gamme_FM();
  587. affiche_Signal_Level();
  588. delay(50);
  589. }
  590. }
  591. delay(10);
  592. }
  593. efface_nom_station();
  594. uint8_t num1= detect_num_station(frq_Int);
  595. if (num1 !=255) {affiche_nom_station(num1);}
  596. }
  597.  
  598.  
  599.  
  600. if (bouton2_etat != memo_bouton2_etat)
  601. {
  602. memo_bouton2_etat = bouton2_etat;
  603.  
  604. if (bouton2_etat == 0) // suite à un appui bref, on avance de 1 pas
  605. {
  606. mode_affi=0;
  607. dec_frq_int();
  608. frq_out = (float)frq_Int / 10;
  609. radio.setFrequency(frq_out);
  610. affiche_frq_out();
  611. dessine_gamme_FM();
  612. affiche_Signal_Level();
  613. }
  614. //MAJ_frq();
  615.  
  616. bouton2_etat = digitalRead(bouton2);
  617. while(bouton2_etat == 0) // puis on détecte un éventuel appui long (500ms)
  618. {
  619. bouton2_etat = digitalRead(bouton2);
  620. compte++;
  621. if (compte>=50) // l'appui long est advenu
  622. {
  623. compte=0;
  624. bouton2_etat = digitalRead(bouton2);
  625. while(bouton2_etat == 0) // tant que le bouton reste appuyé, on incrémente rapidement les pas
  626. {
  627. bouton2_etat = digitalRead(bouton2);
  628. dec_frq_int();
  629. frq_out = (float)frq_Int / 10;
  630. radio.setFrequency(frq_out);
  631. affiche_frq_out();
  632. dessine_gamme_FM();
  633. affiche_Signal_Level();
  634. delay(50);
  635. }
  636.  
  637. }
  638. delay(10);
  639. }
  640. efface_nom_station();
  641. uint8_t num1= detect_num_station(frq_Int);
  642. if (num1 !=255) {affiche_nom_station(num1);}
  643. }
  644.  
  645.  
  646. }
  647.  
  648.  
  649. bouton3_etat = digitalRead(bouton3);
  650. if (bouton3_etat != memo_bouton3_etat)
  651. {
  652. memo_bouton3_etat = bouton3_etat;
  653. if (bouton3_etat == 0)
  654. {
  655. mode_boutons=1-mode_boutons;
  656. affiche_frq_out();
  657. delay(20);
  658. }
  659. if(mode_boutons==0){tft.fillRect(0, 138, 25, 25, TFT_BLUE);}
  660. if(mode_boutons==1){tft.fillRect(0, 138, 25, 25, TFT_GREY);}
  661. }
  662.  
  663.  
  664.  
  665. if(memo_mode_affi != mode_affi)
  666. {
  667. if(mode_affi==0)
  668. {
  669. tft.drawRect(boite_FFT.x, boite_FFT.y, boite_FFT.w, boite_FFT.h, TFT_CYAN);
  670. dessine_VuMetre();
  671. }
  672.  
  673.  
  674. }
  675.  
  676. if (mode_affi==0) {compteur1++;}
  677. if (compteur1>=100)
  678. {
  679. compteur1=0;
  680. mode_affi=1;
  681. tft.fillRect(0, 0, 239, 126, TFT_GREY);
  682. }
  683.  
  684.  
  685. if(mode_affi==1)
  686. {
  687. // ----------- FFT ---------------
  688. effacer_trace();
  689. RAZ_tableau_echantillons();
  690.  
  691. acquisition();
  692.  
  693. //tracer_signal();
  694. bit_reverse_tableau_X();
  695. calcul_fourier();
  696.  
  697. affi_FFT();
  698. }
  699.  
  700. delay(20);
  701.  
  702. }
  703.  
  704.  
  705. void dessine_gamme_FM()
  706. {
  707. // cadre rectangulaire
  708. tft.fillRect(0, 210, 239, 28, 0x040004);
  709. tft.drawRect(0, 212, 239, 28, TFT_WHITE);
  710.  
  711. tft.drawLine(20, 224, 215, 224, TFT_WHITE); // TFT_DARKGREEN
  712.  
  713. uint16_t x=20;
  714. for(int n=0; n<20; n++)
  715. {
  716.  
  717. tft.drawLine(3+x, 215, 3+x, 230, TFT_WHITE); // TFT_DARKGREEN
  718. x+=10;
  719.  
  720. }
  721.  
  722. tft.setTextColor(TFT_YELLOW); //TFT_GREEN
  723.  
  724. tft.setFreeFont(FSS9);
  725. tft.drawString("88", 4, 218, GFXFF);
  726.  
  727. tft.setFreeFont(FSSB9);
  728. tft.drawString("108", 208, 218, GFXFF);
  729.  
  730. uint16_t frq1 = 15 + (( frq_out) - 88) * 10;
  731.  
  732. tft.fillCircle(frq1, 224, 5, TFT_GREEN);
  733.  
  734. }
  735.  
  736.  
  737.  
  738. // -------------------------------------------------------------------------
  739. void dessine_VuMetre()
  740. {
  741. // cadre rectangulaire
  742. tft.fillRect(0, 0, 239, 126, TFT_GREY);
  743. tft.fillRect(5, 3, 230, 119, TFT_WHITE);
  744.  
  745. tft.setTextColor(TFT_BLACK);
  746.  
  747. // graduation chaque 5 deg entre -50 et +50 deg
  748. for (int i = -50; i < 51; i += 5)
  749. {
  750. int tl = 15; // tiret plus long
  751.  
  752. // Coordonnées du tiret à dessiner
  753. float sx = cos((i - 90) * 0.0174532925);
  754. float sy = sin((i - 90) * 0.0174532925);
  755. uint16_t x0 = sx * (100 + tl) + 120;
  756. uint16_t y0 = sy * (100 + tl) + 140;
  757. uint16_t x1 = sx * 100 + 120;
  758. uint16_t y1 = sy * 100 + 140;
  759.  
  760. // Coordonnées of next tick for zone fill
  761. float sx2 = cos((i + 5 - 90) * 0.0174532925);
  762. float sy2 = sin((i + 5 - 90) * 0.0174532925);
  763. int x2 = sx2 * (100 + tl) + 120;
  764. int y2 = sy2 * (100 + tl) + 140;
  765. int x3 = sx2 * 100 + 120;
  766. int y3 = sy2 * 100 + 140;
  767.  
  768. // zone verte
  769. if (i >= 0 && i < 25)
  770. {
  771. tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN);
  772. tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN);
  773. }
  774.  
  775. // zone orange
  776. if (i >= 25 && i < 50)
  777. {
  778. tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE);
  779. tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE);
  780. }
  781.  
  782. if (i % 25 != 0) tl = 8; // Short scale tick length
  783.  
  784. // Recalcule coords in case tick lenght changed
  785. x0 = sx * (100 + tl) + 120;
  786. y0 = sy * (100 + tl) + 140;
  787. x1 = sx * 100 + 120;
  788. y1 = sy * 100 + 140;
  789.  
  790. // Draw tick
  791. tft.drawLine(x0, y0, x1, y1, TFT_BLACK);
  792.  
  793. // Check if labels should be drawn, with position tweaks
  794. if (i % 25 == 0)
  795. {
  796. // Calculate label positions
  797. x0 = sx * (100 + tl + 10) + 120;
  798. y0 = sy * (100 + tl + 10) + 140;
  799. switch (i / 25)
  800. {
  801. case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break;
  802. case -1: tft.drawCentreString("25", x0, y0 - 9, 2); break;
  803. case 0: tft.drawCentreString("50", x0, y0 - 6, 2); break;
  804. case 1: tft.drawCentreString("75", x0, y0 - 9, 2); break;
  805. case 2: tft.drawCentreString("100", x0, y0 - 12, 2); break;
  806. }
  807. }
  808.  
  809. // draw the arc of the scale
  810. sx = cos((i + 5 - 90) * 0.0174532925);
  811. sy = sin((i + 5 - 90) * 0.0174532925);
  812. x0 = sx * 100 + 120;
  813. y0 = sy * 100 + 140;
  814. // Draw scale arc, don't draw the last part
  815. if (i < 50) {tft.drawLine(x0, y0, x1, y1, TFT_BLACK);}
  816. }
  817.  
  818. tft.drawRect(5, 3, 230, 119, TFT_BLACK); // Draw bezel line
  819.  
  820. }
  821.  
  822.  
  823. void plotAiguille(int value)
  824. {
  825. tft.setTextColor(TFT_BLACK, TFT_WHITE);
  826. char buf[8]; dtostrf(value, 4, 0, buf);
  827.  
  828. if (value < -10) value = -10; // Limit value to emulate aiguille end stops
  829. if (value > 110) value = 110;
  830.  
  831. float sdeg = map(value, -10, 110, -150, -30); // Map value to angle
  832. // Calcul tip of aiguille coords
  833. float sx = cos(sdeg * 0.0174532925);
  834. float sy = sin(sdeg * 0.0174532925);
  835.  
  836. // Calcul x delta of aiguille start (does not start at pivot point)
  837. float tx = tan((sdeg + 90) * 0.0174532925);
  838.  
  839. // Erase old aiguille image
  840. tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_WHITE);
  841. tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_WHITE);
  842. tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_WHITE);
  843.  
  844. // Re-plot texte sous l'aiguille
  845. tft.fillRect(100, 70, 40, 25, TFT_WHITE);
  846. tft.setTextColor(TFT_BLACK);
  847. tft.drawCentreString((String)value, 120, 70, 4);
  848.  
  849. // Store new aiguille end coords for next erase
  850. ltx = tx;
  851. osx = sx * 98 + 120;
  852. osy = sy * 98 + 140;
  853.  
  854. // Draw the aiguille in the new postion, magenta makes aiguille a bit bolder
  855. // draws 3 lines to thicken aiguille
  856. tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_RED);
  857. tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_MAGENTA);
  858. tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_RED);
  859. }
  860.  
  861.  
  862.  
  863.  
  864.  
  865.  


CODE SOURCE en C++
  1.  
  2. #include "complexe628.h"
  3. //
  4.  
  5. void Complexe::multi_lambda(float lambda)
  6. {
  7. a *= lambda;
  8. b *= lambda;
  9. }
  10.  
  11.  
  12. Complexe Complexe::operator+ (Complexe c)
  13. {
  14. Complexe r;
  15. r.a=this->a+c.a;
  16. r.b=this->b+c.b;
  17. return r;
  18. }
  19.  
  20.  
  21. Complexe Complexe::operator- (Complexe c)
  22. {
  23. Complexe r;
  24. r.a=this->a-c.a;
  25. r.b=this->b-c.b;
  26. return r;
  27. }
  28.  
  29.  
  30. Complexe Complexe::operator* (Complexe c)
  31. {
  32. Complexe r;
  33. r.a=this->a * c.a - this->b * c.b;
  34. r.b=this->a * c.b + this->b * c.a;
  35. return r;
  36. }
  37.  
  38.  
  39.  
  40. /* rappel: fonction en pascal
  41. function produit_cplx(c1,c2:complexe):complexe;
  42.  begin
  43.   produit_cplx[1]:=c1[1]*c2[1]-c1[2]*c2[2];
  44.   produit_cplx[2]:=c1[1]*c2[2]+c1[2]*c2[1];
  45.  end;
  46. */
  47.  
  48.  

4 Une imprimante 3D, c'est chouette !

Si il y a un achat que je ne regrette pas, c'est bien celui d'une imprimante 3D (Ender 3 Pro) ! ça permet de faire ça ->

5 Ouvrons la boite :

J'ai ajouté un petit ampli BF et deux haut-parleurs aux éléments décrits plus haut, à savoir : la carte ESP32 - 30pins, le récepteur FM à TEA5767 et les trois boutons.

Comme je destine le tout à une utilisation en poste fixe, j'ai alimenté le tout par un adaptateur secteur 5V - 1A.

On peut alimenter les ESP32 par un accu LiPo, mais pas directement :
  • en fin de décharge ils descendent trop bas, 3V << l'entrée 5V
  • et en direct sur l'entrée alim 3V3 il montent trop haut (4V2 >> 3.6 V max)

Les régulateurs linéaires LDO (LOW DROPOUT VOLTAGE) tel que AMS1733 ou HT7133, n'ont un drop-out vraiment "low" que pour des courants très faibles (~10mA).

Quant aux régulateurs "step up" à découpage, ils génèrent trop de parasites HF pour cette application de réception FM large bande. (J'ai testé). Il y a peut-être une soluce en les blindant et en filtrant énergiquement la sortie.

Reste la solution bourrin avec une batterie LiPo 2S (8V4 donc) sur l'entrée 5V (qui est en fait l'input d'un régulateur 3V3 (AMS1117) implanté sur les cartes ESP32)

6 Disposition des éléments

La carte ESP32 est disposée au dessus de l'afficheur TFT avec deux pièces identiques en PLA en forme de H horizontal. Sur la photo ces deux pièces sont orientées perpendiculairement à la représentation sur l'image de droite. Les vis (2mm) sont en plastique noir.

Tous les fichiers source pour l'impression 3D sont disponibles au bas de cet article dans la partie "documents".

7 Evolution...

Et maintenant, que faire de plus ?

Je vais ajouter l'affichage d'un "Analyseur de Spectre temps réel", vous savez, ces petites barres verticales qui dansent en fonction de la musique...
C'est le genre de chose qui devient facile à mettre en oeuvre sur les ESP grâce à leur grande vitesse de calcul (bon d'accord, on va rigoler dans dix ans, quand on lira ça. Quand je vois les performances d'un moustique en vol, vu sa taille, ou de deux papillons qui se poursuivent, je pense qu'il reste encore quelques progrès à faire).

En attendant je vous invite à consulter mes propres publications sur la transformée de Fourier, ici même (je n'ai rien inventé, je tiens juste à rendre les choses abordables pour tout le monde) :

05 avril 2021 :
Pour ne rien vous cacher les choses avancent très favorablement : J'ai terminé le portage de mon code (open source LIBRE de droits) de la transformée de Fourier sur ESP32 avec cet afficheur. Il me reste à l’incorporer dans le code de ce récepteur FM.
Déjà je peux dire que la puissance des ESP est bluffante : Une quarantaine de barres fréquentielles affichées, en couleur, en temps réel avec une fluidité totale, comme on peut le constater sur la petite vidéo ci-contre (qui est un peu saccadée par rapport à la réalité !).

à suivre ici-même très bientôt...


8 Ajout de l'afficheur de spectre audio

06 avril 2021 :
L'incorporation de "l'analyseur de spectre" audio n'a posé aucun problème. L'ESP32 s'en sort très bien, entre la gestion des boutons, le pilotage de la carte récepteur FM TEA5767, l'affichage de la fréquence, du nom de la station, et maintenant le calcul en temps réel de la transformée de Fourier... Je publie le code source à jour et je vais faire une petite vidéo du récepteur qui devient bien plus vivant !

9 Vidéo de bête


10 Documents

code source : :

fichiers boitier 3D : :

11 -

Liens...

7218