![]() |
Oscilloscope sur PC
Remerciement à Mr Buzer, Professeur à l'ESIEE et notre tuteur pour ce projet, pour son aide et sa sympathie.
Le projet réalisé est un oscilloscope sur PC qui est composé d’une carte d’acquisition munie d’un microcontrôleur qui communique avec l’ordinateur grâce au port série.
La carte d’acquisition est gérée par
un Pic18f452 (pdf)
de la marque Microchip cadencé à 20MHz. L’acquisition
est faite grâce à un ADC 0820 8 bits (pdf)
et à plusieurs étages électroniques permettant d’adapter
la tension entre 0 et 5 Volts pour l'ADC.
La carte d'acquisition
Le circuit électrique est inspiré du magasine ELECTRONIQUE PRATIQUE HS n°9.
L’ADC0820 (pdf) ne mesurant des tensions qu’entre 0V et 5V, plusieurs adaptations du signal sont nécessaires.
1)Diviser la tension
Pour pouvoir avoir une tension d’entrée importante ( entre +15V et -15V), il est important de diviser la tension. Pour cela, on utilise un pont de résistances toutes identiques. Avec 8 résistances en série, on peut obtenir une division par 2, 4 et 8. La valeur de la division est sélectionnée grâce à 3 relais qui commutent en fonction des ordres du pic. Un AOP (LM324 pdf) monté en suiveur situé en aval des relais permet de ne pas modifier les valeurs des divisions.
Les tensions d’alimentation des AOPs sont de +9V et -9V. Cette tension
est obtenue grâce à un MAX232.
Lors des phases d'expérimentations, nous avons pu remarquer que la tension mesurée avait tendance à diminuer lorsque la fréquence du signal était supérieure à 6 kHz. La source de ce problème n'a pas pu être identifiée. Cependant, la cause la plus probable semble être l'effet capacitif de la plaquette d'essai utilisée.
2) La tension de référence de l’ADC
L’ADC possède une tension de référence
qui permet d’améliorer la précision pour les petites tensions.
Avec le même principe que précédemment, un pont de résistance
fournit les tensions de référence 5V, 2.5V, 1.25V et 0.625V.
Ces tensions sont obtenues grâce à un composant (LM336Z-5.0 pdf)
qui fournit un 5V de référence, c’est à dire un
5V très stable qui ne varie pas ou très peu en fonction de la
température ou d’autres facteurs. Les tensions sont sélectionnées
ensuite par un multiplexeur analogique 74HC4052 (pdf)
car les tensions sont suffisament faibles.
3) Les tensions négatives
L’ADC ne peut prendre des valeurs qu’entre 0V
et 5V or un signal peut être en partie négatif, comme les signaux
sinusoïdaux par exemple. Ainsi, un signal de 5V d’amplitude, entre
–2.5V et +2.5V doit être ramener entre 0V et 5V.
Pour cela, on ajoute la moitié de la tension de référence
de l’ADC au signal d’entrée.
Par exemple : un signal sinusoïdal variant entre –2.5V et +2.5V.
Avec une tension de référence de 5V, on revient entre 0V et
+5V.
L'AOP du haut permet de prendre la moitié de la tension de référence créée par les résistances. Le montage du deuxième AOP permet de réaliser un additionneur.
1) La Conversion
La liaison avec le port série se faire à 115200
bauds. Une trame est constitué de 10 bits, soit 1 bit de start, 8 bits
de données et 1 bit de stop. L’envoie d’une trame prend
donc 86.8 µs. Ce temps est beaucoup plus long que le temps de conversion
de l’ADC (1.5µs) c’est pourquoi, le PIC stocke d’abord
1024 valeurs avec un temps entre chaque échantillon allant de 3µs
à 200ms. Il envoie ensuite toutes les valeurs d’un seul coup,
en arrêtant de prendre des valeurs.
L’ADC est contrôlé par le PIC uniquement grâce au
bit WR. Ensuite, une attente de plus de 1.5µs permet d’être
sur que la conversion est finie. Ce procédé permet d’avoir
des temps de conversion rigoureusement identique car si pour une raison inconnue,
l’ADC a un problème lors de la conversion, le PIC ne tiens pas
compte du INTR. De plus, pendant la prise des valeurs, les interruptions sont
désactivées car elles pourraient fausser la base de temps.
Le PIC est programmé uniquement en assembleur ce qui permet de connaître
exactement le temps pris par chaque instruction.
Pour faire varier les temps de conversion, c’est le PC qui envoie les
informations au PIC. Ainsi, le PIC possède 4 modes de conversion différents.
- 1 Mode ultra-rapide à 3µs (le PC envoie la
valeur « m » comme minimum)
- 1 Mode rapide à 5µs ( valeur « f » comme fixe)
- 1 Mode variable de 10 µs à 1ms (Le PC envoie « v »
puis une valeur entre 0 et 255)
- 1 Mode variable lent de 2ms à 250ms ( Le PC envoie « l »
puis une valeur entre 0 et 255)
Temps/Divisions Mode Valeur à envoyer après
"mode" Tps entre chaque échantillon en µs
0,15 ms/div | m | 3 µs | |
0,25 ms/div | f | 5 µs | |
0,5 ms /div | v | 1 | 10 µs |
1 ms /div | v |
3 | 20 µs |
2,5 ms/div | v | 9 | 50 µs |
5 ms/div | v | 19 | 100 µs |
10 ms/div | v | 36 | 200 µs |
25 ms/div | v | 99 | 500 µs |
50 ms/div | v | 199 | 1 ms |
100 ms/div | l | 1 | 2 ms |
250 ms/div | l | 4 | 5 ms |
500 ms/div | l | 9 | 10 ms |
2,5 s/div | l | 49 | 50 ms |
5 s/div | l | 99 | 100 ms |
7,5 s/div | l | 149 | 150 ms |
10 s/div | l | 199 | 200 ms |
2) Stockage des données
Le Pic 18F452 possède 1536 bits de mémoire RAM dans laquelle les valeurs prises par l’ADC sont stockées. C’est une mémoire rapide dont l’accès se fait grâce à des registres post ou pré-incrémentable ou post-décrémentable (POSTINC0, PREINC0 ou POSTDEC0). Ceci rend la formation du tableau de valeurs beaucoup plus simple.
movff ADC_DATA, POSTINC0 | ; met la valeur de l’ADC dans le registre POSTINC0 |
La mémoire RAM est divisée en 16 Banks de 256
valeurs chacune dont seulement 5 banks sont exploitables par l’utilisateur.
L’adresse du pointeur mémoire est contenue dans FSRL (FSR Low)
et FSRH (FSR High). En effet, les 1536 bits nécessitent une adresse
de 12 bits or, le Pic ne fonctionne qu’en 8 bits, donc deux registres
sont nécessaires.
Etant donné que le pic envoie 1024 valeurs au PC, il faut utilisé
4 banks. Il suffit donc juste de tester FSRH. La première bank de travail
est la bank 1.
movf NB_VAL_HIGH, W cpfseq FSR0H, 0 bra loop bra envoie |
; W est le register de travail du pic ; Compare FSRH et NB_VAL_HIGH = 5 ; Continue a prendre des valeurs ; Envoyer le tableau de valeurs |
Le Pic18f452 est équipé d’un module de gestion du port série (USART) . Un MAX232 (pdf) est nécessaire pour convertir les tensions du PIC en tension compatible avec le port série.
Le registre TXREG envoie les données sur le port série lorsqu’il est remplit. Un flag indique lorsque la transmission est terminé.
movlw 'm' movwf TXREG btfss TXSTA,TRMT bra $-1 |
; envoie ‘m’ sur le port série ; attend que l’envoie soit fini |
Le registre RCREG active également un flag lorsqu’un bit est reçu. Il est remis à zéro lors de la lecture du registre.
btfss PIR1, RCIF bra $-1 movff RCREG, RC_REG_TMP |
; attend une réception ; met RCREG dans un registre temporaire |
4) Le Trigger du PIC
Le programme est équipé d’un trigger qui envoie les données uniquement lorsque la valeur de la tension dépasse un certain seuil défini par l’utilisateur. Le trigger boucle tant que la valeur n’est pas atteinte, cependant, il peut être désactivé grâce a une interruption sur le port série. Le port série peut provoquer une interruption lorsqu’il est plein. On vérifie alors la donnée reçue. Si celle-ci est « n » alors, le trigger se désactive et le programme prend les valeurs de la sonde.
bsf PIE1, RCIE loop_trig: call Get_Value movff ADC_DATA, VALUE1 movf VAL_TRIG, W cpfslt VALUE1, 1 return ; Si oui, btfsc FLAG_TRIG,0 bra loop_trig return |
;Enable interruption pour que le trigger puisse être stopper.
; Test : VALUE1 < VAL_TRIG ; Si non commence a prendre les valeurs ; Si oui, ; regarde si une interruption a changée le flag ; si non, attend encore ; si trigger désactivé, retour |
5) Organisation du programme
Le programme est organisé de manière centralisée. A la fin de chaque action, le programme retourne en état d’attente d’une instruction du PC. Le programme effectue tout d’abord l’initialisation et la configuration des entrées/sorties ainsi que des registres. Ensuite, il se synchronise avec le PC en réceptionnant « OK » puis en envoyant « O ». Ceci permet notamment au programme PC de savoir si la carte est correctement branchée. Si la carte ne répond pas pour une raison autre que le branchement, le PC a le pouvoir de « reseter » la carte grâce a la patte MCLR du pic. Ceci permet d’éviter tout plantage du programme.
Organigramme du programme du Pic18F452
1)Description générale
Le programme se compose d'un processus principal et de quatre threads :
· Le processus principal pour gérer l'affichage
de la fenêtre et les saisies utilisateur.
· Un thread de synchronisation pour gérer les lectures/écritures
sur le port série et l'affichage de la courbe.
· Un thread pour lire des données sur le port série.
· Un thread pour écrire sur le port série.
· Un thread pour afficher le signal.
Cette architecture permet de garder le programme réactif
face à l'utilisateur. Les opérations d'entrée sortie
sur le port série sont transparentes et un problème avec la
carte ne bloque pas tout le programme car le thread de synchronisation intercepte
l’erreur. Les threads communiquent entre eux à l'aide de signaux
(events selon les API Windows). Le programme n'est pas temps réel et
lit des échantillons de valeurs numérisés avec la carte
puis les traite dans la partie graphique. La lecture et l'affichage étant
dans deux threads séparés, ces deux opérations peuvent
se faire simultanément.
Le programme a été développé avec Borland C++
Builder 5. Ce logiciel comporte une librairie de composants visuels nommée
VCL. Cette bibliothèque associe à chaque objet une classe qui
simplifie grandement son utilisation. L'utilisation de cette bibliothèque
nous a permis de ne pas trop nous embarrasser avec la gestion des messages
Windows, et nous a grandement simplifié l'interface graphique et la
gestion des contrôles. Il suffit juste d'ajouter les composants sur
une fiche (une fenêtre Windows vierge). Il faut ensuite associer à
chacun d'eux des valeurs adéquates et des fonctions pour qu'ils réagissent
à certains évènement, comme le clic d'un bouton par exemple.
Néanmoins, la gestion du port série ainsi que les threads sont
des API Windows standards, et ne font pas appels aux composants de la VCL.
2)Le Processus Principal
A la création de la fenêtre, toutes les ressources
nécessaires sont allouées. On crée en premier lieu les
évènements pour que les threads communiquent entre eux, puis
les différents buffers nécessaires et enfin les threads. En
dernier lieu, on initialise tout ce qui est nécessaire. A la fermeture
du programme, on attend que les threads se terminent et on libère la
mémoire.
Le processus principal est chargé de la partie graphique, il gère
l'interface utilisateur avec les boutons et les cases à cocher. Il
communique avec le thread de synchronisation à l'aide de signaux. Quand
l'utilisateur change quelque chose, un signal est envoyé au thread
de synchronisation qui va s'occuper des lectures, écritures et affichage
nécessaire ou encore de la fermeture des autres threads.
3)Le Thread de Synchronisation
Le thread de synchronisation est le centre du programme. Ce thread reçoit
les signaux du processus principal et gère les threads de lecture/écriture
et affichage. Ses principales fonctions sont :
- Initialiser le port série et la carte : L'initialisation de la carte
commence par un reset en utilisant la ligne RTS du port série, puis
en envoyant la chaîne "OK". Si la carte répond 'O',
elle est bien présente et prête à recevoir des instructions.
Dans le cas contraire, on informe l'utilisateur.
- Lire les valeurs en continu sur le port série: Selon le calibre de
temps, le thread synchro ordonnera la lecture de 1 octets pour les calibres
lents ou de paquets de 1024 octets pour les plus rapides. Ces octets sont
ensuite placés dans un buffer de lecture. A la fin de la lecture, le
buffer de lecture est transféré dans un buffer d'affichage.
Pour ne pas créer de conflits en lecture/écriture sur ces buffers,
ils sont protégés par des mutex qui autorisent un seul accès
à la fois. En utilisant deux buffers, on peut afficher et lire des
valeurs en même temps dans deux threads différents. La lecture
et l'affichage étant assez lents, cela permet de gagner en fluidité.
Le thread vérifie que tout se passe bien et informe l'utilisateur en
cas de problème. Il détecte par exemple si la carte est débranchée
et demande à l'utilisateur d'agir en conséquence.
- Changer les calibres de mesures, pour modifier les volts par division ou
les temps par divisions.
- Activer ou désactiver le trigger: Le trigger permet de lire un paquet
de 1024 valeurs et de l'analyser. La carte envoie ce paquet quand la tension
a atteint le niveau du trigger du programme. Le programme affiche ces valeurs
et attend que l'utilisateur lui dise de reprendre un affichage normal des
valeurs. Le trigger est très utile pour voir des envoies de données
par le port série par exemple ou encore après une antenne. Ainsi
dès que le message arrive, le pic enregistre la trame et le pc l’affiche.
4)Le Thread de Lecture
Ce thread est composé d'une boucle qui attend deux
signaux. Le premier signal sert à fermer le thread et l'autre à
commencer la lecture. Quand il reçoit ce dernier, le thread appelle
une fonction qui va lire des octets sur le port série. Cette lecture
non bloquante : si le thread attend de lire quelque chose mais ne reçoit
rien, il se fermera s'il reçoit le signal qui lui en donne l'ordre,
ou se terminera après un temps d'attente définit à l'avance.
La méthode d'attente sur le port est passive, par exemple si le thread
doit lire 1024 octets, il sera "endormi" jusqu'à ce qu'il
soit réveillé par un signal de Windows quand la lecture sera
terminée. Pendant ce temps, le reste du programme continue de s'exécuter
normalement.
Cette méthode de lecture asynchrone est plus compliquée que
la méthode synchrone. Néanmoins elle est beaucoup plus sûre
et plus souple. Le programme nécessite beaucoup d'opérations
d'entrée/sorties et cette méthode est plus appropriée.
Le principe général est de lancer une opération de lecture/écriture
puis d'attendre que Windows envoie un signal quand cette opération
est finie. Il faut ensuite vérifier si tout s'est bien passé
et retourner dans le thread principal pour attendre un nouveau signal du thread
de synchronisation.
5)Le Thread Ecriture
Ce thread a exactement la même structure que le thread de lecture, il est aussi asynchrone et non bloquant. Il est néanmoins plus simple car il n’écrit qu'un seul octet à la fois.
6)Le Thread Affichage
Tout comme les autres threads, il possède une boucle qui attend deux signaux, un pour se terminer et l'autre pour afficher le dessin. Pour dessiner, ce thread attend d'avoir le mutex pour pouvoir lire le buffer d'affichage, et le libère quand il a finit. Suite à quelques problèmes à cause de la VCL de Borland, la fonction de dessin est dans une section critique. C'est à dire qu'aucun autre processus ne peut l'interrompre, il n'y a donc pas d'interaction avec la VCL et moins de risque de conflit avec les hardwares descriptors utilisé dans l’affichage. La fonction de dessin sera étudiée plus loin.
1)Affichage du signal
On affiche les signaux en utilisant le Hardware Descriptor,
car l’affichage est alors beaucoup plus rapide qu’en utilisant
les fonctions de Borland.
On définit une fenêtre d’affichage sur laquelle le signal
sera représenté.
A chaque appel de la fonction de dessin, aussi bien pour une courbe que pour
une FFT, on commence par remplir la zone d’affichage en blanc avec un
brush. Puis on dessine les lignes horizontales et verticales des divisions
à l’aide de crayons.
On sélectionne ensuite un crayon de couleur différente pour
dessiner la courbe en reliant entre eux les différentes valeurs du
tableau (ou en dessinant des raies verticales dans le cas de la FFT).
Un bouton permet de basculer entre l’affichage de la courbe et l’affichage
de la FFT.
Un autre bouton permet de geler l’affichage.
2)Fonctionnement des curseurs
Lorsque le curseur est au dessus de la courbe, il prend la
forme d’une croix. Nous avons choisi la forme à donner au curseur
en définissant une zone TImage transparente sur laquelle le curseur
change lorsque il passe dessus. La zone Timage recouvre exactement la fenêtre
d’affichage de la courbe.
Lorsque c’est necessaire, deux zones de texte indiquent la position
du curseur X et Y, c’est-à-dire les coordonnées en temps
et en valeur de la tension où l’on pointe le curseur.
La selection des curseurs se fait à l’aide de trois boutons :
Lorsque l’un des curseurs est sélectionné, on dessine la croix qui le pointe grâce à un crayon et l’autre curseur devient inactif.
Les Deltas :
L’utilisateur déplace son curseur et peut, s’il souhaite
mesurer une différence de temps ou de tension, sauvegarder deux points
dans X1Y1 et X2Y2. Pour cela, il lui suffit de déplacer l’un
des curseurs sur la courbe, de cliquer pour enregistrer sa position, et de
recommencer avec le second.
Il existe deux booléens click_save1 et click_save2 qui nous indiquent
si l’un des curseurs a déjà été sauvegardé.
Si c’est le cas, la croix signalant ce point reste affichée.
Si l’un des curseurs est sauvegardé et que le second est sélectionné
par l’utilisateur, les cases DX et DY afficheront directement les différences
entre la valeur sauvegardée et la valeur courante.
Si l’utilisateur choisit « No Curseur »,
alors les booléens sont resetés et les croix effacées.
Lorsque l’on change d’affichage (courbe ou FFT) les curseurs sont
supprimés.
3)Fonctionnement du trigger
Le trigger permet de stabiliser le signal. En effet, comme
l’affichage ne se fait pas en temps réel, le tableau des 1024
valeurs peut commencer à n’importe quel endroit de la courbe.
Le trigger permet d’indiquer à partir de quel valeur de tension,
le signal doit être afficher. Ainsi, quelques soient les valeurs du
tableau, le signal sera toujours affiché à partir de la même
valeur.
L’affichage d’une ligne horizontale liée à la position
d’une scrollbar verticale visualise la position du trigger.
On affichera la courbe à partir de sa première valeur si le
trigger est positionné en dehors de la courbe.
Dans le cas contraire, on parcourt la courbe pour trouver l’abscisse
correspondant au trigger (ordonnée) sélectionné.
On choisit d’afficher à partir d’une pente positive. On
vérifie donc que le point obtenu répond bien à ce critère
en regardant l’ordonnées de l’une des abscisses suivantes
: si celle-ci est supérieure à celle du point choisi, on est
bien sur une pente positive, dans le cas contraire on continue de parcourir
le tableau.
On a également la possibilité de définir la sensibilité
du trigger : dans le cas où le signal est bruité, on peut augmenter
la sensibilité pour être sûr de la position de début
d’affichage. La recherche de l’abscisse s’effectue alors
sur un intervalle dépendant de la sensibilité sélectionnée.
4)Les Opérations sur la courbe
La fonction MinMax parcourt le tableau de valeurs pour trouver les extremum de la courbe.
La fonction Période parcourt le tableau
de valeurs pour calculer la valeur moyenne du signal. Pour calculer sa période,
on parcourt le tableau de valeurs et on repère les points pour lesquels
les valeurs passent la barre de la moyenne. Une période correspond
à trois passages de cette barrière. On effectue cette opération
sur toute la longueur du signal, et on effectue une moyenne de toutes ces
périodes calculées, car les résultats peuvent parfois
être faussés dans le cas où le signal est très
bruité. Ce système permet d’avoir un résultat relativement
satisfaisant, les erreurs étant peu nombreuses.
L’opération renvoie –1 si le signal récupéré
ne permet pas de mettre une période en évidence.
Il est également possible d'effectuer une trasformée de Fourier du signal. La fonction nous a été fournie par notre tuteur, Mr Buzer. Pour calculer les fréquences en abscisse, il faut multiplier le nombre d'échantillons numérisés par le temps d'échantillonage. Nous obtenons un temps T0 = N * Temps_echantillion. La fréquence F0 = 1/T0 correspond au pas entre deux fréquences. La première valeur du tableau de FFT est égale à 0*F0, c'est la composante continue du signal. La deuxième valeur correpond à F0, la troisième à 2*F0 et ainsi de suite.
5) Les Calibres
On crée des structures qui contiennent les différents
calibres par pixels (c’est-à-dire par points relevés,
pour l’affichage) en temps et en Voltage, ce qui nous fournit des coefficients
par lesquels multiplier les valeurs avant de les afficher (la période,
les minimum, maximum et deltas). Les calibres seront choisis parmi les valeurs
proposées et modifiés par l’utilisateur.
Ce projet nous a permis de mettre en oeuvre des connaissances acquises lors de ces 3 ans d'études à l'ESIEE. L'aspect pluridisciplinaire de ce projet nous a donné l'occasion d'aborder des problèmes d'informatique, d'électronique analogique, d'électronique numérique ainsi que des problèmes de mathématiques et d'algorithmique. La plupart des problèmes rencontrés au cours de ces 3 semaines de travail ont été resolus, mais il reste quelques problemes électronique et informatique que nous n'avons pas pu résoudre par faute de temps. Nous avons également appris sur des domaines que nous ne connaissions pas bien tels que la gestion du port serie ou encore la programmation Windows grâce au logiciel Borland C++ Builder 5.
Ce projet a également été l'occasion d'améliorer nos capacités de travail en équipe. La séparation des tâches, le suivi des membres et le regroupement des parties de chacun ont été a été faits de manière organisée tout au long de ce projet
Electronique Pratique Hors-Serie n°9, Juin 2001.
Aide de Borland
Win32 Developer's Reference : Aide du SDK de Windows
Guide du développeur - Borland C++ Builder 5
MSDN Library : http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp