Girino - Oscilloscope Arduino rapide

Je suis physicien et le plus beau travail dans ce domaine, c'est que je peux construire mes propres instruments. Avec cette façon de penser, j'ai décidé de construire un oscilloscope Arduino homebrew. Cet instructable a été écrit dans le but d'enseigner un peu les microcontrôleurs et l'acquisition de données. C'est un projet extrême car je voulais faire sortir d'Arduino autant de vitesse que possible, je n'ai vu aucun autre oscilloscope Arduino aussi rapide que celui-ci.

Il y a quelque temps, je travaillais sur un projet Arduino et j'avais besoin de voir si le signal de sortie était conforme aux spécificités. J'ai donc passé un peu de temps sur Internet à chercher des oscilloscopes Arduino déjà implémentés, mais je n'ai pas aimé ce que j'ai trouvé. Les projets que j'ai trouvés étaient principalement composés d'une interface utilisateur graphique pour l'ordinateur écrite en traitement et d'un croquis arduino très simple. Les croquis ressemblaient à: void setup () {

Serial.begin (9600);

}

boucle vide () {

int val = analogRead (ANALOG_IN);

Serial.println (val);

} Cette approche n'est pas fausse et je ne veux insulter personne, mais c'est trop lent pour moi. Le port série est lent et l'envoi de chaque résultat d'un analogRead () à travers lui est un goulot d'étranglement.

J'étudie les numériseurs de forme d'onde depuis un certain temps et je sais assez bien comment ils fonctionnent, alors je me suis inspiré d'eux. Ce sont les points de départ de l'oscilloscope que j'ai voulu créer:

  • le signal entrant doit être découplé de l'arduino pour le conserver;
  • avec un décalage du signal, il est possible de voir des signaux négatifs;
  • les données doivent être mises en mémoire tampon;
  • un déclencheur matériel est nécessaire pour capter les signaux;
  • un tampon circulaire peut donner la forme du signal avant le déclenchement (plus à suivre sur ce point);
  • en utilisant des fonctions de levier inférieures que celles standard, le programme s'exécute plus rapidement.

Le croquis de l'Arduino est attaché à cette étape, ainsi que le schéma du circuit que j'ai fait.

Le nom que j'ai trouvé, Girino, est un jeu de mots frivole en italien. Giro signifie rotation et en ajoutant le suffixe -ino, vous obtenez une petite rotation, mais Girino signifie également têtard . De cette façon, j'ai obtenu un nom et une mascotte.

Étape 1: Avis de non-responsabilité

L'AUTEUR DE CET INSTRUCTABLE N'OFFRE AUCUNE GARANTIE DE VALIDITÉ ET AUCUNE GARANTIE QUE CE SOIT .

L'électronique peut être dangereuse si vous ne savez pas ce que vous faites et que l'auteur ne peut garantir la validité des informations trouvées ici. Ce n'est pas un conseil professionnel et tout ce qui est écrit dans ce document peut être inexact, trompeur, dangereux ou erroné. Ne vous fiez pas aux informations trouvées ici sans vérification indépendante.

Il vous appartient de vérifier toute information et de vérifier à nouveau que vous ne vous exposez pas, vous ou quiconque, à des dommages ou que vous exposez quoi que ce soit à des dommages; Je ne prends aucune responsabilité. Vous devez suivre par vous-même les précautions de sécurité appropriées, si vous souhaitez reproduire ce projet.

Utilisez ce guide à vos risques et périls!

Étape 2: ce dont vous avez besoin

Ce dont nous avons vraiment besoin pour ce projet est une carte Arduino et la fiche technique de l'ATMega328P.
La fiche technique est ce qui nous dit comment fonctionne le microcontrôleur et il est très important de le conserver si nous voulons un levier de contrôle plus bas.

La fiche technique peut être trouvée ici: //www.atmel.com/Images/doc8271.pdf

Le matériel que j'ai ajouté à l'Arduino est en partie nécessaire, son but est simplement de former le signal pour l'ADC et de fournir un niveau de tension pour le déclencheur. Si vous le souhaitez, vous pouvez envoyer le signal directement à l'Arduino et utiliser une référence de tension définie par un diviseur de tension, ou même le 3, 3 V fourni par l'Arduino lui-même.

Étape 3: sortie de débogage

J'ai l'habitude de mettre beaucoup de sortie de débogage dans mes programmes parce que je veux garder une trace de tout ce qui se passe; le problème avec Arduino est que nous n'avons pas de sortie standard pour écrire. J'ai décidé d'utiliser le port série comme sortie standard.

Sachez cependant que cette approche ne fonctionne pas tout le temps! Parce que l'écriture sur le port série nécessite un certain temps pour l'exécution et cela peut changer considérablement les choses au cours de certaines routines sensibles au temps.

Je définis généralement les sorties de débogage dans une macro de préprocesseur, donc lorsque le débogage est désactivé, elles disparaissent simplement du programme et ne ralentissent pas l'exécution:
  • dprint (x); - Écrit sur le port série quelque chose comme: # x: 123
  • dshow ("Une chaîne"); - Écrit la chaîne

Voici la définition:

#if DEBUG == 1
#define dprint (expression) Serial.print ("#"); Serial.print (#expression); Serial.print (":"); Serial.println (expression)
#define dshow (expression) Serial.println (expression)
#autre
#define dprint (expression)
#define dshow (expression)
#fin si

Étape 4: définition des bits de registre

Dans le but d'être rapide, il est nécessaire de manipuler les fonctionnalités du microcontrôleur avec des fonctions de levier inférieures à celles standard fournies par l'IDE Arduino. Les fonctions internes sont gérées par le biais de certains registres, qui sont des collections de huit bits où chacun régit quelque chose de particulier. Chaque registre contient huit bits car l'ATMega328P a une architecture à 8 bits.

Les registres ont des noms qui sont spécifiés dans la fiche technique en fonction de leur signification, comme ADCSRA pour le registre de configuration ADC A. De plus, chaque bit significatif des registres a un nom, comme ADEN pour le bit d'activation ADC dans le registre ADCSRA.

Pour définir leurs bits, nous pourrions utiliser la syntaxe C habituelle pour l'algèbre binaire, mais j'ai trouvé sur Internet quelques macros qui sont très belles et propres:

// Définit la définition et la suppression des bits de registre
#ifndef cbi
#define cbi (sfr, bit) (_SFR_BYTE (sfr) & = ~ _BV (bit))
#fin si
#ifndef sbi
#define sbi (sfr, bit) (_SFR_BYTE (sfr) | = _BV (bit))
#fin si

Leur utilisation est très simple, si nous voulons mettre à 1 le bit d'activation de l'ADC, nous pouvons simplement écrire:

sbi (ADCSRA, ADEN);

Alors que si nous voulons le mettre à 0 ( idest clear it), nous pouvons simplement écrire:

cbi (ADCSRA, ADEN);

Étape 5: Quelles sont les interruptions

Comme nous le verrons dans les prochaines étapes, l'utilisation d'interruptions est requise dans ce projet. Les interruptions sont des signaux qui indiquent au microcontrôleur d' arrêter l'exécution de la boucle principale et de la transmettre à certaines fonctions spéciales. Les images donnent une idée du déroulement du programme.

Les fonctions qui sont exécutées sont appelées routines de service d'interruption (ISR) et sont des fonctions plus ou moins simples, mais qui ne prennent pas d'arguments.

Voyons un exemple, quelque chose comme compter quelques impulsions. L'ATMega328P possède un comparateur analogique auquel est associée une interruption qui est activée lorsqu'un signal dépasse une tension de référence. Vous devez tout d'abord définir la fonction qui sera exécutée:

ISR (ANALOG_COMP_vect)
{
counter ++;
}

C'est vraiment simple, l'instruction ISR () est une macro qui indique au compilateur que la fonction suivante est une routine de service d'interruption. Tandis que ANALOG_COMP_vect est appelé vecteur d'interruption et indique au compilateur quelle interruption est associée à cette routine. Dans ce cas, il s'agit de l'interruption du comparateur analogique. Donc, chaque fois que le comparateur voit un signal plus grand qu'une référence, il dit au microcontrôleur d'exécuter ce code, id dans ce cas pour incrémenter cette variable.

L'étape suivante consiste à activer l'interruption associée. Pour l'activer, nous devons définir le bit ACIE (Analog Comparator Interrupt Enable) du registre ACSR (Analog Comparator Setting Register):

sbi (ACSR, ACIE);

Dans le site suivant, nous pouvons voir la liste de tous les vecteurs d'interruption:
//www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html

Étape 6: Acquérir en continu avec un tampon circulaire

Le concept d'utilisation d'un tampon circulaire est assez simple:

Acquérir en continu jusqu'à ce qu'un signal soit trouvé, puis envoyer le signal numérisé à l'ordinateur.

Cette approche permet d'avoir la forme du signal entrant également avant l'événement déclencheur.


J'ai préparé quelques diagrammes pour être clair. Les points suivants se réfèrent aux images.
  • Sur la première image, nous pouvons voir ce que je veux dire par acquisition continue . Nous définissons un tampon qui stockera les données, dans mon cas un tableau avec 1280 emplacements, puis nous commençons à lire en continu le registre de sortie ADC (ADCH) et à remplir le tampon avec les données. Lorsque nous arrivons à la fin du tampon, nous recommençons à partir du début sans l'effacer. Si nous imaginons le tableau disposé de manière circulaire, il est facile de voir ce que je veux dire.
  • Lorsque le signal dépasse le seuil, l'interruption du comparateur analogique est activée. Ensuite, nous entamons une phase d'attente au cours de laquelle nous continuons à acquérir le signal mais gardons un décompte des cycles ADC passés depuis l'interruption du comparateur analogique.
  • Lorsque nous avons attendu N cycles (avec N <1280), nous gelons la situation et arrêtons les cycles ADC. On se retrouve donc avec un tampon rempli de numérisation de la forme temporelle du signal. La grande partie de cela, c'est que nous avons également la forme avant l'événement déclencheur, car nous avions déjà acquis avant cela.
  • Maintenant, nous pouvons envoyer le tampon entier au port série dans un bloc de données binaires, au lieu d'envoyer les lectures ADC uniques. Cela a réduit les frais généraux requis pour envoyer les données et le goulot d'étranglement des croquis que j'ai trouvés sur Internet.

Étape 7: Déclenchement de l'oscilloscope

Un oscilloscope montre sur son écran un signal, sur lequel nous sommes tous d'accord, mais comment peut-il le montrer de façon constante et ne pas le faire sauter autour de l'écran? Il dispose d'un déclencheur interne capable d'afficher le signal toujours à la même position de l'écran (ou au moins la plupart du temps), créant l'illusion d'un tracé stable.

Le déclencheur est associé à un seuil qui active un balayage lorsque le signal le passe. Un balayage est la phase dans laquelle l'oscilloscope enregistre et affiche le signal. Après un balayage, une autre phase se produit: le holdoff, dans lequel l'oscilloscope rejette tout signal entrant. La période d'attente peut être composée d'une partie du temps mort, pendant laquelle l'oscilloscope ne peut accepter aucun signal, et d'une partie qui peut être sélectionnée par l'utilisateur. Le temps mort peut être causé par diverses raisons, comme le fait de devoir dessiner sur l'écran ou de stocker les données quelque part.

En regardant l'image, nous avons une idée de ce qui se passe.
  1. Le signal 1 dépasse le seuil et active le balayage;
  2. le signal 2 est à l'intérieur du temps de balayage et se rattrape avec le premier;
  3. après le holdoff, le signal 3 active à nouveau le balayage;
  4. au lieu de cela, le signal 4 est rejeté car il tombe à l'intérieur de la région de retenue.
La raison d'être de la phase de blocage est d'empêcher certains signaux indésirables de pénétrer dans la zone de balayage. Il est un peu long d'expliquer ce point et il échappe au but de cet instructable.

