Générateur HF
90 à 160 MHz sortie sinus
synthétisé par PLL, piloté par un ATmega8
pas de 100, 50 et 25 kHz

Le but de cette étude n'est pas de réaliser un émetteur de radio mais un générateur de laboratoire de très faible puissance (<1mW c.a.d < 0 dBm), permettant d'expérimenter et de régler des circuits VHF comme des filtres par exemple (c'est d'ailleurs ce à quoi il va servir immédiatement), afin de confronter la théorie à l'expérience, sans rayonner. L'appareil doit être enfermé dans un boîtier métallique relié à la masse. La sortie du signal se fera sur une prise BNC. Un filtre VHF sera intercalé sur l'alimentation.

Voir les liens en bas de page concernant l'attribution des fréquences radio.
Les fréquences générées par cet appareil couvrent des bandes aviation(autour de 120MHz), amateur (144MHz ou "bande des 2m"), militaire... des perturbations occasionnées dans ces bandes peuvent avoir des conséquences graves, autant dire que toute tentative d'émission dans ces bandes serait rapidement repérée et peut vous amener directement en prison.

A la rigueur les radio-amateurs en possession de la licence et ayant des connaissances en électronique et en VHF pourront envisager d'adapter cette réalisation pour leurs besoins propres.

Cette étude fait suite à celle du générateur 90MHz à PLL de type MC145170 décrit sur ce site.

1 Vues d'ensemble

2 Détail du datasheet du circuit de PLL - LM 7001 de SANYO

Afin d'obtenir un signal de fréquence très précise, on utilisera une boucle d'asservissement de fréquence (PLL) et un oscillateur à quartz comme référence.

J'ai choisi comme PLL le LM7001 qui permet de synthétiser des fréquences par pas de 100, 50 et 25kHz jusqu'à 160MHz.

Ci-contre un extrait  du data sheet du LM7001.

3 Le schéma de la partie HF:

J'ai décrit précédemment l'étage VCO à transistor JFET 2N3819 (voir ma page sur le générateur 90 MHz à LM145170). J'aurais pu remplacer purement et simplement cet étage par un VFO intégré, par exemple un POS150 mais le prix (35 euro) de ce dernier me semble injustifié. Et puis vous l'aurez remarqué, je suis adepte du "je réalise tout moi-même", c'est tellement plus satisfaisant!

Le signal de référence est fixé = 100kHz (ou 50kHz ou 25kHz) afin d'obtenir le pas de synthèse correspondant. Il est obtenu par division par 72 (ou 144 ou 288) de la fréquence d'un oscillateur à quartz de 7,200 MHz (oscillateur intégré compensé en température. Un simple quartz de 7,2MHz est possible). Ces valeurs de division sont pré-câblées dans le LM7001 et sélectionnables par envoi d'un mot de commande adéquat par l'ATmega8 (en mode série sur DATA, CL, CE voir datasheet du LM7001).
Le signal du VCO est injecté sur l'entrée FM-IN du LM7001 afin d'être divisé par une valeur N (codée par les bits D0..D13) choisie de façon à obtenir également la valeur du signal de référence .

Si on veut obtenir un signal de sortie de 144,000 MHz en mode pas de 100kHz, l'ATmega8 devra écrire N = 1440 dans le registre N de la PLL. (144MHz / 1440 = 100kHz) de même:

Si on veut obtenir un signal de sortie de 145,325 MHz en mode pas de 25kHz, l'ATmega8 devra écrire N = 5813 dans le registre N de la PLL. (145325 kHz / 25kHz = 5813) etc...

On voit que le mode opératoire diffère quelque peu de celui du MC145170...

Les deux signaux de 100kHz (ou 50 ou 25kHz) (quartz/R , VCO / N) sont comparés au sein du LM7001 et le signal d'erreur est disponible sur le pin14. Ce signal d'erreur sert à piloter la varicap (D1) après calibrage en tension (24V) par T1, filtre passe bas (R4 - C5 - R5 - C6). La variation possible de fréquence obtenue avec une seule self est de l'ordre de 2 / 1 compte tenu de la tension de commande élevée pour la varicap, obtenue à partir du 12V par le doubleur de tension (40106 et diodes Schottky).

La variation possible de fréquence obtenue permet de synthétiser des fréquences de 90MHz à 160MHz sans commutation de selfs ni de condensateurs.

A NOTER:

Contrairement au générateur 90MHz à PLL de type MC145170 décrit précédemment sur ce site, le signal obtenu, bien que sinusoïdal, n'est pas dépourvu d'harmoniques. Il reste à concevoir un étage de sortie avec filtrage musclé ( filtre passe bas du second ordre (ou mieux) coupant à 160MHz). Chose passionnante elle aussi, et application directe de cette réalisation!
Je vous tiens au courant.

4 Réalisation de la carte HF:

Détails de la self à prise médiane:

  • 6 spires avec prise médiane (2 x 3 spires donc)
  • fil de cuivre émaillé de diamètre 6/10mm
  • bobinée sur un cylindre de 4mm de diamètre
  • spires non jointives
  • longueur totale de la bobine = 6mm
Un fil de plus gros diamètre fausse totalement le fonctionnement... Donc ces caractéristiques sont critiques.

On notera C3 (1nF, le tout petit jaune en bas à gauche) de type céramique.

Quant à l'oscillateur intégré, il peut-être remplacé par un simple quartz, au détriment de la stabilité en température de la fréquence.

5 Le verdict du fréquencemètre:

Fréquence désirée... (Pourquoi 144MHz? Allez savoir...)

...et fréquence obtenue. Une fois la PLL verrouillée,
la précision ne dépend plus que de celle de l'oscillateur 7,2MHz.

6 Le circuit imprimé:

Des pistes courtes pour les signaux VHF (partie en haut à gauche)

7 Fréquences de l'oscillateur Colpitts (sans PLL) en fonction de la valeur du registre OCR2 (qui détermine le rapport cyclique du signal PWR sur la sortie OC2 (=PB3))

8 Détermination de la valeur de OCR2 pour centrer la fenêtre de capture sur la fréquence désirée:

La diode varicap du VCO est alimenté par la combinaison de deux tensions:
  • une première tension générée classiquement par le comparateur de phase de la PLL après filtre passe-bas
  • une seconde tension générée par l'ATmega (par intégration d'un signal PWM [modulation à largeur d'impulsion]) afin de centrer en permanence la fenêtre de capture de la PLL sur la fréquence désirée, ce qui permet d'étendre considérablement la plage de fréquences couvertes. La largeur d'impulsion (et donc la valeur de la tension obtenue après filtrage) est directement fonction de la valeur écrite dans le registre "OCR2" de l'ATmega. (à noter en passant que l'obtention de ce signal PWM n'utilise pas d'interruption, et par voie de conséquence ne consomme PAS de temps machine sur le microcontrôleur)
Voici la méthode empirique que j'ai utilisée pour obtenir une fonction analytique représentant la valeur de OCR2, fonction mise en œuvre dans le programme de l'ATmega8.
(J'ai mesuré les valeurs maximales et minimales admissibles de OCR2 pour quelques fréquences réparties sur la gamme couverte, et j'ai pondu une fonction (en vert) qui entre dans le gabarit obtenu)

9 -

10 Le schéma de la carte

La carte logique



Sous l'ATmega8 on voit le connecteur de programmation "en-circuit". Le composant noir rectangulaire à droite est le récepteur infra-rouge dédié à la liaison RC5 par télécommande universelle TV.

Où trouver l'afficheur 4 lignes 20 caractères? sur Ebay!

On peut tout à fait se contenter d'un afficheur 2 lignes 16 caractères, en modifiant légèrement le soft.

A noter: Pour cette réalisation, le récepteur de télécommande infra-rouge n'est pas utilise, et peut donc ne pas être câblé.

Un connecteur (au pas de 2.54mm) pour relier les ports permet d'utiliser la carte pour tester diverses réalisations.

11 Le

Un bouton rotatif qui permet de faire évoluer la fréquence pas-à-pas et très rapidement, en avant ou en arrière. Les digits ne buttent pas sur les valeurs 0 et 9, la retenue est reportée sur le digit adjacent, on passe par exemple automatiquement de 100,900 à 101,000 MHz... et vice-versa.

Ce bouton rotatif est un commutateur à résistances CMS (19 résistances de 3k3 câblées en série), commutateur récupéré sur un lave-linge à programmateur électronique (et servant sur cette machine à sélectionner les programmes de lavage).

12 -

Détail du commutateur.

On peut le remplacer par un commutateur rotatif classique et câbler des résistances classiques (d'ailleurs pour cette application, il est nul besoin de 19 positions, 3 suffiraient pour discriminer le sens de rotation, mais une dizaine ou plus permettent d'obtenir un plus grand confort d'utilisation) en modifiant le soft en conséquence. L'avantage des résistances commutées consiste en l'utilisation d'un seul bit de port en entrée de l'ATmega8 (en utilisant son convertisseur analogique-numérique).

13 CODE SOURCE du programme pour l'ATmega8

Voici le code source version 1.3 (cette source évoluera... reste 4k libres!)
Il est donc très court (700 lignes seulement)



Voir aussi ma page sur les microcontroleurs ATmega pour la programmation de ATmega en langage C, et sous Linux.

14 -

CODE SOURCE en langage C
  1. /*==============================================================================
  2. =
  3. par Silicium628
  4. versions: voir plus bas dans la partie "const" - derniere mise à jour 15 avril 2009
  5. ================================================================================
  6. Notes de versions:
  7. la version 3.1 en Pascal depassait en taille les 8 ko max de l'ATmega8 si compilee en mode normal
  8. ET la compilation en mode 'make and optimise' semble boguee: il se produit des erreurs a l'execution
  9. ================================================================================
  10. Pilotage de la PLL integree LM7001 en mode FM, pas = 100, 50, et 25kHz
  11. -Fréquences synthétisées 96 à 160 MHz avec la même self dans le VCO
  12. -Affichage LCD 2 x 20 caractères ("splitté entre deux ports de l'ATmega)
  13. -Sélections rapide des fréquences par bouton rotatif ('en continu')
  14. -pas de synthese sélectionnable = 100, 50, et 25kHz
  15. -sauts en frequences de 10MHz, 1MHz, 100kHz, 50kHz et 25kHzpar rotation du
  16. bouton pas-a-pas
  17.  
  18.   F_kHz := Facteur_N * PAS; (Facteur_N est le coeficient de division de la frequence envoyé à la PLL)
  19.  
  20. on definit 5 modes de fonctionnement du bouton rotatif (variable 'mode' et numero de la LED allumee)
  21. à chaque mode correspond une longueur de saut de la frequence de sortie ainsi qu'un PAS de synthese suivant le tableau suivant:
  22.  
  23. MODE SAUT PAS
  24. 1 25kHz 25kHz
  25. 2 50kHz 50kHz
  26. 3 100kHz 100kHz
  27. 4 1MHz 100kHz
  28. 5 10MHz 100kHz
  29.  
  30. La diode varicap du VCO est alimenté par la combinaison de deux tensions:
  31. -un première tension genérée classiquement par le comparateur de phase de la PLL apres filtre passe-bas
  32. -une seconde tension generée par l'ATmega (par integration d'un signal PWM [modulation à largeur d'impultion])
  33. afin de centrer en permanence la fenêtre de capture de la PLL sur la fréquence désirée, ce aui permet
  34. d'étendre considérablement la plage de fréquences couvertes.
  35.  
  36. ================================================================================
  37. */
  38. // #include <math.h>
  39.  
  40.  
  41. #define F_CPU 16000000
  42.  
  43. #include <avr/io.h>
  44. #include <util/delay.h>
  45.  
  46. #include "dm_lcd.c"
  47.  
  48.  
  49.  
  50. #define bouton_UP 0b00000100
  51. #define bouton_DOWN 0b00001000
  52. #define RAZ_4017 0b00000001
  53. #define clk_4017 0b00000010
  54.  
  55. char * version = "1.4";
  56.  
  57. int mode; // = 1..5 pointeur vers longueur de sauts et numero de la LED à allumer.
  58. int PAS; // 10M, 1M, 25, 50, 100 kHz ; longueur des sauts effectues avec le bouton rotatif
  59. unsigned long int facteur_N; // facteur de division à envoyer à la PLL dans le registre "Programmable divider"
  60. unsigned long int F_kHz;
  61. int memo_pos_rot;
  62. int pos_rot;
  63. unsigned long int data; //D0..D13 + T0..1 Note: D0=LSB
  64. unsigned int commande; //B0..B2 + TB + R0..2 + S Note: B0=LSB , S=MSB
  65.  
  66. void init_ports (void) // ports perso
  67. // 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  68. {
  69. PORTB = 0b00000000;
  70. DDRB |= 0b00001000; // portB[3] = sortie (OC2)
  71.  
  72. DDRC &= 0b001111; //PC4 en entree (IR) PC5 en entree (ADC5)
  73.  
  74. DDRD = 0b11110011;
  75. PORTD = 0b00001100; // active R de pullup sur PD2 et PD3 en entrées
  76. }
  77.  
  78.  
  79. void InitADC (void)
  80. {
  81. ADCSRA = _BV(ADEN) | _BV(ADPS2); // Activate ADC with Prescaler 16 --> 1Mhz/16 = 62.5kHz
  82. ADMUX = 5; // Select pin ADC5 using MUX
  83. }
  84.  
  85.  
  86. void InitINTs (void)
  87. /*
  88.  TCCR2:
  89. wgm21,20 =11 ->Fast PMW
  90.  
  91. com21,com20=01 ->Set OC2 on Compare Match, clear OC2 at TOP (valable pour le mode Fast PWM); voir p:116
  92.  
  93. bits2,1,0: prescaler (010 = 1/8)
  94.  
  95. */
  96. {
  97. // div
  98.  
  99. TCCR2= 0b01111010; // Timer2 utilisé. mode Fast PWM (WGM21,20 = 11); OC2 = sortie PWM voir p:115
  100. TIMSK |= 0b00000000; // INT Timer2 comp disable; INT Timer2 overflow disable;
  101. GICR |= 0b00000000; // gere les INTs voir page 67 du pdf
  102. MCUCR |= 0b00000010; // The falling edge of INT0 generates an interrupt request. p:67 du pdf
  103.  
  104. }
  105.  
  106.  
  107. void lcd_gotoxy_clrEOL (int x, int y)
  108. // place le curseur en x,y et efface jusqu'a la fin de la ligne
  109. {
  110. lcd_gotoxy(x, y);
  111. int i;
  112. for (i=x; i<20; i++)
  113. { lcd_puts(" "); }
  114. lcd_gotoxy(x, y);
  115. }
  116.  
  117.  
  118. void lcd_aff_nb (unsigned long int valeur, int nb_chiffres, int nb_decimales )
  119. //affiche un nombre en representation decimale
  120. // 266 octets
  121. {
  122. unsigned char r ;
  123. char tbl[7];
  124. unsigned i;
  125.  
  126. for (i=1; i<=nb_chiffres; i++)
  127. {
  128. r=48 + valeur % 10; // modulo (reste de la division)
  129. valeur /= 10; // quotient
  130. tbl[i]=r;
  131. };
  132. for (i=1; i<=nb_chiffres; i++)
  133. {
  134. if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
  135. lcd_putc(tbl[nb_chiffres +1 -i]);
  136. }
  137.  
  138. }
  139.  
  140.  
  141. void lcd_aff_bin (unsigned long int valeur, int nb_digits)
  142. //affiche un nombre en representation binaire
  143. // 16 bits max
  144. {
  145. unsigned char r ;
  146. char tbl[17];
  147. unsigned i;
  148.  
  149. for (i=1; i<=nb_digits; i++)
  150. {
  151. r= 48 + valeur % 2; // modulo (reste de la division)
  152. valeur /= 2; // quotient
  153. tbl[i]=r;
  154. };
  155.  
  156. for (i=1; i<=nb_digits; i++)
  157. {
  158. lcd_putc(tbl[nb_digits +1 -i]);
  159. }
  160. }
  161.  
  162.  
  163.  
  164. void calcul_PAS(void)
  165. {
  166. switch (mode)
  167. {
  168. case (1): PAS= 25 ;
  169. break;
  170. case (2): PAS= 50;
  171. break;
  172. default: PAS= 100;
  173. break;
  174. }
  175. }
  176.  
  177.  
  178. void init_variables(void)
  179. {
  180. mode= 3;
  181. calcul_PAS();
  182. facteur_N= 1440; // pour F_out = 100 * 1440 = 144000 kHz = 144 MHz
  183. }
  184.  
  185.  
  186. void clk_PLL(void) // sur pin CL
  187. {
  188. _delay_us(10);
  189. PORTD |= 0b01000000;
  190. _delay_us(10);
  191. PORTD &= 0b10111111;
  192. _delay_us(10);
  193. }
  194.  
  195.  
  196. void out_PLL(void) // 24 bits vers LM7001; voir datasheet LM7001
  197.  
  198. {
  199. int n;
  200. //calcul du mot de commande
  201. // ligne ci-dessous = config pour pas=10kHz avec Qz=7.2MHz
  202. // ATTENTION: sur le datasheet, les mots binaires sont tous écrit avec le LSB à GAUCHE. Merci Sanyo!
  203. // octet de commande = S,R2,R1,R0,TB,B2,B1,B0 (écrit à l'endroit, LSB à DROITE)
  204.  
  205. switch (PAS)
  206. {
  207. case (10) : commande = 0b10010000; //S=1 (FM-IN); R2..0=001 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
  208. break;
  209. case (25) : commande = 0b10100000; //S=1 (FM-IN); R2..0=010 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
  210. break;
  211. case (50) : commande = 0b11000000; //S=1 (FM-IN); R2..0=100 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
  212. break;
  213. case (100) : commande = 0b10000000; //S=1 (FM-IN); R2..0=000 ; TB,B2,B1,B0,TB=0 (diviseur de la ref fixé par R2..0)
  214. break;
  215. default: {}
  216. }
  217.  
  218. //envoi de la frequence
  219.  
  220. PORTD &= 0b10111111; // CL = 0
  221. // active_CE; (pin CE du LM7001)
  222. PORTD |= 0b00100000; // ENB=1 (active le transfert entre le uC et le MC145170)
  223.  
  224. unsigned long int masque;
  225.  
  226. for (n=0; n<= 13; n++) // on sort le LSB en premier
  227. {
  228. masque = (1 << n); // masque = 00000000000001 .. 10000000000000 le '1' se deplacant de droite a gauche, 14 bits en tout
  229. // if bit(facteur_N, n)
  230. if ( (facteur_N & masque) != 0) PORTD |= 0b10000000;
  231. else PORTD &= 0b01111111;
  232.  
  233. clk_PLL();
  234. }
  235.  
  236. PORTD &= 0b01111111; // T0 (toujours =0)
  237. clk_PLL();
  238.  
  239. PORTD &= 0b01111111; // T1 (toujours =0)
  240. clk_PLL();
  241.  
  242. // envoi de la commande
  243.  
  244. for (n=0; n<=7; n++) // on sort le LSB = B0 en premier et le MSB (S) en dernier.
  245. {
  246. // ATTENTION: sur le datasheet, les mots binaires sont tous écrit avec le LSB à GAUCHE. Merci Sanyo!
  247. masque = (1 << n);
  248. // if bit(commande, n)
  249. if (commande & masque) PORTD |= 0b10000000;
  250. else PORTD &= 0b01111111;
  251.  
  252. clk_PLL();
  253. }
  254.  
  255. // desactive_CE;
  256. PORTD &= 0b11011111; // ENB=0 (desactive ENB/ ce qui effectue le transfert dans les registres internes)
  257. }
  258.  
  259.  
  260.  
  261. void calcul_Frequence(void)
  262. {
  263. F_kHz=facteur_N * PAS;
  264. }
  265.  
  266.  
  267. void affiche_facteur_N (void)
  268. {
  269. lcd_gotoxy_clrEOL(0, 3);
  270. lcd_aff_nb(facteur_N, 4, 0);
  271. }
  272.  
  273.  
  274. void affiche_frequence(void)
  275. {
  276. calcul_Frequence();
  277. lcd_gotoxy_clrEOL (4, 0);
  278. lcd_aff_nb(F_kHz ,6, 3);
  279. lcd_puts("MHz");
  280. }
  281.  
  282.  
  283. void Affiche_OCR2(void)
  284. {
  285. lcd_gotoxy_clrEOL (6, 3);
  286. lcd_puts("OCR2= ");
  287. lcd_aff_nb(OCR2, 3, 0);
  288. }
  289.  
  290. void affiche_mode(void)
  291. {
  292. lcd_gotoxy(8, 1);
  293.  
  294. switch (mode)
  295. {
  296. case (1) : lcd_puts("25 kHz ");
  297. break;
  298.  
  299. case (2) : lcd_puts("50 kHz ");
  300. break;
  301.  
  302. case (3): lcd_puts("100 kHz");
  303. break;
  304.  
  305. case (4) : lcd_puts("1 MHz ");
  306. break;
  307.  
  308. case (5) : lcd_puts("10 MHz ");
  309. break;
  310.  
  311. default: {}
  312. }
  313.  
  314. // LCDclrEOL;
  315. lcd_gotoxy(11, 2);
  316. // LCDclrEOL;
  317. lcd_aff_nb(PAS ,3, 0);
  318. lcd_puts("kHz");
  319. }
  320.  
  321.  
  322. void out_PWM(void)
  323. //le signal PWM sorti sur le pin OC2 est convertit en une tension continue (0..24V)
  324. //qui permet de centrer la fenetre de capture de la PLL (VCO + LM7001) sur la frequence desirée
  325. //La valeur est obtenue par la formule analytique OCR2 = 2*( F - 60 ) obtenue empiriquement
  326. // voir la courbe F-f(OCR2) sur feuille de calcul Open Office jointe
  327.  
  328. {
  329. int v1;
  330.  
  331. //cette partie commentee consomme 3000 octets! donc on evite!
  332. // (en particulier la multiplication de deux float)
  333. //je les remplace par une approche discrete qui consomme moin de 200 octets
  334. // float a;
  335. // float b;
  336. // a= F_kHz / 1000;
  337. // b= (a - 80) / 20;
  338. // OCR2_real = b * b;
  339. // // if (OCR2_real > 255) {OCR2_real= 255;}
  340. // // // OCR2= floor(OCR2_real);
  341.  
  342. calcul_Frequence();
  343. v1=5;
  344. if (F_kHz > 95000) { v1= 11; }
  345. if (F_kHz > 100000) { v1= 20; }
  346. if (F_kHz > 105000) { v1= 31; }
  347. if (F_kHz > 110000) { v1= 40; }
  348. if (F_kHz > 115000) { v1= 58; }
  349. if (F_kHz > 120000) { v1= 80; }
  350. if (F_kHz > 125000) { v1= 101; }
  351. if (F_kHz > 130000) { v1= 125; }
  352. if (F_kHz > 133000) { v1= 140; }
  353. if (F_kHz > 135000) { v1= 151; }
  354. if (F_kHz > 137000) { v1= 162; }
  355. if (F_kHz > 140000) { v1= 180; }
  356. if (F_kHz > 143000) { v1= 198; }
  357. if (F_kHz > 145000) { v1= 211; }
  358. if (F_kHz > 150000) { v1= 245; }
  359. OCR2=v1;
  360. Affiche_OCR2();
  361. }
  362.  
  363.  
  364. void eteint_toutes_LED (void)
  365. {
  366. PORTD |= RAZ_4017;
  367. _delay_us(10);
  368. PORTD &= ~RAZ_4017;
  369. _delay_us(10);
  370. }
  371.  
  372. void allume_LED (int num)
  373. {
  374. int n;
  375. eteint_toutes_LED();
  376. num++;
  377. for(n=1; n<num; n++)
  378. {
  379. PORTD |= clk_4017;
  380. _delay_us(1);
  381. PORTD &= ~clk_4017;
  382. _delay_us(1);
  383. }
  384. }
  385.  
  386.  
  387. void incremente_PAS(void)
  388. {
  389. switch (PAS)
  390. {
  391. case (25) :
  392. PAS= 50;
  393. facteur_N /= 2;
  394. break;
  395.  
  396. case (50) :
  397. PAS= 100;
  398. facteur_N /= 2;
  399. break;
  400. }
  401. // Affiche_mode;
  402.  
  403. affiche_frequence();
  404. affiche_facteur_N();
  405. out_PLL();
  406. out_PWM();
  407. }
  408.  
  409.  
  410. void decremente_PAS(void)
  411. {
  412. switch (PAS)
  413. {
  414. case (100) :
  415. PAS= 50;
  416. facteur_N *= 2;
  417. break;
  418.  
  419. case (50) :
  420. PAS= 25;
  421. facteur_N *= 2;
  422. break;
  423. }
  424. // Affiche_mode;
  425.  
  426. affiche_frequence();
  427. affiche_facteur_N();
  428. out_PLL();
  429. out_PWM();
  430. }
  431.  
  432.  
  433.  
  434. void incremente_N (void)
  435. // fonction appelee lorsqu'on tourne le selecteur rotatif
  436. {
  437. switch (mode)
  438. {
  439. case(4) :
  440. facteur_N= facteur_N + 10;
  441. if (facteur_N > 1600) { facteur_N= 1600; }
  442. break;
  443. case(5) :
  444. facteur_N= facteur_N + 100;
  445. if (facteur_N > 1600) { facteur_N= 1600; }
  446. break;
  447.  
  448. default:
  449. if (F_kHz < 160000) {facteur_N= facteur_N + 1;}
  450. }
  451. affiche_frequence();
  452. affiche_facteur_N();
  453. out_PLL();
  454. out_PWM();
  455. }
  456.  
  457.  
  458. void decremente_N (void)
  459. // fonction appelee lorsqu'on tourne le selecteur rotatif
  460. {
  461. switch (mode)
  462. {
  463. case(4) :
  464. facteur_N= facteur_N - 10;
  465. if (facteur_N < 900) {facteur_N= 900;}
  466. break;
  467.  
  468. case(5) :
  469. facteur_N= facteur_N - 100;
  470. if (facteur_N < 900) { facteur_N= 900;}
  471. break;
  472. default:
  473. if (F_kHz > 90000) { facteur_N= facteur_N - 1; }
  474. }
  475. affiche_frequence();
  476. affiche_facteur_N();
  477.  
  478. out_PLL();
  479. out_PWM();
  480.  
  481. }
  482.  
  483.  
  484. void scrute_boutons(void)
  485. // 336 octets
  486. // deux boutons poussoirs permettent de changer la longueur des sauts en frequences (que j'ai appelé "mode")
  487. // met le mode à jour
  488. // le tableau presente en preambule montre que le PAS doit changer entre les modes 1 et 2 ainsi que 2 et 3
  489. {
  490. int n=0;
  491.  
  492. if ( (PIND & 0b00000100) == 0) {
  493. switch (mode)
  494. {
  495. case(1) :
  496. {
  497. mode= 2;
  498. allume_LED(mode);
  499. incremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
  500. affiche_mode();
  501. _delay_ms(200);
  502. n++;
  503. //while (((PIND & 0b00000100 ) != 0) | (n < 10) ) {}
  504. }
  505. break;
  506.  
  507. case(2) :
  508. {
  509. mode= 3;
  510. allume_LED(mode);
  511. incremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
  512. affiche_mode();
  513. _delay_ms(200);
  514. n++;
  515. //while (((PIND & 0b00000100 ) != 0) | (n < 10) ) {}
  516. }
  517. break;
  518.  
  519. case(3) :
  520. case(4) :
  521. {
  522. mode++;
  523. allume_LED(mode);
  524. affiche_mode();
  525. _delay_ms(200);
  526. }
  527.  
  528. default: {}
  529. }
  530.  
  531. lcd_gotoxy(0, 1);
  532. // lcd_puts (string(mode));
  533. } else {} ;
  534.  
  535. if ( (PIND & 0b00001000) == 0) {
  536. switch (mode) {
  537. case(2) :
  538. {
  539. mode= 1;
  540. allume_LED(mode);
  541. decremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
  542. affiche_mode();
  543. _delay_ms(200);
  544. n++;
  545. // while (((PIND & 0b00001000 ) != 0) | (n < 10) ) {}
  546. }
  547. break;
  548.  
  549. case(3) :
  550. {
  551. mode= 2;
  552. allume_LED(mode);
  553. decremente_PAS(); // recalcule facteur_N en fonction du nouveau PAS
  554. affiche_mode();
  555. _delay_ms(200);
  556. n++;
  557. // while (((PIND & 0b00001000 ) != 0) | (n < 10) ) {}
  558. }
  559. break;
  560.  
  561. case(4):
  562. case(5):
  563. {
  564. mode--;
  565. allume_LED(mode);
  566. affiche_mode();
  567. _delay_ms(200);
  568. n++;
  569. // while (((PIND & 0b00001000 ) != 0) | (n < 10) ) {}
  570. }
  571. break;
  572.  
  573. default: {}
  574. }
  575. } else { }
  576.  
  577. if (mode <1) mode= 1; else {};
  578. if (mode >5) mode=5; else {};
  579. // allume_LED(mode);
  580. }
  581.  
  582.  
  583. void acqui_pos_rot (void)
  584. // lit la position d'un potentiometre à resistances CMS discretes (63k au total)
  585.  
  586. {
  587. unsigned long int acqui_ADC_rot;
  588.  
  589. ADCSRA |= _BV(ADSC); //Start conversion - resolution 10bits
  590.  
  591.  
  592. while (ADCSRA & _BV(ADSC) ) {} // attend la fin de la converstion
  593.  
  594. acqui_ADC_rot = ADCW; // lit la value convertie
  595.  
  596. pos_rot= 19;
  597. //la structure suivante en "if then" est moins gourmande en memoire qu'un case of.
  598.  
  599. if (acqui_ADC_rot > 200) { pos_rot= 18; }
  600. if (acqui_ADC_rot > 300) { pos_rot= 17; }
  601. if (acqui_ADC_rot > 450) { pos_rot= 16; }
  602. if (acqui_ADC_rot > 550) { pos_rot= 15; }
  603. if (acqui_ADC_rot > 630) { pos_rot= 14; }
  604. if (acqui_ADC_rot > 670) { pos_rot= 13; }
  605. if (acqui_ADC_rot > 700) { pos_rot= 12; }
  606. if (acqui_ADC_rot > 735) { pos_rot= 11; }
  607. if (acqui_ADC_rot > 755) { pos_rot= 10; }
  608. if (acqui_ADC_rot > 775) { pos_rot= 9; }
  609. if (acqui_ADC_rot > 790) { pos_rot= 8; }
  610. if (acqui_ADC_rot > 810) { pos_rot= 7; }
  611. if (acqui_ADC_rot > 825) { pos_rot= 6; }
  612. if (acqui_ADC_rot > 838) { pos_rot= 5; }
  613. if (acqui_ADC_rot > 850) { pos_rot= 4; }
  614. if (acqui_ADC_rot > 859) { pos_rot= 3; }
  615. if (acqui_ADC_rot > 868) { pos_rot= 2; }
  616. if (acqui_ADC_rot > 874) { pos_rot= 1; }
  617. if (acqui_ADC_rot > 880) { pos_rot= 0; }
  618.  
  619. // LCDxy(10, 3);
  620. // LCDclrEOL;
  621. // Write(LCDout, ByteToStr(pos_rot : 3));
  622.  
  623. }
  624.  
  625.  
  626.  
  627. int main (void)
  628. {
  629. init_variables();
  630. init_ports();
  631. InitADC();
  632. allume_LED(mode);
  633. InitINTs();
  634. out_PLL();
  635.  
  636.  
  637. lcd_init(LCD_DISP_ON);
  638. lcd_clrscr();
  639. lcd_home();
  640. // lcd_puts("APE2008 version ");
  641. lcd_puts(version);
  642. _delay_ms(1000);
  643.  
  644. lcd_gotoxy (0, 0);
  645. lcd_puts("Out ");
  646.  
  647. lcd_gotoxy (0, 1);
  648. lcd_puts("SAUTS:");
  649. lcd_gotoxy (0, 2);
  650. lcd_puts("pas synth");
  651.  
  652. lcd_gotoxy (8, 3);
  653.  
  654. affiche_frequence();
  655. affiche_mode();
  656.  
  657. //unsigned long int masque_de_test;
  658. //int n1;
  659.  
  660. while(1)
  661. {
  662. scrute_boutons();
  663.  
  664. memo_pos_rot= pos_rot;
  665. acqui_pos_rot();
  666.  
  667. int suite =1; // utilise comme boolean. ah ce C peu type!
  668. // oui bon l'instruction if du C est invraissemblablement confuse pour quelqu'un qui connait le Pascal !
  669. // (en particulier les variantes avec if elsif endif)
  670. // d'ou l'utilisation de ce drapeau. C'est moins pire que des goto quand meme!
  671.  
  672.  
  673. if (pos_rot != memo_pos_rot) // si le selecteur a bouge
  674. {
  675. //quand le selecteur rotatif passe de 19 a 0, il faut continuer a incrementer et vice-versa
  676. if ( (memo_pos_rot == 0) & (pos_rot == 19) ) { decremente_N(); suite =0; }
  677. if ( (memo_pos_rot == 19) & (pos_rot == 0) ) { incremente_N(); suite =0; }
  678. //le reste du temps l'incrementation va dans le sens des valeurs croissantes
  679. if (suite)
  680. {
  681. if (pos_rot > memo_pos_rot) { incremente_N(); }
  682. if (pos_rot < memo_pos_rot) {decremente_N(); }
  683. }
  684. }
  685.  
  686. _delay_ms(100);
  687.  
  688. //=============== pour TEST =================
  689. // for (n1=0; n1<= 13; n1++) // on sort le LSB en premier
  690. // {
  691. // masque_de_test = (1 << n1);
  692. //
  693. // lcd_gotoxy_clrEOL (0, 1);
  694. // lcd_aff_bin(facteur_N,16);
  695. //
  696. // lcd_gotoxy_clrEOL (0, 2);
  697. // lcd_aff_bin(masque_de_test,16);
  698. //
  699. // lcd_gotoxy_clrEOL (0, 3);
  700. // lcd_aff_bin(facteur_N & masque_de_test,16);
  701. //
  702. // lcd_gotoxy (19, 3);
  703. //
  704. // if (facteur_N & masque_de_test) // != 0)
  705. // lcd_puts("i");
  706. // else
  707. // lcd_puts(" ");
  708. //
  709. // _delay_ms(1000);
  710. // }
  711. //=======================================
  712.  
  713. }
  714. }
  715.  
  716.  

15 L'ensemble des cartes interconnectées

A noter le connecteur qui me permet d'utiliser la carte logique avec d'autres montages.

16 Le filtre de sortie:

Le signal issu directement de l'oscillateur n'est pas sinusoïdal pur. Vu à l'oscilloscope la partie montante de la sinusoïde (sur le drain du 3819) est un peu moins raide que la partie descendante, ce qui se traduit par des harmoniques 2, 3, 4, 5 etc s'échelonnant de -10dB en -10dB , bien visibles à l'analyseur de spectre. C'est tout à fait inacceptable pour un géné HF.

Il est toutefois très simple de les atténuer fortement sans toucher à la fondamentale car la plage de fréquences couvertes s'étend sur un peu moins d'une octave.

(90MHz à 160MHz, avec 160MHz < 180MHz ( 2 fois 90MHz)

L'harmonique la plus basse à supprimer étant égale à deux fois la fréquence la plus basse générée, ( 2x90 = 180MHz), il est facile de concevoir un filtre passe bas en PI qui coupe à partir de 170MHz par exemple, ce qui ne touchera pas la fréquence fondamentale la plus haute générée qui est égale à 160MHz. et atténuera fortement TOUTES les harmoniques de TOUTES les fréquences générées.

Pour calculer un tel filtre, j'ai utilisé le programme RFSim99 bien connu des radioamateurs. Ci-contre des saisies d'écran de ce logiciel, pour le filtre envisagé ici.

Il faut également concevoir un étage séparateur et abaisseur d'impédance pour attaquer le filtre sans perturber l'oscillateur (avec un transistor BFR93 par exemple).

17 L'ETAGE DE SORTIE:

18 REALISATION DE L'ETAGE DE SORTIE:

Voici le filtre passe bas précédé de l'étage séparateur.
Les fils de liaison avec la carte PLL doivent êtres aussi courts que possible.
Idem pour la liaison de la prise BNC. J'avais prévu, comme on le voit sur le circuit imprimé, un étage à transistor supplémentaire à la sortie du filtre, ce qui est une erreur car tout étage à composant actif utilisé aux grandes amplitudes crée des harmoniques par sa non-linéarité. Donc il est préférable de terminer par le filtre.
D'ailleurs dans un émetteur radio, les étages de sortie, pour des raisons de rendement, travaillent en général en classe C (le transistor est polarisé de telle manière que l'angle de conduction soit très faible), ce qui génère bien évidemment un maximum d'harmoniques (on obtient des impulsions de courant, pas des Diracs mais presque, dont la décomposition spectrale est bien connue pour sa richesse en harmoniques), et c'est le filtre de sortie qui permet de n'obtenir que la fréquence fondamentale.

19 LE PANNEAU BOUTONS et LEDS

20 -

J'ai ajouté cette petite carte comprenant 5 LED et deux boutons poussoirs de commande, accessibles sur la face avant du boitier. Les LED, disposées verticalement, fonctionnent comme celles qu'on trouve sur les oscilloscopes, une seule allumée à la fois, sélection par les deux boutons (monter, descendre). Elle indiquent le pas de sauts en fréquence (10MHz - 1MHz - 100kHz - 50kHz - 25kHz).

Du coup il n'y a plus de boutons sur la platine HF (qui n'est pas vraiment leur place).

Les LED sont commandées par un compteur décimal décodé CD4017, lié au uC par seulement deux signaux (RAZ et Clock). Les boutons consomment deux autres bits sur le portD, ce qui fait 4 bits, plus 3 pour écrire dans la PLL LM2901, ce qui fait en tout 7 bits. Reste un de libre.

Le panneau fonctionne correctement.
Le soft est à jour pour la prise en charge de ce panneau.

21 Nouvelle vue d'ensemble

22 UN PETIT COUP D'OSCILLOSCOPE

Le signal (F=100MHz) en sortie du filtre



Amplitude = 220mV crête à crête sur 50 ohm. (soit P < 0 dBm)

Une jolie sinusoïde me direz-vous? Il ne faut toutefois pas trop s'y fier. Il est normal qu'un oscillo de bande passante 100MHz représente un signal de 100MHz de n'importe quelle forme comme une sinusoïde, étant donné que ce qui donne la forme d'un signal ce sont les harmoniques. Et un oscillo 100MHz ne peut pas afficher les harmoniques de 100MHz dont les fréquences sont 200, 300, 400MHz etc.. (en fait la coupure est progressive, de sorte que H2 est encore visible...)

Il faut donc utiliser un autre appareil pour juger de la qualité du signal (on parle de "pureté spectrale").

Nous allons donc nous servir d'un analyseur de spectre 1GHz.

23 Le filtre de sortie sert-il vraiment a quelque chose?

Voici une mesure faite (sans rayonner) à l'analyseur de spectre montrant l'utilité et la nécessité du filtre en sortie.
  • l'échelle horizontale est de 50MHz par carreau.
  • l'échelle verticale est logarithmique, 10dB par carreau.
  • Le tout premier pic tout à fait à gauche, c'est le marqueur 0Hz de l'analyseur. Ensuite, à 2 carreaux vient la fondamentale (100MHz) suivie des harmoniques (200, 300, 400, 500 MHz etc...)

24 -

Signal de sortie (100MHz) sans le filtre, c'est la cata.

Signal de sortie AVEC le filtre, reste un petit peu de H2 dans l'herbe.

25 -

On notera qu'il serait utile (et nécessaire pour certaines applications comme l'émission radio) d'augmenter l'ordre du filtre (ou d'ajouter des étages de sortie et un second filtre) afin d'atténuer d'avantage H2 (réjection par exemple à -60dB au lieu de -40dB). Il y a des réglementations à respecter suivant les domaines concernés. (Voir la règlementation de l'ART citée en lien externe).

