Table des matières

Boucle à verrouillage de phase à oscillateur numérique

1. Introduction

La boucle à verrouillage de phase (B.V.P.) est un dispositif permettant de générer un signal périodique dont la phase est verrouillée sur celle d'un autre signal. Son fonctionnement est expliqué dans Boucle à verrouillage de phase.

On s'intéresse à une application de la boucle à verrouillage de phase : le suivi de la fréquence d'un signal quasi périodique dont la fréquence varie (signal modulé en fréquence).

Ce document présente une B.V.P. dont l'oscillateur commandé en tension (O.C.T.) est numérique et implémenté par un microcontrôleur. La tension de commande de l'O.C.T. est fournie sur une entrée du convertisseur A/N. Le microcontrôleur gènère un signal binaire sur une sortie tout en ajustant sa fréquence en temps réel en fonction de la tension de commande. Le détecteur de phase est réalisé avec une porte OU-exclusif (détecteur XOR). Le comparateur de phase est constitué du détecteur de phase et d'un filtre passe-bas (la distinction entre détecteur et comparateur est expliquée dans Boucle à verrouillage de phase).

Nous utiliserons un programme Arduino qui gère l'O.C.T. et qui génère aussi un signal binaire modulé en fréquence, afin de tester le fonctionnement de la B.V.P..

L'utilisation d'un oscillateur numérique permet de régler très précisément la plage de fréquence de fonctionnement de la B.V.P., ce qui permet d'optimiser le fonctionnement de la boucle en fonction du signal analysé.

2. Montage

montage.svgFigure pleine page

Le détecteur de phase est constitué d'une porte logique OU-exclusif. L'entrée analogique A0 constitue la commande de l'O.C.T. (tension de commande uf en sortie du filtre passe-bas). La sortie D11 constitue la sortie de L'O.C.T. (signal u2(t) ). Cette sortie délivre donc un signal binaire (0 et 5 V) dont la fréquence est commandée par la tension uf . La sortie D6 est utilisée pour tester la B.V.P. : elle délivre un signal binaire u1(t) dont la fréquence est modulée.

On testera la B.V.P. avec une fréquence de référence fo=1000Hz. Les valeurs choisies pour le filtre sont R1=10kΩ , R2=0 et C=1,0μF , ce qui donne une fréquence de coupure de 16 Hz, convenable pour atténuer assez les signaux de fréquence 1000 Hz et plus en sortie du détecteur de phase.

3. Fonctionnement

3.a. Oscillateur commandé en tension

Le fonctionnement de la boucle à verrouillage de phase, en particulier avec un comparateur de phase XOR, est expliqué dans Boucle à verrouillage de phase.

Le tension de commande de l'O.C.T. (tension uf ) est comprise entre 0 et 5 V. La tension de référence de l'O.C.T. est uref=2,5V car c'est la tension que donne le comparateur de phase lorsque les deux signaux u1(t) et u2(t) sont en quadrature (le déphasage central d'un comparateur OU-exclusif est π/2.) La tension de commande est numérisée avec le convertisseur (ADC) 10 bits du microcontrôleur (ATmega 2560).

L'O.C.T. fonctionne avec le Timer 1. Le signal généré u2(t) a un rapport cyclique 1/2. La figure suivante montre comment le compteur 16 bits du Timer 1 est utilisé pour générer ce signal.

pwm-mode.svgFigure pleine page

Le registre 16 bits TCNT1 contient la valeur du compteur, qui varie d'une unité à chaque front d'horloge. La fréquence de base de l'horloge (16 MHz) peut être divisée par 8, 64, 256 ou 1024. Lorqu'il est dans sa phase de croissance et atteint la valeur stockée dans le registre ICR1, le compteur passe en phase de décroissance jusqu'à revenir à zéro. Le registre OCR1A contient une valeur qui est comparée à TCNT1 afin de générer un signal sur la sortie OC1A (qui est reliée à la borne D11 de la platine Arduino). Lorsque TCNT1 atteint OCR1A par valeurs croissantes, la sortie bascule à 0 (clear OC1A) et lorsqu'il atteint OCR1A par valeurs décroissantes la sortie bascule au niveau haut (5 volts). En choisissant OCR1A égal à ICR1/2, on génère ainsi un signal binaire de rapport cyclique 1/2 dont la période est égale à :