La morale de cette histoire est que nous avons besoin de:
  1. un niveau de seuil auquel nous pouvons comparer le signal entrant;
  2. un signal qui indique au microcontrôleur de démarrer la phase d'attente (voir étape précédente).
Nous avons plusieurs solutions possibles pour le point 1.:
  • en utilisant un trimmer, nous pouvons régler manuellement un niveau de tension;
  • en utilisant le PWM de l'Arduino, nous pouvons régler le niveau par logiciel;
  • en utilisant le 3, 3 V fourni par l'Arduino lui-même;
  • en utilisant la référence bangap interne, nous pouvons utiliser un niveau fixe.
Pour le point 2. nous avons la bonne solution: nous pouvons utiliser l'interruption du comparateur analogique interne du microcontrôleur.

Étape 8: Fonctionnement de l'ADC

Le microcontrôleur Arduino dispose d'un seul ADC d'approximation successive de 10 bits. Avant l'ADC, il y a un multiplexeur analogique qui nous permet d'envoyer à l'ADC les signaux provenant de différentes broches et sources (mais une seule à la fois).

ADC d'approximation successive signifie que l'ADC prend 13 cycles d'horloge pour terminer la conversion (et 25 cycles d'horloge pour la première conversion). Il y a un signal d'horloge dédié à l'ADC qui est "calculé" à partir de l'horloge principale de l'Arduino; c'est parce que l'ADC est un peu lent et ne peut pas suivre le rythme des autres parties du microcontrôleur. Il nécessite une fréquence d'horloge d'entrée comprise entre 50 kHz et 200 kHz pour obtenir une résolution maximale. Si une résolution inférieure à 10 bits est nécessaire, la fréquence d'horloge d'entrée vers l'ADC peut être supérieure à 200 kHz pour obtenir une fréquence d'échantillonnage plus élevée.

Mais combien de taux plus élevés pouvons-nous utiliser? Il y a quelques bons guides sur l'ADC aux Open Music Labs que je suggère de lire:
  • //www.openmusiclabs.com/learning/digital/atmega-adc/
  • //www.openmusiclabs.com/learning/digital/atmega-adc/in-depth/
Puisque mon but est d'obtenir un oscilloscope rapide, j'ai décidé de limiter la précision à 8 bits. Cela a plusieurs bonus:
  1. le tampon de données peut stocker plus de données;
  2. vous ne perdez pas 6 bits de RAM par donnée;
  3. l'ADC peut acquérir plus rapidement.
Le prédéfinisseur nous permet de diviser la fréquence, par certains facteurs, en définissant les bits ADPS0-1-2 du registre ADCSRA. En voyant l'intrigue de la précision de l'article Open Music Labs, nous pouvons voir que pour une précision de 8 bits, la fréquence pourrait aller jusqu'à 1, 5 MHz, c'est bien! Mais comme la possibilité de modifier le facteur de mise à l'échelle nous permet de modifier le taux d'acquisition, nous pouvons également l'utiliser pour modifier l'échelle de temps de l'oscilloscope.

Les registres de sortie présentent une bonne caractéristique: nous pouvons décider du réglage des bits de conversion, en réglant le bit ADLAR dans le registre ADMUX. Si c'est 0, ils sont ajustés à droite et vice - versa (voir l'image). Comme je voulais une précision de 8 bits, je l'ai mis à 1 pour pouvoir lire uniquement le registre ADCH et ignorer l'ADCL.

J'ai décidé de n'avoir qu'un seul canal d'entrée pour éviter d'avoir à changer de canal à chaque conversion.

Une dernière chose à propos de l'ADC, il a différents modes de fonctionnement, chacun avec une source de déclenchement différente:
  • Mode de course libre
  • Comparateur analogique
  • Demande d'interruption externe 0
  • Minuterie / compteur0 Comparer la correspondance A
  • Timer / Counter0 Overflow
  • Minuterie / compteur1 Comparer le match B
  • Timer / Counter1 Overflow
  • Événement de capture Timer / Counter1
Je m'intéressais au mode de fonctionnement libre qui est un mode dans lequel l'ADC convertit en continu l'entrée et déclenche une interruption à la fin de chaque conversion (vecteur associé: ADC_vect).

Étape 9: Tampons d'entrée numérique

Les broches d'entrée analogique de l'Arduino peuvent également être utilisées comme broches d'E / S numériques, elles ont donc un tampon d'entrée pour les fonctions numériques. Si nous voulons les utiliser comme broches analogiques, vous devez désactiver cette fonction.

L'envoi d'un signal analogique à une broche numérique le fait basculer entre les états HIGH et LOW, surtout si le signal est proche de la frontière entre les deux états; ce basculement induit du bruit dans les circuits proches comme l'ADC lui-même (et induit une consommation d'énergie plus élevée).

Pour désactiver le tampon numérique, nous devons définir les bits ADCnD du registre DIDR0:

sbi (DIDR0, ADC5D);
sbi (DIDR0, ADC4D);
sbi (DIDR0, ADC3D);
sbi (DIDR0, ADC2D);
sbi (DIDR0, ADC1D);
sbi (DIDR0, ADC0D);

Étape 10: Configuration de l'ADC

Dans le croquis, j'ai écrit une fonction d'initialisation qui définit tous les paramètres du fonctionnement de l'ADC. Comme j'ai tendance à écrire du code propre et commenté, je vais juste passer la fonction ici. On peut se référer à l'étape précédente et aux commentaires pour la signification des registres. void initADC (void)
= (ADCPIN & 0x07);