Petite précision: dans un émetteur radio, l'étage de sortie travaille le plus souvent en classe C (brefs pics de conduction du transistor) pour des raisons évidentes de rendement. Des lors le filtre doit être place après cet étage amplificateur et non avant. Dans un générateur de labo, avec de faibles puissances mises en jeu, les choses peuvent être vues différemment (amplis linéaires).

Bonne nouvelle: je me suis aperçu que les harmoniques résiduelles à la sortie du filtre ne passaient pas par le filtre mais étaient tout simplement induites en sortie par rayonnement (eh oui, la VHF ce n'est pas de la BF!) En plaçant un blindage en cuivre faisant également plan de masse on obtient une amplitude des H2, H3 et H4 à -30dB pour Fo=160MHz et à -40dB à Fo=100MHz (dB mesurés par rapport à la fondamentale). En utilisant un filtre de Tchebychev strict (moi j'ai utilisé des valeurs des selfs et de capas arrondies), en augmentant l'ordre du filtre et en soignant les blindages (boites fermées, les miens sont "ouverts", justes une surface plane), on doit pouvoir descendre sous les -60dB ce qui devient vraiment sérieux. Je dis "on doit pouvoir" et non on peut, parce que si le calcul d'un filtre, qui est une affaire totalement déterministe permet en théorie d'atténuer une fréquence autant qu'on le désire, les fuites de rayonnement hors des blindages, les pistes de masses difficiles à optimiser, les bouclages par courants dans les plans de masse, créent des résiduelles qui deviennent en fait prépondérantes.

