Comment générer une tension analogique ?
NIVEAU 1
Objectifs
- Configurer et utiliser une broche de la carte Nucléo en sortie analogique
- Générer un signal analogique avec une carte Nucléo
Pré-requis
Convertisseur numérique/analogique
Sur les systèmes numériques, et les microcontrôleurs en particulier, les broches sont naturellement des entrées/sorties numériques.
Or, certains actionneurs doivent être pilotés à l’aide de grandeurs analogiques. Pour palier à ce manque, la plupart des fabricants de microcontrôleurs ont intégré des convertisseurs numériques-analogiques (DAC – Digital to Analog Converter) à leur système, afin d’éviter de passer par des composants externes.
Les cartes Nucleo L476RG possèdent 1 convertisseur numérique-analogique de 12 bits (c’est à dire convertissant des tensions allant de 0 à 3.3V sur 4096 niveaux différents). Chacun de ses convertisseurs peut restituer un signal jusqu’à 5 MHz.
Sorties analogiques
Le convertisseur présent sur ces cartes possède deux sorties indépendantes (notées AnalogOut sur les connecteurs de la carte).
Déclaration
L’ensemble des classes et des fonctions permettant de réaliser des opérations sur les entrées/sorties de la carte se trouvent dans la bibliothèque “mbed.h” (ou “mbed-os.h”). Il est donc indispensable d’avoir au préalable inclut cette bibliothèque :
#include "mbed.h"
La première étape est la déclaration au compilateur de cette broche en sortie analogique. Il faut la placer après la déclaration des ressources externes (bibliothèques) et avant toute définition de fonction (voir tutoriel Tester ma première application sur Nucléo – Code d’exemple /* Déclaration des entrées/sorties */).
AnalogOut ma_sortie(PA_5);
Dans l’exemple précédent, on configure la broche PA_5 (du connecteur Morpho) en sortie analogique (ou analog en anglais). Dans ce code, il y a trois notions importantes :
- ‘AnalogOut‘, la classe qui définit le type de la broche, ici une sortie analogique
- ‘ma_sortie‘, le nom de la variable qui va être associée au port donné dans les parenthèses
- ‘PA_5‘, le nom de la broche que l’on va associer sur le composant
Utilisation
L’intérêt d’utiliser une sortie analogique est de pouvoir imposer un potentiel analogique vers l’extérieur depuis son programme.
Il existe deux façons différentes de définir la valeur de la sortie analogique :
- la fonction void write(float val) qui représente la tension de sortie par un nombre réel compris entre 0.0 et 1.0 (représentant un pourcentage de la tension réellement appliquée sur la sortie analogique entre 0 et 3.3V)
- la fonction void write_u16(int val) qui convertit un entier non signé sur 16 bits passé en paramètre vers une tension entre 0 et 3.3V
Ecriture avec write(double val)
Dans l’exemple ci-dessous, nous utilisons la conversion à l’aide de la fonction write(float val) qui prend en paramètre un nombre réel compris entre 0 et 1.
#include "mbed.h" AnalogOut analog_out(PA_4); int main() { double value=0.00; while(1) { value = value + 0.10; wait_us(1); if(value>=1.00) { value =0.00; } analog_out.write(value); } }
Dans l’exemple précédent, on incrémente, toutes les microsecondes, la valeur de la variable value par pas de 0.1, de 0 à 1 (puis on la refait passer à 0). Cette variable est ensuite passée en paramètre de la fonction write qui l’envoie sur la sortie analogique nommée out (sur la broche PA_4 du composant).
On obtient alors une rampe de tension en sortie (l’échelle des temps est en microsecondes) :
La tension de sortie vaut alors : \(V_S = value \cdot 3.3 V\).
Ecriture avec write_u16(int val)
Dans l’exemple ci-dessous, nous utilisons la conversion à l’aide de la fonction write_u16(int val) qui prend en paramètre un nombre entier compris entre 0 et 65535.
#include "mbed.h" AnalogOut analog_out(PA_4); int main() { unsigned short value=0; while(1) { value = value + 0.1 * 65535; wait_us(1); if(value>=65535) { value =0; } analog_out.write_u16(value); } }
Dans l’exemple précédent, on incrémente, toutes les microsecondes, la valeur de la variable value par pas de \((0.1 \cdot 65535)\), de 0 à 65535 (puis on la refait passer à 0) – N.B. \(65535 = 2^{16} – 1\). Cette variable est ensuite passée en paramètre de la fonction write_u16 qui l’envoie sur la sortie analogique nommée out (sur la broche PA_4 du composant).
Attention La valeur à transmettre est un nombre entier sur 16 bits. Or le convertisseur numérique-analogique attend une information binaire sur 12 bits. Le paramètre attendu dans la fonction write_u16(int val) est une valeur de 12 bits justifiée à gauche sur un entier de 16 bits.
On peut représenter l’information de la façon suivante :
Entre la valeur sur 12 bits attendue par le DAC et la valeur à transmettre à la fonction write_u16, il faut effectuer un décalage de l’information de 4 bits vers la gauche :
int valeur_DAC = 200; // valeur entre 0 et 4095 (12 bits) int valeur_transmise = valeur_DAC << 4; out.write_u16(valeur_transmise);
Tester une sortie analogique
Pour pouvoir tester une sortie analogique, il suffit de connecter un oscilloscope entre la masse et la sortie analogique configurée.
Exemple de génération d’un signal sinusoïdal
Dans l’exemple suivant, on génère un signal sinusoïdal sur 64 points par période, centré autour de 1.65V :
#include "mbed.h" /* Tableau contenant les points du sinus */ const int sinu[64] = { 2048, 2149, 2250, 2349, 2445, 2537, 2624, 2706, 2781, 2848, 2908, 2959, 3001, 3033, 3056, 3069, 3071, 3064, 3046, 3018, 2981, 2934, 2879, 2815, 2744, 2666, 2581, 2492, 2398, 2300, 2200, 2099, 1996, 1895, 1795, 1697, 1603, 1514, 1429, 1351, 1280, 1216, 1161, 1114, 1077, 1049, 1031, 1024, 1026, 1039, 1062, 1094, 1136, 1187, 1247, 1314, 1389, 1471, 1558, 1650, 1746, 1845, 1946, 2048}; /* Déclaration de la sortie analogique */ AnalogOut analog_out(PA_4); /* Programme principal */ void main(void){ int i = 0; while(1){ for(i = 0; i < 64; i++){ analog_out.write(sinu[i]/4096.0); wait_ms(10); } } }
On obtient sur l’oscilloscope (échelle des temps en secondes, échelle des tensions en V) :
Amélioration possible
Dans l’exemple précédent, le signal est généré dans le boucle principale du programme. Il se peut que vous ajoutiez des étapes de calcul supplémentaires dans cette boucle, ce qui aura pour conséquence d’augmenter le temps d’exécution de cette boucle et donc de modifier la fréquence à laquelle vous allez générer le signal “périodique”.
De plus, si vous incluez dans cette boucle des étapes de calcul facultatives (par l’intermédiaire de structures conditionnelles – if / while…), ce temps de calcul peut ne plus être constant… Ainsi votre signal ne sera plus périodique et chacun des échantillons pourront être présents en sortie sur des délais différents – donc une période d’échantillonnage non constante (que dit Monsieur Fourrier à ce propos ? sans parler de Messieurs Nyquist et Shannon…)
Pour améliorer ceci, il est donc indispensable de réaliser cette étape de génération du signal à l’intérieur d’une fonction d’interruption qui sera appelée de manière régulière. On peut pour cela
Filtre de reconstruction
Afin de lisser la sortie échantillonnée du convertisseur numérique-analogique, on ajoute un filtre passe-bas de fréquence de coupure un peu inférieure à la fréquence de restitution des données. On parle d’un filtre de reconstruction.
On peut utiliser un simple filtre d’ordre 1, type RC. On obtient alors le signal suivant :
Remarque Il est également possible d’utiliser des filtres intégrés d’ordre plus grand, mais nécessitant souvent un signal d’horloge pour les piloter (filtres à capacités commutées – MF4 ou MAX292/296).
Générateur de signaux périodiques
Il est alors possible de générer des signaux périodiques à l’aide d’un microcontroleur. Pour cela, on peut utiliser la structure suivante (déjà utilisée dans l’exemple précédent) :
Tutoriel lié
MInE Prototyper Prototyper avec Nucleo et MBED