// ------------------------------------------------ ---------------------
// Paramètres ADCSRA
// ------------------------------------------------ ---------------------
// L'écriture de ce bit sur un active le CAN. En l'écrivant à zéro, le
// ADC est désactivé. Désactivation de l'ADC pendant une conversion
// progress, mettra fin à cette conversion.
cbi (ADCSRA, ADEN);
// En mode de conversion unique, écrivez ce bit à un pour commencer chaque
// conversion. En mode Free Running, écrivez ce bit à un pour démarrer le
// première conversion. La première conversion après l'écriture d'ADSC
// après que l'ADC a été activé, ou si ADSC est écrit en même temps
// heure à laquelle l'ADC est activé, prendra 25 cycles d'horloge ADC au lieu de
// le normal 13. Cette première conversion effectue l'initialisation du
// ADC. ADSC lira aussi longtemps qu'une conversion est en cours.
// Une fois la conversion terminée, elle revient à zéro. Écriture de zéro dans
// ce bit n'a aucun effet.
cbi (ADCSRA, ADSC);
// Lorsque ce bit est écrit sur un, le déclenchement automatique de l'ADC est
// activée. L'ADC commencera une conversion sur un bord positif de la
// signal de déclenchement sélectionné. La source de déclenchement est sélectionnée en définissant
// les bits ADC Trigger Select, ADTS dans ADCSRB.
sbi (ADCSRA, ADATE);
// Lorsque ce bit est écrit dans un et que le bit I dans SREG est défini, le
// L'interruption de conversion ADC est activée.
sbi (ADCSRA, ADIE);
// Ces bits déterminent le facteur de division entre l'horloge système
// fréquence et l'horloge d'entrée dans l'ADC.
// ADPS2 ADPS1 ADPS0 Facteur de division
// 0 0 0 2
// 0 0 1 2
// 0 1 0 4
// 0 1 1 8
// 1 0 0 16
// 1 0 1 32
// 1 1 0 64
// 1 1 1 128
sbi (ADCSRA, ADPS2);
sbi (ADCSRA, ADPS1);
sbi (ADCSRA, ADPS0);

// ------------------------------------------------ ---------------------
// Paramètres ADCSRB
// ------------------------------------------------ ---------------------
// Lorsque ce bit est écrit en logique un et que l'ADC est éteint
// (ADEN dans ADCSRA est nul), le multiplexeur ADC sélectionne le négatif
// entrée dans le comparateur analogique. Lorsque ce bit est écrit zéro logique,
// AIN1 est appliqué à l'entrée négative du comparateur analogique.
cbi (ADCSRB, ACME);
// Si ADATE dans ADCSRA est écrit sur un, la valeur de ces bits
// sélectionne la source qui déclenchera une conversion ADC. Si ADATE est
// effacé, les paramètres ADTS2: 0 n'auront aucun effet. Une conversion
// être déclenché par le front montant du drapeau d'interruption sélectionné. Remarque
// ce passage d'une source de déclenchement effacée à un déclencheur
// la source qui est définie, générera un front positif sur le déclencheur
// signal. Si ADEN dans ADCSRA est défini, cela lancera une conversion.
// Le passage en mode Free Running (ADTS [2: 0] = 0) ne provoquera pas de
// déclenche un événement, même si l'indicateur d'interruption ADC est défini.
// ADTS2 ADTS1 ADTS0 Source de déclenchement
// 0 0 0 Mode Free Running
// 0 0 1 Comparateur analogique
// 0 1 0 Demande d'interruption externe 0
// 0 1 1 Minuterie / compteur0 Comparer la correspondance A
// 1 0 0 Minuterie / compteur0 Débordement
// 1 0 1 Minuterie / compteur1 Comparer la correspondance B
// 1 1 0 Timer / Counter1 Overflow
// 1 1 1 Timer / Counter1 Capture Event
cbi (ADCSRB, ADTS2);
cbi (ADCSRB, ADTS1);
cbi (ADCSRB, ADTS0);

// ------------------------------------------------ ---------------------
// Paramètres DIDR0
// ------------------------------------------------ ---------------------
// Lorsque ce bit est écrit en logique un, le tampon d'entrée numérique sur le
// la broche ADC correspondante est désactivée. Le registre PIN correspondant
// bit sera toujours lu comme zéro lorsque ce bit est défini. Quand un analogique
// le signal est appliqué à la broche ADC5..0 et l'entrée numérique de ce
// la broche n'est pas nécessaire, ce bit doit être écrit en logique un pour réduire
// consommation d'énergie dans le tampon d'entrée numérique.
// Notez que les broches ADC ADC7 et ADC6 n'ont pas de tampons d'entrée numérique,
// et ne nécessitent donc pas de bits de désactivation d'entrée numérique.
sbi (DIDR0, ADC5D);
sbi (DIDR0, ADC4D);
sbi (DIDR0, ADC3D);
sbi (DIDR0, ADC2D);
sbi (DIDR0, ADC1D);
sbi (DIDR0, ADC0D);

Étape 11: Fonctionnement du comparateur analogique

Le comparateur analogique est un module interne du microcontrôleur et il compare les valeurs d'entrée sur la broche positive (broche numérique 6) et la broche négative (broche numérique 7). Lorsque la tension sur la broche positive est supérieure à la tension sur la broche négative AIN1, le comparateur analogique émet un 1 dans le bit ACO du registre ACSR.

En option, le comparateur peut déclencher une interruption, exclusive au comparateur analogique. Le vecteur associé est ANALOG_COMP_vect.

On peut aussi paramétrer l'interruption à lancer sur front montant, front descendant ou sur bascule d'état.

Le comparateur analogique est exactement ce dont nous avons besoin pour le déclenchement de la connexion du signal d'entrée à la broche 6, maintenant ce qui reste est un niveau de seuil sur la broche 7.

Étape 12: Configuration du comparateur analogique

Dans l'esquisse, j'ai écrit une autre fonction d'initialisation qui définit tous les paramètres du fonctionnement du comparateur analogique. Le même problème concernant les tampons numériques ADC s'applique au comparateur analogique, comme nous pouvons le voir au bas de la routine.

