Géné VHF 1Hz à 100MHz à DDS AD9951 piloté par GPS

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, 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. Toute émission dans les bandes VHF est interdite (hors bande radio-amateur) et serait rapidement repérée ce qui 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 40MHz à DDS AD9850 décrit sur ce site.
Cliquez sur une image ci-dessous pour visualiser l'album. Certaines sont commentées
Table des matières :
1 - Première approche
2 - Un peu de théorie :
3 - Premier contact avec le circuit AD9951
4 - Skippy est très intéressé !
5 - La soudure du boitier CMS
6 - soudure ok
7 - Le schéma
8 - Le firmware pour l'ATmega8
9 - Dessin du circuit imprimé
10 - Réalisation du circuit imprimé
11 - Carte terminée :
12 - Le DDS est en place sur la carte
13 - Premier signal !
14 - Tout fonctionne correctement !
15 - Etage de sortie
16 - Schéma de l'étage de sortie
17 - Version avec un Oscillateur Intégré comme ref. de fréquence (MCO-1510A)
18 - Schéma avec oscillateur intégré MCO-1510A :
19 - Vue d'ensemble de la nouvelle version :
20 - Le verdict du fréquencemètre :
21 - Test sur la porteuse de France Inter GO
22 - Comment obtenir une grande précision de la fréquence ?
23 - Le module GPS - NEO-7M
24 - Remarque :
25 - Configuration du module GPS - NEO-7M
26 - Captures d'écran du logiciel
27 - Détecteur de synchronisation
28 - Le circuit imprimé du détecteur de synchronisation :
29 - Circuit imprimé flashé
30 - Proto terminé
31 - Un petit détail :
32 - TCXO en commande...
33 - Passage à une base de temps TCXO
34 - Tableau de précision temporelle
35 - Cogitations en direct :
36 - Une solution sérieuse en vue
37 - Base de temps GPS
38 - Circuit imprimé de la base de temps
39 - circuit gravé :
40 - EVOLUTION
41 - Le jitter de la base de temps GPS (suite...)
42 - VCXO maison pour PLL base de temps GPS
43 - Le circuit imprimé :
44 - Le VCXO dans son boîtier blindé
45 - L'ensemble de la base de temps
46 - Le circuit imprimé de la base de temps
47 - ça avance...
48 - Les essais ne devraient pas tarder
49 - Toutes les cartes fonctionnent
50 - Modulation AM du signal
51 - Schéma du modulateur
52 - Le circuit imprimé du modulateur AM
53 - Modulateur terminé
54 - Le firmware en C pour l'ATmega2560 de l'Arduino
55 - L'appareil dans son boîtier
56 - Précisions sur la précision
57 - La parole est au Rubidium
58 - La fonction Phase-offset de l'AD9951
59 - Calcul du glissement de phase
60 - Application pratique :
61 - En vidéo
62 - Filtrage du signal de sortie
63 - Le circuit imprimé du filtre :
64 - Configuration automatique du NEO-7M
65 - DOCUMENTS
66 - LIENS

1 Première approche

29 août 2014: J'ai commencé par le commencement, l'étude du datasheet du composant principal, le circuit DDS AD9951 de Analog Device.



Puis j'ai commandé le circuit.

2 Un peu de théorie :

La circuiterie interne du DDS est un diviseur digital de fréquence dont la résolution incrémentale (la finesse du pas) est déterminée par la fréquence de l'horloge de référence (ici 200 MHz) divisée la 2^N nombre de bits du mot de commande de fréquence (un grand (32 bits) nombre binaire qui détermine la fréquence de sortie). L'accumulateur (la mémoire) de phase est un compteur à module variable qui incrémente (ajoute) le nombre enregistré à chaque fois qu'il reçoit une impulsion d'horloge. Lorsque le compteur déborde, il reboucle sur lui-même de sorte que le signal est généré continuellement. Le mot de commande de fréquence (il sera fourni par l'ATmega en fonction de la fréquence désirée) constitue la valeur du module du compteur, ce qui en fin de compte détermine la taille de l'incrément (delta phase) qui est ajouté dans l'accumulateur de phase lors de l'impulsion d'horloge suivante. Plus l'incrément est large et plus vite l'accumulateur se remplit et déborde ce qui produit une fréquence plus élevée. Le signal numérique de sortie est issu d'un calcul mathématique d'une fonction cosinus avec comme variable la valeur de la phase.

3 Premier contact avec le circuit AD9951

Réception du circuit... Boudiouuu c'est tout petit ! Le boitier est un QFP48 au pas de 0.5mm. Heureusement j'avais en même temps commandé un adaptateur QFP48 -> DIP sur lequel j'ai réussi à souder l'AD9951. Un partie de plaisir quand on sait faire ! (y'a un truc !)

Bon d'accord, sur le plan HF l'utilisation de cet adaptateur ne va pas optimiser les performances, il faudra souder les capas de découplage des alims 1.8V et 3.3V au plus près du boîtier. Mais il s'agit pour l'instant de réaliser un prototype qui permette de mettre au point le programme pilote. Je vais utiliser un ATmega8 pour driver ce DDS et un affichage LCD 4x20.

4 Skippy est très intéressé !

5 La soudure du boitier CMS

Après soudure, il convient de vérifier soigneusement l'absence de courts circuits entre les pattes, par transparence et à la loupe. Je déconseille l'utilisation de l’ohmmètre étant donné que la partie analogique de ce circuit fonctionne avec une tension très faible de 1,8V.

6 soudure ok

7 Le schéma

La saisie de la fréquence désirée se fait par deux encodeurs pas-à-pas rotatifs code Gray. J'ai consacré un article à cette méthode sur ce site.

L'interface logique du DDS s'alimente en 3,3V et la partie analogique ainsi que le convertisseur CAN interne en 1,8V. La documentation conseille d'utiliser deux régulateurs séparés pour les deux alims de 1,8V. Quatre résistances de 1k + diodes schottky réalise l'interface entre les ports en sortie 5V de l'ATmega (because l'affichage en 5V) et les entrées data en 3,3V du DDS.

La sortie du CAN est un géné de courant en mode différentiel. Les deux R de 47 ohm permettent de récupérer des tensions symétriques.

L'ensemble est alimenté par un "adaptateur USB" délivrant 5V DC. Certains "adaptateurs" fournissent une tension pleine de parasites (découpage mal filtré) voire une composante 50Hz non négligeable. D'où la self et le 1000uF en entrée.

8 Le firmware pour l'ATmega8

CODE SOURCE en c
  1. /***********************************************************************
  2. par Silicium628
  3. derniere mise à jour 25 oct 2014
  4. ========================================================================
  5. Generateur HF 200MHz - DDS AD9951 et ATmega8
  6. -Affichage LCD 2 x 16 caractères
  7. -base de temps = TCXO 20.000 MHz (quartz compensé en température)
  8. ************************************************************************/
  9.  
  10.  
  11. /*
  12. Quelques points clés du datasheet.pdf de l'AD9951 :
  13.  
  14. - Chip select can be tied low in systems that maintain control of SCLK (p:22) -> donc on ne pilotera pas le pin CSB
  15. - CFR1<9> = 1. The serial data I/O pin (SDIO) is configured as an input only pin (3-wire serial programming mode). (p:15) . Ce n'est pas la valeur par défaut. il faut le programmer
  16.  
  17. */
  18.  
  19.  
  20. #define F_CPU 16000000
  21.  
  22. #include <avr/io.h>
  23. #include <avr/interrupt.h>
  24. #include <util/delay.h>
  25. #include <math.h>
  26.  
  27. #include "dm_lcd.c" // l'attribution des pins/ports pour le LCD (et le type de LCD) est faite dans le fichier "dm_lcd.h"
  28. #include "lcd_curseur.c"
  29.  
  30. #define pin_RST 0b00010000 // (sortie) sur portD
  31. #define pin_SCLK 0b10000000 // (sortie) sur portD
  32. #define pin_SDI 0b01000000 // (sortie) sur portD
  33. #define pin_io_update 0b00100000 // (sortie) sur portD
  34. #define pin_SD0 0b00000010 // (ENTREE) sur portB
  35. #define pin_mode 0b00100000 // (ENTREE) sur portC
  36.  
  37.  
  38. char * version = "5.0";
  39.  
  40.  
  41. uint8_t IB; // INSTRUCTION BYTE
  42.  
  43. uint64_t FTW; // le mot de 32 bits à transmettre au AD9951 en mode série sur le pin SDIO
  44. uint32_t f_out , memo_f_out; // fréquence du signal de sortie sinusoïdal
  45.  
  46.  
  47. uint8_t pos; // position du multiplicateur
  48. uint8_t pos_curseur; // position du curseur (pour affichage)
  49.  
  50. uint32_t pas;
  51. uint8_t etat;
  52. uint16_t compteur1;
  53.  
  54. uint8_t demande_calcul;
  55.  
  56. float position;
  57.  
  58.  
  59. /**
  60. RAPPEL variables avr-gcc (vérifiable avec le .map)
  61.  
  62. char 1 -128 .. 127 ou caractères
  63. unsigned char 1 0 .. 255
  64. uint8_t 1 (c'est la même chose que l'affreux 'unsigned char')
  65. char toto[n] n
  66. int 2 -32768 .. 32767
  67. int16_t 2 idem 'int'
  68. short int 2 pareil que int (?)
  69. unsigned int 2 0 .. 65535
  70. uint16_t 2 idem 'unsigned int'
  71. long int 4 octets -2 147 483 648 à 2 147 483 647
  72. int32_t 4 octets -> 32 bits ; idem long int
  73. long long int 8 octets -> 64 bits
  74. unsigned long int 4 octets -> 32 bits ; 0 .. 4 294 967 295 (4,2 x 10^9)
  75. uint32_t 4 32 bits ; idem 'unsigned long int'
  76. float 4
  77. double ATTENTION ! 4 octets (oui, 32 bits ! et pas 64 bits (8 octets) comme en C standard)
  78.  
  79. La déclaration char JOUR[7][9];
  80. réserve l'espace en mémoire pour 7 mots contenant 9 caractères (dont 8 caractères significatifs).
  81. **/
  82.  
  83.  
  84. void init_ports (void) // ports perso
  85. // 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  86. {
  87. DDRB |= 0b11111101; // portB[1] = entree (pin SDO inversé du AD9951 )
  88. PORTB = 0b00000000;
  89.  
  90. DDRC = 0b11011111; // PINC[5] = entrée bouton poussoir (Mode)
  91. PORTC = 0b00100000;
  92.  
  93. DDRD = 0b11110000; // portD[0] et portD[1] entrées codeur_ROTs ; portD[2] et portD[3] entrées INT0 et INT1
  94. PORTD = 0b00001111;
  95. }
  96.  
  97.  
  98.  
  99.  
  100. void InitINTs (void)
  101. {
  102. GICR |= 0b11000000; // gere les INTs - voir page 67 du pdf
  103. MCUCR |= 0b00001010; // The falling edge of INT0 generates an interrupt request. The falling edge of INT1 generates an interrupt request. p:67 du pdf
  104. }
  105.  
  106.  
  107.  
  108. void init_variables(void)
  109. {
  110. demande_calcul = 1;
  111. f_out = 10000000;
  112. pas = 1000;
  113. pos = 4;
  114.  
  115. }
  116.  
  117.  
  118.  
  119. void init_lcd()
  120. {
  121. uint8_t i;
  122.  
  123. lcd_init(LCD_DISP_ON_CURSOR); // cursor on
  124. //cd_init(LCD_DISP_ON); // cursor off
  125.  
  126. lcd_cree_5_caract_echelle();
  127.  
  128. lcd_clrscr();
  129. }
  130.  
  131.  
  132.  
  133. void lcd_gotoxy_clrEOL (int x, int y)
  134. // place le curseur en x,y et efface jusqu'a la fin de la ligne
  135. {
  136. lcd_gotoxy(x, y);
  137. uint8_t i;
  138. for (i=x; i<20; i++){ lcd_puts(" "); }
  139. lcd_gotoxy(x, y);
  140. }
  141.  
  142.  
  143.  
  144.  
  145. void lcd_aff_nb (uint32_t valeur, uint8_t nb_chiffres, uint8_t nb_decimales, uint8_t affi_zeros)
  146. {
  147. //affiche un nombre en representation decimale
  148. // affi_zeros = 1 affiche les zéros non significatifs à gauche.
  149. unsigned char r ;
  150. char tbl[7];
  151. char digit;
  152. uint8_t i;
  153.  
  154. for (i=1; i<=nb_chiffres; i++)
  155. {
  156. r=48 + valeur % 10; // modulo (reste de la division)
  157. valeur /= 10; // quotient
  158. tbl[i]=r;
  159. }
  160. uint8_t debut = 1-affi_zeros; // pour ne pas afficher des zeros à gauche du 1er chiffre
  161. for (i=1; i<=nb_chiffres; i++)
  162. {
  163. if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
  164. digit = tbl[nb_chiffres +1 -i];
  165. if (digit != '0') {debut = 0;}
  166. if ((digit != '0') || (debut == 0)) {lcd_putc(digit);}
  167. }
  168. }
  169.  
  170.  
  171. void lcd_aff_nb_relatif (int16_t valeur, uint8_t nb_chiffres, uint8_t nb_decimales)
  172. {
  173. //affiche un nombre relatif (c.a.d positif ou négatif) en representation decimale
  174. unsigned char r ;
  175. char tbl[7];
  176. char digit;
  177. uint8_t i;
  178. uint8_t affi_zeros = 1; // affi_zeros = 1 affiche les zéros non significatifs à gauche.
  179.  
  180. if (valeur <0)
  181. {
  182. valeur = -valeur;
  183. lcd_putc('-');
  184. }
  185. else
  186. {
  187. lcd_putc('+');
  188. }
  189.  
  190. for (i=1; i<=nb_chiffres; i++)
  191. {
  192. r=48 + valeur % 10; // modulo (reste de la division)
  193. valeur /= 10; // quotient
  194. tbl[i]=r;
  195. }
  196. uint8_t debut = 1-affi_zeros; // pour ne pas afficher des zeros à gauche du 1er chiffre
  197. for (i=1; i<=nb_chiffres; i++)
  198. {
  199. if (i== (nb_chiffres - nb_decimales +1) ) { lcd_puts("."); }
  200. digit = tbl[nb_chiffres +1 -i];
  201. if (digit != '0') {debut = 0;}
  202. if ((digit != '0') || (debut == 0)) {lcd_putc(digit);}
  203. }
  204. }
  205.  
  206.  
  207.  
  208. void lcd_aff_nb_form3 (uint32_t valeur, uint8_t nb_chiffres)
  209. {
  210. //affiche un nombre en representation decimale en separant les groupes de 3 chiffres
  211. unsigned char r ;
  212. char tbl[7];
  213. uint8_t i;
  214.  
  215. for (i=1; i<=nb_chiffres; i++)
  216. {
  217. r=48 + valeur % 10; // modulo (reste de la division)
  218. valeur /= 10; // quotient
  219. tbl[i]=r;
  220. }
  221. for (i=1; i<= nb_chiffres; i++)
  222. {
  223. if ((((i-1) % 3) == 0 ) && (i>1)) { lcd_puts("."); }
  224. lcd_putc(tbl[nb_chiffres +1 -i]);
  225. }
  226. }
  227.  
  228.  
  229.  
  230. void lcd_aff_bin (uint16_t valeur, uint8_t nb_digits)
  231. {
  232. //affiche un nombre en representation binaire
  233. // 16 bits max
  234. uint8_t r ;
  235. char tbl[17];
  236. uint8_t i;
  237.  
  238. for (i=1; i<=nb_digits; i++)
  239. {
  240. r= 48 + valeur % 2; // modulo (reste de la division)
  241. valeur /= 2; // quotient
  242. tbl[i]=r;
  243. };
  244.  
  245. for (i=1; i<=nb_digits; i++)
  246. {
  247. lcd_putc(tbl[nb_digits +1 -i]);
  248. }
  249. }
  250.  
  251.  
  252.  
  253. void affiche_pas()
  254. {
  255. lcd_gotoxy_clrEOL(0, 1);
  256. lcd_puts("PAS = ");
  257.  
  258. switch (pas)
  259. {
  260. case 1 : lcd_puts("1Hz");
  261. break;
  262.  
  263. case 10 : lcd_puts("10Hz");
  264. break;
  265.  
  266. case 100 : lcd_puts("100Hz");
  267. break;
  268.  
  269. case 1000 : lcd_puts("1kHz");
  270. break;
  271.  
  272. case 10000 : lcd_puts("10kHz");
  273. break;
  274.  
  275. case 100000 : lcd_puts("100kHz");
  276. break;
  277.  
  278. case 1000000 : lcd_puts("1MHz");
  279. break;
  280.  
  281. case 10000000 : lcd_puts("10MHz");
  282. break;
  283.  
  284. case 100000000 : lcd_puts("100MHz");
  285. break;
  286.  
  287. }
  288. }
  289.  
  290.  
  291. void affiche_frequence()
  292. {
  293.  
  294. lcd_gotoxy(0, 0);
  295. lcd_puts("F=");
  296. lcd_aff_nb_form3 (f_out, 9);
  297. lcd_puts(" Hz");
  298.  
  299. uint8_t nb_chiffres = 9;
  300.  
  301. pos_curseur = nb_chiffres+4-pos;
  302. if (pos > 3) {pos_curseur = nb_chiffres+3-pos;};
  303. if (pos > 6) {pos_curseur = nb_chiffres+2-pos;};
  304.  
  305. lcd_gotoxy(pos_curseur, 0);
  306.  
  307. }
  308.  
  309.  
  310.  
  311.  
  312. /*
  313. void test_codeur_rot()
  314. {
  315. // pour test des états (diffèrent suivant modèles) du codeur rotatif pas à pas code gray
  316. // le mien est toujours à 11 en position stable
  317. // dans le sens CW il fait 11->01->00->10->11
  318. // et par conséquent dans le sens CCW il fait 11->10->00->01->11
  319. lcd_clrscr();
  320. while (1)
  321. {
  322. code_rot = PIND & 0b00000011;
  323. lcd_gotoxy(0,0);
  324. lcd_aff_bin (code_rot, 2);
  325. _delay_ms(10);
  326. }
  327. }
  328. */
  329.  
  330.  
  331.  
  332. void impulse_clk(void) // sur pin_SCLK
  333. {
  334. _delay_us(5); // temps necessaire à la stabilisation du niveau envoyé
  335. PORTD |= pin_SCLK;
  336. _delay_us(5);
  337. PORTD &= ~pin_SCLK; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  338. _delay_us(5);
  339. }
  340.  
  341.  
  342. void impulse_IO_update(void) // sur pin_SCLK
  343. {
  344. // _delay_us(10);
  345. PORTD |= pin_io_update;
  346. _delay_us(10);
  347. PORTD &= ~pin_io_update; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  348. _delay_us(10);
  349.  
  350. }
  351.  
  352.  
  353.  
  354. void out_DDS(uint8_t octet_i)
  355. {
  356. // MSB first (= mode par defaut)
  357. uint8_t i;
  358.  
  359. //envoi de la frequence
  360. uint8_t masque;
  361.  
  362. for (i=0; i < 8; i++)
  363. {
  364. masque = 0b10000000 >> i; // le '1' se deplace de gauche a droite
  365. if ( (octet_i & masque) != 0) { PORTD |= pin_SDI; } else { PORTD &= ~pin_SDI; }
  366. impulse_clk();
  367. }
  368. }
  369.  
  370.  
  371. void reset_DDS()
  372. {
  373. _delay_ms(1);
  374. PORTD |= pin_RST;
  375. _delay_ms(10);
  376. PORTD &= ~pin_RST; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  377. _delay_ms(10);
  378. }
  379.  
  380.  
  381. void init_DDS()
  382. {
  383. uint8_t CFR1[4];
  384. uint8_t CFR2[3];
  385.  
  386. // ============================ REGISTRE CRF1 - 32 bits ===================================================================
  387. // Control Function Register No.1 (CFR1) (0x00)
  388.  
  389.  
  390. //PREMIER OCTET :
  391.  
  392. // CFR1<0>: Not Used, Leave at 0
  393. // CFR1<1> SYNC_CLK Disable Bit; CFR1<1> = 0 (default). The SYNC_CLK pin is active.
  394. // CFR1<2>: Not Used
  395. // CFR1<3>: External Power-Down Mode; CFR1<3> = 0 (default). The external power-down mode selected is the rapid recovery power-down mode. In this mode,
  396. //when the PWRDWNCTL input pin is high, the digital logic and the DAC digital logic are powered down. The DAC bias circuitry, PLL, oscillator, and clock input circuitry are not powered down.
  397. // CFR1<4>: Clock Input Power-Down Bit; CFR1<4> = 0 (default). The clock input circuitry is enabled for operation.
  398. // CFR1<5>: DAC Power-Down Bit; CFR1<5> = 0 (default). The DAC is enabled for operation.
  399. // CFR1<6>: Not Used
  400. // CFR1<7>: Digital Power-Down Bit; CFR1<7> = 0 (default). All digital functions and clocks are active.
  401.  
  402. CFR1[0]=0b00000000;
  403.  
  404. //DEUXIEME OCTET
  405.  
  406. // CFR1<8> = 0 (default). MSB first format is active.
  407. // CFR1<9>: SDIO Input Only; CFR1<9> = 1. The serial data I/O pin (SDIO) is configured as an input only pin (3-wire serial programming mode). (CE N'EST PAS LA VALEUR PAR DEFAUT)
  408. // CFR1<10>: Clear Phase Accumulator; CFR1<10> = 0 (default). The phase accumulator functions as normal.
  409. // CFR1<11>: Not Used
  410. //CFR1<12>: Sine/Cosine Select Bit; CFR1<12> = 0 (default). The angle-to-amplitude conversion logic employs a COSINE function.
  411. // CFR1<13>: Auto-Clear Phase Accumulator Bit; CFR1<13> = 0 (default), the current state of the phase accumulator remains unchanged when the frequency tuning word is applied.
  412. // CFR1<14..15>: Not Used
  413.  
  414. CFR1[1]=0b00000010; // CFR1<9>: SDIO Input Only;
  415.  
  416. //TROISIEME OCTET
  417.  
  418. // CFR1<16..21>: Not Used
  419. // CFR1<22>: Software Manual Synchronization of Multiple AD9951; CFR1<22> = 0 (default). The manual synchronization feature is inactive.
  420. // CFR1<23>: Automatic Synchronization Enable Bit; CFR1<23> = 0 (default). The automatic synchronization feature of multiple AD9951s is inactive.
  421.  
  422. CFR1[2]=0b00000000;
  423.  
  424. // CFR1<24>: Auto Shaped On-Off Keying Enable Bit (Only Valid when CFR1<25> Is Active High); CFR1<24> = 0 (default). When CFR1<25> is active, a Logic 0 on CFR1<24> enables the
  425. //manual shaped on-off keying operation. Each amplitude sample sent to the DAC is multiplied by the amplitude scale factor. See the Shaped On-Off Keying section for details
  426. // CFR1<25>: Shaped On-Off Keying Enable Bit; CFR1<25> = 0 (default). Shaped on-off keying is bypassed.
  427. // CFR1<26>: Amplitude Ramp Rate Load Control Bit; CFR1<26> = 0 (default). The amplitude ramp rate timer is loaded only upon timeout (timer == 1) and is not loaded due to an I/O UPDATE input signal.
  428. // CFR1<31:27>: Not Used
  429.  
  430. CFR1[3]=0b00000000;
  431.  
  432. // ENVOI DE CETTE CONFIGURATION AU DDS : (voir page 22 : "Example Operation")
  433.  
  434. IB= 0b00000000;
  435. out_DDS( IB); // instruction byte -> ordre d'ecrire dans le registre CRF1 (32 bits attendus)
  436.  
  437. out_DDS( CFR1[3]);
  438. out_DDS( CFR1[2]) ;
  439. out_DDS( CFR1[1]);
  440. out_DDS( CFR1[0]);
  441.  
  442. impulse_IO_update();
  443.  
  444. // ============================ REGISTRE CRF2 - 24bits ============ voir le pdf du composant =========================================================
  445.  
  446. // CFR2<1:0>: Charge Pump Current Control Bits
  447. // CFR2<2>: VCO Range Control Bit
  448. /*
  449. This bit is used to control the range setting on the VCO.
  450. When CFR2<2> == 0 (default), the VCO operates in a range of
  451. 100 MHz to 250 MHz. When CFR2<2> == 1, the VCO operates
  452. in a range of 250 MHz to 400 MHz.
  453. */
  454. // CFR2<7:3>: Reference Clock Multiplier Control Bits
  455. /*
  456. This 5-bit word controls the multiplier value out of the clock-
  457. multiplier (PLL) block. Valid values are decimal 4 to 20 (0x04 to
  458. 0x14). Values entered outside this range will bypass the clock
  459. multiplier. See the Phase-Locked Loop (PLL) section for details
  460. 20 = 0b10100
  461. */
  462. // CFR2<8>: Not Used
  463. // CFR2<9>: CRYSTAL OUT Enable Bit
  464. // CFR2<10>: Hardware Manual Sync Enable Bit
  465. // CFR2<11>: High Speed Sync Enable Bit
  466. // CFR2<23:12>: Not Used
  467.  
  468. //CFR2[0] = 0b00100100; // bit2 =1 -> the VCO operates in a range of 250 MHz to 400 MHz; bits7..3 = 0b00100 -> = 4 (REFCLK Multiplier) -> cas d'un Qx 100MHz
  469. CFR2[0] = 0b10100100; // bit2 =1 -> the VCO operates in a range of 250 MHz to 400 MHz; bits7..3 = 0b10100 -> = 20 (REFCLK Multiplier) -> cas d'un Qx ou CTXO 20MHz
  470.  
  471. CFR2[1] = 0b00000010; // bit CFR2<9>: CRYSTAL OUT Enable Bit -> =1
  472. CFR2[2] = 0b00000000;
  473.  
  474. IB= 0b00000001;
  475. out_DDS(IB); // instruction byte -> ordre d'ecrire dans le registre CRF2 (24 bits attendus)
  476.  
  477. out_DDS( CFR2[2]);
  478. out_DDS( CFR2[1]);
  479. out_DDS( CFR2[0]);
  480.  
  481. impulse_IO_update();
  482. }
  483.  
  484.  
  485.  
  486.  
  487. /***********************************************************************
  488. f_out = FTW x SYSCLK / 2³² nous dit le datasheet
  489. en en déduit :
  490.  
  491. FTW = f_out * 2³² / SYSCLK
  492. // ici SYSCLK = 20MHz x 20 = 400 MHz = 4E8 // **** cas d'un TCXO 20MHz ****
  493. ici SYSCLK = 10MHz x 20 = 200 MHz = 2E8 // cas d'une synchro par GPS
  494.  
  495. //FTW = f_out * (2³² / 4E8) = f_out * (2³⁰ / 1E8) = 10.73741824 x f_out **** cas d'un TCXO 20MHz ****
  496. FTW = f_out * (2³² / 2E8) = 21.47483648 x f_out
  497.  
  498. ************************************************************************/
  499.  
  500.  
  501. void calcul_FTW()
  502. {
  503. uint64_t y;
  504.  
  505. y = f_out * 4294967296;
  506. FTW = y / 200000000; // ici on introduit forcément une erreur d'arrondi, le résustat est à +/- 1 digit
  507. // pour une fréquence de sortie de 10MHz le glissemment par rapport au 10MHz étalon est de 26s pour une période de 100ns -> 3,8 E-9 soit 1seconde/3ans ce qui n'est pas si mal !!
  508. // si l'on veut ottenir la précision de E-12 du GPS, il faut envisager d'utiliser autre chose qu'un synthétiseur DDS 32 bits.
  509. // toutefois pour des fréquences multiples ou sous-multiples de 10MHz, ou dans un rapport fractionnaire simple, pas de problème, une simple PLL convient avec diviseur en logique câblée.
  510. // pour d'autres fréquences, il faut étudier la question de plus près.
  511.  
  512. }
  513.  
  514.  
  515.  
  516. void out_FTW()
  517. {
  518. uint8_t octets[4];
  519.  
  520. octets[0] = FTW & 0b00000000000000000000000011111111;
  521. octets[1] = (FTW & 0b00000000000000001111111100000000) >> 8;
  522. octets[2] = (FTW & 0b00000000111111110000000000000000) >> 16;
  523. octets[3] = (FTW & 0b11111111000000000000000000000000) >> 24;
  524.  
  525. IB= 0b00000100;
  526. out_DDS( IB); // instruction byte -> ordre d'écrire 32 bits dans le registre 0x04
  527.  
  528. out_DDS(octets[3]);
  529. out_DDS(octets[2]);
  530. out_DDS(octets[1]);
  531. out_DDS(octets[0]);
  532.  
  533. impulse_IO_update();
  534. }
  535.  
  536.  
  537. /***********************************************************************
  538. // traitement du codeur_rot()
  539.  
  540. // codeur incrémental code Gray avec en plus fonction bouton poussoir
  541. // le codeur rotatif utilisé est toujours à 11 en position stable
  542. // dans le sens CW il fait 11->01->00->10->11
  543. // et par conséquent dans le sens CCW il fait 11->10->00->01->11
  544.  
  545. // 11
  546. // 01
  547. // 00
  548. // 10
  549. // 11
  550.  
  551. // il suffit de connecter un bit sur le pin INT0 (PD2) et de déclencher l'INT sur front descendant (passage de 0 à 1)
  552. // cette INT lira alors l'état de l'autre bit (connecté à PD0 par exemple). Cet état diffère suivant le sens de rotation
  553.  
  554. ************************************************************************/
  555.  
  556.  
  557. ISR(BADISR_vect)
  558. {
  559. // évite de planter si une int est enable et pas de procedure associée écrite (ce qui fait reseter l'ATmega)
  560. }
  561.  
  562.  
  563. ISR (INT0_vect)
  564. {
  565. //interruption sur front descendant sur l'entree Int0
  566. // declenchee par la rotation du codeur_ROT (1) -> +/-frequence ou +/-trim ajustement fréquence 8MHz pour étalonnage par GPS
  567.  
  568. etat = PIND & 0b00000001;
  569. // +/-frequence
  570.  
  571. if (etat == 0)
  572. {
  573. if (f_out >= pas) {f_out -= pas;}
  574. }
  575. else
  576. {
  577. if ( (f_out+pas) <= 200000000) {f_out += pas;}
  578. if ( (f_out+pas) > 200000000) {f_out = 200000000;}
  579. }
  580.  
  581. demande_calcul = 1;
  582. }
  583.  
  584.  
  585. ISR (INT1_vect)
  586. {
  587. //interruption sur front descendant sur l'entree Int0
  588. // declenchee par la rotation du codeur_ROT (2) -> pas
  589. uint8_t n;
  590.  
  591. etat = PIND & 0b00000010;
  592.  
  593. if (etat == 0)
  594. {
  595. if (pos < 9) {pos++ ;}
  596. }
  597. else
  598. {
  599. if (pos > 1) {pos--;}
  600. }
  601.  
  602. pas=1;
  603. for (n=1; n<pos; n++)
  604. {
  605. pas *=10;
  606. }
  607. demande_calcul = 1;
  608. }
  609.  
  610.  
  611.  
  612. int main (void)
  613. {
  614. cli();
  615.  
  616. init_variables();
  617. init_ports();
  618. InitINTs();
  619.  
  620. init_lcd();
  621.  
  622. _delay_ms(100);
  623.  
  624. reset_DDS();
  625. init_DDS();
  626.  
  627. calcul_FTW();
  628. out_FTW();
  629.  
  630. _delay_ms(100);
  631.  
  632. lcd_clrscr();
  633. lcd_puts("GeneHF AD9951");
  634. lcd_gotoxy(0,1);
  635. lcd_puts("version ");
  636. lcd_puts(version);
  637. _delay_ms(1000);
  638.  
  639. lcd_clrscr();
  640.  
  641. sei(); // enable interruptions
  642.  
  643.  
  644. while(1)
  645. {
  646. compteur1++;
  647. if (compteur1 >= 1000)
  648. {
  649. /*
  650. if ((PINC & pin_mode) == 0)
  651. {
  652. demande_calcul = 1;
  653. lcd_clrscr();
  654. lcd_puts("mode normal");
  655. _delay_ms(1000);
  656. lcd_command(LCD_DISP_ON_CURSOR);
  657. lcd_clrscr();
  658.  
  659. f_out = memo_f_out;
  660. calcul_FTW();
  661. out_FTW();
  662. _delay_us(10);
  663.  
  664. affiche_pas();
  665. _delay_us(10);
  666. affiche_frequence();
  667. _delay_us(10);
  668. }
  669. */
  670.  
  671. if(demande_calcul == 1)
  672. {
  673. cli();
  674. calcul_FTW();
  675. out_FTW();
  676. _delay_us(10);
  677.  
  678. affiche_pas();
  679. _delay_us(10);
  680. affiche_frequence();
  681. _delay_us(10);
  682.  
  683. sei();
  684. demande_calcul = 0;
  685. }
  686. compteur1=0;
  687. }
  688. _delay_us(100);
  689. }
  690. }
  691.  
  692.  