Heureusement il y a moyen de savoir par où passent les signaux (ou plutôt par où il ne passent PAS !), tout simplement en ouvrant les circuits du filtre à des endroits judicieusement choisis et en constatant les variations d'amplitude en sortie qui en résultent. C'est comme ça que j'ai vu que les harmoniques contournaient le filtre sans les blindages.

Afin de se familiariser avec les filtres passifs, je conseille vivement la consultation et l'étude sérieuse des pages citées en liens externes concernant les filtres de Butterworth, Tchebychev, et elliptique. Cela est tout à fait abordable (et passionnant) avec un niveau BAC+2 (BTS, IUT...) en électronique. C'est d'ailleurs (enfin c'était) au programme, ainsi que les transformées de Laplace, lorsque j'ai obtenu mon DUT.

26 L'APPAREIL DANS SON BOITIER:

27 Dernière minute:

De nombreuses informations complémentaires peuvent être trouvées dans les commentaires du programme source pour l'ATmega.

12 novembre 2008:

Il reste à concevoir un circuit de sortie avec réglage et calibrage de l'amplitude (digital par diodes PIN) et filtrage. La possibilité de moduler le signal serait aussi intéressante afin d'obtenir un générateur exploitable dans une large gamme d'applications.

21 novembre 2008:
Le circuit imprimé est terminé, les composants sont soudés.