void initAnalogComparator (void)
{
// ------------------------------------------------ ---------------------
// Paramètres ACSR
// ------------------------------------------------ ---------------------
// Lorsque ce bit est écrit en logique un, l'alimentation de l'analogique
// Le comparateur est désactivé. Ce bit peut être réglé à tout moment pour tourner
// hors du comparateur analogique. Cela réduira la consommation d'énergie
// Mode actif et inactif. Lors de la modification du bit ACD, l'analogique
// L'interruption du comparateur doit être désactivée en effaçant le bit ACIE dans
// ACSR. Sinon, une interruption peut se produire lorsque le bit est modifié.
cbi (ACSR, ACD);
// Lorsque ce bit est activé, une tension de référence de bande interdite fixe remplace le
// entrée positive au comparateur analogique. Lorsque ce bit est effacé,
// AIN0 est appliqué à l'entrée positive du comparateur analogique. Quand
// la référence de bande interdite est utilisée comme entrée pour le comparateur analogique, elle
// prendra un certain temps pour que la tension se stabilise. Si non
// stabilisé, la première conversion peut donner une mauvaise valeur.
cbi (ACSR, ACBG);
// Lorsque le bit ACIE est écrit en logique un et le bit I dans le statut
// Le registre est défini, l'interruption du comparateur analogique est activée.
// Lorsque la logique écrite est nulle, l'interruption est désactivée.
cbi (ACSR, ACIE);
// Lorsqu'il est écrit en logique un, ce bit active la fonction de capture d'entrée
// dans Timer / Counter1 à déclencher par le comparateur analogique. le
// la sortie du comparateur est dans ce cas directement connectée à l'entrée
// capture la logique frontale, faisant utiliser le comparateur au bruit
// Fonctions d'annulation et de sélection de front de l'entrée Timer / Counter1
// Capture interruption. Lorsque la logique écrite est nulle, aucune connexion entre
// le comparateur analogique et la fonction de capture d'entrée existent. À
// oblige le comparateur à déclencher la capture d'entrée Timer / Counter1
// interruption, le bit ICIE1 dans le registre de masque d'interruption du temporisateur
// (TIMSK1) doit être défini.
cbi (ACSR, ACIC);
// Ces bits déterminent quels événements de comparateur qui déclenchent l'analogique
// Interruption du comparateur.
// Mode ACIS1 ACIS0
// 0 0 Basculer
// 0 1 Réservé
// 1 0 Front descendant
// 1 1 Front montant
sbi (ACSR, ACIS1);
sbi (ACSR, ACIS0);

// ------------------------------------------------ ---------------------
// Paramètres DIDR1
// ------------------------------------------------ ---------------------
// Lorsque ce bit est écrit en logique un, le tampon d'entrée numérique sur le
// La broche AIN1 / 0 est désactivée. Le bit de registre PIN correspondant sera
// toujours lu comme zéro lorsque ce bit est positionné. Lorsqu'un signal analogique est
// appliqué à la broche AIN1 / 0 et l'entrée numérique de cette broche n'est pas
// nécessaire, ce bit doit être écrit en logique pour réduire la puissance
// consommation dans le tampon d'entrée numérique.
sbi (DIDR1, AIN1D);
sbi (DIDR1, AIN0D);
}

Étape 13: seuil

Rappelant ce que nous avons dit sur le déclencheur, nous pouvons implémenter ces deux solutions pour le seuil:
  • en utilisant un trimmer, nous pouvons régler manuellement un niveau de tension;
  • en utilisant le PWM de l'Arduino, nous pouvons régler le niveau par logiciel.
Sur l'image, nous pouvons voir l'implémentation matérielle du seuil dans les deux chemins.

Pour la sélection manuelle, un potentiomètre multi-tours placé entre +5 V et GND suffit.

Alors que pour la sélection de logiciels, nous avons besoin d'un filtre passe-bas qui filtre un signal PWM provenant de l'Arduino. Les signaux PWM (plus de détails à suivre) sont des signaux carrés avec une fréquence constante mais une largeur d'impulsion variable. Cette variabilité apporte une valeur moyenne variable du signal qui peut être extraite avec un filtre passe-bas. Une bonne fréquence de coupure pour le filtre est d'environ un centième de la fréquence PWM et j'ai choisi environ 560 Hz.

Après les deux sources de seuil, j'ai inséré quelques broches qui permettent de sélectionner, avec un cavalier, quelle source je voulais. Après la sélection, j'ai également ajouté un émetteur suiveur pour découpler les sources de la broche Arduino.

Étape 14: Fonctionnement de la modulation de largeur d'impulsion

Comme indiqué précédemment, un signal de modulation de largeur d'impulsion (PWM) est un signal carré à fréquence fixe mais à largeur variable. Sur l'image, nous voyons un exemple. Sur chaque ligne, il y a l'un de ces signaux avec un rapport cyclique différent ( id est la partie de période pendant laquelle le signal est élevé). En prenant le signal moyen sur une période, on obtient la ligne rouge qui correspond au rapport cyclique par rapport au maximum du signal.

Electroniquement, "prendre la moyenne d'un signal" peut se traduire par "le passer à un filtre passe-bas", comme on le voit à l'étape précédente.

Comment l'Arduino génère-t-il un signal PWM? Il y a un très bon tutoriel sur PWM ici:
//arduino.cc/en/Tutorial/SecretsOfArduinoPWM
Nous verrons juste les points nécessaires à ce projet.

Dans l'ATMega328P, il y a trois temporisateurs qui peuvent être utilisés pour générer des signaux PWM, chacun d'eux ayant des caractéristiques différentes que vous pouvez utiliser. Pour chaque temporisateur correspondent deux registres appelés registres de comparaison de sortie A / B (OCRnx) qui sont utilisés pour définir le rapport cyclique du signal.

Quant à l'ADC, il y a un pré-échelle (voir image), qui ralentit l'horloge principale pour avoir un contrôle précis de la fréquence PWM. L'horloge ralentie est envoyée à un compteur qui incrémente un registre de minuterie / compteur (TCNTn). Ce registre est comparé en continu à l'OCRnx, quand ils sont égaux, un signal est envoyé à un générateur de forme d'onde qui génère une impulsion sur la broche de sortie. L'astuce consiste donc à définir le registre OCRnx sur une certaine valeur pour modifier la valeur moyenne du signal.

Si nous voulons un signal de 5 V (maximum), nous devons définir un rapport cyclique de 100% ou 255 dans l'OCRnx (maximum pour un nombre de 8 bits), tandis que si nous voulons un signal de 0, 5 V, nous devons définir un rapport cyclique de 10% ou un 25 dans l'OCRnx.