Documents :

9 Dessin du circuit imprimé

10 Réalisation du circuit imprimé

06 setp 2014: Le circuit imprimé est fait, il ne reste plus qu'à souder les composants...

11 Carte terminée :

08 sept 2014:

Les composants sont soudés, sauf la platine AD9951 (celle en place ne comporte pas de circuit).

J'ai ainsi pu tester les alims 5V, 3.3V, 1.8V et leur présence sur les pins correspondants.

12 Le DDS est en place sur la carte


L'ATmega, les boutons codeurs et l'affichage fonctionnent correctement. Je dispose donc d'une platine d'expérimentation complète qui va me permettre de développer le soft qui pilotera l'AD9951.

La partie la plus intéressante commence donc maintenant.

13 Premier signal !

12 sept 2014:

Eurêka ! L'AD9951 vient de générer pour la première fois des signaux sinusoïdaux dans une gamme étendue de fréquence (de qq HZ à qq dizaines de MHz).
Ouf ! Il était temps. Il m'a juste rendu fou pendant deux jours !! Certains points ne sont pas clairs dans le datasheet. En particulier l'application du signal I/O_UPDATE pour un DDS unique.

Je vais maintenant pouvoir le piloter précisément et lui faire générer la fréquence exacte désirée. Et bien entendu je publierai tous les codes sources en C ici.

14 Tout fonctionne correctement !

13 sept 2014:

Tout fonctionne correctement. La saisie de la fréquence (et du pas) par les boutons codeurs rotatifs est opérationnelle, la fréquence de sortie réagit instantanément.

Il reste à:
  • vérifier le comportement au delà de ce que j'ai testé avec un oscillo 30MHz ( donc oscillo 100MHz puis analyseur de spectre 1GHz).
  • concevoir un étage de sortie sérieux (actuellement les signaux de sorties sont en mode différentiel à très bas niveau), assorti d'un filtre VHF.
  • re-test à l'analyseur de spectre
  • mise en boitier
  • re-re-test à l'analyseur de spectre

15 Etage de sortie

10 octobre 2012:

Après avoir délaissé cette réalisation pendant un mois, je m'y suis replongé depuis hier et j'ai mis au point un étage de sortie qui délivre 1V crête à crête jusqu'à la fréquence maximum que j'ai pu visualiser et mesurer, soit 160 MHz.

Mais auparavant je vous dis le pourquoi de ce mois de retard. Une lecture attentive de ce site vous montrera que pendant ce temps j'ai réparé mon oscilloscope HM303-4. Mais ce n'est pas la vraie raison. En fait j'ai opéré cette modification sur mon oscillo (qui en avait certainement besoin) pour... passer le temps !

Tiens donc, monsieur joue alors que les choses sérieuses attendent ! Ben c'est à dire que la chose sérieuse en question ne marchait plus ! Ah ? et pourquoi ? Ben parce que j'avais grillé l'AD9951 !!!! Et de la manière la plus idiote qui soit ! J'ai voulu faire une vidéo de la carte en fonctionnement, posée sur l'oscilloscope avec dans le champ le fréquencemètre, et le camescope sur un pied photo. ça fonctionnait correctement puis clac, d'un coup plus rien, même l'affichage et l'Atmega se sont éteints.