29 novembre 2008:
L'ensemble fonctionne correctement.
La réalisation de l'étage de sortie avec filtrage par réseau LC en "PI" est en cours.

30 novembre 2008:
J'ai grillé le 3819 (la masse de la sonde de l'oscillo à fait un bouclage à la terre des fuites de mon alim 15V en passant bien entendu par la gate du 3819 ce qui l'a claqué), rien de grave MAIS cela m'a permis de m'apercevoir, lors du remplacement du transistor 2N3819, que l'oscillation n'était pas garantie, en fonction des dispersions de caractéristiques d'un transistor à l'autre. Solution: j'ai ajouté un 22p entre les pins 11 (entrée signal) et 9 (masse) du LM7011, ce qui a pour effet d'augmenter le gain de l'étage en découplant la résistance de drain. Et cette fois l'oscillateur fonctionne avec tous les transistors à ma disposition.

6 décembre 2008:
L'étage de sortie comprenant un petit ampli à BFR93A + filtre en PI 160MHz + un étage de sortie séparateur est au point.

12 décembre 2008:
L'étage de sortie réalisé en CMS fonctionne correctement. Reste à loger le tout dans un boitier métallique et à publier des photos de l'ensemble ainsi que de l'écran de l'analyseur de spectre. Je vais également apporter quelques améliorations au soft.