Puisque l'horloge doit remplir le registre TCNTn avant de recommencer depuis le début pour une nouvelle impulsion, la fréquence de sortie du PWM est:

f = (horloge principale) / pré-échelle / (TCNTn maximum)

à titre d'exemple, pour le temporisateur 0 et 2 (8 bits) sans pré-échelle, ce sera: 16 MHz / 256 = 62, 5 KHz tandis que pour le temporisateur 1 (16 bits) ce sera 16 MHz / 65536 = 244 Hz.

J'ai décidé d'utiliser le Timer numéro 2 parce que
  • Le temporisateur 0 est utilisé en interne par l'IDE Arduino pour des fonctions telles que millis ();
  • Le minuteur 1 a une fréquence de sortie trop lente car il s'agit d'un minuteur 16 bits.

Dans l'ATMega328P, il existe différents types de mode de fonctionnement des minuteries, mais ce que je voulais, c'était le PWM rapide sans pré-mise à l'échelle pour obtenir la fréquence de sortie maximale possible.

Étape 15: Configuration du PWM

Dans l'esquisse, j'ai écrit une autre fonction d'initialisation qui configure tous les paramètres du fonctionnement du temporisateur et initialise quelques broches. void initPins (void)
{
// ------------------------------------------------ ---------------------
// Paramètres TCCR2A
// ------------------------------------------------ ---------------------
// Ces bits contrôlent le comportement de la broche de comparaison de sortie (OC2A). Si un ou
// les deux bits COM2A1: 0 sont définis, la sortie OC2A remplace la
// fonctionnalité de port normal de la broche d'E / S à laquelle il est connecté.
// Cependant, notez que le bit DDR (Data Direction Register)
// correspondant à la broche OC2A doit être défini pour permettre la
// pilote de sortie.
// Lorsque OC2A est connecté à la broche, la fonction du COM2A1: 0 bits
// dépend du paramètre WGM22: 0 bit.
//
// Mode PWM rapide
// COM2A1 COM2A0
// 0 0 Fonctionnement normal du port, OC2A déconnecté.
// 0 1 WGM22 = 0: Fonctionnement normal du port, OC0A déconnecté.
// WGM22 = 1: bascule OC2A sur la comparaison de correspondance.
// 1 0 Effacez OC2A sur Compare Match, réglez OC2A sur BOTTOM
// 1 1 Clear OC2A sur Compare Match, clear OC2A sur BOTTOM
cbi (TCCR2A, COM2A1);
cbi (TCCR2A, COM2A0);
sbi (TCCR2A, COM2B1);
cbi (TCCR2A, COM2B0);

// Combiné avec le bit WGM22 trouvé dans le registre TCCR2B, ces bits
// contrôle la séquence de comptage du compteur, la source du maximum
// (TOP) valeur du compteur et type de génération de forme d'onde à utiliser
// Les modes de fonctionnement pris en charge par l'unité Timer / Counter sont:
// - Mode normal (compteur),
// - Clear Timer en mode Compare Match (CTC),
// - deux types de modes de modulation de largeur d'impulsion (PWM).
//
// Mode WGM22 WGM21 WGM20 Fonctionnement TOP
// 0 0 0 0 Normal 0xFF
// 1 0 0 1 PWM 0xFF
// 2 0 1 0 CTC OCRA
// 3 0 1 1 PWM rapide 0xFF
// 4 1 0 0 Réservé -
// 5 1 0 1 PWM OCRA
// 6 1 1 0 Réservé -
// 7 1 1 1 Fast PWM OCRA
cbi (TCCR2B, WGM22);
sbi (TCCR2A, WGM21);
sbi (TCCR2A, WGM20);

// ------------------------------------------------ ---------------------
// Paramètres TCCR2B
// ------------------------------------------------ ---------------------
// Le bit FOC2A n'est actif que lorsque les bits WGM spécifient un non-PWM
// mode.
// Cependant, pour garantir la compatibilité avec les futurs appareils, ce bit
// doit être mis à zéro lorsque TCCR2B est écrit lors d'un fonctionnement en PWM
// mode. Lors de l'écriture d'un logique sur le bit FOC2A, un
// Compare Match est forcé sur l'unité de génération de forme d'onde. L'OC2A
// la sortie est modifiée en fonction de son paramètre COM2A1: 0 bits. Notez que
// le bit FOC2A est implémenté comme un stroboscope. C'est donc la valeur
// présent dans le COM2A1: 0 bits qui détermine l'effet de la
// comparaison forcée.
// Un stroboscope FOC2A ne générera aucune interruption, ni n'effacera
// le temporisateur en mode CTC utilisant OCR2A comme TOP.
// Le bit FOC2A est toujours lu comme zéro.
cbi (TCCR2B, FOC2A);
cbi (TCCR2B, FOC2B);

// Les trois bits de sélection d'horloge sélectionnent la source d'horloge à utiliser par
// la minuterie / compteur.
// CS22 CS21 CS20 Prescaler
// 0 0 0 Aucune source d'horloge (minuterie / compteur arrêté).
// 0 0 1 Pas de mise à l'échelle
// 0 1 0 8
// 0 1 1 32
// 1 0 0 64
// 1 0 1 128
// 1 1 0 256
// 1 1 1 1024
cbi (TCCR2B, CS22);
cbi (TCCR2B, CS21);
sbi (TCCR2B, CS20);

pinMode (errorPin, OUTPUT);
pinMode (seuilPin, SORTIE);

analogWrite (thresholdPin, 127);
}

Étape 16: Variables volatiles

Je ne me souviens pas où, mais j'ai lu que les variables qui sont modifiées à l'intérieur d'un ISR doivent être déclarées volatiles .

Les variables volatiles sont des variables qui peuvent changer au cours du temps, même si le programme en cours d'exécution ne les modifie pas. Tout comme les registres Arduino qui peuvent changer la valeur de certaines interventions externes.