To=2*ICR1*Th

Th est la période de l'horloge. Par exemple, si le diviseur d'horloge est 64 on a Th=256/16=16 microsecondes.

La fréquence du signal, c'est-à-dire la valeur de ICR1, doit être ajustée à chaque cycle en fonction de la tension lue sur l'entrée A0.

La théorie générale de la B.V.P. (Boucle à verrouillage de phase) fait appel à la relation linéaire suivante pour exprimer la fréquence délivrée par l'O.C.T. :

ω2=ω0+Ko(uf-uref)

La boucle peut aussi fonctionner si la rétroaction n'est pas linéaire mais si l'on veut que le signal uf corresponde linéairement aux variations de fréquences, il faut utiliser un O.C.T. à commande linéaire (cela permettra aussi d'utiliser la théorie linéaire).

Notons icr1 la valeur de référence de ICR1 et icr11 la valeur permettant d'obtenir la pulsation ω2 en sortie de l'O.C.T.. Soit x le nombre 10 bits fourni par l'ADC. La relation précédente s'écrit :

2π2Th(1icr11-1icr1)=5Ko1024(x-xref)(1)

ou encore :

icr11=icr11+Go(x-xref)(2)Go=5KoTo2048π=5Ko2048πfo(3)

Pour modifier ICR1 à chaque cycle, on programme le Timer 1 afin qu'il génère une interruption lorsque TCNT1 atteint ICR1 (Overflow interrupt). Dans le gestionnaire d'interruption correspondant, on lit la tension sur l'entrée A0 au moyen de l'ADC, qui fournit un nombre (x) 10 bits (compris entre 0 et 1024) que l'on place dans la variable input. La tension de référence, qui correspond à u1 et u2 en quadrature, est uref=2,5V , soit xref=512 .

Une particularité importante de cet oscillateur commandé en tension est la quantification de la tension de commande et de la fréquence de sortie. En effet, la tension de commande est quantifiée de 5000/10245mV . La quantification de la fréquence vient du fait que ICR1 ne peut varier que par unité. La variation relative de fréquence correspondante est environ égale à l'inverse de l'ICR1. Pour réduire le pas de fréquence, on aura donc intérêt à utiliser une valeur de ICR1 la plus grande possible, c'est-à-dire utiliser le plus plus petit diviseur de fréquence d'horloge possible. Par exemple, pour générer un signal de 1 kHz, on ne divise pas la fréquence d'horloge.

Pour que la B.V.P. fonctionne correctement, il faut que les variations de la tension de commande soient beaucoup plus grandes que le pas de quantification. Il faudra donc choisir la valeur de Ko assez petite pour que la variation de fréquence recherchée soit obtenue pour des valeurs assez grandes de x-xref (de l'ordre de la centaine). Cependant, la valeur de Ko a aussi un effet sur la plage de fréquence de verrouillage de la boucle. Pour un comparateur XOR la plage de fréquence de verrouillage est définie par :

|f-fo|<πKo20(4)

Il faudra donc choisir la valeur de Ko la plus petite compte tenu de la plage de fréquence voulue. Dans l'exemple montré ci-après, fo=1000Hz et icr1=8000 (sans division de la fréquence d'horloge).

3.b. Signal modulé en fréquence

Afin de tester la boucle à verrouillage de phase, on génère sur la sortie D6 un signal binaire dont la fréquence est modulée à une fréquence variable.

Le signal est généré avec le Timer 4 sur la sortie OC4A, qui est reliée à la borne D6 de la carte Arduino. Le Timer 3 est utilisé pour déclencher des interruptions périodiques, dont le gestionnaire est chargé de modifier la valeur de ICR4.

Notons Δf l'amplitude de la modulation de fréquence et T sa période. Notons icr4 la valeur de ICR4 correspondant à la fréquence moyenne et icr41 la valeur à l'instant t, donnée par :

1icr41=1icr4+2ThΔfsin(2πtT)(5)

Les valeurs de TCR4 sur une période sont stockées dans un tableau contenant NMOD valeurs. Le gestionnaire d'interruption lit ce tableau en incrémentant l'indice d'une unité à chaque appel et actualise ICR4. La fréquence des interruptions est donc F*NMOD.

4. Programme Arduino

Le programme suivant fonctionne sur Arduino MEGA. La fonction setup effectue la configuration des deux sorties utilisées, la configuration des trois Timers et la configuration de l'ADC. La fonction loop ne joue aucun rôle dans le fonctionnement de la B.V.P.. Elle affiche la fréquence délivrée par l'O.C.T., ce qui n'a d'intérêt que si la fréquence du signal u1(t) varie très lentement.

arduinoTestPLL.ino
#include "Arduino.h"
volatile uint16_t icr_1, ocra_1, icr_11, ocra_11, icr_4;
volatile int16_t input=0;
uint32_t period1, period4, period3;
uint16_t diviseur[6] = {0,1,8,64,256,1024};
uint8_t d1,d4,d3;
#define NMOD 100
volatile uint16_t icr_41[NMOD];
uint16_t imod;
float Th;
float Go;

float fo = 1000; // fréquence de référence
float Ko = 100; // gain de l'OCT (en rad/V/s)
float Delta_f = 10.0; // amplitude de la modulation de fréquence
float F = 1.0; // Fréquence de la modulation


void setup() {
  Serial.begin(115200);
DDRB |= 1 << PORTB5; // OC1A (timer 1) configurée en sortie (D11)
//DDRE |= 1 << PORTE3; // OC3A (timer 3) configurée en sortie (D5)
DDRH |= 1 << PORTH3; // OC4A (Timer 4) configurée en sortie (D6)

cli(); // désactivation des interruptions
// Configuration de l'OCT
// Oscillation générée avec Timer1 (sortie D11)
TCCR1A = 0;
TCCR1A |= (1 << COM1A1) ;// clear OC1A on compare match when up-counting, reset OC1A on compare match when down-counting
TCCR1B = 1 << WGM13; // phase and frequency correct pwm mode, top = ICR1
period1 = 1/fo*1e6; // période en microsecondes
d1 = 1;
icr_1 = (F_CPU/1000000*period1/2/diviseur[d1]); // valeur maximale du compteur, pour définir la période
Th = 1.0/(F_CPU/1000000/diviseur[d1]); // période de l'horloge (en us)
Go = 5*Ko/(2*PI*1024)*period1*1e-6;
Serial.print("Go = "); Serial.println(Go,10);
ocra_1 = icr_1/2; // rapport cyclique = 1/2
ICR1 = icr_1;
OCR1A = ocra_1;
TIMSK1 = 1 << TOIE1; // overflow interrupt enable
TCCR1B |= d1; // déclenchement du compteur avec choix du diviseur d'horloge

// Timer 4 pour générer le signal de test de la PLL
TCCR4A = 0;
TCCR4A |= (1 << COM4A1) ;
TCCR4B = 1 << WGM43; 
d4 = 1;
period4 = period1;
Th = 1.0/(F_CPU/1000000/diviseur[d4]); // période de l'horloge (en us)
icr_4 = (F_CPU/1000000*period4/2/diviseur[d4]);
for (uint16_t i=0; i<NMOD; i++) icr_41[i] = 1.0/(1.0/icr_4+2*Th*1e-6*Delta_f*sin(2*PI*i/NMOD));
imod = 0;
ICR4 = icr_4;
OCR4A = icr_4/2;
TCCR4B |= d4; 


//Timer 3 pour générer le signal de modulation de fréquence (pour tester la PLL)
TCCR3A = 0;
TCCR3A |= (1 << COM3A1) ;
TCCR3B = 1 << WGM13; 
d3 = 3;
period3 = (1e6/F)/NMOD; // période du Timer3 pour la modulation (en uS)
ICR3 = (F_CPU/1000000*period3/2/diviseur[d3]);
OCR3A = ICR3 / 2;
TIMSK3 = 1 << TOIE3; // overflow interrupt enable
TCCR3B |= d3;

// configuration de l'ADC
ADCSRA = 0;
ADCSRA |= (1 << ADEN); // enable ADC
ADCSRA |= 0 ; // prescaler
ADCSRB = 0;
ADMUX = 0b01000000 | 0; // REFS0 et ADC0
ADCSRA |= 0b01000000; // start ADC
sei();// activation des interruptions
}


// Changement de la fréquence de l'OCT à chaque cycle
ISR(TIMER1_OVF_vect) { // Timer 1 Overflow interrupt
  input = ADCL | (ADCH <<8);
  icr_11 = 1.0*icr_1/(1+Go*(input*1.0-512.0));
  ocra_11 = icr_11/2;
  ICR1 = icr_11;
  OCR1A = ocra_11;
  ADCSRA |= 0b01000000; // start ADC
}

// modulation de fréquence de Timer4
ISR(TIMER3_OVF_vect) {
   imod += 1;
   if (imod==NMOD) imod=0;
   ICR4 = icr_41[imod];
   OCR4A = ICR4/2;
}

void loop() {
  delay(1000);
  float f = 16e6/(icr_11*2*diviseur[d1]);
  Serial.print(", "); Serial.print(f); Serial.println(" Hz"); 
}	   
    	   

5. Test de la boucle à verrouillage de phase

5.a. Choix des paramètres

La théorie (Boucle à verrouillage de phase) permet de déterminer la réponse fréquentielle de la boucle. Voici sa fonction de transfert :

H̲(s)=1+τ2s1+2ξsΩn+s2Ωn2(6) Ωn=KdKoτ1+τ2(7) ξ=Ωn2(τ2+1KdKo)(8)

avec τ1=R1C et τ2=R2C . On a choisi R1=10 et C=1μF (valeurs nominales). Après mesure de R1 et C on obtient τ1=0,99810-2s . Le temps τ2 doit être au moins 100 fois plus petit. Dans le cas présent, on peut choisir τ2=0 , ce qui implique :

ξ=Ωn2KdKo=12τ1KdKo(9)

avec Kd=5/π V/rad (car le niveau logique haut est à 5 V).

Par ailleurs, la valeur de Ko détermine la demi-plage de fréquence de verrouillage de la boucle :

|f-fo|<5 Ko4π(10)

La valeur optimale du facteur d'amortissement est ξ=1/2 . On peut choisir Ko pour satisfaire la plage de fréquence souhaitée puis calculer τ1 , ou bien calculer Ko si la valeur de τ1 est donnée par le choix de R1 et C. Nous adoptons cette seconde méthode et obtenons :

Ko=π10τ1=31,5radV-1s-1(11)

ce qui implique 1/(KoKd)=0,01s et justifie le choix τ2=0 . L'amplitude maximale de variation de la fréquence est donc Δf=12,5Hz. La fréquence de référence est 1000Hz et cette valeur est obtenue avec icr1=8000. On a donc Go=2,410-5 et, en conséquence, un changement d'une unité de x (la valeur donnée par l'ADC) se traduit par un changement de icr1 égal à environ icr1Go=0,2, ce qui signifie qu'il faut un changement de x de 5 unités pour modifier la fréquence.

Les paramètres de la réponse fréquentielle sont finalement :

Ωn=71rads-1Fn=11,2Hzξ=0,71
import numpy as np
from matplotlib.pyplot import *
f = np.logspace(0,2,500)
def H(f,xi,Omega_n):
    x = 2*np.pi*f/Omega_n
    return 1/(1+2*1j*xi*x-x**2)
H1 = H(f,0.71,71)
GdB1 = 20*np.log10(np.absolute(H1))
figure()
plot(f,GdB1)
xscale('log')
xlabel(r"$F (Hz)$")
ylabel(r"$G_{\rm dB}$")
grid()
    	          
reponse1reponse1.pdf

Si l'on veut choisir la plage de fréquences tout en gardant la condition ξ=1/2 , il faut calculer τ1 en conséquence, puis choisir des valeurs de R1 et C adaptées.

Remarque : la quantification de la tension uf impose de ne pas choisir une valeur de Ko trop grande. Il faut en effet que le bruit de quantification n'ait pas d'effet sur la fréquence, c'est-à-dire Goicr1< 1, condition qui s'écrit aussi :

Ko<2048πfo5icr1(12)

qui pour la fréquence de référence adoptée donne Ko<161 rad/V/s.

Dans le cas d'une B.V.P. dont l'O.C.T. est analogique, il est possible d'employer une constante Ko beaucoup plus grande, ce qui a pour effet de réduire l'amplitude des variations de uf. Dans ce cas, le temps τ2 n'est plus négligeable dans l'expression de ξ et sert alors de variable d'ajustement pour obtenir la valeur optimale du taux d'amortissement. Si la valeur de τ1 ne change pas, augmenter Ko permet d'augmenter Ωn, donc d'augmenter la largeur de bande passante de la boucle (c.a.d. augmenter sa rapidité), mais cela a pour inconvénient la nécessiter d'amplifier les variations de uf pour en tirer l'information sur la fréquence. Sachant que l'amplification peut elle-même introduire un temps de réponse non négligeable, il faut trouver un compromis.

5.b. Test de la boucle

La vidéo suivante montre les deux signaux u1 (en bleu) et u2 (en rouge), pour une amplitude de modulation de 3Hz et une fréquence de modulation de 1Hz.

Le point de déclenchement, synchronisé sur u1, est à visible gauche (à -0.005 ms). La modulation de fréquence est visible sur u1. Le signal u2 est bien toujours synchrone avec u1, ce qui montre que la boucle est en permanence verrouillée, mais le déphasage entre les deux signaux oscille au rythme de la modulation, avec une valeur moyenne de π/2.

L'observation du signal uf, c'est-à-dire la sortie du filtre et la tension de commande de l'O.C.T., permet de suivre la modulation de fréquence. La valeur de cette tension est d'ailleurs accessible dans le programme Arduino (variable globale input), mais il est plus judicieux d'utiliser directement icr_1 pour calculer la fréquence, comme cela est fait dans la fonction loop.

Voici le signal uf à une fréquence de modulation de 1Hz :

signal-uf

Voici le signal uf a une fréquence de modulation de 10Hz :

signal-uf

Il n'y a pas d'atténuation alors que le modèle prévoyait environ -2 dB pour cette fréquence.

Voici le signal pour une modulation de 20Hz :

signal-uf

puis pour une modulation de 40Hz :

signal-uf

Revenons à une fréquence de modulation de 10Hz et triplons l'amplitude de la modulation (9Hz) :

signal-uf

Comme prévu, l'amplitude de variation de la tension uf est trois fois plus grande.

5.c. Modulation FSK

La modulation FSK (Frequency Shift Keying) est une méthode de modulation de fréquence utilisée en transmission numérique, consistant à attribuer une fréquence pour les bits de valeur 0 et une autre fréquence voisine pour les bits de valeur 1. Pour tester ce type de modulation, il suffit de remplir la table des valeurs de ICR4 avec un signal carré :

for (uint16_t i=0; i<NMOD/2; i++) icr_41[i] = 1.0/(1.0/icr_4+2*Th*1e-6*Delta_f);
for (uint16_t i=NMOD/2; i<NMOD; i++) icr_41[i] = 1.0/(1.0/icr_4-2*Th*1e-6*Delta_f);         
                      

Voici le signal uf pour une fréquence de modulation de 1 Hz :

signal-uf

Ce signal montre en fait la réponse indicielle de la B.V.P.. Le temps de réponse, relevé entre les deux traits verticaux, est 91ms, ce qui est conforme à la valeur Fn=11Hz calculée d'après le modèle linéaire. En revanche, on observe un léger dépassement, contrairement à ce qu'indiquait la valeur du coefficient d'amortissement.

Afin d'éliminer ce dépassement, nous réduisons la valeur de Ko (pour augmenter le coefficient d'amortissement). Voici le signal pour Ko=15rad⋅V-1⋅s-1 :

signal-uf

Le temps de réponse est 75ms.

5.d. Décrochage de la boucle

Voici le signal uf pour Δf=13Hz, valeur légèrement au dessus de la plage de fréquences d'accrochage de la boucle.

signal-uf

Cela montre qu'il ne faut pas dépasser la plage de fréquence autorisée.

5.e. Conclusion

Les réponses fréquentielles et indicielles sont notablement différentes de ce que prévoit le modèle pour les paramètres choisis. Il faut remarquer que ce modèle suppose une réponse instantanée de l'O.C.T., la variation de fréquence étant proportionnelle à la variation de la tension de commande. Or l'O.C.T. a un temps de réponse de l'ordre de grandeur de la période, soit 1ms, qui est bien négligeable devant le temps de réponse de la boucle.

Creative Commons LicenseTextes et figures sont mis à disposition sous contrat Creative Commons.