Récepteur Radio FM avec un TEA5767 + ATmega8

Le TEA5767 est souvent présenté sur le net, mais presque toujours piloté par un Arduino. Cette réalisation n'utilise pas un Arduino ni les bibliothèques associées, mais un simple ATmega8 avec un code hyper léger en comparaison (des dizaines de "library" en moins !).

1 Petite vidéo montrant les deux modes d'utilisation :

Le changement de la fréquence de réception se fait par un bouton rotatif pas à pas "code gray" (numérique). Deux modes de fonctionnement :
  • Sauts de stations en stations à chaque pas du bouton
  • ou sauts par pas de 0.1MHz pour explorer toute la gamme FM de 88.0 à 108.0 MHz

2 Le premier schéma :

Je travaille sur un second schéma qui utilisera un petit afficheur LCD Nokia 5110 à la place de l'afficheur 2x16 caractères et un ATmega en boitier SMD.

3 Le firmware en langage C

CODE SOURCE en c
  1. /*
  2. Récepteur radio basé sur un module TEA5767
  3. * Version A du firmware -> pour afficheur LCD 2x16c
  4.  
  5. */
  6.  
  7. #define F_CPU 3276800
  8.  
  9. #include <util/delay.h>
  10. #include <avr/io.h>
  11. #include <stdint.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <avr/interrupt.h>
  15.  
  16. #include "dm_lcd3.cpp" // l'attribution des pins/ports pour le LCD (et le type de LCD) est faite dans le fichier "dm_lcd.h"
  17. #include "lcd_curseur_628.cpp"
  18. #include "I2C_628.c"
  19.  
  20.  
  21. char* version = "version 2.1";
  22.  
  23.  
  24. uint16_t f_out; // 880..1080 ; 894 -> pour 89.4MHz // incrémentable par le bouton rotatif
  25. uint8_t freq_H = 0; // pour la PLL
  26. uint8_t freq_L = 0; // pour la PLL
  27. uint8_t num_station;
  28. uint8_t nb_stations;
  29.  
  30. uint16_t pos_curseur; // 0..80
  31. uint8_t status = 0;
  32. uint8_t demande_calcul;
  33. uint8_t etat1, etat2;
  34. uint8_t mode; // 0 = sélection par fréquence; 1 = sélection par stations
  35. uint16_t compteur1;
  36.  
  37.  
  38.  
  39.  
  40. //double freq_available=0;
  41. uint8_t TEA5767_buffer[5]={0x00,0x00,0xB0,0x10,0x00};
  42.  
  43. uint8_t addr1= 0 ; // 0x60;
  44.  
  45. struct station
  46. {
  47. uint16_t frq;
  48. char nom[10+1];
  49. };
  50.  
  51.  
  52. struct station liste_stations[20]; // 0..19
  53.  
  54.  
  55. void init_ports (void) // ports perso
  56. // 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  57. {
  58. DDRB |= 0b11111111;
  59. PORTB = 0b00000000;
  60.  
  61. DDRC = 0b11001111; // bit4=SDA ; bit5=SLC
  62. PORTC = 0b00110000;
  63.  
  64. DDRD = 0b11101010; // portD[0] entrée codeur_ROT ; portD[2] entrée INT0 ; portD[4] = entrée bouton "MODE"
  65. PORTD = 0b00010101; // enable R de rappel sur ports en entrée
  66. }
  67.  
  68.  
  69.  
  70. void InitINTs (void)
  71. {
  72. GICR |= 0b11000000; // gere les INTs - voir page 67 du pdf
  73. MCUCR |= 0b00000010; // The falling edge of INT0 generates an interrupt request. p:67 du pdf
  74.  
  75. TWCR |= 0b00000001; // Bit 0 – TWIE: TWI Interrupt Enable - p:192
  76. }
  77.  
  78.  
  79.  
  80. void init_variables(void)
  81. {
  82. num_station =0;
  83. f_out = liste_stations[num_station].frq;
  84. mode = 1;
  85. demande_calcul = 1;
  86. }
  87.  
  88.  
  89. void init_TWI()
  90. {
  91. // Initialise le périphérique TWI
  92. TWSR = 0x00; // Select Prescaler 1/1 // voir Tableau 20-10. TWI Bit Rate Prescaler p:193
  93. TWBR =72; // SCL frequency = 16000000 / (16 + 2 * 72 * 1) = 100.000 khz
  94. }
  95.  
  96.  
  97.  
  98. void init_lcd()
  99. {
  100. uint8_t i;
  101.  
  102. lcd_init(LCD_DISP_ON_CURSOR); // cursor on
  103. //cd_init(LCD_DISP_ON); // cursor off
  104.  
  105. lcd_cree_5_caract_echelle();
  106.  
  107. lcd_clrscr();
  108. }
  109.  
  110.  
  111. void init_stations()
  112. {
  113. nb_stations = 13; // max 19
  114.  
  115. strcpy(liste_stations[0].nom , "Inter");
  116. liste_stations[0].frq = 894;
  117.  
  118. strcpy(liste_stations[1].nom , "Musique");
  119. liste_stations[1].frq = 929;
  120.  
  121. strcpy(liste_stations[2].nom , "RFM");
  122. liste_stations[2].frq = 956;
  123.  
  124. strcpy(liste_stations[3].nom , "Culture");
  125. liste_stations[3].frq = 978;
  126.  
  127. strcpy(liste_stations[4].nom , "RF-HERAULT");
  128. liste_stations[4].frq = 1011;
  129.  
  130. strcpy(liste_stations[5].nom , "Perso");
  131. liste_stations[5].frq = 885;
  132.  
  133. strcpy(liste_stations[6].nom , "Fun Radio");
  134. liste_stations[6].frq = 918;
  135.  
  136. strcpy(liste_stations[7].nom , "Rd Clapas");
  137. liste_stations[7].frq = 935;
  138.  
  139. strcpy(liste_stations[8].nom , "Cherie FM");
  140. liste_stations[8].frq = 969;
  141.  
  142. strcpy(liste_stations[9].nom , "FIP");
  143. liste_stations[9].frq = 997;
  144.  
  145. strcpy(liste_stations[10].nom , "Nostalgie");
  146. liste_stations[10].frq = 1039;
  147.  
  148. strcpy(liste_stations[11].nom , "Fr Info");
  149. liste_stations[11].frq = 1051;
  150.  
  151. strcpy(liste_stations[12].nom , "Rd Classiq");
  152. liste_stations[12].frq = 1073;
  153. }
  154.  
  155.  
  156. void lcd_gotoxy_clrEOL (int x, int y)
  157. // place le curseur en x,y et efface jusqu'à la fin de la ligne
  158. {
  159. lcd_gotoxy(x, y);
  160. uint8_t i;
  161. for (i=x; i<20; i++){ lcd_puts(" "); }
  162. lcd_gotoxy(x, y);
  163. }
  164.  
  165.  
  166.  
  167.  
  168. void lcd_aff_nb (uint32_t valeur, uint8_t nb_chiffres, uint8_t nb_decimales, uint8_t affi_zeros)
  169. {
  170. //affiche un nombre en representation decimale
  171. // affi_zeros = 1 affiche les zéros non significatifs à gauche.
  172. unsigned char r ;
  173. char tbl[7];
  174. char digit;
  175. uint8_t i;
  176.  
  177. for (i=1; i<=nb_chiffres; i++)
  178. {
  179. r=48 + valeur % 10; // modulo (reste de la division)
  180. valeur /= 10; // quotient
  181. tbl[i]=r;
  182. }
  183. uint8_t debut = 1-affi_zeros; // pour ne pas afficher des zeros à gauche du 1er chiffre
  184. for (i=1; i<=nb_chiffres; i++)
  185. {
  186. if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
  187. digit = tbl[nb_chiffres +1 -i];
  188. if (digit != '0') {debut = 0;}
  189. if ((digit != '0') || (debut == 0)) {lcd_putc(digit);}
  190. }
  191. }
  192.  
  193.  
  194. void lcd_aff_nb_relatif (int16_t valeur, uint8_t nb_chiffres, uint8_t nb_decimales)
  195. {
  196. //affiche un nombre relatif (c.a.d positif ou négatif) en representation decimale
  197. unsigned char r ;
  198. char tbl[7];
  199. char digit;
  200. uint8_t i;
  201. uint8_t affi_zeros = 1; // affi_zeros = 1 affiche les zéros non significatifs à gauche.
  202.  
  203. if (valeur <0)
  204. {
  205. valeur = -valeur;
  206. lcd_putc('-');
  207. }
  208. else
  209. {
  210. lcd_putc('+');
  211. }
  212.  
  213. for (i=1; i<=nb_chiffres; i++)
  214. {
  215. r=48 + valeur % 10; // modulo (reste de la division)
  216. valeur /= 10; // quotient
  217. tbl[i]=r;
  218. }
  219. uint8_t debut = 1-affi_zeros; // pour ne pas afficher des zeros à gauche du 1er chiffre
  220. for (i=1; i<=nb_chiffres; i++)
  221. {
  222. if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
  223. digit = tbl[nb_chiffres +1 -i];
  224. if (digit != '0') {debut = 0;}
  225. if ((digit != '0') || (debut == 0)) {lcd_putc(digit);}
  226. }
  227. }
  228.  
  229.  
  230.  
  231. void lcd_aff_nb_form3 (uint32_t valeur, uint8_t nb_chiffres)
  232. {
  233. //affiche un nombre en representation decimale en separant les groupes de 3 chiffres
  234. unsigned char r ;
  235. char tbl[7];
  236. uint8_t i;
  237.  
  238. for (i=1; i<=nb_chiffres; i++)
  239. {
  240. r=48 + valeur % 10; // modulo (reste de la division)
  241. valeur /= 10; // quotient
  242. tbl[i]=r;
  243. }
  244. for (i=1; i<= nb_chiffres; i++)
  245. {
  246. if ((((i-1) % 3) == 0 ) && (i>1)) { lcd_puts("."); }
  247. lcd_putc(tbl[nb_chiffres +1 -i]);
  248. }
  249. }
  250.  
  251.  
  252.  
  253. void lcd_aff_bin (uint16_t valeur, uint8_t nb_digits)
  254. {
  255. //affiche un nombre en representation binaire
  256. // 16 bits max
  257. uint8_t r ;
  258. char tbl[17];
  259. uint8_t i;
  260.  
  261. for (i=1; i<=nb_digits; i++)
  262. {
  263. r= 48 + valeur % 2; // modulo (reste de la division)
  264. valeur /= 2; // quotient
  265. tbl[i]=r;
  266. };
  267.  
  268. for (i=1; i<=nb_digits; i++)
  269. {
  270. lcd_putc(tbl[nb_digits +1 -i]);
  271. }
  272. }
  273.  
  274.  
  275. void affiche_echelle()
  276. {
  277. // Trace l'échelle graduée
  278. uint8_t i;
  279. lcd_gotoxy(0,1);
  280. for(i = 0; i < 15; i++) { lcd_putc(CARACT_GRADUATION); }
  281. }
  282.  
  283.  
  284. void affiche_frequence()
  285. {
  286. uint8_t i;
  287. uint8_t Ok=0;
  288.  
  289. lcd_gotoxy_clrEOL (0, 0);
  290. lcd_aff_nb(f_out, 4, 1, 0);
  291.  
  292. // if (mode == 0) { lcd_puts(" MHz"); }
  293. lcd_puts(" ");
  294.  
  295. for (i=0; i<=nb_stations; i++)
  296. {
  297. if (f_out == liste_stations[i].frq )
  298. {
  299. lcd_puts (liste_stations[i].nom);
  300. Ok=1;
  301. break;
  302. }
  303. }
  304. if (Ok == 0) {lcd_puts("MHz ");}
  305.  
  306. pos_curseur = (f_out-880) * 4 / 10;
  307. affiche_curseur2(pos_curseur);
  308. }
  309.  
  310.  
  311. void affiche_num_station()
  312. {
  313. lcd_gotoxy(15,0);
  314. if (mode == 0) { lcd_gotoxy(15,0); lcd_putc('F'); }
  315. else
  316. {
  317. lcd_gotoxy(14,1);
  318. lcd_aff_nb (num_station, 2, 0, 1);
  319. }
  320.  
  321. }
  322.  
  323.  
  324.  
  325.  
  326. /***********************************************************************
  327. // traitement du codeur_rot()
  328. // codeur incrémental code Gray avec en plus fonction bouton poussoir
  329. // le codeur rotatif utilisé est toujours à 11 en position stable
  330. // dans le sens CW il fait 11->01->00->10->11 et par conséquent dans le sens CCW il fait 11->10->00->01->11
  331. // 11
  332. // 01
  333. // 00
  334. // 10
  335. // 11
  336. // il suffit donc de connecter un bit sur le pin INT0 (PD2) et de déclencher l'INT sur front descendant (passage de 0 à 1)
  337. // cette INT lira alors l'état de l'autre bit (connecté à PD0 par exemple). Cet état diffère suivant le sens de rotation
  338.  
  339. ************************************************************************/
  340.  
  341. ISR(BADISR_vect)
  342. {
  343. // évite de planter si une int est enable et pas de procedure associée écrite (ce qui fait reseter l'ATmega)
  344. }
  345.  
  346.  
  347. ISR (INT0_vect)
  348. {
  349. //interruption sur front descendant sur l'entree Int0
  350. // déclenchee par la rotation du codeur_ROT (1) -> +/-fréquence ou +/-trim ajustement fréquence 8MHz pour étalonnage par GPS
  351.  
  352. etat1 = PIND & 0b00000001;
  353.  
  354. if(mode==0)
  355. {
  356. if (etat1 == 0)
  357. {
  358. if ( f_out >= 880) {f_out -= 1;}
  359. if ( f_out < 880)
  360. {
  361. f_out = 880;
  362. mode = 1;
  363. }
  364. }
  365. else
  366. {
  367. if ( f_out <= 1080) {f_out += 1;}
  368. if ( f_out > 1080)
  369. {
  370. f_out = 1080;
  371. mode = 1;
  372. }
  373. }
  374. affiche_frequence();
  375. demande_calcul = 1;
  376. }
  377.  
  378. if(mode==1)
  379. {
  380. if (etat1 == 0)
  381. {
  382. if ( num_station > 0) {num_station -= 1;}
  383. }
  384. else
  385. {
  386. if ( num_station < nb_stations) {num_station += 1;}
  387. if ( num_station > (nb_stations-1))
  388. {
  389. num_station = nb_stations;
  390. f_out = 880;
  391. mode = 0;
  392. }
  393. }
  394. if (mode==1) { f_out = liste_stations[num_station].frq; }
  395. affiche_echelle();
  396. affiche_frequence();
  397. demande_calcul = 1;
  398. }
  399.  
  400. }
  401.  
  402.  
  403.  
  404. void envoi_frequence(uint8_t addr_i)
  405. {
  406. uint8_t r1;
  407. /**
  408. Writing to TEA5767
  409. Radio chip is controlled by writing 5 bytes one by one.
  410. During writing TEA5767 IC2 address is 0x60, reading – 0x61.
  411. Frequency is controlled by 14 bit word, that is written to two 8 bit registers.
  412. **/
  413.  
  414. uint8_t datas[5]; // tableau
  415. datas[0]=freq_H;
  416. datas[1]=freq_L; // bit7 = MUTE ; bit6 = search mode
  417. datas[2]=0xB0; // high side LO injection is on
  418. datas[3]=0x10; // Xtal is 32.768 kHz
  419. datas[4]=0x00;
  420.  
  421. r1=i2c_write_n_bytes(addr_i, datas, 5); // Passage du tableau en paramètre (on n'écrit ni indice ni étoile dans le cas d'un tableau)
  422. }
  423.  
  424.  
  425.  
  426. calcul_frequence_out()
  427. {
  428. float f1;
  429. uint32_t f2;
  430. f1 = (float) f_out / 10;
  431. f2=4*(f1 * 1000000 + 225000) / 32768 +1; //calcul du mot pour la PLL
  432. freq_H=f2 >> 8;
  433. freq_L=f2 & 0xFF;
  434. }
  435.  
  436.  
  437.  
  438. int main()
  439. {
  440. uint8_t addr1;
  441.  
  442. init_stations();
  443. init_variables();
  444.  
  445. init_ports();
  446. InitINTs();
  447. init_lcd();
  448.  
  449. sei(); // enable interruptions
  450.  
  451. lcd_clrscr();
  452. lcd_puts("Radio FM TEA5767");
  453. lcd_gotoxy(0,1);
  454. lcd_puts(version);
  455. _delay_ms(100);
  456.  
  457. init_TWI();
  458.  
  459. calcul_frequence_out();
  460.  
  461. TWCR = (1<<TWEN); //enable TWI
  462.  
  463. envoi_frequence(0x60);
  464. _delay_ms(2);
  465.  
  466. // lcd_command(LCD_DISP_ON); // cursor off
  467. lcd_clrscr();
  468.  
  469. affiche_echelle();
  470. affiche_frequence();
  471.  
  472. //PORTB |= 0b00000010;
  473.  
  474. while(1)
  475. {
  476.  
  477. if (demande_calcul == 1)
  478. {
  479. demande_calcul=0;
  480. calcul_frequence_out();
  481. envoi_frequence(0x60);
  482. // if (mode == 1) {affiche_num_station();}
  483. affiche_num_station();
  484. }
  485.  
  486. etat2 = PIND & 0b00010000; // bouton MODE
  487. if (etat2 != 0b00010000)
  488. {
  489. mode ^= 0b00000001;
  490. lcd_clrscr();
  491. _delay_ms(10);
  492.  
  493. // if (mode == 0) {PORTB &= 0b11111101;} else {PORTB |= 0b00000010;} // LED
  494. demande_calcul=1;
  495. affiche_echelle();
  496. affiche_frequence();
  497. _delay_ms(10);
  498. }
  499.  
  500. _delay_ms(2);
  501. }
  502.  
  503. }
  504.  
  505.  
  506.  
  507.  
  508.  