Pourquoi le compilateur veut-il connaître ces variables? C'est parce que le compilateur essaie toujours d'optimiser le code que nous écrivons, pour le rendre plus rapide, et il le modifie un peu, en essayant de ne pas changer sa signification. Si une variable change d'elle-même, il pourrait sembler au compilateur qu'elle n'est jamais modifiée pendant l'exécution, disons, d'une boucle et elle pourrait l'ignorer; alors qu'il pourrait être crucial que la variable change sa valeur. Ainsi, la déclaration de variables volatiles empêche le compilateur de modifier le code concernant celles-ci.

Pour plus d'informations, je suggère de lire la page Wikipedia: //en.wikipedia.org/wiki/Volatile_variable

Étape 17: Écriture du noyau de l'esquisse

Enfin, nous sommes arrivés au noyau du programme!

Comme nous l'avons vu auparavant, je voulais une acquisition continue et j'ai écrit la routine de service d'interruption ADC pour stocker en continu dans le tampon circulaire les données. Il s'arrête chaque fois qu'il atteint l'index égal à stopIndex. Le tampon est implémenté comme circulaire en utilisant l'opérateur modulo.

// ------------------------------------------------ -----------------------------
// Interruption complète de la conversion ADC
// ------------------------------------------------ -----------------------------
ISR (ADC_vect)
{
// Lorsque ADCL est lu, le registre de données ADC n'est pas mis à jour tant qu'ADCH
// est lu. Par conséquent, si le résultat est ajusté et non plus
// une précision de 8 bits est requise, il suffit de lire ADCH.
// Sinon, ADCL doit être lu en premier, puis ADCH.
ADCBuffer [ADCCounter] = ADCH;

ADCCounter = (ADCCounter + 1)% ADCBUFFERSIZE;

si (attendez)
{
if (stopIndex == ADCCounter)
{
// Geler la situation
// Désactivez ADC et arrêtez le mode de conversion en cours d'exécution
cbi (ADCSRA, ADEN);

gel = vrai;
}
}
}

La routine de service d'interruption du comparateur analogique (qui est appelée lorsqu'un signal dépasse le seuil) se désactive et indique à l'ADC ISR de démarrer la phase d'attente et définit le stopIndex.

// ------------------------------------------------ -----------------------------
// Interruption du comparateur analogique
// ------------------------------------------------ -----------------------------
ISR (ANALOG_COMP_vect)
{
// Désactiver l'interruption du comparateur analogique
cbi (ACSR, ACIE);

// Activer errorPin
// digitalWrite (errorPin, HIGH);
sbi (PORTB, PORTB5);

attendre = vrai;
stopIndex = (ADCCounter + waitDuration)% ADCBUFFERSIZE;
}


C'était vraiment facile après toute cette mise à la terre!

Étape 18: Formation du signal entrant

Passons maintenant au matériel. Le circuit peut sembler compliqué mais c'est vraiment simple.
  • Il y a une résistance de 1 MΩ à l'entrée, pour donner une référence de masse au signal et avoir une entrée à haute impédance. Une impédance élevée «simule» un circuit ouvert si vous le connectez à un circuit à impédance inférieure, de sorte que la présence du Girino ne dérange pas trop avec le circuit que vous souhaitez mesurer.
  • Après la résistance, il y a un émetteur suiveur pour découpler le signal et protéger l'électronique suivante.
  • Il existe un simple décalage qui génère un niveau de 2, 5 V avec un diviseur de tension. Il est attaché à un condensateur pour le stabiliser.
  • Il existe un amplificateur de somme non inverseur qui additionne le signal entrant et le décalage. J'ai utilisé cette technique parce que je voulais pouvoir voir également des signaux négatifs, car l'ADC Arduino ne pouvait voir des signaux qu'entre 0 V et 5 V.
  • Après le sum-amp il y a un autre émetteur suiveur.
  • Un cavalier nous permet de décider si nous voulons alimenter le signal avec un décalage ou non.
L'amplificateur opérationnel que j'avais l'intention d'utiliser était un LM324 capable de fonctionner entre 0 V à 5 V mais également entre, disons, -12 V à 12 V. Cela nous donne plus de possibilités avec les alimentations. J'ai également essayé un TL084 bien plus rapide que le LM324 mais qui nécessite une double alimentation. Ils ont tous deux le même brochage et peuvent donc être modifiés sans aucune modification du circuit.

Étape 19: Condensateurs de dérivation

Les condensateurs de dérivation sont des condensateurs qui sont utilisés pour filtrer les alimentations des circuits intégrés (CI) et ils doivent être placés aussi près que possible des broches d'alimentation du CI. Ils sont généralement utilisés en couple, une céramique et une électrolytique car ils peuvent filtrer différentes fréquences.

Étape 20: Sources d'alimentation

J'ai utilisé une double alimentation pour le TL084 qui peut être convertie en une seule alimentation pour le LM324.

Sur l'image, nous pouvons voir que j'ai utilisé quelques régulateurs de tension, un 7812, pour +12 V, et un 7912, pour -12 V. Les condensateurs sont, comme d'habitude, utilisés pour stabiliser les niveaux et leurs valeurs sont celles suggérées dans les fiches techniques.

Évidemment, pour avoir une tension de ± 12 V, nous devons avoir au moins environ 30 V à l'entrée car les régulateurs de tension nécessitent une entrée plus élevée pour fournir une sortie stabilisée. Comme je n'avais pas une telle alimentation, j'ai utilisé l'astuce d'utiliser deux alimentations 15 V en série. L'un des deux est connecté au connecteur d'alimentation Arduino (il alimente donc à la fois l'Arduino et mon circuit) et l'autre directement au circuit.

Ce n'est pas une erreur de connecter le +15 V de la deuxième alimentation au GND de la première! C'est ainsi que nous obtenons un -15 V avec des alimentations isolées .

Si je ne veux pas transporter un Arduino et deux alimentations, je peux toujours utiliser le +5 V fourni par l'Arduino en changeant ces cavaliers (et en utilisant le LM324).

Étape 21: Préparation d'un connecteur de blindage

