Et si l’on tentait de générer un signal carré avec un Arduino, histoire de s’amuser un peu ? Et d’ailleurs, quelle fréquence pourrions-nous réellement atteindre, en pratique ? C’est ce que je vous propose de découvrir ici, au travers d’une approche simple et ludique de faire les choses, et idéale pour tous ceux qui veulent faire leurs premiers pas avec arduino !
Mais tout d’abord, afin de mettre les choses dans leur contexte, sachez que cet article est le premier d’une longue série, visant à créer un générateur de signaux Arduino. Ici, on va simplement s’attacher à créer un signal rectangulaire avec des instructions simples, sans trop pousser le code (afin que tous les débutants puissent suivre au maximum, et bien comprendre les choses !). Ce signal sera mesuré en sortie par un oscilloscope USB, qui permettra également de voir la forme, et le rapport cyclique du signal généré.
Au niveau du code arduino, je vais essayer de coder les choses le plus simplement possible, et donc, sans faire appel à des fonctions évoluées, telles que l’utilisation de timer ou d’interruptions, par exemple. En fait, le but de cet article est de présenter une solution simple et ludique, destinée à tous ceux qui font leurs premiers pas avec Arduino. L’accent est donc mis sur l’aspect éducatif et didactique, et non sur la performance, vous l’aurez donc compris 😉
Par contre, histoire d’avoir un projet abouti, je rajouterai une interface de commande sérielle, afin de pouvoir piloter la fréquence de sortie de l’Arduino, directement depuis le moniteur série de l’IDE arduino. Ainsi, ce projet aura l’aspect d’un projet fini, même si bien évidemment, tout ceci n’aura d’autre utilité que l’apprentissage de l’arduino, et découvrir ses possibilités. D’ailleurs, cela nous permettra même de voir les limites d’un Arduino Uno suivant la méthode employée, afin d’arriver à générer un signal propre et équilibré. Ça vous dit ? Alors c’est parti !
Matériels utilisés
Aperçu | Désignation |
---|---|
Arduino Uno R3 – Microcontrôleur : ATMEL ATmega328P – 100% compatible avec la carte UNO R3 version officielle – Avec câble USB d’alimentation, et de programmation | |
Oscilloscope USB Instrustar ISDS205X – Oscilloscope à faible coût, à 2 voies – Bande passante de 20 MHz – Analyseur de spectre et analyseur logique intégrés – Avec fonction générateur de signal DDS (sinus, carré, triangle, dents de scie, …) |
Générateur signal carré : description du montage
Pour ce projet de générateur de signal carré avec arduino, je vais me servir d’un « simple » Arduino Uno. Pour info, ce type de carte tourne autour d’un microcontrôleur ATmega328P de chez ATMEL (racheté par Microchip), et cadencé à 16 MHz, par un oscillateur à quartz externe. L’alimentation de cette carte se fera ici via le cordon USB de programmation, qui servira également à la communication série entre le PC et l’ARDUINO.
Arbitrairement, j’ai choisi la broche 12 de l’arduino pour y faire sortir le signal généré, mais vous pourrez bien évidemment choisir une autre sortie, si vous le souhaitez ! Concernant les mesures, celles-ci seront faites par un simple oscilloscope USB, qui nous indiquera la fréquence du signal de sortie, tout en nous montrant sa forme (très intéressante, vous verrez !).
Au niveau du codage, j’ai choisi de faire quelque chose de simple et ludique avant tout, car encore une fois, cette première approche est surtout destinée à tous ceux qui débutent dans l’univers d’Arduino. Cela étant dit, nous allons tout de même pousser les choses à leurs limites, afin d’obtenir une fréquence de sortie la plus rapide possible. Et ne vous inquiétez pas si vous aimez les choses plus pointues, car je partagerai d’autres articles ici, dans cette même série 😉
Du reste, voici le montage basique que j’ai effectué pour ce projet :
Comme vous le voyez, il n’y a rien de compliqué ! Car au final, j’utilise seulement :
- Une carte Arduino Uno pour générer le signal carré
- Un oscilloscope USB pour visualiser ce signal, et mesurer sa fréquence
- Et un PC, pour alimenter et piloter ces 2 appareils
Au passage, le PC nous permettra d’envoyer des instructions à l’Arduino, via le moniteur série de l’IDE arduino, lorsque souhaité. Mais tout d’abord, commençons par quelque chose de simple, et rudimentaire !
Programme n°1 : arduino en mode « DigitalWrite »
Pour commencer, nous allons faire commuter le plus rapidement possible une des sorties de l’Arduino, avec la fonction DigitalWrite. Ceci, afin de voir quelle sera la fréquence max atteignable.
Pour ce faire, il suffit de placer 2 lignes de code dans la boucle « loop » du programme Arduino, pour faire passer une sortie de l’état bas à l’état haut, puis de l’état haut à l’état bas. Simple, non ?
Nota : perso, j’ai choisi de prendre la sortie D12 pour y faire sortir le signal. Mais vous pourriez très bien prendre une autre sortie, si cela vous plaît plus 😉
Voici le code de programmation :
#define pinSignalDeSortie 12 // On prendra arbitrairement la sortie D12 de l'Arduino UNO, pour y sortir le signal carré +5V/0V
void setup() {
pinMode(pinSignalDeSortie, OUTPUT); // On définit la broche D12 comme étant une broche de SORTIE
}
void loop() {
digitalWrite(pinSignalDeSortie, HIGH); // On met la sortie D12 à l'état haut(+5V)
digitalWrite(pinSignalDeSortie, LOW); // On met la sortie D12 à l'état bas (0V)
// ... et on boucle indéfiniment !
}
Au final, ce sont donc simplement deux lignes de code qui bouclent en permanence, faisant passer alternativement la sortie D12 à l’état haut, puis à l’état bas. À présent, voyons à quoi ressemble le signal de sortie, en branchant un oscilloscope sur la sortie 12 de l’arduino :
Hummm… on trouve un signal de fréquence 150 kHz en D12, alors que le microcontrôleur qui fait tourner ce programme roule à 16 MHz ! Soit un signal près de 1000 fois plus lent que le quartz qui cadence l’Arduino Uno ! Surprenant, non ? Car notre code comporte que 2 lignes au final, qui bouclent indéfiniment. Il y a donc sûrement quelque chose qui s’exécute lentement, dans ces deux lignes de programme 😉
En fait, pour bien comprendre ce qui se passe, il faut avant tout examiner de plus près les choses. Et plus précisément, voir ce que fait réellement la fonction digitalWrite. Car c’est la seule fonction appelée ici, et donc, la seule source de lenteur possible.
Pour voir le contenu de la fonction digitalWrite, il faut aller dans les fichiers sources de l’IDE Arduino. Parmi ces fichiers, on en trouve un, qui s’appelle « wiring_digital.c ». Et c’est dans ce fichier là qu’on retrouve notre fameuse fonction « digitalWrite ».
Voici d’ailleurs un extrait de ce fichier, montrant la fonction digitalWrite :
void digitalWrite(uint8_t pin, PinStatus val)
{
/* Get bit mask for pin */
uint8_t bit_mask = digitalPinToBitMask(pin);
if(bit_mask == NOT_A_PIN || isDoubleBondedActive(pin)) return;
/* Turn off PWM if applicable */
turnOffPWM(pin);
/* Get port */
PORT_t *port = digitalPinToPortStruct(pin);
/* Output direction */
if(port->DIR & bit_mask){
/* Set output to value */
if (val == LOW) { /* If LOW */
port->OUTCLR = bit_mask;
} else if (val == CHANGE) { /* If TOGGLE */
port->OUTTGL = bit_mask; /* If HIGH OR > TOGGLE */
} else {
port->OUTSET = bit_mask;
}
/* Input direction */
} else {
/* Get bit position for getting pin ctrl reg */
uint8_t bit_pos = digitalPinToBitPosition(pin);
/* Calculate where pin control register is */
volatile uint8_t* pin_ctrl_reg = getPINnCTRLregister(port, bit_pos);
/* Save system status and disable interrupts */
uint8_t status = SREG;
cli();
if(val == LOW){
/* Disable pullup */
*pin_ctrl_reg &= ~PORT_PULLUPEN_bm;
} else {
/* Enable pull-up */
*pin_ctrl_reg |= PORT_PULLUPEN_bm;
}
/* Restore system status */
SREG = status;
}
}
Comme vous le voyez, cette fonction va bien au delà du simple changement d’état de telle ou telle broche de sortie. Elle intègre par ailleurs d’autres sous-fonctions, qui ne feront que ralentir davantage encore le temps d’exécution de cette fonction.
À présent, je suis sûr que vous vous demandez pourquoi ce code de base arduino est si lourd, sachant ce qu’il est censé faire ! Et bien, croyez le ou non, mais ce code est parfaitement optimisé, pour répondre à tous les types de demande, et ce, avec un maximum de sécurité. Car cette fonction vérifie ou manipule beaucoup de choses, telles que les timer et les interruptions, qui en « temps normal », ont besoin d’être prises en compte.
Mais dans notre cas à nous, comme nous n’utilisons ni timer, ni interruption, ni aucune autre fonction avancée, nous allons pouvoir écrire du code simplifié, en nous passant de cette fonction digitalWrite. Nous allons ainsi manipuler directement les ports d’entrée/sortie, sans nous soucier du reste ! Et comme vous pouvez déjà le suspecter, le signal généré sera bien plus rapide que ces 150 kHz en sortie 😉
Programme n°2 : arduino à vitesse maximale !
À présent, histoire d’aller plus vite, nous allons nous affranchir de la fonction digitalWrite, et manipuler directement les PORTS d’entrée/sortie de l’arduino. Pour se faire, nous allons directement attaquer les registres « DDRx » et « PORTx », en sachant que « x » indique le port visé (par exemple : DDRC servira à viser le port C, PORTA servira à viser le port A, etc…).
Mais avant d’aller plus loin : savez-vous à quoi servent les registres DDRx et PORTx, et comment s’en servir ? Non ? Ne vous inquiétez pas, car c’est tout simple, en fait ! Car si je simplifie les choses :
- DDRx permet d’indiquer pour chaque broche du port visé, laquelle sera une entrée, et laquelle sera une sortie
- PORTx, quant à lui, permet de mettre à l’état haut ou à l’état bas chacune des broches de sorties de ce port, parmi celles qui sont configurées en sortie via DDRx
Si ce n’est toujours pas clair, ne paniquez pas ! Car tout va devenir concret, dans quelques instants 😉
Dans notre projet, nous allons nous servir de la broche 12 d’un Arduino Uno (aussi notée « D12 »). Mais tout d’abord, il faut savoir à quoi correspond cette broche 12, au niveau des ports d’entrées/sorties du microcontrôleur ATmega328P, qui est le coeur même de cette carte arduino. Pour ce faire, voici un schéma des entrées/sorties d’un Arduino Uno, mettant en exergue la sortie D12 :
Si vous regardez de près, vous verrez que la sortie n°12 (notée D12) est en fait la sortie « PB4 », interne au microcontrôleur. En d’autres termes, cela signifie que la sortie D12 correspond au bit n°4 du port B du microcontrôleur. Du coup, pour manipuler l’état de sortie de la sortie 12, il nous faudra simplement manipuler le 4ème bit du PORTB. Mais pour cela, nous aurons besoin d’un « masque de sélection » binaire, car la fonction « PORTx » vise toutes les broches PBx à la fois, et pas seulement PB4.
Pour nous aider, voici un petit tableau montrant le PORTB et les sorties de l’Arduino reliées dessus :
PORT « B » | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|---|---|---|
Broche digitale correspondante, sur l’Arduino Uno | – | – | D13 | D12 | D11 | D10 | D9 | D8 |
Masque de sélection binaire, pour cibler uniquement la broche « D12 » | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Comme vous le voyez, il suffit en fait de mettre un bit à « 1 » en face de la sortie qui nous intéresse, et le reste à « 0 ». Ainsi, le mot binaire à 8 chiffres qui en résulte sera tout simplement notre masque de sélection, permettant de ne toucher qu’à la sortie PB4, sans altérer l’état des autres. Ainsi, d’après le tableau ci-dessus, le masque binaire sera donc égal à 0b00010000.
À présent, nous allons nous servir de ce masque, en l’incluant dans le code de programmation précédent. Nous allons également remplacer les anciennes commandes de type « digitalWrite » par du « DDRx » et du « PORTx », pour manipuler directement les registres d’entrée/sortie de l’Arduino. Ainsi, voici à quoi ressemble le nouveau code (avec la transcription de chaque ancienne commande en face du nouveau code) :
// On va faire sortir le signal sur la broche D12 de l'Arduino Uno. Cette broche correspond à la sortie PB4 du microcontrôleur
#define pinSignalDeSortie B00010000 // masque permettant de viser la 4ème broche du PORTB, correspondant donc à D12 de la carte Arduino Uno
void setup() {
DDRB = DDRB | pinSignalDeSortie; // équivalent de la fonction : pinMode(pinSignalDeSortie, OUTPUT);
}
void loop() {
PORTB = PORTB | pinSignalDeSortie; // équivalent de la fonction : digitalWrite(pinSignalDeSortie, HIGH);
PORTB = PORTB & ~pinSignalDeSortie; // équivalent de la fonction : digitalWrite(pinSignalDeSortie, LOW);
// ... et on boucle indéfiniment !
}
Si vous n’êtes pas coutumier des fonctions binaires et des fonctions PORTx et DDRx, cela peut être quelque peu déroutant au début. Pourtant, il n’y a rien de bien sorcier ici, juste un peu de cuisine binaire 😉
Pour ceux que ça intéresse, et sans trop entrer dans les détails : la fonction « OU », symbolisée par un « | », nous permet ici de mettre un bit particulier à 1 (ou plus précisément, de mettre la sortie D12 à l’état haut). Et la fonction « ET », symbolisée par un « & », permet quant à elle de mettre un bit particulier à 0, en inversant le masque de sélection au passage, via le symbole « ~ » (et cela permet donc de mettre la sortie D12 à l’état bas). Là encore, ne vous inquiétez pas si vous êtes un peu perdu. Car il s’agit juste de quelques opérations binaires, visant à cibler une sortie particulière de l’arduino, afin de la mettre à 0 ou à 1, tout simplement !
À présent, voyons ce que cela donne en sortie, si on branche notre oscilloscope sur la sortie 12 de l’Arduino Uno :
On a désormais un signal de 2 MHz, bien plus rapide, donc, que nos 150 kHz précédents. En bref, on va 13 fois plus vite ! Par contre, comme ça ne vous aura pas échappé, ce signal n’est clairement pas équilibré (rapport cyclique différent de 50%). Il y a pas ailleurs pas mal de distorsions, mais celles-ci sont tout simplement induites par différentes capacités parasites sur le circuit (notamment au niveau de mes sondes d’oscillo, qui ne sont pas de grande qualité !). Qui plus est, on approche ici du domaine des radios fréquences (RF), et ni mon montage ni mes câblages ne sont adaptés pour de telles fréquences.
Cela étant dit, nous avons quand même obtenu un signal particulièrement rapide ! À présent, il s’agira d’équilibrer tout ça, afin d’obtenir un beau signal carré en sortie, ce qui est l’objet du programme suivant 😉
Programme n°3 : tempo d’équilibrage, pour générer un signal carré « parfait »
Maintenant que nous avons réussi à faire tourner notre Arduino Uno plus rapidement, il va falloir le ralentir un peu, afin d’équilibrer le rapport cyclique du signal de sortie (donc le temps passé à l’état haut, et celui passé à l’état bas). Et pour ce faire, nous allons tout simplement rajouter des petites « pauses » de quelques nanosecondes après chacun de ces états, jusqu’à atteindre l’équilibre. C’est donc par la pratique que nous allons pouvoir déterminer la valeur de ces pauses, à défaut de pouvoir le déterminer par calcul (ce qui n’est pas faisable ici, car nous ne codons pas en assembleur).
Pour introduire ces pauses, nous allons nous servir de la fonction « _delay_us ». Car celle-ci permet de rajouter facilement quelques microsecondes d’attentes au niveau d’un programme, ou plus exactement, quelques nanosecondes, devrais-je dire ! En effet, cette fonction accepte des nombres à virgule, pouvant même être inférieurs à 1 (mais dans une certaine limite, compte tenu de la fréquence du microcontrôleur !). En bref, tout cela nous permettra d’ajuster plus précisément chaque partie de notre signal de sortie, afin qu’il devienne … vraiment carré !
Et au niveau du code, voici ce que cela donne, en rajoutant 2 lignes de _delay_us :
#define pinSignalDeSortie B00010000 // Masque binaire, permettant de viser la broche D12 de l'Arduino Uno
void setup() {
DDRB = DDRB | pinSignalDeSortie; // Définit la broche D12 comme étant une "sortie"
}
void loop() {
PORTB = PORTB | pinSignalDeSortie; // Met la sortie D12 à l'état haut
_delay_us(0.85); // Ajoute un délai supplémentaire, avant de repasser à l'état bas
PORTB = PORTB & ~pinSignalDeSortie; // Met la sortie D12 à l'état bas
_delay_us(0.6); // Ajoute un délai supplémentaire, avant de repasser à l'état haut
// ... et on boucle indéfiniment !
}
Comme vous pouvez le voir dans ce programme, j’ai donc rajouté 0,85 µs de pause à l’état haut, et 0.6 µs de pause à l’état bas. Comme indiqué plus haut, ces valeurs ont été déterminées par la pratique, sans calcul théorique, jusqu’à d’obtenir un signal carré sur la broche de sortie D12 de l’arduino.
Et comme vous pourrez le voir à l’oscillo, j’ai pu atteindre une fréquence de 500 kHz, avec un beau rapport cyclique de 50 % ! Avec au passage, quasiment plus aucune distorsion ! Ce qui est normal, en fait, comme nous sommes redescendus en fréquence 😉
À présent, tout ceci ne servira pas à grand chose, si on ne peut pas définir soi-même la fréquence qu’on souhaite en sortie. Car pour l’instant, nous n’avons pu générer qu’une fréquence fixe, de 500 kHz. Il va donc falloir tâcher de rendre tout cela paramétrable, afin d’avoir un « vrai » générateur de signal carré, digne de ce nom ! C’est ce que nous allons faire ensemble, dans le programme qui suit 😉
Programme n°4 : génération d’un signal carré, réglable de 1 Hz à 500 kHz, et paramétrable dans le programme Arduino
Nous allons à présent modifier le programme précédent, afin de rendre paramétrable la fréquence de sortie, selon ce qu’on souhaite. Bien sûr, il faudra tenir compte de la limite que nous venons de trouver par la pratique, à savoir les 500 kHz de fréquence maxi. Mais avant toute chose, nous allons devoir faire quelques petits calculs, afin de trouver les formules de calcul permettant d’ajuster chaque fréquence à la valeur désirée.
Pour commencer, il faut bien comprendre comment se décompose un signal de 500 kHz. En fait, il s’agit d’un signal durant 2 µS (issus du calcul suivant : Période = 1/500000Hz, soit 2 µs). On a donc 1 µs à l’état haut, et 1 µs à l’état bas. Or, comme nous avions, dans le programme précédent, des pauses égales à 0.85 µs pour l’état haut, et 0.6 µs pour l’état bas, on peut donc en déduire la vitesse d’exécution du programme, pour chaque état à cette fréquence. Ainsi :
- la durée d’exécution du programme à l’état haut = 1 – 0.85 µs, soit 0.15 µs
- la durée d’exécution du programme à l’état bas = 1 – 0.6 µs, soit 0.4 µs
Les temporisations (pauses) à mettre en œuvre, peuvent donc s’exprimer de la manière suivante :
- tempo à l’état haut = (Période du signal / 2) – 0.15
- tempo à l’état bas = (Période du signal / 2) – 0.4
Et si l’on vérifie avec une fréquence de 500 kHz, on a bien :
- une tempo état haut pour 500 kHz = (2µs / 2) – 0.15µs = 0.85 µs (confirme bien ce qu’on avait dans le programme précédent)
- une tempo état bas pour 500 kHz = (2µs / 2) – 0.4µs = 0.6 µs (confirme également ce qu’on avait dans le programme précédent)
Du coup, si je modifie notre programme précédent, en insérant ces 2 nouvelles formules au niveau des temporisations état haut et état bas, cela nous donne le programme suivant (nota : par défaut, j’ai paramétré une fréquence de sortie égale à 2000 Hz, pour faire des essais) :
#define pinSignalDeSortie B00010000 // Masque binaire, permettant de viser la broche D12 de l'Arduino Uno
#define frequenceDeSortie 2000 // On entre ici la fréquence souhaitée en sortie (2000 Hz par défaut)
#define periodeDuSignal (float)1/frequenceDeSortie*1000000 // Période = 1/Fréquence (et multiplié par 1 million, pour l'exprimer en microsecondes)
#define tempo (float)periodeDuSignal/2 // ce seront les délais à rajouter à l'état haut et à l'état bas, afin d'atteindre la fréquence souhaitée
void setup() {
DDRB = DDRB | pinSignalDeSortie; // Définit la broche D12 comme étant une "sortie"
}
void loop() {
PORTB = PORTB | pinSignalDeSortie; // Met la sortie D12 à l'état haut
_delay_us(tempo-0.15); // Ajoute un délai supplémentaire, avant de repasser à l'état bas
PORTB = PORTB & ~pinSignalDeSortie; // Met la sortie D12 à l'état bas
_delay_us(tempo-0.4); // Ajoute un délai supplémentaire, avant de repasser à l'état haut
// ... et on boucle indéfiniment !
}
Il ne reste donc plus qu’à lancer le programme et voir ce que ça donne à l’oscillo ! Pour rappel, j’ai paramétré la fréquence de sortie (notée « frequenceDeSortie » dans le programme) à 2000, pour espérer obtenir un signal de 2000 Hz en sortie (donc 2 kHz !). Et effectivement, c’est quasiment ce que l’on obtient dans la pratique, comme visible ci-dessous à l’oscilloscope :
Et franchement, c’est pas trop mal ! Car il suffit simplement de définir la fréquence qu’on souhaite en sortie dans la variable « frequenceDeSortie », et le tour est joué. Par contre, comme vu précédemment, il ne faudra pas dépasser la limite de 500000 Hz (500 kHz) ici, sans quoi ce programme arduino dysfonctionnera.
Maintenant, il y a une dernière amélioration que nous pourrions ajouter à ce programme. Car ce qui est dommage ici, c’est que ce signal est défini « une fois pour toute » au démarrage de l’arduino, sans pouvoir être changé ensuite, « à chaud », en cours d’exécution du programme. C’est pourquoi, il faudrait encore rajouter un bout de code, afin d’interagir avec le PC, pour recevoir d’éventuelles instructions de changement de fréquence. C’est d’ailleurs l’objet du programme suivant 😉
Programme n°5 : Arduino en mode générateur de signal carré, piloté via l’interface série de l’IDE arduino
Pour rendre tout cela plus fonctionnel encore, nous allons à présent intégrer un pilotage de l’arduino, via le moniteur série de l’IDE Arduino (ou toute autre interface de commande série, une fois le programme transféré en mémoire). Ainsi, la fréquence souhaitée pourra être modifiée à n’importe quel moment, « à chaud », et ce, sans avoir à modifier quoi que ce soit au niveau du code en lui-même.
Comme vous vous en doutez, le rajout de ces lignes de code va forcément réduire les performances de notre petit générateur de signal Arduino. Car si l’on souhaite modifier la fréquence à chaud, il faudra inévitablement que l’Arduino écoute de temps en temps le port série, afin de voir si le PC n’essaye pas de lui transmettre une instruction, visant à modifier la fréquence du signal de sortie. Qui plus est, il faudra également rajouter un peu de code pour s’assurer que les valeurs reçues du PC soient bien correctes, et dans la gamme de fréquence qu’on pourra effectivement couvrir.
Pour cela, nous allons tout d’abord rajouter plusieurs fonctions à notre code précédent :
- une fonction « ecouteInstructionSerie », pour checker toute arrivée d’instruction venant du PC, demandant de modifier la fréquence du signal de sortie
- et une fonction « isValidNumber » pour s’assurer que le nombre reçu soit bien un entier (parce qu’on enverra par exemple « 5000 » si on veut une fréquence de 5000 Hz en sortie)
En pratique, pour la modification de la fréquence de sortie, tout se passera donc au niveau du PC, et plus précisément au niveau du moniteur série de l’IDE Arduino. Et il suffira par exemple de taper « 1000 » pour avoir une fréquence de 1000 Hz en sortie ! Tout simplement ! D’ailleurs, voici un aperçu de l’interface de commande (rudimentaire, je vous l’accorde, mais efficace !) :
Et comme vous allez voir, le programme a pris pas mal de poids ! Et la conséquence de ça est, vous vous en doutez, une « forte » réduction de la fréquence maximale atteignable en sortie. D’ailleurs, en pratique, j’ai du limiter la plage de fréquence à 20 kHz, car au delà, le signal devenait instable.
Du coup, au niveau du code de programmation, voici la dernière mouture (nota : par défaut, le programme génère un signal de 2 kHz en sortie, paramétrée dans la variable « start_freq ») :
#define pinSignalDeSortie B00010000 // Masque binaire, permettant de viser la broche D12 de l'Arduino Uno
#define freq_max 500000 // c'est la fréquence maximale que j'ai pu atteindre en sortie, sans trop de distorsion
#define start_freq 2000 // c'est la fréquence du signal de sortie par défaut (2 kHz), au démarrage du programme
String freq_txt_value;
long freq_num_value;
float tempo=(float)freq_max/start_freq;
boolean isValidNumber(String str){
for(byte i=0;i<(str.length()-1);i++)
if(!isDigit(str.charAt(i)))
return false;
return true;
}
void ecouteInstructionSerie() {
if (Serial.available() > 0) {
// Lecture de l'instruction reçue sur le port série
freq_txt_value = Serial.readString();
if(!isValidNumber(freq_txt_value)) {
Serial.println("Valeur saisie non numérique ...");
Serial.println("");
Serial.println("Entrez une fréquence, entre 40 et 20.000 Hz (chiffres uniquement) :");
return;
}
// Conversion de la chaîne de caractère en nombre, et vérification si elle est bien dans les clous
freq_num_value = freq_txt_value.toInt();
if(freq_num_value < 40) {
Serial.println("Valeur trop petite ...");
Serial.println("");
Serial.println("Entrez une fréquence, entre 40 et 20.000 Hz (chiffres uniquement) :");
return;
}
if(freq_num_value > 20000) {
Serial.println("Valeur trop grande ...");
Serial.println("");
Serial.println("Entrez une fréquence, entre 40 et 20.000 Hz (chiffres uniquement) :");
return;
}
// Calcul des délais à appliquer, afin d'atteindre cette fréquence
tempo = (float)freq_max/freq_num_value;
Serial.print("Nouvelle fréquence = "); Serial.print(freq_num_value); Serial.println(" Hz");
//Serial.print("Tempo = "); Serial.println(tempo4);
Serial.println("");
Serial.println("Entrez une fréquence, entre 40 et 20.000 Hz (chiffres uniquement) :");
}
}
void setup() {
Serial.begin(9600);
Serial.println("Démarrage du script v5");
Serial.println("----------------------");
Serial.println("");
Serial.print("Fréquence de démarrage = "); Serial.print(start_freq); Serial.println(" Hz");
Serial.println("");
Serial.println("Entrez une fréquence, entre 40 et 20.000 Hz (chiffres uniquement) :");
DDRB = DDRB | pinSignalDeSortie; // Définit la broche D12 comme étant une "sortie"
ecouteInstructionSerie();
}
void loop() {
ecouteInstructionSerie();
PORTB = PORTB | pinSignalDeSortie; // Met la sortie D12 à l'état haut
delayMicroseconds(tempo-0.15-21); // Délai supplémentaire, avant de repasser à l'état bas (ajusté)
PORTB = PORTB & ~pinSignalDeSortie; // Met la sortie D12 à l'état bas
delayMicroseconds(tempo-0.6-25); // Délai supplémentaire, avant de repasser à l'état haut (ajusté)
// ... et on boucle indéfiniment !
}
Ce code vous parait long ? Eh bien, en fait, il ne l’est pas vraiment. Car si vous regardez simplement la boucle « loop », à la fin du programme, vous verrez que, mis à part écouter d’éventuelles instructions via le port série, il n’y a rien de nouveau ici (juste quelques légers ajustements au niveau des tempo, issus de la mise en pratique).
Au niveau de la sortie Arduino, voici ce que ça donne à l’oscillo (ici, j’ai paramétré la fréquence la plus basse, à savoir 40 Hz) :
Voici un autre exemple, avec la commande « 1000 » envoyée (on s’attend donc à un signal de sortie de 1 kHz) :
Et un autre exemple, pour 10 kHz demandés :
Et enfin, pour demander 20 kHz, soit la fréquence maximale que j’ai pu atteindre avec mon Arduino, avant que tout devienne instable :
Comme vous le voyez, côté forme du signal et précision, on est pas si mal que ça ! Par contre, côté performance, fabriquer un générateur de signal carré arduino limité à 20 kHz est plutôt médiocre, je vous l’accorde ! Car on aurait espéré mieux que ça ! Mais compte tenu de la technique rudimentaire employée ici, je trouve que c’est pas si mal que ça, pour un simple Arduino Uno 😉
Conclusion
Pour conclure, on voit qu’on peut faire pas mal de choses avec un Arduino ! Car on a pu générer un signal carré ajustable de 1 Hz à 500 kHz, sans trop de difficultés. Par contre, dès qu’on a voulu rajouter un peu de code, afin de pouvoir le piloter l’Arduino à chaud, la fréquence max a alors chuté drastiquement. Car la plage de fréquence finale n’était plus que de 1 Hz à 20 kHz.
Cela étant dit, comme c’était le côté ludique que j’ai souhaité mettre en avant ici, je dirai que le résultat n’est pas si mal que ça ! D’ailleurs, j’espère que tous ceux qui débutent dans le monde d’Arduino auront pu apprendre plein de choses ici, même si clairement, l’Arduino n’est pas prêt de remplacer le moindre générateur de fonction que ce soit, programmé de la sorte !
Aussi, j’espère que vous aurez pu apprendre de nouvelles choses, telles que comment manipuler les PORTS d’entrée/sortie directement, ou, comment interagir avec votre Arduino depuis votre PC, via le moniteur série de votre IDE arduino. Car l’intérêt de tous ces programmes était avant tout de voir tout ce qu’il était possible de faire, de manière simple, pour transformer son Arduino Uno en générateur de signal carré, dans le but d’apprendre.
Cela étant dit, sachez qu’il existe bien d’autres approches pour rendre tout cela bien plus efficace ! Ce sera d’ailleurs l’objet d’autres articles, à venir prochainement dans cette série ! Alors restez à l’écoute 😉
Alors, à bientôt !
Jérôme.
(*) Mis à jour le 15/05/2021
Bonsoir Jérôme,
Tout d’abord félicitations pour tes chapitres sur les « générateurs de signaux carrés Arduino pilotés ». Je suis « un bleu » sur Arduino et j’espère avoir beaucoup appris avec toutes tes explications.
Je vais essayer en suivant ta chronologie de réflexion de générer un signal carré périodique allant de 20 à 1000 hertz maximum, avec affichage de la fréquence dans la fenêtre moniteur lors de la génération du programme source sur une petite carte Arduino Nano. Penses-tu que cette carte puisse convenir ?
Par ailleurs je cherche des informations ou un exemple pour faire varier cette fréquence non pas par la liaison USB série du PC comme toi, mais en agissant sur un potentiomètre extérieur à la carte me permettant d’entrer une tension sur une entrée analogique pour piloter ce générateur. Ainsi une fois le programme source compilé et téléchargé dans la carte, j’aimerai pouvoir faire varier cette fréquence sans utiliser de PC, juste la carte et un potentiomètre.
Qu’en penses-tu? A bientôt de te lire j’espère et encore BRAVO ET merci par avance pour tes réponses à mes questions. Cordialement. Jacky.
Bonsoir Jacky !
Tout d’abord merci à toi pour ce commentaire, et tes remerciements !
Concernant le type d’Arduino : oui, un Arduino Nano est parfait pour ce que tu veux faire. En fait, les Arduino Uno et Nano ne sont pas si différents en pratique, car ils utilisent tous deux le même microcontrôleur (l’ATmega328P), cadencé à 16 MHz. Ils ont également la même quantité de mémoire. On peut donc basculer de l’un à l’autre, sans trop de problèmes.
Concernant la source de pilotage : tu peux parfaitement et « facilement » remplacer le pilotage série USB par un potentiomètre branché sur ton Arduino. Dans ce cas, il faudra simplement :
– lire la valeur de ce potentiomètre à chaque cycle (via la fonction analogRead, par exemple, si elle n’est pas trop gourmande en ressources)
– et de faire un petit calcul de conversion, pour dire au programme que 0V correspondrait par exemple à 20 Hz, et que 5V correspondrait à 1000 Hz
En tous cas, je suis ravi de voir que cet article ait pu t’inspirer, et espère avoir de tes nouvelles très bientôt, une fois ton projet réalisé !
Bon courage à toi, et à bientôt !
Jérôme.
Hi,
Super intéressant, super instructif, super clair… Il est super ton article !!
Bon j’ai en tête de me faire un petit générateur carré et PWM (avec potentiomètre pour un réglage analogique, écran LCD 4*20 de récup et un encodeur rotatif pour les menus), je crois qu’avec ça j’ai tout ce qu’il me faut, plus qu’a expérimenter maintenant, et vue mon niveau en prog, c’est les erreurs qui me feront progresser 😉
Merci à toi pour le travail accomplie.
PS: J’ai réussi à retrouver le câblage de mon écran exotique hier soir et ai affiché un beau « Hello Word » ça commence bien ! (c’est tellement satisfaisant quand au bout d’un moment on y arrive 😉 )
Salut Mickaël !
Merci, et surtout, bravo à toi ! Car tu persévères, et c’est bien là le principal ! Parce qu’on ne réussira jamais tout du premier coup. Il faut donc savoir persévérer, et surtout ne rien lâcher, ni tout abandonner au premier petit écueil.
En tout cas félicitation, car tu as vraiment le bon état d’esprit ! D’autant plus que tu as déjà pu constater par toi même que les erreurs ne sont pas forcément des échecs, mais simplement des choses qui nous font progresser ! Alors encore bravo à toi !
Jérôme.
Bonjour,
Merci pour cet article.
Je viens de remettre en état un compteur de fréquences Schneider à tubes nixies des années 1970 presque aussi vieux que moi.
Je n’avais pas générateur BF sous la main pour commencer des tests. En voici un réalisé en une minute. Parfait.
Cordialement.
Hey, super ! Comme quoi, on peut se dépatouiller avec peu de choses, des fois 😉
Bon courage !
Jérôme.
Bonjour,
Merci pour ton article. Je suis tombé dessus en faisant des recherche pour fabriquer une générateur un GBF avec un arduino.
Je ne sais pas si c’est le meilleur endroit pour poser des questions mais j’en profite.
Est-il possible de générer une signal sinusoïdale à fréquence variable avec un arduino et l’ultime question est-ce qu’il existe un module ou composant électronique additionné à l’arduino qui permette de générer un signal sinusoïdale non pas en 0-5V mais en +5V -5V ?
Bonne continuation et encore bravo.
Salut Aurélien !
Alors oui, c’est parfaitement possible ! Par contre :
Pour générer une onde sinusoïdale avec un Arduino, il y a 3 façons de faire qui me viennent spontanément à l’esprit :
À noter qu’il existe encore bien d’autres façons de faire, avec des circuits spécialisés !
Du reste, pour transformer un signal 0-5V en -5/+5V, cela se fait très simplement, un condensateur en série sur la sortie (pour « naturellement » transformer le 0/5V en -2,5/+2,5V), et un ampli op (pour amplifier le signal).
Je pense que je ferais des tuto détaillés sur toutes ces solutions un jour, dès que j’aurais du temps (là malheureusement, ce n’est pas le cas !).
Voilà, a+
Jérôme.
Merci encore
Tout fonctionne très bien, des beaux signaux carrés, commandés par le moniteur série. « Carrément » un générateur de signaux caché dan l’Arduino Uno ! Sur mon oscilloscope les signaux paraissent beaux.
Je découvre !
Bravo merci
Héhé ! Mais j’irais quand même pas jusqu’à dire qu’on pourrait en faire un « vrai » générateur de signaux, tant on est limité au niveau fréquentiel. En tout cas, on peut effectivement faire de belles choses avec un Arduino, comme ici, en générant de simples signaux carrés 😉
Excellente journée à toi !
Jérôme.
Afin de filtrer au maximum les messages de type "spam" ou "inappropriés", chaque commentaire est soumis à modération, et validé manuellement. Du coup, il se peut que certains commentaires ne soient pas publiés, ou sinon, avec un peu de retard. Par ailleurs, j'ai malheureusement plus de messages à traiter que de temps pour y répondre ; c'est pourquoi je ne pourrais pas répondre à tout le monde. Désolé …