Cette page montre comment mesurer la fréquence d'un oscillateur avec un arduino (UNO ou MEGA). On considère tout d'abord la programmation d'un fréquencemètre avec l'Arduino, capable de mesurer des fréquences allant de quelques Hz jusqu'à 4 MHz.
La mesure de fréquence d'un oscillateur LC permet de mesurer l'inductance d'une bobine. On verra aussi un oscillateur à relaxation, qui permet de mesurer la capacité d'un condensateur.
Pour réaliser un fréquencemètre avec un Arduino, on utilise une entrée reliée à une entrée Tn du mode Capture d'un Timer 16 bits :
La mesure de fréquence se fait en comptant les fronts montants sur l'entrée Dn pendant une durée déterminée, par exemple 1 seconde. La manière la plus précise de faire ce comptage est d'utiliser un Timer 16 bits en mode capture, comme expliqué dans Mesure de fréquence et de temps.
L'arduino UNO (ATmega328) ne possède qu'un seul Timer 16 bits, le Timer1. L'entrée utilisée est T1, reliée à la borne D5 de la carte. Il faut aussi programmer des interruptions périodiques pour fixer précisément la durée du comptage. On utilise pour cela le Timer2 (8 bits).
Le Timer2 est configuré en mode CTC (clear timer on compare), qui consiste à remettre à zéro le registre du compteur 8 bits (TCNT2) lorsqu'il atteint la valeur du registre OCR2A. On choisit OCR2A=0xFF, ce qui signifie que la période d'interruption est égale à 256 tops d'horloge. L'horloge du compteur est l'horloge de la carte (16 MHz), à laquelle une division est appliquée (prescaler). On choisit le facteur de division le plus élevé (1024), ce qui fait une fréquence d'horloge 16/1024 MHz. La période d'interruption est alors :
soit environ 1/60 ième de seconde.
Le compteur d'impulsion a 16 bits. On peut facilement faire un comptage à 32 bits, en déclenchant une interruption lorsque le compteur 16 bits revient à zéro. On définit donc deux variables 16 bits pour le comptage count_high (16 bits de poids fort) et count_low (16 bits de poids faible).
Avec ce comptage sur 32 bits, on a intérêt à augmenter la durée de comptage pour faire des mesures en basse fréquence. On arrête le comptage lorsque le nombre d'interruptions atteint une valeur ninter, ce qui fait une durée de comptage ΔT=niter*Ti. Un durée de 1 seconde (environ) est obtenue avec niter=60.
Voici le programme pour l'Arduino UNO. Le nombre d'interruptions pour faire un comptage complet est fixé comme variable globale dans l'en-tête. Par exemple pour un nombre de 60, la durée du comptage est environ égale à 1 seconde. Cette durée devra être choisie en fonction de la précision et de la durée de la mesure souhaitées.
#include "Arduino.h" char inputPin = 5; // entrée T1 pour Timer1 volatile uint16_t count_high,count_low; volatile uint32_t count; volatile uint16_t ni; // compteur d'interruptions uint16_t ninter = 60; // nombre d'interruptions pour un comptage float deltaT;
La fonction suivante configure et déclenche le Timer1 (pour le comptage en mode capture) et le Timer2 pour les interruptions.
void start_count() { // Timer 2 : génération d'interruptions périodiques TCCR2A |= (1 << WGM21); // CTC mode 'clear timer on compare), top = OCR2A OCR2A = 0xFF; // fréquence d'interruption 16 MHz /1024/256 TIMSK2 = 1 << TOIE2; // overflow interrupt enable TCNT2 = 0; ni = 0; // Timer 1 : compteur d'impulsions TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TIMSK1 = 1<<TOIE1; // overflow interrupt enable count = 0; sei(); // activation des interruptions TCCR2B |= (1 << CS12) | (1 << CS11) | (1 << CS10); // prescaler = 1024 TCCR1B |= (1 << CS12) | (1 << CS11) | (1 << CS10); // external clock on rising edge }
Voici la fonction appelée lors de l'interruption déclenchée par le débordement du Timer 2. Lorsque le nombre d'interruptions atteint ninter, on enregistre le nombre d'impulsions comptées dans count puis on remet le compteur d'impulsions à zéro.
ISR(TIMER2_OVF_vect) { // Overflow interrupt ni++; if (ni==ninter) { ni = 0; count_low = TCNT1; TCNT1 = 0; count = ((uint32_t)count_high)<<16 | count_low; count_high = 0; } }
La fonction d'interruption déclenchée lors du débordement du compteur d'impulsions doit incrémenter les 16 bits de poids fort :
ISR(TIMER1_OVF_vect) { count_high++; }
La fonction setup calcule la valeur de la durée de comptage et déclenche le comptage.
void setup() { Serial.begin(115200); pinMode(inputPin,INPUT); deltaT = 1.0*ninter*1024*256/F_CPU; start_count(); }
La fonction loop affiche, toutes les secondes, la fréquence et la précision.
void loop() { delay(1000); float freq = count/deltaT; float prec = 1.0/deltaT; Serial.print("Frequence (Hz) = "); Serial.println(freq,DEC); Serial.print("Precision (Hz) = "); Serial.println(prec,DEC); }
Sur l'arduino MEGA, l'entrée T1 du Timer1 n'est pas cablée. On utilise l'entrée T5 du Timer5, qui est reliée à la borne 47. Par ailleurs, on peut utiliser un Timer 16 bits pour déclencher les interruptions, car le microcontrôleur ATmega 2560 en possède 4 (Timers 1,3,4,5). Il est ainsi possible de déclencher une seule interruption pour un comptage complet. On utilise pour cela le Timer3, en mode Phase and frequency correct pwm, avec une valeur maximale fixée par le registre ICR3. La période d'interruption sera choisie en faisant varier la valeur de ICR3 et le facteur de division appliqué à l'horloge.
Le compteur d'impulsion a 16 bits. On peut facilement faire un comptage à 32 bits, en déclenchant une interruption lorsque le compteur 16 bits revient à zéro. On définit donc deux variables 16 bits pour le comptage count_high (16 bits de poids fort) et count_low (16 bits de poids faible).
#include "Arduino.h" char inputPin = 47; // entrée T5 pour Timer5 char pwmPin = 5; // sortie TTL pour contrôle des interruptions uint16_t diviseur[6] = {0,1,8,64,256,1024}; volatile uint16_t count_high,count_low; volatile uint32_t count; float deltaT;
La fonction suivante configure et déclenche les Timers. Pour une explication détaillée, voir Mesures de fréquence et de temps. Pour le Timer de déclenchement des interruptions, on choisit ici un facteur de division d'horloge 1024 et une valeur maximale du compteur de 0x1FFF, ce qui donne une durée ΔT d'environ 1 seconde. Pour mesurer précisément des très basses fréquences (inférieures à 100 Hz), il faut augmenter la valeur maximale du compteur. Pour les fréquences inférieures à 1 Hz, il est préférable d'utiliser une méthode de mesure de l'intervalle entre deux fronts (voir Mesures de fréquence et de temps).
Le registre OCR3A, dont la valeur est la moitié de ICR3, permet de générer un signal PWM de rapport cyclique 1/2 sur la sortie 5. Ce signal permet de contrôler le bon déroulement des interruptions avec un oscilloscope. D'une manière générale, il est bon d'avoir un moyen de contrôler le bon déroulement des interruptions périodiques.
void start_count() { char clockBits; // Timer 3 : génération d'interruptions périodiques TCCR3A = 0; TCCR3A |= (1 << COM3A1) | (1 << COM3A0); //set OC3A on compare match when up-counting, clear OC3A on compare match when down-counting TCCR3B = 1 << WGM13; // phase and frequency correct pwm mode, top = ICR3 clockBits = 5; // prescaler = 1024 ICR3 = 0x1FFF; // environ 1 seconde avec prescaler = 1024 deltaT = diviseur[clockBits]*1.0*ICR3*2/F_CPU; OCR3A = ICR3 >> 1; TIMSK3 = 1 << TOIE3; // overflow interrupt enable TCNT3 = 0; // Timer 5 : compteur d'impulsion TCCR5A = 0; TCCR5B = 0; TCNT5 = 0; TIMSK5 = 1 << TOIE5; // overflow interrupt enable count = 0; sei(); TCCR3B |= clockBits; TCCR5B |= (1 << CS32) | (1 << CS31) | (1 << CS30); // external clock on rising edge }
La fonction d'interruption du Timer 3 (Overflow) enregistre la valeur du compteur TCNT5 et le remet à zéro.
ISR(TIMER3_OVF_vect) { // Overflow interrupt count_low = TCNT5; TCNT5 = 0; count = ((uint32_t)count_high)<<16 | count_low; count_high = 0; }
La fonction d'interruption déclenchée lors du débordement du compteur d'impulsions doit incrémenter les 16 bits de poids fort :
ISR(TIMER5_OVF_vect) { count_high++; }
Dans la fonction setup, la durée du comptage est fixée puis les timers sont déclenchés.
void setup() { Serial.begin(115200); pinMode(inputPin,INPUT); pinMode(pwmPin,OUTPUT); start_count(); }
La fonction loop affiche périodiquement la fréquence et la précision.
void loop() { delay(1000); float freq = count/deltaT; float prec = 1.0/deltaT; Serial.print("Frequence (Hz) = "); Serial.println(freq,DEC); Serial.print("Precision (Hz) = "); Serial.println(prec,DEC); }
Les tests sont réalisés avec la sortie TTL d'un générateur de fonctions basse fréquence (jusqu'à 3 MHz). Un compteur intégré au générateur permet d'obtenir la fréquence avec une précision comparable à celle du fréquencemètre réalisé ici.
Voici quelques mesures réalisées avec l'arduino UNO pour une durée de comptage de 1 seconde. Pour les fréquences les plus élevées, la fréquence obtenue varie notablement d'un comptage à l'autre, probablement à cause des variations de fréquence de l'oscillateur du GBF. On affiche donc seulement les chiffres qui ne varient pas de plus d'une unité.
f arduino (Hz) | f gbf (Hz) |
156 | 156.36 |
1448 | 1448.5 |
15743 | 15743 |
163460 | 163470 |
1.56658e+06 | 1.56658e+06 |
Voici des mesures réalisées avec l'arduino MEGA, pour une durée de comptage de 1 seconde :
f arduino (Hz) | f gbf (Hz) |
156 | 156.36 |
1447 | 1448 |
15737 | 15748 |
163420 | 163530 |
1.5653e+06 | 1.56638e+06 |
La précision est moins bonne qu'avec l'arduino UNO. Il s'agit probablement d'une erreur sur la fréquence de l'horloge de la carte. Pour le vérifier, on analyse le signal TTL émis par la sortie 5 avec un fréquencemètre. Pour augmenter la fréquence, on choisit ICR3 = 0xFF. La fréquence des interruptions est (pour une horloge à 16 MHz) : 16e6/(2*255*1024)=30.637 Hz. La mesure au fréquencemètre donne 30.657 Hz. L'horloge de la carte est donc légèrement plus rapide que 16 MHz. Pour faire un fréquencemètre précis, il faut tenir compte de cet écart. Dans le cas présent, le facteur correctif à appliquer à la fréquence mesurée est 1.00064.
L'oscillateur est constitué d'une cellule RLC et d'un amplificateur. Il nécessite une alimentation double, par exemple -15/0/15 V. Pour simplifier, la bobine est représentée sous sa forme idéale, par une inductance L1.
Figure pleine pageLorsque le gain de l'amplificateur est suffisant, la tension aux bornes de la bobine oscille de manière sinsuoïdale à la fréquence
Si l'on connait la capacité, la mesure de fréquence permet de déterminer l'inductance de la bobine.
Le gain de l'amplificateur doit compenser l'atténuation de la cellule RLC à la résonance. En fonction de la bobine utilisée ce gain varie entre 3 et 20. Le potentiomètre permet de l'ajuster. Le gain doit être légèrement supérieur au minimum nécessaire pour enclencher l'oscillation. Il s'en suit une légère saturation en sortie de l'ampli-op CI1, mais la tension aux bornes de la bobine reste sinusoïdale. Cette tension est envoyée vers le comparateur LM392 (un circuit intégré qui comporte un comparateur et un ampli-op). Ce comparateur est alimenté par la tension +5 V de l'arduino, ce qui permet d'obtenir en sortie un signal carré envoyé sur une entrée Dn de l'arduino. Cette entrée doit être reliée à une entrée Tn du mode Capture d'un compteur 16 bits :
Pour un condensateur de capacité C1=951 nF (mesurée) et une bobine marquée 100 mH, voici la tension aux bornes de la bobine et en sortie du comparateur :
La tension en sortie du comparateur n'atteint pas 5 V. On pourrait monter cette valeur en abaissant la résistance de collecteur R3 (au prix d'une plus forte consommation), mais cela n'est pas nécessaire pour que le compteur fonctionne. La tension ne descend pas à zéro à cause de la tension collecteur-émetteur du transistor de sortie.
La fréquence mesurée (avec un temps de comptage de 2 s) est f=479.6 Hz, qui nous permet de calculer l'inductance avec une précision de 1 pour 100 :
import math C=951.0e-9 f=479.6 L = 1.0/(4*math.pi**2*f**2*C)
print(L) --> 0.115798090371709
Voici un autre exemple avec une inductance plus faible, marquée 10 mH :
import math C=951.0e-9 f=1588.0 L = 1.0/(4*math.pi**2*f**2*C)
print(L) --> 0.010562306119349739
Plus l'inductance est faible, plus il faut augmenter le gain de l'amplificateur.
Ce dispositif est très utile pour déterminer l'inductance de bobines que l'on fait soi-même. Voici l'exemple d'une inductance réalisée avec 5 spires dans un tore en ferrite de 91 mm2 de section.
import math C=951.0e-9 f=11809 L = 1.0/(4*math.pi**2*f**2*C)
print(L) --> 0.00019100005888481457
Voici le résultat pour le même tore avec 1 spire :
import math C=951.0e-9 f=34886.0 L = 1.0/(4*math.pi**2*f**2*C)
print(L) --> 2.1885546382206292e-05
Voici une mesure pour une bobine (sans noyau) comportant environ 100 spires de diamètre 40 mm enroulées sur une longueur de 45 mm :
import math C=951.0e-9 f=9786.0 L = 1.0/(4*math.pi**2*f**2*C)
print(L) --> 0.0002781309527530423
Un multivibrateur astable peut être facilement réalisé avec un circuit NE555 :
Figure pleine pageD'après la documentation du NE555, la fréquence d'oscillation est :
et le rapport cyclique :
Voici des exemples avec R1=R2=1,0 kΩ.
Pour un condensateur marqué 1 μF (utilisé plus haut dans l'oscillateur LC), voici le signal sur la sortie du NE555 :
Le relevé de la fréquence permet de calculer la capacité :
R1=R2=1000.0 f=502.0 C1 = 1.44/((R1+2*R2)*f)
print(C1) --> 9.561752988047808e-07
Voici le résultat pour un condensateur marqué 68 nF
f=6785.0 C1 = 1.44/((R1+2*R2)*f)
print(C1) --> 7.074428887251289e-08