Il se trouve que j'avais soudé un petit bout de fil de 5 cm sur une des sorties de l'AD9951 pour y connecter la sonde de l'oscillo. Et dans l'installation bancale pour la prise de vue, ce point non isolé sur une longueur d'environ 1mm a trouvé le moyen d'aller toucher l'alimentation générale de la carte, soit 8V 2A. Je confirme que l'AD9951 fonctionnant à 1,8V n'a pas apprécié. Quant au mois d'attente, c'est le temps de recevoir un circuit tout neuf commandé à Hong Kong.

Bref, voici le schéma de l'étage de sortie :

16 Schéma de l'étage de sortie

La fonction de cet étage de sortie N'EST PAS d'obtenir un signal puissant. Le but est de fournir un signal propre (sinusoïdal) d'amplitude juste deux fois plus grande (1V crête à crête) exploitable par un fréquencemètre numérique (c'est la moindre des choses...)
C'est aussi de protéger l'AD9951 contre les mauvais traitements susceptibles d'être infligés à la sortie. ;)

C'est un étage différentiel qui en tant que tel atténue les perturbations de mode commun. Comme la sortie du DDS est elle même en mode différentiel, ça tombe bien.

J'ai dans un premier temps testé le principe avec des transistors BC549B, dont la fréquence de transition est de 300MHz, mais dont la capacité collecteur-base importante (qq pF) a pour effet (Miller), dans ce montage émetteur commun, de diminuer la bande passante. Et en effet, cette configuration ne convenait que jusqu'à 5MHz.

Mais avec des BFR93A dont la Ft est = 5GHz, et la capa collecteur-base = 0.45pF, cette version fonctionne jusqu'à 160MHz. Au delà l'analyseur de spectre montre que l'atténuation augmente régulièrement jusqu'à 200MHz (limite haute de la fréquence générée).

Concernant la précision de la fréquence produite, le fréquencemètre numérique affiche, pour une Fo = 10.000000 MHz, une valeur de 10.00192 MHz.
Peut mieux faire, donc. Une petite capa de 15pF entre le pin 8 et AGND réduit cette erreur de moitié.

Ne perdons pas de vue que la fréquence du quartz = 20MHz est x20 par une boucle PLL interne au DDS, pour obtenir 400MHz...

La prochaine amélioration que je vais faire consistera à remplacer le quartz par un oscillateur 100.000 MHz, ou mieux un oscillateur intégré compensé en température (TCXO).

17 Version avec un Oscillateur Intégré comme ref. de fréquence (MCO-1510A)

23 oct 2014:

Pour cette nouvelle version j'ai donc remplacé le quartz 20MHz par Oscillateur intégré ( le MCO 1510A, donné pour une précision de +/- 25ppm) de 100MHz. L'entrée OSC/REFCLK (pin 8 de l'AD9851) est maintenant reliée à AGND par un 100nF comme préconisé sur le datasheet (pourquoi une valeur si forte pour de fréquences > 20MHZ ???) et l'entrée OSCREFCLK (pin 9 de l'AD9851) est attaquée par le signal d'horloge à 100MHz issu du MCO-1510A via un pont diviseur de mise à niveau (5V du MCO-1510A vers 1.8V du DDS) et un condensateur de liaison de 10nF (et pas 100 !) Quelques modifications ont dû être apportées au programme, en particulier :
  • CFR2<7:3>: Reference Clock Multiplier Control Bits = 00100; -> F x 4 (au lieu de x 20)
Voici le schéma obtenu :

18 Schéma avec oscillateur intégré MCO-1510A :

Remarques :
  • La résistance de 4k7 entre VEE de l'afficheur et le +5V peut-être une 2k2 entre VEE et GND. C'est suivant le type d'afficheur. (en principe il faut utiliser un potentiomètre, mais ça prend une place énorme).
  • Le MCO-1510A s'alimente en 5V alors que le MCO-1510B s'alimente en 3V3.

19 Vue d'ensemble de la nouvelle version :

Comme vous pouvez le voir je n'ai pas refait le circuit imprimé, j'ai juste implanté les nouveaux composants plus ou moins "en l'air".

De toute manière si je devais refaire la carte ce serait sans doute en CMS avec ma CNC phototraceuse laser ( qui est en cours de réalisation, voir l'article qui lui est consacré sur ce site)...

20 Le verdict du fréquencemètre :


21 Test sur la porteuse de France Inter GO

Cela mérite un commentaire:

avec la version à quartz 20MHz, pour une Fo désirée = 10.000000 MHz,
nous obtenions une valeur mesurée de 10.00192 MHz

avec cette version à Oscillateur Intégré 100MHz +/- 25ppm,
nous obtenons une valeur mesurée de 10.00006 MHz ( se situe bien dans la marge d'incertitude de +/- 25ppm )

L'erreur est donc en l’occurrence divisée par 32 !

Toutefois le modèle d'oscillateur intégré que j'ai utilisé (MCO 1510A) n'est pas un TCXO (TC=compensé en température), ces derniers offrant une précision de +/- 0.5ppm à +/- 2ppm, et pas 25ppm !

Reste maintenant à connaître la précision du fréquencemètre qui permettra d'y voir plus clair !
Pour cela une solution simple (dit-on ici et là...) est de prendre comme étalon la fréquence de la porteuse de France Inter GO (162 kHz) qui est pilotée par une horloge atomique au césium avec une précision de +/- 10E-12 (ça devrait le faire), pour étalonner le fréquencemètre.
Dans cette optique, je viens de faire un test rapide : j'ai placé ce générateur à côté d'un recepteur GO calé sur France Inter, voici ce que ça donne à l'oreille :
-sortie 163.000 kHz -> sifflement à 1kHz : c'est "normal".
-sortie 162.001 kHz -> battement audible , période à priori de 1s
-sortie 162.000 kHz -> pas de battement audible
-sortie 161.999 kHz -> battement audible , période à priori de 1s

ça permet de borner l'erreur en fréquence, qui est donc inférieure à
1/162000 = 6x10E-6
ce qui nous donne le même résultat que le fréquencemètre.
( 60Hz / 10 000 060 Hz = 6x10E-6 )

Vous remarquerez au passage que l'afficheur a changé de couleur.
Celui-ci a l'avantage d'être rétroéclairé.
Autre précision : Les points ne sont évidemment pas des points décimaux
mais des séparateurs de milliers.

22 Comment obtenir une grande précision de la fréquence ?

Oui, mais pourquoi cela ne nous donne-t-il pas un bornage avec une précision de 10E-12 qui est celle de France Inter GO ?
Tout simplement parce que pour atteindre cette précision il faut faire une mesure pendant 1 million de secondes, c'est à dire 10 jours, ou détecter une erreur de phase de 1/1000 de la période au terme d'une mesure de 1000s (17 minutes environ, ce qui est déjà plus raisonnable, quoi que 1/1000 de la période c'est 6ns !!! ).

Très bien, mais ne pourrait-on pas obtenir plus simplement et rapidement une mesure précise ?
Oui, c'est possible, en prenant comme étalon de fréquence une station de radio WWV en ondes courtes à 10MHz, 15, ou 20MHz par exemple.(Je lis sur Wikipédia : Les fréquences porteuses peuvent être reçues sur la terre entière selon la propagation et permettent de caler avec précision les récepteurs de trafic, en particulier la fréquence 15 MHz en Europe).

25 oct 2014:

Très bien, mais la réception des émetteurs lointains en ondes courtes est connue pour être aléatoire, et sujette au "fading" et à toutes sortes d'interférences... Pas d'autres solutions ?

Si bien sûr !
  • La plus simple consiste à utiliser un OCXO (oscillateur thermostaté ) à priori 100 fois plus précis (10E-8) qu'un TCXO. Mais ça consomme bien plus et il est bon de le recalibrer périodiquement...
  • Se baser sur la fréquence de balayage H reconstituée par les récepteur TNT qui sont censés être synchrones et précis... à vérifier.
  • Acquérir un générateur 10MHz à horloge atomique au rubidium (ne riez pas, en en trouve à partir de 200€, moi je pensais 10 x plus cher !)
  • Peut-être une excellente solution, utiliser un récepteur GPS. Ils délivrent un signal 10MHz avec une précision de 10E-12 (si calés sur les satellites) !! ouah ! Cherchez "NEO-7M GPS Module" sur Internet"
ATTENTION : seules les versions NEO-6T et NEO-7...) permettent de configurer le pin TIME PULSE pour fournir un signal de 10MHz. La version la plus courante, la NEO-6M ne permet pas de sortir autre chose que 1Hz !

23 Le module GPS - NEO-7M

26 oct 2014:
J'ai commandé un module GPS - NEO-7M

24 Remarque :

Quoi qu'il en soit de la précision de l'oscillateur de référence utilisé, on pourra toujours compenser son inexactitude absolue puisque le "tuning word" est un mot de 32 bits, ce qui permet d'ajuster la fréquence de sortie avec une acuité de 1/2³² soit 0,2 x 10E-9 ce qui est très "honorable". Toutefois cet ajustement ne règle pas un éventuel problème de dérive de la fréquence de référence dans le temps. D'où l’intérêt de la possibilité de contrôler la fréquence au moyen d'un récepteur GPS. Je vais donc persister dans cette voie.

25 Configuration du module GPS - NEO-7M

05 mai 2015: Je reprends cette étude après l'avoir mise sur la touche pendant six mois durant lesquels je me suis consacré à la fabrication de ma machine photo-flasheuse de circuits imprimés par diode laser 405nm, dont vous trouverez la description sur ce site.

Depuis j'ai bien sûr reçu le module GPS - NEO-7M et je dois dire de suite qu'il tient ses promesses : Le signal fourni est facilement exploitable pour étalonner un fréquencemètre. Il faut toutefois commencer par configurer ce module afin qu'il produise un signal de 8.000000 MHz, car par défaut il sort... 1Hz qui fait clignoter une led !

La configuration se fait par une simple liaison au PC par l'intermédiaire d'un adaptateur USB/TTL.
La marche à suivre est très bien expliquée sur cette page :

La page est écrite en russe ce qui ne pose aucun problème (un petit coup de G...l traducteur et la transcription s'affiche dans un français presque parfait ce qui est loin d'être le cas pour la traduction des sites en anglais !! Le russe serait-il plus proche du français que l'anglais ? Il me semble que Cavanna nous avait dit ça en effet).

Le datasheet du récepteur NEO-7 laisse entrevoir que sa programmation n'est pas simple. Heureusement le fabriquant fourni un petit programme de configuration très complet (mais très déroutant) "u-center". Petit problème ce programme n'est disponible que pour Window$ ou Androïd. Petite solution : Utiliser wine sous Linux. Et ça marche. (après avoir créé un lien symbolique de com1 vers ttyUSB0 dans le dossier de config de wine par la commande suivante :

ln -s /dev/ttyUSB0 ~/.wine/dosdevices/com1

puis il faut donner les permissions :

cd ~/.wine/dosdevices/com1
sudo chmod 777 ./com1


Le signal obtenu est extrêmement précis en fréquence mais très bruité en phase ce qui le rend impropre à constituer directement une base de temps, mais permet une comparaison de fréquences facile à l'oscilloscope par figure de Lissajous. Il est d'ailleurs impressionnant de voir une figure de Lissajous stable à cette fréquence !

26 Captures d'écran du logiciel

Ces deux images vous montrent la marche à suivre. Ce logiciel est une usine à gaz. Sans lire la doc je pense qu'il est très improbable de pouvoir l'utiliser.

27 Détecteur de synchronisation

Nous avons donc notre module récepteur GPS NEO-7M correctement paramétré. Ne pourrait-on pas se passer d'un oscilloscope pour étalonner le fréquencemètre ? Si, grâce à ce ce montage utilisant un circuit PLL en HC-MOS (rapide donc, pas un simple CD4046 !) 74HC4046, dont je n'utilise que le détecteur de phase (il en comporte 3 différents en plus d'un VCO). La sortie de ce comparateur de phase pilote deux LEDs, une rouge et une verte, ce qui permet de détecter visuellement et facilement la synchronisation entre le signal 8.000000MHz issu du récepteur GPS et celui produit par le géné VHF. (Les leds clignotent de plus en plus lentement lorsqu'on se rapproche de la synchronisation, jusqu'à devenir fixes. On voit alors qu'un simple souffle d'air frais sur l'oscillateur à quartz intégré du géné suffit à faire dériver la fréquence (certes d'une valeurs très faible de quelques ppm... mais pas nulle)).

Pour éviter de déformer le précieux signal de sortie du géné, un jumper à deux positions connecte cette sortie soit à la prise BNC de l'appareil soit au détecteur de synchronisation, mais pas les deux à la fois.

Le petit "circuit bouchon" LC câblé sur la sortie 8MHz du récepteur GPS sert à diminuer le "jitter" (bruit de phase) très important dont je vous ai parlé précédemment. Il importe de caler précisément la fréquence de résonance de ce circuit sur 8.000MHz (rechercher l'amplitude maximale à l'oscilloscope, en tournant le petit noyau de la self).

28 Le circuit imprimé du détecteur de synchronisation :



16 mai 2015:
Ce circuit imprimé est en cours de réalisation.
Il sera flashé avec ma toute nouvelle machine laser !
Le petit module GPS NEO-7M viendra se connecter directement sur le connecteur vertical visible en bas à gauche. Sur la partie droite sont disposées les deux LEDs de détection de glissement de phase.

29 Circuit imprimé flashé

20 mai 2015:
Voici donc le circuit imprimé prêt pour le perçage et soudage des composants. Le flashage a été effectué par ma flasheuse laser 405nm, en image miroir puisqu'il s'agit du coté cuivre alors que le dessin du circuit sous le logiciel libre Kicad se fait côté éléments.

J'ai expérimenté des "plans de masse" sous forme de pistes de largeur 1mm. Le résultat est précis en ce sens que les espaces entre ces pistes ont été reproduits (ce dont je me serais bien passé !). Il suffira de resserrer ces "pistes de masse" à l'avenir. Dans le cas présent ces pistes ne servent qu'à... économiser le perchlorure de fer lors de la gravure tout en accélérant ce temps de gravure.

30 Proto terminé

Et ça marche ? Non, puisque en l'électronique tout prototype non testé est réputé NE PAS fonctionner (alors que tout proptotype testé tombera en panne de préférence lors d'une démonstration). Sauf bien sûr dans le domaine spatial où en envoie à des milliards de km des trucs non testés parce qu'on n'a pas d'astéroïde dans le labo...

Dernière minute : Il y avait bien une erreur dans la saisie du schéma !!! (confusion entres les pins 9 et 14 du 74HC4046) J'ai corrigé le schéma. Je vais faire la correction sur le circuit imprimé et je continuerai à tester.

31 Un petit détail :

Le signal 8MHz généré par le module GPS fait clignoter (il est livré paramétré sur 1Hz) une LED cms, mais n'est pas câblé d'origine sur un pin de sortie. Cela oblige à souder un fil sur la piste qui alimente (par une 1K) cette LED. Vu la taille des composants cms, le fil (émaillé) doit être fin comme un cheveu (15/100mm). J'ai ajouté un point de soudure via un petit rectangle de circuit imprimé (en 8/10mm) soudé au plan de masse sous la carte, d'où part un fil (très) souple un peu plus gros (mais en fait très fin lui aussi).

22 mai 2015:

J'ai rajouté un petit buzzer piezo sur une des trois sorties du comparateur de phase, ce qui augmente l'ergonomie du synchroniseur : Pour un décalage de fréquence de plusieurs dizaines (voire centaines) de Hz l’œil ne permet plus de suivre les clignotements des LED, mais l'oreille prend alors tout naturellement le relais. Tout ça dans le but de ne pas devoir utiliser l'oscilloscope. Et comme le signal en sortie du comparateur n'est pas très "propre" (dû tout simplement au fait que le signal 8.000000MHz en sortie du GPS est entaché d'un très important bruit de phase comme nous l'avons vu plus haut), l'oreille continue de percevoir quelque chose même pour des fréquences de l'ordre du Hz (une sorte de respiration issue tout droit d'un film de science fiction, il doit y avoir une attaque d'Alien sur un des satellites du GPS).

32 TCXO en commande...

23 mai 2015:
Je viens de commander aux USA un vrai TCXO 20MHz (oscillateur intégré compensé en température) pour environ 11€. Voici le lien vers cet article :

En principe la fréquence ne devrait plus dériver lorsqu'on souffle dessus ! Nous verrons bien.

33 Passage à une base de temps TCXO

13 août 2015:
Cela fait bien deux mois que j'ai reçu le TCXO, mais faute de temps il est resté dans le tiroir. Aujourd'hui je l'ai essayé et comparé au signal GPS ( précis à 10E-12 je le rappelle) et je peux déjà dire qu'en effet la fréquence ne bronche plus lorsqu'on souffle dessus ! Je suis en train de l'incorporer au générateur et de modifier sa programmation en conséquence.

34 Tableau de précision temporelle

j'ai calculé ce tableau qui donne les écarts de temps correspondants à une précision donnée. Je reconnais que certains résultats m'ont étonnés moi-même. La précision du GPS en particulier est remarquable (0,3s en 10 000 ans... waouh !)

35 Cogitations en direct :

15 août 2015:
Je suis en train d"expérimenter autour du bruit de phase du signal 10MHz (par exemple, c'est programmable on l'a vu) reconstitué à la sortie du module récepteur GPS - NEO-7M.
Ce bruit de phase est épouvantable ! Le signal apparaît propre et dépourvu de tout bruit de phase lorsqu'on le visualise seul à l'oscilloscope synchronisé par lui même. Mais il en va tout autrement si l'on synchronise l'ocsillo sur un autre signal 10MHz issu d'un générateur indépendant précis et stable. Et le résultat est le même quel que soit le générateur utilisé comme référence de phase. Le signal issu du GPS est entaché d'une modulation de phase qui atteint 30% de la durée de la période pour cette fréquence de 10MHz. Autant dire qu'il n'est pas concevable de l'utiliser tel quel pour piloter la base de temps d'un générateur (comme celui que nous décrivons ici par exemple).

Il y a la solution qui consiste à piloter une PLL avec une grande constante de temps dans la boucle d'asservissement. Mais devant les difficultés rencontrées je creusé un peu plus et je me suis aperçu que les fluctuations de phases se font d'un manière qui semble aléatoire, suivant toutes sortes de fréquences dont les plus basses sont vraiment très... basses. J'ai essayé de calmer le bruit de phase en faisant passer le signal par un filtre à quartz 8.000MHz. Le signal qui sort est tout joli bien sinusoïdal, mais de phase tout autant fluctuante ! Je sais que le bruit de phase à plusieurs causes :
  • L'effet Doppler dû au mouvement des satellites qui ne sont pas géostationnaires.
  • Le fait qu'on reçoive les signaux de plusieurs satellites.
  • Les fluctuations de la célérité des ondes électromagnétique lorsqu'elles traversent l’ionosphère (elle est alors < c) qui est... ionisée et fluctuante.
  • Un brouillage volontaire par l'Oncle Sam pour dégrader la précisions de la position sur les appareils civils.
Mais j'ai aussi entendu dire que sur des durées plus longues (la seconde par exemple) le nombre de périodes reçues est exact.
Je me suis donc demandé à partir de quelle fréquence on peut considérer que le rattrapage de périodes masque le bruit de phase. j'ai donc programmé le module NEO-7M afin qu'il génère diverses fréquences, de plus en plus basses, voici ce que je constate :
  • 10MHz -> bruit de phase épouvantable
  • 1MHz -> bruit important
  • 100 kHz -> bruit moyen
  • 10kHz -> bruit très faible, à peine perceptible à l'oscillo
Pour cette dernière valeur (10kHz) j'ai mesuré une fluctuation de +/- 0.1us autour de la valeur moyenne de la durée de la période (100us), soit 0,1%.
Je pense que dans ce cas on peut utiliser une PLL comme le 4046 vu plus haut afin d'obtenir un signal parfaitement propre et d'une précision en fréquence excellente.

Oui mais obtenir en fin de compte un signal de 200MHz à partir d'une base de temps à 10kHz sans rajouter... de bruit de phase, me semble bien présomptueux, voire impossible !

Voyons dans le tiroir s'il n'y a pas un morceau de rubidium qui traîne.

36 Une solution sérieuse en vue

18 août 2014:
Nous avons vu que l'utilisation d'un quartz comme filtre ne supprimait pas le bruit de phase. Une PLL (boucle à verrouillage de phase non plus). Je me suis aperçu que cela est dû non pas à un suivi de la modulation de phase par le biais de l'asservissement, ce qui me paraissait inexplicable vu le filtre passe bas à longue période inséré dans ladite boucle, mais... par un couplage HF entre le signal entrant (issu du GPS) et le VCO de la PLL, couplage très "lâche" certes, j'avais quand même découplé les alims, couplage sans doute électromagnétique (à 8MHz il faut en fait tout blinder, ce n'est plus de la BF), et couplage d'autant plus efficace que le VCO de la PLL utilisée (un HCT40106) et constitué par un simple circuit "à relaxation" RC, dont le Q équivalent est au raz des pâquerettes.

J'ai alors pensé à câbler une PLL maison avec comme VCO un circuit oscillant LC, avec un Q de plusieurs centaines ou plus. Mais s'est vite imposé la conclusion que la stabilité en fréquence de ce genre d'oscillateur est vraiment minime, sans compter les signaux parasites qui pourraient être reçus. A tel point que la mise au point s'est avérée trop délicate.

Le TCXO offre quant à lui une excellente précision et stabilité de la fréquence, il est ajustable, mais il a un défaut rédhibitoire : L'ajustage ultra précis de la fréquence est mécanique, en tournant un microscopique condensateur ajustable, dont la vis de réglage n'est pas faite pour être utilisée à répétition : fragile, fente pas assez profonde, pratiquement HS après une journée de manip. Il existe des TCXO ajustables par tension, mais on les trouve très difficilement, c'est la galère.

J'ai eu alors l'idée de réaliser une PLL maison dont le VCO serait un simple quartz dans un oscillateur Colpitts (un seul transistor et qq condos) dont je ferais (très) légèrement varier la fréquence par une self ou un condensateur en série. L'idéal est d'utiliser un quartz de fréquence nominale 10MHz (ou 20) mais de fréquence réelle légèrement inférieure (9.999MHz environ) dont on augmente la fréquence par un condensateur, une diode varicap en occurrence située dans la boucle d'asservissement. J'ai donc expérimenté cette solution, et alors surprise, pas besoin de varicap ni d'asservissement, un simple couplage (très très très très très très) lâche suffit pour que cet oscillateur se synchronise sur la fréquence du signal 10MHz issu du module GPS. Et sans bruit de phase s'il vous plaît !!!! Voyez plutôt la photo, sachant que l'oscillo n'est pas synchronisé sur l'un des deux signaux affichés, mais sur un troisième signal de référence, ce qui prouve que le bruit de phase visible affecte bien le signal GPS et non celui généré par l'oscillateur à quartz.

Une fois tout cela incorporé au géné à AD9951, nous obtiendrons l'équivalent d'une référence au rubidium.

37 Base de temps GPS

Il est beau le titre de ce paragraphe, n'est-ce pas ? Et oui, ça fonctionne ! J'ai ajouté un étage adaptateur d'impédance pour attaquer le DDS AD9951, et ça fonctionne en continu, sans problème sur le plan électronique. Voici donc le schéma de cette base de temps.

Q1, C3 et C4 et le quartz constituent un oscillateur Colpitts classique. Le condensateur en série a pour effet de rehausser très très légèrement la fréquence d'oscillation du quartz pour l'amener aussi proche que possible d'exactement 10.000000000 MHz pour que le verrouillage sur le signal 10MHz du GPS se fasse (il faut donc choisir un quartz oscillant à une fréquence légèrement inférieure à 10.000MHz, mais marqué 10MHz sur le boitier, n'allez pas essayer un quartz de 9,5 MHz !!!!). Le couplage très lâche entre le GPS et l'oscillateur Colpitts est obtenu par un condensateur de 1pF.

Remarque : cet appareil était au départ en mesure de fournir un signal de sortie à 200MHz tant qu'il utilisait comme base de temps un quartz à 100MHz ou 20MHz ou un TCXO 20MHz. Le passage à une base de temps GPS ne permet qu'une référence à 10MHz, et vu que la PLL interne au 9951 ne permet qu'une multiplication par 20, cela nous donne une référence interne à 200MHz. Oui mais le signal de sortie ne doit pas dépasser la moitié de la valeur de cette référence interne, donc 100MHz. La limitation (à 10MHz en entrée) n'est sans doute pas incontournable mais je préfère m'en tenir à ça tout en bénéficiant de la précision du GPS.

38 Circuit imprimé de la base de temps

22 août 2015:
Le circuit imprimé est en cours d'étude

39 circuit gravé :

24 août 2015:

J'ai flashé le circuit avec ma machine laser décrite sur ce site.
Reste maintenant à souder les composants cms.

40 EVOLUTION

25 août 2015:

Je suis en train de faire évoluer cette réalisation. Un nouveau projet va voir le jour qui fera l'objet d'un nouvel article. Je vous laisse découvrir sur ce schéma en quoi cela consistera : Un générateur de porteuse de fréquence hyper précise modulable par un second signal synthétisé. Le tout orchestré par une carte ATmega2560 et un affichage TFT couleur 480x320. Je dispose de tous les composants nécessaires sauf le multiplieur analogique 500MHz (AD834) qui est en commande.

5 septembre 2015:

En attendant de recevoir le multiplieur analogique, j'ai, ces derniers temps, fait avancer le soft pour la carte Arduino Mega2560. Actuellement les deux génés (AD9850 et AD9951) sont correctement pilotés, l'affichage des deux fréquences est également ok. Donc le projet est très avancé.

Je me suis ensuite repenché sur la base de temps GPS. Je vous en parle plus en détail ci-dessous.

41 Le jitter de la base de temps GPS (suite...)

J'ai relu le "document russe", je me suis intéressé plus particulièrement au fait qu'il parle, à propos de la sortie HF du récepteur GPS NEO-7M de "bonnes" fréquences (celles ayant un faible jitter) et de "mauvaises" fréquences (toutes les autres, ayant un jitter abominable). Et il se trouve que j'ai choisi jusqu'à présent d'utiliser un signal de 10MHz qui figure parmi les "mauvaises" fréquences et qui est effectivement affublé d'un bruit de phase rendant carrément le signal flou vu avec un oscilloscope synchronisé sur une base de temps 10MHz de bonne qualité.

Pour mémoire, les bonnes fréquences sont des sous-multiples de 48MHz, inférieures (ou égales) à 24MHz, (48 MHz est la fréquence pilote au sein du récepteur GPS), c'est à dire :
  • 24.000 MHz
  • 16.000 MHz
  • 12.000 MHz
  • 8.000 MHz
  • 6.000 MHz
  • 4.000 MHz
  • etc...
J'avais quand même choisi 10MHz parce que je voulais un "chiffre rond" pour attaquer la PLL de l'AD9951. MAIS CE N'EST PAS DU TOUT une nécessite !! N'importe quelle fréquence peut faire l'affaire, il suffit d'adapter la formule du calcul du mot de commande dans le soft de pilotage du DDS.

Dès lors j'ai décidé d'expérimenter avec la fréquence de 12MHz (à programmer dans le NEO-7M), et un quartz 'Colpitts' 12MHz au lieu du 10MHz. Ouf ça repose les yeux à l'oscillo !

Oui mais les choses ne peuvent pas pour autant en rester là. En effet le jitter sur 10MHz était tellement important que mon étage oscillateur Colpitts à quartz était nécessaire, et efficace, divisant par au moins 100 (au pif) le jitter. Mais alors, ce doit être encore mieux pour le signal de fréquence 12MHz qui est déjà bien moins entaché par le bruit de phase ?? Et bien NON ! Dans ce cas je m’aperçois que ce bruit de phase plus faible... n'est pas (ou presque) diminué par l'oscillateur à quartz. En fait la synchronisation par couplage capacitif lâche donne un "petit coup de pouce" à chaque période qui est du même ordre de grandeur que le petit jitter résiduel. Le jitter en sortie de l'oscillateur est donc pratiquement égal à celui su signal de sychronisation ! L'oscillateur ne sert plus à rien ! Peut-on s'en passer alors ? Non, parce que ce jitter résiduel est quand même énorme compte tenu de la prétention de cet appareil à être une référence de fréquence. Autant qu'elle soit propre !

Et bien entendu je me suis assuré que ce bruit de phase résiduel n'est pas un bruit thermique, de grenaille, ou dû à un mauvais découplage d'alim... Et c'est très simple à vérifier, il suffit de déconnecter le couplage par la capa de 1pF pour que le bruit cesse immédiatement (mais alors la fréquence se met à dériver...)

Il faut donc trouver une solution. J'avais dit qu'un VCO RC n'est pas du tout assez stable et qu'il a trop tendance à se synchroniser "par sympathie" sur le signal du GPS. Reste la solution de conserver l'oscillateur à quartz (qui est apte à produire un signal de très bonne qualité) mais synchronisé d'une autre manière, par une varicap, dans une boucle PLL à filtre passe bas bien conçu. ça porte un nom, c'est un VCXO (Voltage-Controlled Crystal Oscillator). Attention : on trouve des VCXO réglables dans une plage de fréquences de 1.5MHz à 80MHz, je doute qu'ils soient à quartz !!!!!!

Donc je vais le faire moi-même !

42 VCXO maison pour PLL base de temps GPS

10 septembre 2015:
Voici donc les caractéristiques de cette nouvelle version de la base de temps:
  • Exploitation d'une "bonne" fréquence (12MHz) du récepteur GPS NEO7-M à relativement faible bruit de phase.
  • Génération d'un signal 12MHz "propre", sans bruit de phase, par un oscillateur colpitts à quartz 12MHz,
  • Asservissement fin de la fréquence de cet oscillateur par une diode varicap pilotée par le comparateur de phase (entre le VCO et le GPS) d'un 74HC4046 suivi d'un filtre passe bas de grande période,
  • Blindage soigné de l'oscillateur colpitts afin qu'il ne se synchronise pas directement sur le signal bruité en phase du GPS par couplage HF (sinon la PLL ne sert plus à rien et le résultat est désastreux)

43 Le circuit imprimé :

Le circuit imprimé a été flashé avec ma machine laser. Le quartz 12MHz (qui n'est pas en CMS) se situe sur l'autre face.

44 Le VCXO dans son boîtier blindé

Le circuit imprimé est fixé sur le fond du boîtier par deux vis de 2mm en laiton + entretoises (il faut loger le quartz dessous) assurant la conduction électrique (mise à la masse).

Les fils de liaison doivent être le plus court possible dans leur partie interne au boîtier sinon ils constitueront une antenne réémettant à l'intérieur ce qu'ils auront capté à l'extérieur (comme l'antenne de l'auto-radio) rendant le blindage inefficace. Le boîtier est relié à la masse du montage par les vis de fixation en laiton ainsi que par une liaison externe rajoutée. En fin ce compte c'est l'oscillo synchronisé sur une base de temps indépendante propre et fiable qui permettra de juger de l'absence de bruit de phase et donc de l’efficacité du blindage, en comparant le signal GPS entrant et le signal 12MHz reconstitué sortant, ou bien un analyseur de spectre.

45 L'ensemble de la base de temps

Seul le VCXO est enfermé dans un blindage HF (contre les perturbations entrantes) en alu. Bien entendu l'ensemble du générateur sera enfermé dans un boitier métallique constituant un blindage CEM contre les perturbations sortantes cette fois qui pourraient affecter l'environnement. Je rappelle qu'en VHF le moindre bout de fil constitue un antenne relativement efficace. A ce sujet j'ai ajouté quelques liens vers des sites traitant des CEM au bas de cet article.

13 septembre 2015:
Ce schéma a été modifié plusieurs fois ces derniers jours, suite à maints et maints tests et mesures concernant la qualité de la synchronisation, la pureté des signaux, y compris du signal de sortie synthétisé par l'AD9951. Ce dernier sera situé sur une carte à part, avec ses propres alims +5V, +3V3 et +1V8. Dés lors la liaison entre cette carte base de temps et la la carte du synthé AD9951, liaison qui transmet le signal d'horloge 12MHz, doit se faire par un câble blindé HF reliant les masses au plus près des circuits 74LS04 (pin 8) d'un côté et AGND de l'AD9951 (pin7) sous peine de se retrouver avec une boucle de masse rayonnant du 12MHz qui se retrouverait sur le signal de sortie. Tous les autres fils actifs au sein d'une même carte doivent être le plus courts possible, et il faut éviter tout couplage par retours de masse communs. Ne perdons pas de vue que le 12MHz d'horloge se trouve dans la gamme de fréquences synthétisées, donc une fois que la pollution 12MHz entre dans un étage on ne peut plus la filtrer pour s"en débarrasser.

46 Le circuit imprimé de la base de temps

14 septembre 2015:
Le dessin du circuit imprimé est fait, il ne reste plus qu'à le flasher.
Les grosses pastilles jaunes reliées à GND correspondent à des trous qui accueilleront des vis de 2mm servant à fixer un plan de masse de toute la surface du circuit, à une distance de quelques mm au dessous. Un circuit double face aurait pu convenir également, mais les capacités rapportées auraient été plus importantes du fait de la distance beaucoup plus faible ainsi que par la nature du diélectrique autre que l'air.

47 ça avance...

22 septembre 2015:
Tout doucement... Faut dire que je ne fais pas que ça de mon temps libre, par exemple je viens de remplacer une fenêtre de toit de 1,50m x 1,50m...

48 Les essais ne devraient pas tarder

Comme d'habitude maintenant, le circuit a été flashé par ma machine laser. J'ai conservé une bande de cuivre reliée à la masse en bordure de carte simplement en appliquant une bande de scotch juste avant la gravure au perchlo.

49 Toutes les cartes fonctionnent

Toutes les cartes sont interconnectées et fixées sur un plan de masse. La photo ayant été prise en extérieur, l'affichage sur le LCD vu de biais est bien peu visible. Bientôt tout cela sera placé dans un joli coffret métallique alu vert.

30 septembre 2015:
Maintenant que toutes les carte sont assemblées sur un grand circuit vierge faisant office de plan de masse, que les liaisons des signaux s'effectuent par câbles blindés et que les alimentations sont proprement découplées (selfs sur tores + capas) les signaux générés sont impeccables, tant le signal principal généré par le DDS AD9951 piloté par GPS que le signal secondaire généré par le DDS AD9850.

Entre temps j'ai acquit une horloge atomique de référence au rubidium (d'occasion à un prix très abordable sur eBay). C'est dire que question fréquence je vois nettement ce qui se passe.

A propos des horloges atomiques au Rubidium :
  • Une horloge atomique, ce n'est PAS une horloge nucléaire. Ce qui est mis à profit dans une horloge atomique, c'est une fréquence de résonance des ELECTRONS de l'atome, rien à voir donc avec les constituants du noyau, pas de radioactivité au programme !
  • Le Rubidium existe dans la nature sous forme de deux isotopes, le rubidium 85 stable, et le rubidium 87 très légèrement radioactif (demi-vie 47 milliards d'année) bien inférieure donc à celle du potassium 40 que nous hébergeons dans notre corps.
  • Les horloges au rubidium utilisent bien l'isotope 87 radioactif, mais compte tenu de la remarque précédente, cette radioactivité n'est pratiquement pas mesurable. J'ai passé cette horloge au compteur Geiger, et je n'ai pas trouvé d'augmentation de la radioactivité par rapport à la radioctivité naturelle du lieu.
Je constate d'ors et déjà que le glissement de phase entre le signal issu du récepteur GPS NEO-7M et celui du rubidium correspond à une erreur en fréquence de 3x10E-10 ce qui est un ordre de grandeur plus important que la précision donnée pour cette horloge au rubidium (FE 5680A de chez FEI Communications, Inc.) Toutefois je me garderai bien dès à présent d'incriminer le rubidium dont les atomes bien éduqués savent qu'ils doivent osciller à la fréquence correspondant à la transition hyperfine 5²S1/2 égale à 6.834 682 610 904 290 GHz. Quant aux satellites GPS, ils embarquent me semble-t-il des horloges semblables.

Alors où est l'erreur ? J'ai bien lu ici et là que les horloges au rubidium "vieillissent mal"... Mais de quel point de vue ? ce n'est pas précisé. Deux autres sources d'erreurs me paraissent bien plus plausibles:
  • Le récepteur GPS ne sort pas directement la fréquence de son horloge interne (64MHz) mais une fréquence reconstituée par... un circuit DDS.
  • D'une manière analogue, l'horloge rubidium FE 5680A ne sort pas directement le signal 6GHz de son VFO couplé à la cavité atomiquement résonnante, ni même un 50255 MHz (après division exacte par /136), mais une fréquence (10MHz) également reconstituée par un synthétiseur qui si j'ai bien compris la doc, est un DDS. Et alors... la précision est peut-être dégradée, suivant la longueur du mot de programmation de ce DDS (nb de bits)


Quoi qu'il en soit, ne perdons pas de vue que nous obtenons une précision d'une seconde par siècle...
Mais comme je me connais, ça ne m'empêchera pas un de ces quatre d'aller choper directement le signal 6.834 682 610 904 290 GHz à l'intérieur du boîtier et de le diviser moi-même pour en avoir le cœur net.

Je vais maintenant travailler sur la partie modulateur d'amplitude. Une note d'ANALOG DEVICES (AN-423) concernant le circuit AD9850, mais sans doute transposable à l'AD9951 indique une manière très simple d'obtenir une modulation AM du signal de sortie du DDS avec un simple transistor FET utilisé en résistance dynamique variable dans le circuit (interne) de sortie. Je vais étudier ça.

50 Modulation AM du signal

En définitive le multiplieur analogique AD834 que j'avais déjà cité plus haut me donne entière satisfaction. Ses entrées différentielles symétriques s'adaptent particulièrement bien aux sorties également différentielles symétriques des DDS AD9850 et AD9851. Voici le résultat obtenu pour une porteuse de 1MHz modulée par un signal de 2kHz. Le taux de modulation est facilement réglable par un ajustable de 20K. On pourra aussi prévoir de le faire par une diode PIN pilotée par l'ATmega2560...

Je vais comme il se doit réaliser un petit circuit imprimé pour cet étage dont je publierai ici le schéma.

A suivre...

51 Schéma du modulateur

L'étage de sortie, un ampli différentiel constitué par deux transistors UHF BFR93A permet d'obtenir un signal en tension unique référencée à la masse, de bonne amplitude (1V) avec un faible bruit. Il reste à ajouter un filtre de sortie passe bas à pente raide, de type Butterworth ou Tchebychev.


52 Le circuit imprimé du modulateur AM

Il est en cours de conception. Attention : Le boîtier AD834 devra être soudé côté cuivre, c'est à dire sur la même face que les composants cms. (comme si c'était un composant cms lui aussi, mais je n'en ai pas en stock dans ce format).

53 Modulateur terminé

7 octobre 2015:
Les trois ajustables 10k sont sur l'autre face. Je les remplacerai peut-être par trois tensions générées par l'ATmega2560 sous la forme de signaux PWM, intégrés par circuit RC.

Pour l'instant ceci fonctionne correctement.

Il est maintenant plus urgent de mettre en place un filtre de sortie de type Butterworth ou Tchebychev.

Je vous tiens au courant...

54 Le firmware en C pour l'ATmega2560 de l'Arduino

CODE SOURCE en c
  1. /**
  2. Firmware pour générateur de signaux à deux DDS : AD9951 et AD9850.
  3. POUR carte Uno Mega2560 + afficheur 480x320 non tactile.
  4. par Silicium628.
  5.  
  6. Ce fichier source "GeneHF_AD9951_MODUL.ino" est libre, vous pouvez le redistribuer et/ou le modifier selon
  7. les termes de la Licence Publique Générale GNU.
  8.  
  9. En ce qui concerne certaines bibliothèques incluses, issues du domaine "Arduino", il faut voir au cas par cas.
  10. **/
  11.  
  12. // REMARQUE : les ports utilisés doivent être définis dans les fichiers "AD9850.h" et "AD9951.h"
  13.  
  14. //================================
  15. #define version "v3.4"
  16. //================================
  17.  
  18. #include <avr/io.h>
  19. #include <stdint.h>
  20. #include <stdlib.h>
  21. #include <util/delay.h>
  22. #include <math.h>
  23.  
  24.  
  25. #include "UTFT.cpp"
  26. #include "uart2560_628.c"
  27.  
  28. #include <AD9951-628v6.cpp>;
  29. #include <AD9850-628v1.cpp>;
  30.  
  31.  
  32.  
  33. //#include "timeout.h" // comprend : #define F_CPU 16000000UL // 16 MHz
  34.  
  35.  
  36.  
  37. #define portPIN_switch PINH
  38. #define pin_switch1 0b00000001
  39. #define pin_switch2 0b00000010
  40.  
  41. #define portPIN_RAZ PINK
  42. #define pin_RAZ 0b00000001
  43.  
  44.  
  45.  
  46. // Declare which fonts we will be using
  47. extern uint8_t SmallFont[];
  48. extern uint8_t BigFont[];
  49. extern uint8_t SevenSegNumFont[];
  50.  
  51. /****************************************************************************************************************************************************
  52. //choisir le bon driver suivant, en fonction du type d'afficheur (tous deux des 480x320 qui se ressemblent, achetés au même fournisseur) ******
  53. //la library correcte doit être installée dans le bon dossier ARDUINO, avec les permissions d'accès correctes !
  54.  
  55. UTFT TFT480(CTE32HR,38,39,40,41);
  56. UTFT TFT480(HX8357C,38,39,40,41);
  57. ****************************************************************************************************************************************************/
  58.  
  59. UTFT TFT480(HX8357C,38,39,40,41);
  60.  
  61.  
  62. AD9951 DDS_porteuse;
  63. AD9850 DDS_modulation;
  64.  
  65. uint16_t x_7seg, y_7seg;
  66.  
  67. uint32_t f_porteuse , memo_f_porteuse; // fréquence du signal de sortie sinusoïdal
  68. uint32_t f_modulation , memo_f_modulation; // fréquence du signal de sortie sinusoïdal
  69.  
  70. uint32_t FTW_entier;
  71. uint8_t FTW_decim;
  72.  
  73. uint8_t pos; // position du multiplicateur
  74. uint8_t pos_curseur_porteuse, pos_curseur_modulation; // position du curseur (pour affichage)
  75. uint8_t switches, memo_switches; // 8 bits permettant de mémoriser l'état de 8 inverseurs
  76.  
  77. uint32_t pas_porteuse, pas_modulation;
  78. uint8_t etat;
  79. uint8_t depassement;
  80. uint16_t compteur1;
  81.  
  82. uint16_t phase;
  83. uint32_t R3;
  84. double dP_r;
  85. uint16_t dP;
  86.  
  87.  
  88. uint8_t demande_calcul_porteuse, demande_calcul_modulation;
  89. float position;
  90.  
  91.  
  92.  
  93. /**
  94. RAPPEL variables avr-gcc (vérifiable avec le .map)
  95.  
  96. char 1 -128 .. 127 ou caractères
  97. unsigned char 1 0 .. 255
  98. uint8_t 1 (c'est la même chose que l'affreux 'unsigned char')
  99. char toto[n] n
  100. int 2 -32768 .. 32767
  101. int16_t 2 idem 'int'
  102. short int 2 pareil que int (?)
  103. unsigned int 2 0 .. 65535
  104. uint16_t 2 idem 'unsigned int'
  105. long int 4 octets -2 147 483 648 à 2 147 483 647
  106. int32_t 4 octets -> 32 bits ; idem long int
  107. long long int 8 octets -> 64 bits
  108. unsigned long int 4 octets -> 32 bits ; 0 .. 4 294 967 295 (4,2 x 10^9)
  109. uint32_t 4 32 bits ; idem 'unsigned long int'
  110. float 4
  111. double ATTENTION ! 4 octets (oui, 32 bits ! et pas 64 bits (8 octets) comme en C standard)
  112.  
  113. La déclaration char JOUR[7][9];
  114. réserve l'espace en mémoire pour 7 mots contenant 9 caractères (dont 8 caractères significatifs).
  115. **/
  116.  
  117. void init_ports (void) // ports perso
  118. {
  119. // 0 = entree, 1=sortie ; les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  120.  
  121. DDRD = 0b11110000;
  122. PORTD = 0b00001111;
  123.  
  124. DDRF = 0b11111111;
  125. PORTF = 0b00000000;
  126.  
  127. DDRH = 0b11111100;
  128. PORTH = 0b00000011;
  129.  
  130. DDRJ = 0b11111110; // PJ0 = RXD3 - PJ1 = TXD3 (attention c'est PJ1 qui est au bord du connecteur sur la carte Arduino mega2560)
  131. PORTJ = 0b00000001;
  132.  
  133. DDRK = 0b11111110;
  134. PORTK = 0b00000001;
  135.  
  136. // REMARQUE : les ports utilisés doivent être définis dans les fichiers "AD9850.h" et "AD9951.h"
  137. }
  138.  
  139.  
  140.  
  141. void int_EXT_setup()
  142. {
  143. // voir 15. External Interrupts (ATmega2560.pdf p:109)
  144.  
  145. EICRA |= 0b00001010; // bits[1,0] = 10 -> int0 sur front descendant; bits[3,2] = 10 -> int1 sur front descendant; - p:110
  146. EIMSK |= 0b00000011; // bit[0]=1 -> INT0 enable ; bit[1]=1 -> INT1 enable - parag 15.2.3 EIMSK - p:111
  147. }
  148.  
  149.  
  150.  
  151. void timer3_setup()
  152. // Pour
  153. {
  154. cli();
  155. TIMSK3 = 0b00000010; // Bit 1 – OCIE3A: Timer/Counter1, Output Compare A Match Interrupt Enable - p:161
  156.  
  157. TCCR3A = 0b00000000; // p:154
  158. TCCR3B = 0b00001011; // prescaler = 1/64 - CTC mode - p:156 et tableau p:157
  159.  
  160. OCR3A = 250; // compare match register - fixe la période - 16MHz/64/250 = 1kHz
  161. sei();
  162. }
  163.  
  164.  
  165.  
  166. void init_variables(void)
  167. {
  168. demande_calcul_porteuse = 0;
  169. demande_calcul_modulation = 0;
  170.  
  171. // f_porteuse = 100000;
  172. // f_modulation = 1000;
  173.  
  174. f_porteuse = 12000000;
  175. f_modulation = 0;
  176.  
  177. pos_curseur_porteuse = 5;
  178. pos_curseur_modulation = 2;
  179.  
  180. pas_porteuse = 10000;
  181. switches =0;
  182. memo_switches =0;
  183.  
  184. pas_modulation = 10;
  185. // adjust = 128;
  186. phase = 0;
  187. compteur1 =0;
  188. depassement =0;
  189.  
  190. x_7seg =0;
  191. y_7seg =0;
  192. }
  193.  
  194.  
  195.  
  196.  
  197. ISR (INT0_vect)
  198. {
  199. //interruption sur front descendant sur l'entree Int0
  200. // declenchee par la rotation du codeur_ROT (1) -> +/-frequence
  201.  
  202. etat = PIND & 0b00000100;
  203.  
  204. if ( (switches & 0b00000001) == 0)
  205. {
  206. if (etat == 0b00000100)
  207. {
  208. if (f_porteuse >= pas_porteuse) {f_porteuse -= pas_porteuse;}
  209. }
  210. else
  211. {
  212. if ( (f_porteuse+pas_porteuse) <= 120000000) {f_porteuse += pas_porteuse;}
  213. if ( (f_porteuse+pas_porteuse) > 120000000) {f_porteuse = 120000000;}
  214. }
  215. demande_calcul_porteuse = 1;
  216. }
  217. else
  218. {
  219. if (etat == 0b00000100)
  220. {
  221. if (f_modulation >= pas_modulation) {f_modulation -= pas_modulation;}
  222. }
  223. else
  224. {
  225. if ( (f_modulation+pas_modulation) <= 40000000) {f_modulation += pas_modulation;}
  226. if ( (f_modulation+pas_modulation) > 40000000) {f_modulation = 40000000;}
  227. }
  228. demande_calcul_modulation = 1;
  229. }
  230.  
  231. }
  232.  
  233.  
  234.  
  235. ISR (INT1_vect)
  236. {
  237. //interruption sur front descendant sur l'entree Int1
  238. // declenchee par la rotation du codeur_ROT (2) -> pas
  239. uint8_t n;
  240. etat = PIND & 0b00001000;
  241.  
  242. if ( (switches & 0b00000001) == 0) // saisie curseur porteuse
  243. {
  244. if (etat == 0)
  245. {
  246. if (pos_curseur_porteuse > 1) {pos_curseur_porteuse--;}
  247. }
  248. else
  249. {
  250. if (pos_curseur_porteuse < 9) {pos_curseur_porteuse++ ;}
  251. }
  252. pas_porteuse=1;
  253. for (n=1; n<pos_curseur_porteuse; n++) { pas_porteuse *=10; }
  254. demande_calcul_porteuse = 1;
  255. }
  256.  
  257. else // saisie curseur modulation
  258. {
  259. if (etat == 0)
  260. {
  261. if (pos_curseur_modulation > 1) {pos_curseur_modulation--;}
  262. }
  263. else
  264. {
  265. if (pos_curseur_modulation < 8) {pos_curseur_modulation++ ;}
  266. }
  267. pas_modulation=1;
  268. for (n=1; n<pos_curseur_modulation; n++) { pas_modulation *=10; }
  269. demande_calcul_modulation = 1;
  270. }
  271. }
  272.  
  273.  
  274.  
  275. ISR(TIMER3_COMPA_vect)
  276. {
  277. // ajustement de la fréquence par glissement de phase.
  278. // ici 1kHz
  279. // portTEST ^= pin_TEST1;
  280. compteur1++;
  281. if (compteur1 >= 100)
  282. {
  283. // ici 10Hz
  284. // ici on fait des sauts de phase (sauts dans le futur), c'est à dire qu'on raccourci la durée de la période
  285. compteur1=0;
  286. DDS_porteuse.out_POW(phase);
  287. phase +=dP ;
  288. }
  289. }
  290.  
  291.  
  292. void trace_ecran()
  293. {
  294. TFT480.setColor(10, 10, 5);
  295. TFT480.fillRect(0, 14, 479, 309);
  296. trace_entete();
  297. efface_pannel_porteuse();
  298. efface_pannel_bas();
  299. }
  300.  
  301.  
  302.  
  303. void trace_entete()
  304. {
  305. TFT480.setColor(64, 64, 64);
  306. TFT480.fillRect(0, 0, 479, 13); // bandeau en haut
  307.  
  308. TFT480.setColor(30, 30, 30);
  309. TFT480.fillRect(0, 300, 479, 319); // bandeau en bas
  310.  
  311.  
  312. // TFT480.setBackColor(64, 64, 64);
  313. TFT480.setColor(255,255,255);
  314. TFT480.print("GENE 120MHz - AD9951 syncho par GPS + AD9850", CENTER, 1);
  315.  
  316. // TFT480.setBackColor(64, 64, 64);
  317. TFT480.setColor(255,255,255);
  318. TFT480.print(version, 5, 300);
  319. TFT480.setColor(255,255,255);
  320. TFT480.print("ATmega2560 - ", 100, 300);
  321. TFT480.setColor(255,255,255);
  322. TFT480.print("Silicium628", 210, 300);
  323.  
  324. }
  325.  
  326.  
  327. void TFT_aff_ICI()
  328. {
  329. TFT480.setFont(BigFont);
  330. TFT480.setColor(255,255,255);
  331. TFT480.print("ICI", 0, 220);
  332. }
  333.  
  334.  
  335.  
  336.  
  337. void TFT_affiche_chiffre_7seg(uint8_t num)
  338. {
  339. TFT480.setFont(SevenSegNumFont);
  340. TFT480.printNumI(num, x_7seg, y_7seg);
  341. }
  342.  
  343.  
  344.  
  345. void TFT_aff_nb_form3 (uint32_t valeur, uint8_t nb_chiffres, uint8_t curseur, uint8_t R, uint8_t G, uint8_t B)
  346. {
  347. //affiche un nombre en representation decimale en separant les groupes de 3 chiffres
  348. unsigned char r ;
  349. char tbl[7];
  350. uint8_t i;
  351.  
  352. curseur=nb_chiffres +1 -curseur;
  353.  
  354. for (i=1; i<=nb_chiffres; i++)
  355. {
  356. r=valeur % 10; // modulo (reste de la division)
  357. valeur /= 10; // quotient
  358. tbl[i]=r;
  359. }
  360. for (i=1; i<= nb_chiffres; i++)
  361. {
  362. TFT480.setColor(R,G,B);
  363.  
  364. if (i==curseur)
  365. {
  366. TFT480.setColor(0,250,250);
  367. }
  368. TFT_affiche_chiffre_7seg(tbl[nb_chiffres +1 -i]);
  369. x_7seg+=30;
  370.  
  371. uint8_t m, k, u;
  372. m =nb_chiffres-6;
  373. k =nb_chiffres-3;
  374. u =nb_chiffres;
  375.  
  376. //if (i== 3)
  377. if (i== m)
  378. {
  379. TFT480.setFont(BigFont);
  380. TFT480.setColor(100,100,100);
  381. TFT480.print("M",x_7seg, y_7seg+30);
  382. x_7seg+=15;
  383. }
  384. //if (i== 6)
  385. if (i== k)
  386. {
  387. TFT480.setFont(BigFont);
  388. TFT480.setColor(100,100,100);
  389. TFT480.print("k",x_7seg, y_7seg+30);
  390. x_7seg+=15;
  391. }
  392. //if (i== 9)
  393. if (i== u)
  394. {
  395. TFT480.setFont(BigFont);
  396. TFT480.setColor(100,100,100);
  397. TFT480.print("Hz",x_7seg, y_7seg+30);
  398. }
  399.  
  400. }
  401. }
  402.  
  403.  
  404.  
  405. void affiche_frequence_porteuse(uint8_t R, uint8_t G, uint8_t B)
  406. {
  407. uint8_t x0=10;
  408. uint8_t y0=20;
  409.  
  410. TFT480.setBackColor(0, 0, 0);
  411. TFT480.setColor(100,160,230); // couleur de l'étiquette uniquement
  412. TFT480.setFont(BigFont);
  413. TFT480.print("FREQ PORTEUSE", x0, y0); // Etiquette
  414.  
  415. x_7seg = x0;
  416. y_7seg = y0+20;
  417.  
  418. uint8_t p2;
  419. p2 = pos_curseur_porteuse;
  420. if ((switches & 0b00000001) == 0b00000001) {p2=0;} // pour ne pas afficher la surbrillance du "curseur" si le mode de saisie en cours concerne l'autre fréquence
  421.  
  422. TFT_aff_nb_form3 (f_porteuse, 9, p2, R, G, B);
  423.  
  424. }
  425.  
  426.  
  427.  
  428. void affiche_frequence_modulation(uint8_t R, uint8_t G, uint8_t B)
  429. {
  430. uint8_t x0=10;
  431. uint8_t y0=110;
  432.  
  433. TFT480.setBackColor(0, 0, 0);
  434. TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  435. TFT480.setFont(BigFont);
  436. TFT480.print("FREQ MODULATION", x0, y0);
  437.  
  438. x_7seg = x0+30;
  439. y_7seg = y0+20;
  440.  
  441. uint8_t p2;
  442. p2 = pos_curseur_modulation;
  443. if ((switches & 0b00000001) == 0) {p2=0;} // pour ne pas afficher la surbrillance du "curseur" si le mode de saisie en cours concerne l'autre fréquence
  444.  
  445. TFT_aff_nb_form3 (f_modulation, 8, p2, R, G, B);
  446. }
  447.  
  448. /*
  449.  
  450. void affiche_adjust()
  451. {
  452. uint8_t x0=10;
  453. uint8_t y0=215;
  454.  
  455. uint8_t x1;
  456. uint8_t y1;
  457.  
  458. TFT480.setBackColor(0, 0, 0);
  459.   TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  460.   TFT480.setFont(BigFont);
  461. TFT480.print("Adj FRQ", x0, y0);
  462.  
  463. x1 = x0+120;
  464. y1 = y0;
  465.  
  466. uint8_t p2;
  467. p2=0; // pour ne pas afficher la surbrillance du "curseur"
  468.  
  469. TFT480.setColor(0,255,0);
  470. TFT480.print(" ", x1, y1);
  471. TFT480.printNumI(adjust-128, x1, y1);
  472. }
  473. */
  474.  
  475.  
  476. void affiche_phase()
  477. {
  478. uint8_t x0=10;
  479. uint8_t y0=255;
  480.  
  481. uint8_t x1;
  482. uint8_t y1;
  483.  
  484. TFT480.setBackColor(0, 0, 0);
  485. TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  486. TFT480.setFont(BigFont);
  487. TFT480.print("Phase", x0, y0);
  488.  
  489. x1 = x0+100;
  490. y1 = y0;
  491.  
  492. uint8_t p2;
  493. p2=0; // pour ne pas afficher la surbrillance du "curseur"
  494.  
  495. TFT480.setColor(0,255,0);
  496. TFT480.print(" ", x1, y1);
  497. TFT480.printNumI(phase, x1, y1);
  498. }
  499.  
  500.  
  501. void efface_pannel_porteuse()
  502. {
  503. uint16_t x1, y1, x2, y2;
  504. x1=5;
  505. y1=30;
  506. x2=350;
  507. y2=100;
  508.  
  509. TFT480.setColor(10, 10, 5);
  510. TFT480.fillRect(x1, y1, x2, y2);
  511. TFT480.setColor(100, 100, 100);
  512. TFT480.drawRect(x1, y1, x2, y2);
  513. }
  514.  
  515.  
  516. void efface_pannel_bas()
  517. {
  518. uint16_t x1, y1, x2, y2;
  519. x1=5;
  520. y1=190;
  521. x2=350;
  522. y2=290;
  523.  
  524. TFT480.setColor(10, 10, 5);
  525. TFT480.fillRect(x1, y1, x2, y2);
  526. TFT480.setColor(100, 100, 100);
  527. TFT480.drawRect(x1, y1, x2, y2);
  528. }
  529.  
  530.  
  531. void affiche_phaseOff()
  532. {
  533. uint8_t x1=340;
  534. uint8_t y1=250;
  535.  
  536. TFT480.setFont(BigFont);
  537. TFT480.setColor(60,60,60);
  538. TFT480.print("phase adjust OFF", x1, y1);
  539.  
  540. }
  541.  
  542.  
  543. void affiche_FTW_entier()
  544. {
  545. uint8_t x0=10;
  546. uint8_t y0=200;
  547.  
  548. uint8_t x1;
  549. uint8_t y1;
  550.  
  551. TFT480.setBackColor(0, 0, 0);
  552. TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  553. TFT480.setFont(BigFont);
  554. TFT480.print("FTW", x0, y0);
  555.  
  556. x1 = x0+100;
  557. y1 = y0;
  558.  
  559. TFT480.setColor(0,255,0);
  560. TFT480.print(" ", x1, y1);
  561. TFT480.printNumI(FTW_entier, x1, y1);
  562. }
  563.  
  564.  
  565.  
  566. void affiche_FTW_decim()
  567. {
  568. uint8_t x0=10;
  569. uint8_t y0=220;
  570.  
  571. uint8_t x1;
  572. uint8_t y1;
  573.  
  574. TFT480.setBackColor(0, 0, 0);
  575. TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  576. TFT480.setFont(BigFont);
  577. TFT480.print("decim", x0, y0);
  578.  
  579. x1 = x0+100;
  580. y1 = y0;
  581.  
  582. TFT480.setColor(0,255,0);
  583. TFT480.print(". ", x1, y1);
  584. TFT480.printNumI(FTW_decim, x1+10 , y1);
  585. }
  586.  
  587.  
  588.  
  589.  
  590. void affiche_dP_r()
  591. {
  592. uint8_t x0=10;
  593. uint8_t y0=240;
  594.  
  595. uint8_t x1;
  596. uint8_t y1;
  597.  
  598. TFT480.setBackColor(0, 0, 0);
  599. TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  600. TFT480.setFont(SmallFont);
  601. TFT480.print("dP_r", x0, y0);
  602.  
  603. x1 = x0+100;
  604. y1 = y0;
  605.  
  606. TFT480.setColor(200,255,0);
  607. TFT480.print(" ", x1, y1);
  608. TFT480.printNumF(dP_r,5, x1, y1);
  609. }
  610.  
  611.  
  612. void affiche_dP()
  613. {
  614. uint8_t x0=10;
  615. uint8_t y0=255;
  616.  
  617. uint8_t x1;
  618. uint8_t y1;
  619.  
  620. TFT480.setBackColor(0, 0, 0);
  621. TFT480.setColor(150,150,150); // couleur de l'étiquette uniquement
  622. TFT480.setFont(BigFont);
  623. TFT480.print("dP", x0, y0);
  624.  
  625. x1 = x0+100;
  626. y1 = y0;
  627.  
  628. TFT480.setColor(255,0,0);
  629. TFT480.print(" ", x1, y1);
  630. TFT480.printNumI(dP, x1, y1);
  631. }
  632.  
  633.  
  634.  
  635. /**
  636.  Le message à transmettre au module NEO-7M peut-être affiché par u-center
  637.  -ouvrir le fenêtre Messages (par F9 ou menu View / Messages View)
  638.  -choisir dans la liste la fonction à programmer (ici: UBX / CFG(config)/ TP5(Timepulse5)
  639.  -puis dans la liste déroulante qui apparait alors dans la partie droite de la fenêtre 'Messages, selectionner '0 - TIMEPULSE'
  640.  -cocher les options qui vont bien (voir page sur mon site)
  641.  -le message à transmettre s'affiche alors dans le cadre au dessous.
  642.  
  643.  pour UBX-CFG(config)-TP5(Timepulse5)
  644.  envoyé par u-center :
  645.  B5 62 06 31 20 00 00 01 00 00 32 00 00 00 01 00 00 00 00 1B B7 00 00 00 00 00 00 00 00 80 00 00 00 00 6F 00 00 00 4C 95
  646.  
  647.  B5 62 c'est l'entete toujours la même
  648.  
  649.  Nous allons transmettre ces 40 octets au module NEO-6M avec l'USART de l'ATmega, ce qui nous permettra pas la suite de nous passer du programme u-center
  650. **/
  651.  
  652. void config_NEO_7M_GPS()
  653. {
  654. //configure le recepteur GPS NEO-7M afin qu'il génère un signal 12MHz
  655. // ce qui n'est pas le cas par défaut (1 Hz)
  656. uint8_t i;
  657.  
  658. uint8_t paquet[]={0xB5,0x62,0x06,0x31,0x20,0x00,0x00,0x01,0x00,0x00,0x32,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,
  659. 0x1B,0xB7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x6F,0x00,0x00,0x00,0x4C,0x95 };
  660.  
  661. for (i=0; i<40; i++)
  662. {
  663. USART_TxByte(paquet[i]); // utiliser TXD3 (se configure dans le fichier usart2560_628.c)
  664. }
  665. }
  666.  
  667. // ===================================================================================================================================================
  668.  
  669.  
  670.  
  671. void setup()
  672. {
  673. // randomSeed(analogRead(0));
  674.  
  675. init_ports();
  676. USART_Init();
  677.  
  678.  
  679. TFT480.InitLCD();
  680. TFT480.setFont(SmallFont);
  681. TFT480.clrScr();
  682.  
  683. init_variables();
  684. int_EXT_setup();
  685. timer3_setup();
  686.  
  687.  
  688. // ATTENTION : pour l'AD9951, faire le reset AVANT l'init parce que le reset efface tout.
  689. DDS_porteuse.reset();
  690.  
  691.  
  692. // DDS_porteuse.set_ref_clk(20000000, 20); // cas du TCXO 20MHz
  693. // DDS_porteuse.set_ref_clk(10000000, 20); //cas du GPS à 10MHz
  694. DDS_porteuse.set_ref_clk(12000000, 20); // cas du GPS à 12MHz
  695.  
  696. DDS_porteuse.DDS_init();
  697.  
  698. DDS_modulation.reset();
  699.  
  700. trace_ecran();
  701. affiche_leds();
  702.  
  703. config_NEO_7M_GPS();
  704.  
  705. sei(); // enable interruptions
  706. demande_calcul_porteuse = 1;
  707. demande_calcul_modulation = 2; // le AD9850 est un peu dur d'oreille au départ, il faut lui dire deux fois... (?)
  708.  
  709. }
  710.  
  711.  
  712. void affiche_led_verte(uint8_t s)
  713. {
  714. if(s==1)
  715. {
  716. TFT480.setColor(0,255,0); // vert
  717. TFT480.fillCircle(410, 240, 10); // allume la "led" (cercle)
  718. }
  719. else
  720. {
  721. TFT480.setColor(40,40,40); // gris
  722. TFT480.fillCircle(410, 220, 10); // éteint la "led" (cercle)
  723. }
  724. }
  725.  
  726.  
  727. void affiche_leds()
  728. {
  729. if ( (switches & 0b00000001) == 0)
  730. {
  731. TFT480.setColor(0,255,0); // vert
  732. TFT480.fillCircle(380, 62, 10); // allume la "led" (cercle) rouge en haut
  733.  
  734. TFT480.setColor(40,40,40); // gris
  735. TFT480.fillCircle(380, 152, 10); // éteint la "led" (cercle) rouge au milieu
  736.  
  737. }
  738. else
  739. {
  740. TFT480.setColor(40,40,40); // gris
  741. TFT480.fillCircle(380, 62, 10); // éteint la "led" (cercle) rouge en haut
  742. TFT480.setColor(0,255,0); // vert
  743. TFT480.fillCircle(380, 152, 10); // allume la "led" (cercle) rouge au milieu
  744.  
  745. }
  746.  
  747. if ( (switches & 0b00000010) == 0)
  748. {
  749.  
  750. if (depassement == 1) {TFT480.setColor(255,255,0);} else {TFT480.setColor(0,255,0); }
  751. TFT480.fillCircle(380, 260, 10); // allume la "led" (cercle) verte ou jaune en bas
  752. }
  753. else
  754. {
  755. efface_pannel_bas();
  756. affiche_phaseOff();
  757. TFT480.setColor(255,0,0); // rouge
  758. TFT480.fillCircle(380, 260, 10); // allume la "led" (cercle) rouge en bas
  759. }
  760. }
  761.  
  762.  
  763.  
  764. void calcul_dP()
  765. {
  766. if ( (switches & 0b00000010) == 0)
  767. {
  768. R3=f_porteuse/10; // voir ISR(TIMER3_COMPA_vect) -> on modifie à la fréquence de 10Hz.
  769. dP_r=16384.0 / 100;
  770. dP_r *= FTW_decim;
  771. dP_r *= R3;
  772. dP_r /= FTW_entier;
  773. dP= (uint16_t) dP_r;
  774. }
  775. else { dP = 0; }
  776.  
  777. }
  778.  
  779.  
  780. void loop()
  781. {
  782. uint8_t r1;
  783.  
  784. while(1)
  785. {
  786. // memo_mode_saisie = mode_saisie;
  787. memo_switches = switches;
  788.  
  789. // mode_saisie = portPIN_switch & pin_switch1; // mode_saisie = 0 ou 1 suivant la position du switch1
  790. // if ((portPIN_switch & pin_switch2) == 0) mode_saisie = 3; // =2
  791.  
  792. if ((portPIN_switch & pin_switch1) == pin_switch1) { switches |= 0b00000001; } else { switches &= 0b11111110; }
  793. if ((portPIN_switch & pin_switch2) == pin_switch2) { switches |= 0b00000010; } else { switches &= 0b11111101; }
  794.  
  795. calcul_dP();
  796.  
  797. if (memo_switches != switches)
  798. {
  799. affiche_leds();
  800. affiche_frequence_porteuse(0, 100, 255); // pour raffraichier la surbrillance des curseurs sur la bonne ligne de saisie.
  801. affiche_frequence_modulation(200, 200, 0);
  802. if ((switches & 0b00000010) == 0)
  803. {
  804. efface_pannel_bas();
  805. if (depassement == 0)
  806. {
  807. affiche_FTW_entier();
  808. affiche_FTW_decim();
  809. affiche_dP_r();
  810. affiche_dP();
  811. }
  812. }
  813. }
  814.  
  815.  
  816. if(demande_calcul_porteuse > 0)
  817. {
  818. demande_calcul_porteuse = 0;
  819. depassement = DDS_porteuse.calcul_FTW(f_porteuse, &FTW_entier, &FTW_decim); // effectue le calcul de TFW et retourne le reste de la division
  820.  
  821. _delay_ms(1);
  822. if ( (switches & 0b00000010) == 0)
  823. {
  824. affiche_leds();
  825. efface_pannel_bas();
  826. if (depassement == 0)
  827. {
  828. affiche_FTW_entier();
  829. affiche_FTW_decim();
  830. affiche_dP_r();
  831. affiche_dP();
  832. }
  833. }
  834.  
  835. DDS_porteuse.out_FTW();
  836. _delay_ms(1);
  837. affiche_frequence_porteuse(0, 100, 255);
  838. _delay_ms(1);
  839. }
  840.  
  841.  
  842. if(demande_calcul_modulation > 0 )
  843. {
  844. demande_calcul_modulation --;
  845. DDS_modulation.calcul_FTW(f_modulation);
  846. DDS_modulation.out_FTW();
  847. _delay_us(10);
  848. affiche_frequence_modulation(200, 200, 0);
  849. _delay_us(10);
  850. }
  851.  
  852. if((portPIN_RAZ & pin_RAZ) == 0) // RAZ des fréquences sur appui de l'encodeurs rotatif du haut (qui comprend un switch)
  853. {
  854. if ( (switches & 0b00000001) == 0) {f_porteuse =0; demande_calcul_porteuse += 1; } else {f_modulation =0; demande_calcul_modulation = 1;}
  855.  
  856.  
  857. if ((switches & 0b00000010) == 0)
  858. { // soft reset
  859. switches == 0;
  860. TFT480.clrScr();
  861. TFT480.setColor(30, 30, 30);
  862. TFT480.fillRect(0, 150, 479, 200); // bandeau
  863. TFT480.setColor(255,0,0);
  864. TFT480.print("SOFT RESET", CENTER, 160);
  865. _delay_ms(1000);
  866. TFT480.clrScr();
  867. setup();
  868. }
  869.  
  870. }
  871.  
  872. _delay_ms(1);
  873.  
  874.  
  875.  
  876. }
  877.  
  878. }
  879.  
  880.  

Mon fichier "AD9951-628v6.cpp" (bibliothèque de fonctions en C pour piloter le DDS AD9951) ;

CODE SOURCE en c
  1. /**
  2. AD9951-628v6.cpp
  3.  
  4. Firmware pour piloter un circuit DDS : AD9951.
  5. par Silicium628.
  6.  
  7. Ce fichier source "AD9951.cpp" est libre, vous pouvez le redistribuer et/ou le modifier selon
  8. les termes de la Licence Publique Générale GNU.
  9. **/
  10.  
  11.  
  12. #include <stdio.h>
  13. #include <stdint.h>
  14. #include <avr/pgmspace.h>
  15. #include <util/delay.h>
  16.  
  17. #include "AD9951-628v6.h"
  18.  
  19. AD9951::AD9951()
  20. {
  21. FTW = 0;
  22. OSC_REFCLK = 0;
  23. }
  24.  
  25.  
  26. void AD9951::set_ref_clk( uint64_t F_CLK_i, uint8_t REFCLK_Multiplier_i)
  27. {
  28. // cette fonction doit être appellée au moins une fois avant celle "calcul_FTW"
  29. OSC_REFCLK = F_CLK_i * REFCLK_Multiplier_i; // la fréquence d'horloge entrée sur le pin 9 (OSC_REFCLK)
  30. }
  31.  
  32.  
  33.  
  34. void AD9951::reset()
  35. {
  36. /*
  37. Active High Hardware Reset Pin. Assertion of the RESET pin forces the AD9951 to the initial state,
  38. as described in the I/O port register map
  39. Attention : ne pas faire un RESET après l'init, ce qui effacerait le paramètrage
  40. */
  41. _delay_ms(1);
  42. port_AD9951 |= pin_RST;
  43. _delay_ms(10);
  44. port_AD9951 &= ~pin_RST; // l'opérateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  45. _delay_ms(10);
  46. }
  47.  
  48.  
  49.  
  50. void AD9951::impulse_clk()
  51. {
  52. _delay_us(5); // temps necessaire à la stabilisation du niveau envoyé
  53. port_AD9951 |= pin_SCLK;
  54. _delay_us(5);
  55. port_AD9951 &= ~pin_SCLK; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  56. _delay_us(5);
  57. }
  58.  
  59.  
  60. void AD9951::impulse_IO_update()
  61. {
  62. // _delay_us(10);
  63. port_AD9951 |= pin_io_update;
  64. _delay_us(10);
  65. port_AD9951 &= ~pin_io_update; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  66. _delay_us(10);
  67. }
  68.  
  69.  
  70.  
  71. /***********************************************************************
  72. f_out = FTW x SYSCLK / 2³² nous dit le datasheet p:12
  73. 2³² = 4294967296 exactement.
  74. en en déduit :
  75.  
  76. FTW = f_out x 2³² / SYSCLK
  77. // ici SYSCLK = 20MHz x 20 = 400 MHz = 4x10E8 // **** cas d'un TCXO 20MHz ****
  78. // ici SYSCLK = 10MHz x 20 = 200 MHz = 2x10E8 // **** cas d'une synchro par GPS ****
  79. ici SYSCLK = 12MHz x 20 = 240 MHz = 240x10E6 // **** cas d'une synchro par GPS ****
  80.  
  81. //FTW = f_out x (2³² / 4E8) = f_out * (2³⁰ / 1E8) = 10.73741824 x f_out **** cas d'un TCXO 20MHz ****
  82. FTW = f_out x (2³² / 2E8) = 21.47483648 x f_out
  83. Remarque : SYSCLK est aussi nommé OSC_REFCLK dans le datasheet lorsqu'il s'agit du AD9951 en particulier
  84.  
  85. ************************************************************************/
  86. uint8_t AD9951::calcul_FTW(uint32_t frequence, uint32_t *FTW_entier, uint8_t *FTW_decim)
  87. {
  88. static int DIGITS=0;
  89. // rappel : FTW et OSC_REFCLK sont des uint_64
  90. uint64_t y, FTW2;
  91.  
  92. y = frequence * 4294967296; // frequence * 2³²
  93. FTW = y / OSC_REFCLK; // OSC_REFCLK (ici) = 12MHz * 20 = 240 x 10E+6
  94. *FTW_entier = FTW;
  95.  
  96. if (frequence <= 42e+6)
  97. {
  98. FTW2 = y * 100; // ATTENTION : FTW2 est > 64 bits si frequence > 42MHz
  99. FTW2 /= OSC_REFCLK;
  100. *FTW_decim = FTW2 % 100; // modulo = reste de la division - donne les deux chiffres après la virgule
  101. }
  102. else
  103. {
  104. FTW2 = y * 10;
  105. FTW2 /= OSC_REFCLK;
  106. *FTW_decim = 10 * (FTW2 % 10); // modulo = reste de la division - donne les un chiffre après la virgule
  107. }
  108.  
  109.  
  110.  
  111.  
  112. /**
  113. pour vérification du chiffre après la virgule...
  114. calculs effectués avec "calc" en ligne de commande sous Linux (C-style arbitrary precision calculator (version 2.12.4.4))
  115.  
  116. 1*(2^32)/240E6 = 17.89 ok -> 1Hz 1789 ? tiens, tiens...
  117. Remarque : pour les fréquences très petites, la partie décimale perdue est du mmême ordre de grandeur que la partie entière,
  118. donc la précision fréquencielle du DDS sans correction est très mauvaise pour les fréquences faibles.
  119. La première fréquence ayant un FTW qui tombe "presque juste" est 115 Hz. (deux zéros après la virgule)
  120. 115*(2^32)/240E6 = 2058.00516266666666666667
  121. Vient apès 278 Hz.
  122.  
  123.   1e3*(2^32)/240E6 = 17895.69 ok
  124.   69 / 17895 = 3.85582564962279966471E-3
  125.  
  126.  2.4E6*(2^32)/240E6 = 42949672.96 ok
  127.  
  128. 12E6*(2^32)/240E06 = 214748364,80 ok -> 12MHz
  129. 80 / 214748364 = 0.0000003725290312340 = 0.37253e-6 -> valeur de la correcrion à apporter
  130.  
  131. 20E6*(2^32)/240E6 = 357913941.33 ok
  132. 40E6*(2^32)/240E6 = 715827882.66 ok
  133. 42E6*(2^32)/240E6 = 751619276.80 ok
  134.  
  135. 43E6*(2^32)/240E6 = 769514973.86 HS (100 * frequence * 4294967296 déborde sur 64 bits !)
  136. 44E6*(2^32)/240E6 = 787410670.93 HS
  137. donc la correction ne sera plus valable au dela de 43MHz sauf à trouver une astuce pour ne ma déborder
  138.  
  139. 10*42E6*(2^32)/240E6 = 0b 1 11000000 00000000 00000000 00000000
  140. 100*42E6*(2^32) = 0b 11111010 01010110 11101010 00000000 00000000 00000000 00000000 00000000
  141. 100*43E6*(2^32) = 0b 1 00000000 01001100 11001011 00000000 00000000 00000000 00000000 00000000 (ce qui dépasse 64bits !)
  142. **/
  143.  
  144.  
  145. /**
  146. // les deux lignes qui suivent permettent de "forcer manuellement" un (très léger) glissement de fréquence
  147. FTW += 128;
  148. FTW -= adj;
  149. **/
  150.  
  151. /**
  152. On introduit forcément une erreur d'arrondi, et le résultat est à +/- 1 digit
  153. par exemple pour une fréquence de sortie de 10MHz le glissemment par rapport au 10MHz étalon est de 26s
  154. pour une période de 100ns -> 3,8 E-9 soit 1 seconde/3ans ce qui n'est pas si mal !!
  155. si l'on veut obtenir la précision de E-12 du GPS, il faut envisager d'utiliser autre chose qu'un synthétiseur DDS 32 bits.
  156.  
  157. Pour des fréquences multiples ou sous-multiples de 10MHz,
  158. ou dans un rapport fractionnaire simple, une simple PLL convient avec diviseur en logique câblée.
  159. pour d'autres fréquences, il faut étudier la question de plus près.
  160.  
  161. Dans le cas de ce DDS, on peut faire en sorte que SYSCLK / 2³² tombe juste, c'est à dire que SYSCLK = n x 2³²
  162. 2³² étant lui même >> (4 milliards) on ne peut envisager que le cas n < 1 par puissances de 2, c.a.d n=1/2, 1/4, 1/8 ...
  163. mais afin que SYSCLK soit < 400MHz, ce qui est la limite haute pour ce circuit, il faut diviser 2³² par une puissance de 2 supérieure à 10, donc au moins par 16.
  164. ce qui donne SYSCLK = 2³² / 2⁴ = 2²⁸ = 268435456 Hz soit une fréquence de quartz (compte tenu du *16 de la PLL interne) de 16777216 Hz. (= 2²⁴ =16 777 216 MHz)
  165. La formule devient FTW = f_out x 2³²/2²⁸ = f_out * 16
  166. On peut obtenir la fréquence 16.777216 MHz en multipliant par 4 la fréquence d'un quartz de 4.194304MHz (par une PLL en HCMOS)
  167. remarque : on trouve facilement des quartz de 4.194304MHz (=2²²)
  168.  
  169. **/
  170.  
  171. return 0;
  172. }
  173.  
  174.  
  175.  
  176. void AD9951::out(uint8_t octet_i)
  177. {
  178. // MSB first (= mode par defaut)
  179. uint8_t i;
  180.  
  181. //envoi de la frequence
  182. uint8_t masque;
  183.  
  184. for (i=0; i < 8; i++)
  185. {
  186. masque = 0b10000000 >> i; // le '1' se deplace de gauche a droite
  187. if ( (octet_i & masque) != 0) { port_AD9951 |= pin_SDI; } else { port_AD9951 &= ~pin_SDI; }
  188. impulse_clk();
  189. }
  190.  
  191. }
  192.  
  193.  
  194.  
  195. void AD9951::DDS_init()
  196. {
  197. // Attention : ne pas faire un RESET après cette init, ce qui effacerait le paramètrage
  198. uint8_t CFR1[4];
  199. uint8_t CFR2[3];
  200.  
  201. // ============================ REGISTRE CRF1 - 32 bits ===================================================================
  202. // Control Function Register No.1 (CFR1) (0x00)
  203.  
  204.  
  205. //PREMIER OCTET :
  206.  
  207. // CFR1<0>: Not Used, Leave at 0
  208. // CFR1<1> SYNC_CLK Disable Bit; CFR1<1> = 0 (default). The SYNC_CLK pin is active.
  209. // CFR1<2>: Not Used
  210. // CFR1<3>: External Power-Down Mode; CFR1<3> = 0 (default). The external power-down mode selected is the rapid recovery power-down mode. In this mode,
  211. //when the PWRDWNCTL input pin is high, the digital logic and the DAC digital logic are powered down. The DAC bias circuitry, PLL, oscillator, and clock input circuitry are not powered down.
  212. // CFR1<4>: Clock Input Power-Down Bit; CFR1<4> = 0 (default). The clock input circuitry is enabled for operation.
  213. // CFR1<5>: DAC Power-Down Bit; CFR1<5> = 0 (default). The DAC is enabled for operation.
  214. // CFR1<6>: Not Used
  215. // CFR1<7>: Digital Power-Down Bit; CFR1<7> = 0 (default). All digital functions and clocks are active.
  216.  
  217. CFR1[0]=0b00000000;
  218.  
  219. //DEUXIEME OCTET
  220.  
  221. // CFR1<8> = 0 (default). MSB first format is active.
  222. // CFR1<9>: SDIO Input Only; CFR1<9> = 1. The serial data I/O pin (SDIO) is configured as an input only pin (3-wire serial programming mode). (CE N'EST PAS LA VALEUR PAR DEFAUT)
  223. // CFR1<10>: Clear Phase Accumulator; CFR1<10> = 0 (default). The phase accumulator functions as normal.
  224. // CFR1<11>: Not Used
  225. //CFR1<12>: Sine/Cosine Select Bit; CFR1<12> = 0 (default). The angle-to-amplitude conversion logic employs a COSINE function.
  226. // CFR1<13>: Auto-Clear Phase Accumulator Bit; CFR1<13> = 0 (default), the current state of the phase accumulator remains unchanged when the frequency tuning word is applied.
  227. // CFR1<14..15>: Not Used
  228.  
  229. CFR1[1]=0b00000010; // CFR1<9>: SDIO Input Only;
  230.  
  231. //TROISIEME OCTET
  232.  
  233. // CFR1<16..21>: Not Used
  234. // CFR1<22>: Software Manual Synchronization of Multiple AD9951; CFR1<22> = 0 (default). The manual synchronization feature is inactive.
  235. // CFR1<23>: Automatic Synchronization Enable Bit; CFR1<23> = 0 (default). The automatic synchronization feature of multiple AD9951s is inactive.
  236.  
  237. CFR1[2]=0b00000000;
  238.  
  239. // CFR1<24>: Auto Shaped On-Off Keying Enable Bit (Only Valid when CFR1<25> Is Active High); CFR1<24> = 0 (default). When CFR1<25> is active, a Logic 0 on CFR1<24> enables the
  240. //manual shaped on-off keying operation. Each amplitude sample sent to the DAC is multiplied by the amplitude scale factor. See the Shaped On-Off Keying section for details
  241. // CFR1<25>: Shaped On-Off Keying Enable Bit; CFR1<25> = 0 (default). Shaped on-off keying is bypassed.
  242. // CFR1<26>: Amplitude Ramp Rate Load Control Bit; CFR1<26> = 0 (default). The amplitude ramp rate timer is loaded only upon timeout (timer == 1) and is not loaded due to an I/O UPDATE input signal.
  243. // CFR1<31:27>: Not Used
  244.  
  245. CFR1[3]=0b00000000;
  246.  
  247. // ENVOI DE CETTE CONFIGURATION AU DDS : (voir page 22 : "Example Operation")
  248.  
  249. IB= 0b00000000;
  250. out( IB); // instruction byte -> ordre d'ecrire dans le registre CRF1 (32 bits attendus)
  251.  
  252. out( CFR1[3]);
  253. out( CFR1[2]) ;
  254. out( CFR1[1]);
  255. out( CFR1[0]);
  256.  
  257. impulse_IO_update();
  258.  
  259. // ============================ REGISTRE CRF2 - 24bits ============ voir le pdf du composant =========================================================
  260.  
  261. // CFR2<1:0>: Charge Pump Current Control Bits
  262. // CFR2<2>: VCO Range Control Bit
  263. /*
  264. This bit is used to control the range setting on the VCO.
  265. When CFR2<2> == 0 (default), the VCO operates in a range of
  266. 100 MHz to 250 MHz. When CFR2<2> == 1, the VCO operates
  267. in a range of 250 MHz to 400 MHz.
  268. */
  269. // CFR2<7:3>: Reference Clock Multiplier Control Bits
  270. /*
  271. This 5-bit word controls the multiplier value out of the clock-
  272. multiplier (PLL) block. Valid values are decimal 4 to 20 (0x04 to
  273. 0x14). Values entered outside this range will bypass the clock
  274. multiplier. See the Phase-Locked Loop (PLL) section for details
  275. 20 = 0b10100
  276. */
  277. // CFR2<8>: Not Used
  278. // CFR2<9>: CRYSTAL OUT Enable Bit
  279. // CFR2<10>: Hardware Manual Sync Enable Bit
  280. // CFR2<11>: High Speed Sync Enable Bit
  281. // CFR2<23:12>: Not Used
  282.  
  283. //CFR2[0] = 0b00100100; // bit2 =1 -> the VCO operates in a range of 250 MHz to 400 MHz; bits7..3 = 0b00100 -> = 4 (REFCLK Multiplier) -> cas d'un Qx 100MHz
  284. CFR2[0] = 0b10100100; // bit2 =1 -> the VCO operates in a range of 250 MHz to 400 MHz; bits7..3 = 0b10100 -> = 20 (REFCLK Multiplier) -> cas d'un Qx ou CTXO 20MHz
  285.  
  286. CFR2[1] = 0b00000010; // bit CFR2<9>: CRYSTAL OUT Enable Bit -> =1
  287. CFR2[2] = 0b00000000;
  288.  
  289. IB= 0b00000001;
  290. out(IB); // instruction byte -> ordre d'ecrire dans le registre CRF2 (24 bits attendus)
  291.  
  292. out( CFR2[2]);
  293. out( CFR2[1]);
  294. out( CFR2[0]);
  295.  
  296. impulse_IO_update();
  297. }
  298.  
  299.  
  300.  
  301.  
  302. void AD9951::out_FTW() //Frequency Tuning Word
  303. {
  304.  
  305. uint8_t octets[4];
  306.  
  307. octets[0] = FTW & 0b00000000000000000000000011111111;
  308. octets[1] = (FTW & 0b00000000000000001111111100000000) >> 8;
  309. octets[2] = (FTW & 0b00000000111111110000000000000000) >> 16;
  310. octets[3] = (FTW & 0b11111111000000000000000000000000) >> 24;
  311.  
  312. IB= 0b00000100;
  313. out( IB); // instruction byte -> ordre d'écrire 32 bits dans le registre 0x04
  314.  
  315. out(octets[3]);
  316. out(octets[2]);
  317. out(octets[1]);
  318. out(octets[0]);
  319.  
  320. impulse_IO_update();
  321. }
  322.  
  323.  
  324.  
  325. void AD9951::out_POW(uint16_t phase_i) // phase offset word
  326. {
  327.  
  328. uint8_t octets[2];
  329.  
  330. octets[0] = phase_i & 0b0000000011111111;
  331. octets[1] = (phase_i & 0b0011111100000000) >> 8;
  332.  
  333. IB= 0b00000101;
  334. out( IB); // instruction byte -> ordre d'écrire 32 bits dans le registre 0x05
  335.  
  336. out(octets[1]);
  337. out(octets[0]);
  338.  
  339. impulse_IO_update();
  340. }
  341.  


Son homologue pour le AD9850 ;

CODE SOURCE en c
  1. /**
  2. AD9951-628v1.cpp
  3.  
  4. Firmware pour piloter un circuit DDS : AD9850.
  5. par Silicium628.
  6.  
  7. Ce fichier source "AD9850.cpp" est libre, vous pouvez le redistribuer et/ou le modifier selon
  8. les termes de la Licence Publique Générale GNU.
  9. **/
  10.  
  11.  
  12. #include <stdio.h>
  13. #include <avr/pgmspace.h>
  14. #include <util/delay.h>
  15.  
  16. #include "AD9850-628v1.h"
  17.  
  18.  
  19. AD9850::AD9850()
  20. {
  21. }
  22.  
  23.  
  24. void AD9850::reset()
  25. {
  26. phase=0; // à voir...
  27.  
  28. _delay_ms(1);
  29. port_AD980 |= pin_RESET;
  30. _delay_ms(10);
  31. port_AD980 &= ~pin_RESET; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  32. _delay_ms(10);
  33. }
  34.  
  35.  
  36.  
  37. void AD9850::impulse_clk_W() // sur pin W_CL
  38. {
  39. _delay_us(5);
  40. port_AD980 |= pin_WCL;
  41. _delay_us(5);
  42. port_AD980 &= ~pin_WCL; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  43. _delay_us(5);
  44. }
  45.  
  46.  
  47.  
  48. void AD9850::impulse_FQ_U() // sur pin FQ_U
  49. {
  50. _delay_us(5);
  51. port_AD980 |= pin_FQU;
  52. _delay_us(5);
  53. port_AD980 &= ~pin_FQU; // l'operateur "~" donne le complement (inverse des bits). ne pas confondre avec l'opérateur "!"
  54. _delay_us(5);
  55. }
  56.  
  57.  
  58.  
  59. /***********************************************************************
  60. f_out = FTW x CLKIN / 2³² nous dit le datasheet
  61. en en déduit :
  62.  
  63. FTW = f_out * 2³² / CLKIN
  64. ici CLKIN = 125MHz / 2 = 62.5 MHz
  65. 125MHZ c'est la fréquence du quartz
  66. cette fréquence est /2 en interne
  67.  
  68. FTW = f_out * (2³² / 62.5E6 ) = 68.71947674 x f_out
  69.  
  70.  
  71. ************************************************************************/
  72.  
  73.  
  74.  
  75. void AD9850::calcul_FTW(uint32_t frequence)
  76. {
  77. float y;
  78. y = 68.71947674 * frequence;
  79. FTW = (uint32_t) y;
  80.  
  81. }
  82.  
  83.  
  84.  
  85. void AD9850::out_FTW()
  86. {
  87. // 40 bits vers AD9850; voir son datasheet
  88. uint8_t n;
  89.  
  90. //envoi de la frequence
  91. uint32_t masque;
  92.  
  93. masque = 1;
  94. for (n=0; n<= 31; n++) // on sort le LSB (W0) en premier
  95. {
  96. masque += masque; // revient à x2 le masque le '1' se deplacant de droite a gauche, 32 bits en tout
  97. if ( (FTW & masque) != 0) {port_AD980 |= pin_DATA;} else { port_AD980 &= ~pin_DATA; }
  98.  
  99. impulse_clk_W();
  100. }
  101.  
  102. port_AD980 &= ~pin_DATA; // (W32 toujours = 0)
  103. impulse_clk_W();
  104.  
  105. port_AD980 &= ~pin_DATA; // (W33 toujours = 0)
  106. impulse_clk_W();
  107.  
  108. port_AD980 &= ~pin_DATA; // (W34 = Power-Down = 0)
  109. impulse_clk_W();
  110.  
  111. // envoi de la phase (5 bits)
  112.  
  113. for (n=0; n<=4; n++) // on sort le LSB (W35) en premier et le MSB en dernier.
  114. {
  115. masque = (1 << n);
  116. if (phase & masque) {port_AD980 |= pin_DATA;} else { port_AD980 &= ~pin_DATA; }
  117.  
  118. impulse_clk_W();
  119. }
  120. // envoi impulsion FQ_UD
  121. impulse_FQ_U();
  122. }
  123.  
  124.  
  125.  
  126.  
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.  

55 L'appareil dans son boîtier

22 octobre 2015:
Les principaux éléments sont installés dans un grand boîtier en alu, toutefois l'appareil n'est pas terminé pour autant.

Outre le fait qu'il reste à câbler au moins deux prises BNC de façon à sortir le signal sinus de l'AD9951 brut (non modulé) ainsi que le signal de sortie brut 12MHz du récepteur GPS, mais aussi de réaliser le filtre de sortie de type Butterworth ou Tchebychev dont je parle régulièrement (et qui, compte tenu des moyens de calculs actuels et l'utilisation de composants CMS ne devrait pas poser de problème), il reste un point important, essentiel, à résoudre. Je m'explique :

56 Précisions sur la précision

Nous avons vu que la synchronisation du signal d'horloge par un récepteur GPS permet d'obtenir une horloge dont la précision théorique est de l'ordre de 10E-12 dit-on. Mais lorsqu'on alimente un circuit DDS avec une telle horloge, ledit DDS fournit des fréquences recalculées qui ont la stabilité du GPS (pratiquement pas de dérive à long terme), mais pas la précision, cette dernière étant automatiquement perdue par le fait que le calcul dans la DDS génère une erreur, certes très très minime, certes déterministe, connue (si on se penche de très près sur le calcul...), mais différente pour chaque fréquence et très mal venue s'agissant d'un générateur de fréquence.

La solution ? Se pencher justement sur le calcul pour imaginer la parade. L'erreur de fréquence est tellement faible que l'on devrait plutôt parler de lent et régulier glissement de phase.

Le plus simple pour le mettre en évidence consiste à régler le générateur afin qu'il produise un signal de sortie de 12MHz et de comparer ce signal, à l'oscillo en bi-trace ou en Lissajous avec le signal 12MHz pilotant le DDS. Ce devrait être exactement la même fréquence mais du fait du principe même du DDS ce n'est pas le cas. Il y a glissement d'une période entière après environ 22s soit 1/22x10E6 = 3.8x10E-9 ce qui dégrade la précision d'un facteur dix par rapport à celle obtenue en comparant la fréquence du GPS à celle du rubidium (sachant que je ne sais toujours pas vraiment laquelle est la plus exacte des deux, rubidium ou GPS ??) précision qui je le rappelle était dans le pire des cas 3x10E-10. Dommage non ?

La parade pourrait consister à injecter des périodes à des moments précis (toutes les x périodes...), ce qui consiste à provoquer des sauts de phases bien peu sympathiques, ou alors à provoquer un lent et régulier glissement de phase antagoniste ce qui serait bien plus indolore. Cette seconde façon de faire semble justement bien convenir au circuit DDS puisqu'on peut piloter finement la phase du signal de sortie, comme l'explique son datasheet.

Un autre approche consisterait à ne pas garantir la fréquence des signaux sinus avec une telle précision, et en contrepartie générer des fréquences "rondes" par divisions exactes du 12MHz (avec des diviseurs TTL LS ou une PLL avec diviseur programmable telle le LM7001 déjà expérimenté sur ce site, ICI). Comme quoi, parfois, qui peut le moins peut le plus !!

(Remarque : 1/2³² ~= 2.3x10E-10 pour le mot de 32 bits définissant la fréquence dans le DDS, on devrait donc pouvoir faire mieux en revoyant la programmation du DDS de plus près. C'est bizarre, j'ai pourtant effectué le calcul du mot de commande sur 64 bits avant de l'appliquer au DDS). Toutefois cette précision de 2.3x10E-10 ne serait valable que pour des variations autour de la pleine échelle (240MHz en sortie du multiplieur interne) ? Je vais tirer ça au clair.

23 octobre 2015:
Voici la procédure de test que j'ai appliquée : J'ai donc réglé le générateur afin qu'il produise un signal de sortie de 12MHz et comparé ce signal, à l'oscillo avec le signal 12MHz pilotant ce DDS. Léger glissement d'une période en 22s. J'ai alors bricolé le calcul du mot de commande du DDS (voir le fichier AD9951-628v2.cpp) en ajoutant "1" (une unité) au mot FTW avant de l'envoyer au DDS. Et dés lors le glissement d'une période se fait en 1m30s, soit 90s. Ce qui correspond cette fois à une imprécision de 1x10E-9, soit environ quatre fois mieux. Si j'ajoute (ou retranche) plus d'une unité alors le glissemment s'accentue au contraire. Preuve que le calcul est exact à... un bit près. Pas de solution exacte donc à attendre de ce côté, je vais juste voir comment se fait l'arrondi dans mon code source, au passage des uint64 vers un entier 32 bits.

24 octobre 2015:
Etant donné le constat précédent j'ai donc appliqué la solution suivante :
  • calcul du mot de commande avec une précision de 1 bit supplémentaire, avec la technique dite "de virgule fixe" dont j'ai déjà parlé à propos de l'implantation de la FFT sur ATmega.
  • utilisation de ce bit supplémentaire appelé ici "demi_bit" pour arrondir intelligemment le mot de commande FTW codé sur 32 bits
Le résultat est un glissement imperceptible au premier coup d’œil à l'oscillo (pour Fout = 12MHz, mais aussi pour les autres fréquences dans un rapport simple), au point qu'il faut s’asseoir sur une chaise bien confortable et observer les traces pendant plusieurs minutes pour constater le décalage progressif (entre je le rappelle le 12MHz appliqué au DDS et le 12MHz synthétisé en sortie :))

Bien, bien, on va maintenant passer le tout à l'épreuve de l'horloge atomique au RUBIDIUM. (Le modèle dont je dispose, FE-5680A fournit un signal de 10MHz). Je vous tiens au courant.

57 La parole est au Rubidium

10 novembre 2015:
Premier test donc avec le générateur au rubidium comme référence :
Je constate que le signal 12 MHz issu du récepteur GPS, outre son bruit de phase, est l'objet de petits sauts (minimes) de phase et qu'il finit par se décaler d'une période en 2mn52s, soit 172s. Ce qui nous donne un delta de 4.8x10E-10, très proche de ce que j'avais précédemment mesuré (voir #49 ci-dessus). Problème : comment savoir lequel a raison, le rubidium acheté d'occase ou le GPS ? Je me garderai bien de trancher.

Deuxième test, toujours avec le rubidium comme référence :
Je mesure que la fréquence 12MHz synthétisée par le AD9951 se décale d'une période en 64s soit un delta de 1.3x10E-9 ce qui se rapproche de l'erreur introduite par le calcul dans le DDS qui est pour cette fréquence (après arrondi correct du mot de commande de la fréquence codé sur 32 bits comme nous l'avons vu plus haut le 23 octobre 2015, voir le #56 ci-dessus) de 1x10E-9. Cette erreur là on sait d'où elle provient, je vais réfléchir un peu plus sur la possibilité de gérer un glissement de phase doux, en gardant ce même circuit DDS 9951.

58 La fonction Phase-offset de l'AD9951

Nous voyons sur le diagramme fonctionnel de l'AD9951, dans le "DDS core" un sommateur qui ajoute un offset à la phase instantanée calculée du signal généré. Cet offset est codé sur 14 bits ce qui permet un réglage très précis. La principale application consiste à obtenir une modulation de phase du signal. Nous allons mettre à profit cette fonction pour améliorer la précision de la fréquence générée en provoquant un glissement lent et continu de la phase. Je suppose que la phase peut varier de 0 à 2pi ce qui correspondra à une période complète sans discontinuité (saut) de phase perceptible. Je vais tester ça. Je précise qu'il s'agit d'apporter une correction hyperfine de la fréquence afin de pratiquement annuler l'erreur d'arrondi de la valeur du mot de commande sur 32 bits. Cette erreur est déterministe, elle correspond au reste de la division effectuée lors du calcul de FTW. Elle dépend donc de la fréquence générée. L'ATmega 2560 nous calculera la correction à appliquer pour chaque fréquence.

11 novembre 2015:
Comme j'aime bien ne pas brûler les étapes lors de la conception, j'ai dans un premier temps piloté manuellement la valeur de l'offset de phase (avec le bouton encodeur rotatif correctement programmé). Je constate que le résultat est conforme à ce que j'attendais, je peux régler la phase du signal de sortie en temps réel avec une extrême précision, et sans le moindre "glitch" lors de l'envoi du mot POWO à l'AD9951, conformément à ses spécifications. On peut donc contrôler la phase en continu (plus exactement par pas discrets, certes, mais tellement fins... 2pi/16384 ) tout en gardant un signal sinusoïdal très propre. De plus le passage du mot depuis la valeur 16384 vers 0 s'effectue correctement sans saut de phase intempestif (j'avais un tout petit doute sur ce point), ce qui prouve que l'étendue du réglage pour un mot de 14bits correspond bien à l'intervalle [0..2pi] et non pas [0..pi] ou [0..pi/2] comme on aurait pu le craindre. Ce point est essentiel si l'on veut obtenir un glissement (en occurrence l'annulation d'un glissement) de phase en continu. En résumé, l'AD9951 est un circuit très chouette !

Après quelques essais de programmation, je peux dès maintenant vous confirmer qu'avec cette technique d'ajustement logiciel fin et régulier de la phase on peut obtenir TRES SIMPLEMENT une maîtrise de la fréquence avec une précision inimaginable, en fait infinie, il suffit d'espacer ces ajustements dans le temps autant que l'on veut.
Il reste donc maintenant à faire un petit peu de math afin de calculer la valeur de ces ajustements (en durée et en espacement, donc le rapport cyclique) en fonction des "chiffres après la virgule" perdus lors de l'arrondi de la fréquence calculées sur 64bit par l'ATmega vers le mot de commande de 32 bits. Car si actuellement j'ai obtenu par tâtonnement un résultat parfait pour la fréquence synthétisée égale à celle de l'horloge (12MHz), il faut maintenant que la méthode puisse fonctionner pour toutes les cent vingt millions de fréquences synthétisables. Tiens c'est marrant, mon correcteur orthographique me souligne ATmega, et me propose... allume-gaz !!!!! Ben voyons !

59 Calcul du glissement de phase






60 Application pratique :

18 novembre 2015:
L'implantation de ce calcul produit exactement l'effet recherché : Dans le cas de la synthèse d'un signal d'une fréquence de 12 MHz, je ne constate plus aucun glissement de phase entre le signal de sortie et le signal d'horloge issu du récepteur GPS pilotant le DDS.

Et il en va de même pour toutes les autres fréquences que j'ai testées.

Ci-contre une vue du module TFT qui affiche, en plus des fréquences de la porteuse et de la modulation, les valeurs des nouvelles fonctions.

2 décembre 2015:
Toutefois pour ne rien vous cacher la méthode bute sur un problème de grand entier : le codage de la fréquence et des variables intermédiaires utilisées dans le calcul, tous codés sur 64 bits (avec des uint64_t), ne permet pas de dépasser la fréquence de 42MHz. Au delà il faut plus que 64 bits. Mais je ne m'avoue pas vaincu, je compte utiliser une bibliothèque d'entiers en 128 bits (uint128_t).

4 décembre 2015:
En fait l'utilisation d'entiers 128bits s'est révélée trop lourde (à cause de l'utilisation d'une fonction "divmod" courante sous python mais inconnue du compilateur avr-gcc...)
J'ai donc revu mon calcul de façon à ne prendre en compte qu'une décimale au lieu de deux lorsque la fréquence est supérieure à 42 MHz, ce qui a pu se coder avec des uint64_t. La précision relative reste correcte (du fait que plus la fréquence désirée est grande, plus le mot de commande FTW est grand et donc le poids des décimales perdues est plus faible). Je précise que je mets toujours à jour les versions du firmware proposé ci-dessus (au #54).

61 En vidéo

Voici la vidéo montrant comment la figure de Lissajous (entre le signal d'horloge et le signal de sortie) se fige lorsqu'on active la compensation de phase. Même pour des fréquences qui ne sont pas dans un rapport entier, dans ce cas la figure de Lissajous est plus compliquées (plusieurs boucles entrecroisées), mais elle cesse également de tourner, ce qui est très convainquant.. L'oscillo reçoit deux signaux :
-le 12 MHz issu du quartz piloté par GPS (la sortie de ce signal se fait par une prise BNC située à l'arrière du générateur, donc non visible ici), ce signal étant (et c'est ce qui nous importe ici) celui qui constitue l'horloge de référence en entrée du circuit DDS AD9951. -et le signal sinusoïdal généré par le circuit DDS.

Je montre tour à tour ce qui se passe pour des fréquences de :
- 12 MHz; - 10 MHz; - 8 MHz; et 6 MHz
(J'ai de mon côté testé d'autres fréquences de 10 kHz à 100 MHz...)

Le switch que je manipule embraye ou débraye l'ajustement de phase. Le résultat est net : La figure de Lissajous se fige instantanément.
Le calcul de la valeur à ajouter à chaque phase se fait automatiquement et est appliqué toutes les 10ms.

Il en résulte me direz vous une petite "modulation de phase", mais minime.

En fait une figure de Lissajous est stable lorsque les deux fréquences appliquées en X et en Y sont dans un rapport entier ou fractionnaire rationnel, c'est à dire que la division de l'une par l'autre donne une fraction rationnelle (constituée par la division de deux nombres entiers n1/n2, comme par exemple 13/5. Le spot parcourt alors exactement n1 cycles sur l'axe horizontal pendant le temps qu'il met pour parcourir n2 cycles en vertical).

Ici la courbe de Lissajous est parcourue douze millions de fois par seconde ! La synchronisation obtenue semble parfaite, elle gagne en fait un facteur 100, deux ordres de grandeur donc, ce qui permet d'utiliser ce DDS en garantissant des précisions sans cela hors d'atteinte.

Je dois préciser une fois encore, s'il en était besoin, que lorsque je parle ici de précision il s'agit de l'exactitude du rapport entre la fréquence d'horloge et la fréquence générée. De par son principe même, le DDS ne fournit pas un un rapport exact. Avec ce système de glissement de phase, on augmente cette exactitude. Mais en aucun cas on augmente la précision du signal d'horloge incident. Elle est ce qu'elle est, très bonne dans le cas du GPS, 1x10e-12 nous dit-on, mais pas absolue. Les horloges atomiques au césium atteignent 2x10e-16, mais c'est plus cher.

62 Filtrage du signal de sortie

24 décembre 2015:
Le signal generé par le DDS AD9951 est sinusoïdal mais pas exempt de déformations, et donc d'harmoniques. Un filtrage est donc nécessaire. Ci-dessus le schéma du filtre passe bas coupant à 120MHz et ci-contre, en vidéo, le résultat obtenu.

L'échelle horizontale est réglée sur 50MHz par carreau.
La raie complètement à gauche de l'écran c'est le zéro Hz de l'analyseur de spectre. Vient ensuite la raie qui se déplace vers la droite, c'est le signal utile dont j'augmente la fréquence par pas de 10MHz depuis 10MHz jusqu'à 120 MHz. on constate des fréquence images symétriques par rapport à la fréquence de 120MHz ainsi que de nombreuses harmoniques. Dans la seconde partie de la vidéo, nous constatons que le filtre supprime pratiquement toutes les fréquences supérieures à environ 180MHz. Toutefois il reste la première fréquence image pour des fréquences de sortie utiles supérieures à 80MHz. Conclusion : Une optimisation est nécessaire, avec par exemple un filtre plus raide (d'ordre supérieur, avec d'avantage de cellules) et de coupure légèrement plus basse.




63 Le circuit imprimé du filtre :

L'implantation des selfs est à revoir !

64 Configuration automatique du NEO-7M

03 mars 2016:
Comme vu plus haut il faut paramétrer le NEO-7M (avec le logiciel windows "u-center") pour qu'il génère un signal de 12MHz au lieu de 1Hz par défaut. On peut ensuite, toujours à l'aide du logiciel u-center faire en sorte que ce paramétrage soit mémorisé en RAM interne (dans le module) alimentée par un accu lilliputien présent sur le module, ce qui signifie que ces infos sont en fait perdues si le module n'est pas alimenté pendant un ou deux jours...

Solution : ré-paramétrer correctement le NEO-7M automatiquement à chaque mise en service, sans utiliser le logiciel windows u-center.Il se trouve que l'ATmega s’acquitte fort bien de cette tâche ! J'ai donc ajouté une liaison série entre le TXD3 de l'ATmega2560 et le RX du NEO-7M et j'ai complété le code source en conséquence.
Maintenant plus de soucis avec le GPS, il suffit d'attendre qu'il se synchronise sur les satellites le cas échéant. Le petit transducteur audio piézo aide à savoir si on est synchronisé, sachant que lors d'un démarrage à froid la synchronisation n'est pas obtenue d'une manière stable pendant environ une minute, laissant entendre un crépitement digne d'un compteur Geiger. (Si on est pressé on peut alors agir sur le potentiomètre 10tours RV1 visible sur le schéma ici). Remarque : Tant que le NEO-7M n'est pas synchronisé aucun son n'est audible, même en tournant le potentiomètre.

65 DOCUMENTS

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

66 LIENS

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 :

Logiciels pour le calcul et la simulation de filtres :

-RF SIM 99 (sous Linux avec wine) : -Qucs (sous Linux directement) :

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 :


67 -

Liens...

42596