J'ai toujours été ennuyé par les connecteurs que j'ai pu trouver pour faire un blindage Arduino, car ils ont toujours des broches trop courtes et les cartes que j'utilise ne peuvent être soudées que d'un côté. J'ai donc inventé une petite astuce pour allonger les broches afin qu'elles puissent être soudées et insérées dans l'Arduino.

En insérant la bande de broches dans la carte, comme sur la photo, nous pouvons pousser les broches, pour les avoir uniquement sur un côté du plastique noir. Ensuite, nous pouvons les souder du même côté où ils seront insérés dans l'Arduino.

Étape 22: Soudure et test

Je ne suis pas en mesure de vous montrer toute la procédure de soudage du circuit car il a subi de nombreux essais et erreurs. Au final, c'est devenu un peu désordonné mais pas trop mal, même si je ne montrerai pas le dessous parce que c'est vraiment désordonné.

A ce stade il n'y a pas grand chose à dire car j'ai déjà expliqué en détail toutes les parties du circuit. Je l'ai testé avec un oscilloscope, qu'un ami m'a emprunté, pour voir les signaux à chaque point du circuit. Il semble que tout fonctionne bien et je suis plutôt satisfait.

Le connecteur pour le signal entrant peut sembler un peu étrange pour quelqu'un qui ne vient pas de la High Energy Physics, c'est un connecteur LEMO. C'est le connecteur standard pour les signaux nucléaires, du moins en Europe comme aux USA j'ai surtout vu des connecteurs BNC.

Étape 23: Test des signaux

Pour tester le circuit et la Data AcQuisition (DAQ), j'ai utilisé un deuxième Arduino avec un simple croquis qui génère des impulsions carrées avec différentes longueurs. J'ai également écrit un script python qui parle à Girino et lui dit d'acquérir des séries de données et enregistre l'une d'entre elles dans un fichier.
Ils sont tous deux attachés à cette étape.

Pièces jointes

  • TaraturaTempi.ino Télécharger
  • readgirino.py Télécharger

Étape 24: étalonnage de l'heure

En utilisant les signaux de test, j'ai calibré l'échelle horizontale des parcelles. En mesurant les largeurs des impulsions (qui sont connues parce qu'elles ont été générées) et en traçant les largeurs d'impulsions mesurées par rapport aux valeurs connues, nous obtenons un tracé, espérons-le, linéaire. En procédant ainsi pour chaque paramètre de pré-échelle, nous avons l'étalonnage du temps pour tous les taux d'acquisition.

Sur les images, nous pouvons voir toutes les données que j'ai prises et analysées. Le tracé "Pentes ajustées" est le plus intéressant car il nous indique le taux d'acquisition réel de mon système à chaque réglage de pré-échelle. Les pentes ont été mesurées sous la forme d'un nombre [ch / ms] mais cela équivaut à un [kHz], donc les valeurs des pentes sont en fait kHz ou également kS / s (kilo d'échantillons par seconde). Cela signifie qu'avec le prescaler réglé sur 8, nous obtenons un taux d'acquisition de:

(154 ± 2) kS / s

Pas mal, hein?

Tandis que du graphique "Ajustement des ordonnées y" nous obtenons un aperçu de la linéarité du système. Tous les ordonnées à l'origine doivent être nuls car à un signal de longueur nulle doit correspondre une impulsion de longueur nulle. Comme nous pouvons le voir sur le graphique, ils sont tous compatibles avec zéro, mais pas avec l'ensemble de données 18-prescaler. Cet ensemble de données, cependant, est le pire car il ne contient que deux données et son étalonnage n'est pas fiable.

Vous trouverez ci-dessous un tableau avec les taux d'acquisition pour chaque paramètre de pré-échelle.

PrescalerTaux d'acquisition [kS / s]
1289, 74 ± 0, 04
6419, 39 ± 0, 06
3237, 3 ± 0, 6
1675, 5 ± 0, 3
8153 ± 2
Les erreurs citées proviennent du moteur d'ajustement Gnuplot et je n'en suis pas sûr.

J'ai également essayé un ajustement non pondéré des taux, car vous pouvez voir qu'ils doublent à peu près lorsque la mise à l'échelle est divisée par deux, cela ressemble à une loi de proportionnalité inverse. J'ai donc ajusté les taux par rapport aux paramètres du pré-échelle avec une simple loi de

y = a / x

J'ai obtenu une valeur pour un de

a = 1223

avec un χ² = 3, 14 et 4 degrés de liberté, cela signifie que la loi est acceptée avec un niveau de confiance de 95%!

Étape 25: terminé! (Presque)

Au terme de cette longue expérience, je me sens très satisfait car
  • J'ai beaucoup appris sur les microcontrôleurs en général;
  • J'ai beaucoup appris sur l'Arduino ATMega328P;
  • J'ai eu une expérience pratique de l'acquisition de données, non pas en utilisant quelque chose déjà fait mais en faisant quelque chose;
  • J'ai réalisé un oscilloscope amateur qui n'est pas si mal.
J'espère que ce guide sera utile à quiconque le lira. Je voulais l'écrire si en détail parce que j'ai appris tout cela à la dure (surfer sur Internet, lire la fiche technique et par beaucoup d'essais et d'erreurs) et je voudrais épargner à quiconque cette expérience.

Étape 26: À suivre ...

Mais le projet est loin d'être achevé. Ce qu'il manque, c'est:
  1. Un test avec différents signaux analogiques (il me manque un générateur de signaux analogiques);
  2. Une interface utilisateur graphique pour le côté ordinateur.
Quant au point 1. Je ne sais pas quand il sera terminé, car je ne prévois pas d'en acheter / en construire dans un avenir proche.

Pour le point 2. la situation pourrait être meilleure. Quelqu'un est-il disposé à m'aider avec ça? J'ai trouvé un bel oscilloscope Python ici:
//www.phy.uct.ac.za/courses/python/examples/moreexamples.html#oscilloscope-and-spectrum-analyser
Je voudrais le modifier pour l'adapter à Girino, mais j'accepte les suggestions.

Articles Connexes