4 Le circuit imprimé

09 juin 2015:
Je l'ai flashé avec ma machine laser 405nm décrite sur ce site.

5 Ampli BF

Le niveau e sortie étant trop faible et surtout à trop haute impédance pour attaquer directement une paire d'écouteurs stéréo afin d'en faire un baladeur, j'ai conçu un petit ampli BF minimaliste.

J'ai utilisé un simple ampli OP entrées FET, le TL082 plutôt qu'une paire de LM386 ou un ampli stéréo de puissance pour les raisons suivantes :
  • Très petite taille (boîtier SMD 8 pattes)
  • Puissance très faible mais suffisante pour des écouteurs
  • niveau de sortie compatible pour attaquer un ampli BF plus puissant (HP amplifiés pour ordi PC par exemple)
  • Côté basique de la chose, pas de trucs compliqués à l'intérieur...
  • Prix dérisoire

6 Circuit imprimé de l'Ampli BF

Bien entendu j'ai dessiné ce circuit avec le logiciel libre Kicad sous Linux (Mint17), puis exporté au format "gerber" et flashé avec ma photo-flacheuse Laser décrite sur ce site.

7 Le circuit prêt ppour la soudure des composants

Le circuit est vraiment minuscule, il est photographié ici derrière une forte loupe ce qui explique la déformation "en tonneau" (ou plutôt le cintrage) de l'image.