14 décembre 2008:
Voici un petit dessin du projet de présentation en boitier réalisé avec le logiciel libre Open Office draw:


Les indications sur l'afficheur sont différentes sur les dernières versions...

15 mars 2009:
heu.. faut jamais dire "ne saurait tarder", c'est le meilleur moyen de faire tarder les choses, la preuve!
En attendant je confirme que l'ensemble des cartes fonctionnent correctement. Il ne reste donc plus qu'à loger le tout dans le boîtier. Pourvu que ça ressemble au dessin!

26 mars 2009:
Voilà, la bête est dans la boite. Youpiii, c'est plus beau en vrai que sur le dessin!

13 avril 2009:
Je suis en train de réécrire (Linux Ubuntu) le source du programme en langage C avec le logiciel libre KrontrolleurLab. (Voir ma page sur les microcontroleurs ATmega). Le code obtenu sera moitié moins volumineux ( en Pascal, il atteignait les 8ko max de l'ATmega8). Et puis VIVE LE LOGICIEL LIBRE !

Silicium628
Pour me joindre: voir l'Email au bas de la page d'accueil du site.

28 DOCUMENTS TECHNIQUES




29 LIENS

Liens internes:



Liens externes:

Haute fréquence:

Règlementation:

  • Décision n° 00-1364 de l'Autorité de régulation des télécommunications (ART) précisant les conditions d'utilisation des installations de radioamateur (en particulier l'annexe 3: Caractéristiques techniques à respecter lors de l'utilisation d'une installation radioamateur). C'est très restrictif.  ATTENTION: ceci concerne les Radioamateurs, pour les autres personnes (particuliers) toute émission, tout rayonnement en VHF sont purement et simplement interdits.

Filtres:

Bobinages - selfs:

Théorie des diviseurs de fréquence - bascules RS, T, D, JK, circuiterie interne des portes logiques:

Multiplication de fréquences - convertisseurs de fréquences:

Doc:

Réalisation de circuits-imprimés:


30 -

Liens...



43316