8 Le récépteur dans son boitier

Voyons maintenant ce qu'il y a dans la boite !

9 Batterie et antenne :

Au fond de la boite j'ai placé une batterie LI-ion très fine d'APN, dont la tension 3.7V est suffisante pour alimenter le TEA5767, l'ATmega8, l'afficheur LCD et le TL082. Une tension de 5V semble procurer une plus grande sensibilité au récepteur, mais cela imposait deux batteries + 1 régulateur 5V. Un convertisseur DC-DC 3V -> 5V s'avéra produire trop de parasites.

La petite plaquette en alu à la base de l'antenne ne sert qu'à renforcer la solidité lorsqu'on fait tourner l'antenne.

On aperçoit le socle jack femelle au fond près de l'afficheur.

La R de 1k5 au premier plan a été rajouté au dernier moment lorsque j'ai opté pour l'alimentation par l'accu 3V7 au lieu de 5V prévus au départ, elle sert à retoucher le contraste de l'afficheur LCD.

10 Vue de face des circuits

Le fil rouge qui vient du + de la batterie est relié à l'interrupteur situé dans le potentiomètre stéréo, ce qui permet de couper totalement l'alim au repos.
L'annotation 5V en haut est à remplacer par 3V7 !!

11 Les deux circuits côte à côte

A cette échelle, la résistance de 1k5 1/4W parait bien grosse !

12 Le contacteur de batterie

C'est un petit morceau de circuit imprimé de 0.5mm sur lequel j'ai soudé deux languettes flexibles en laiton. Attention : avec une telle batterie, courts circuits strictement interdits !

13 reste à faire...

Je vais faire évoluer le firmware pour :
  • ajouter une mise en veille automatique de l'ATmega et de l'afficheur
  • ajouter un bouton permettant de mémoriser les stations dans la mémoire EEPROM de l'ATmega.


à suivre donc...

14 DOCUMENTS

Ces archives qui comprennent la totalité des fichiers sources sont mis à jour au fur et à mesure de l'avancement du projet.

15 -

Liens...



15409