configuration Atmel studio
En lisant le manuel du programmeur Pololu, j'ai découvert comment compiler et programmer avec la seule touche F5. Dans le menu view, cliquez l'item Available Atmel tools. Dans cette liste à part le simulateur il ne doit y avoir qu'un seul programmeur STK500, celui qui correspond à votre USB AVR Pololu. Ensuite chaque fois que vous créez un projet il faut aller dans les propriétés du projet dans l'onglet tools et s'assurer que ce programmeur est sélectionné pour la progammation.
A partir de là chaque fois qu'on presse la touche F5 le projet et compilé et le MCU est programmé.Lien AVR libc
voici le lien vers la page du projet AVR libc. Très utile, toute la documentation est là.
Conversion numérique/analogique par PWM
Ajourd'hui on va voir comment créer et lire une table de données en mémoire flash et comment synthétiser une onde sinusoïdale en utilisant le périphérique PWM. Le circuit utilisé est le suivant.
R1, R2, C2 et C3 forme un filtre passe bas avec une fréquence de coupure à 5Khz et une pente de 12db/octave. La résistance R3 doit-être d'au moins 10 fois la valeur de R2 pour ne pas charger le filtre. La sortie peut alimenter l'amplicateur utitilisé en partie 4.
Code source, version 1
Voyons ça plus en détail. le fichier pgmspace.h contient les macros nécessaire à la création de tables dans la mémoire programme et à leur utilisation. la macro PROGMEM informe le compilateur que les données qui suivent doivent-être enregistrées en mémoire programme plutôt qu'en RAM. La table sine_table est la valeur des échantillons pour recréer une onde sinusoïdale. Il y a 180 échantillons, donc 1 à tous les 2 degrés du cycle. Si on regarde la routine ISR on voit que pour lire le contenu de la table il faut utiliser la macro pgm_read_byte qui prend en argument l'adresse de sine_table[indice]. Si on écrivais simplement OCR0B=sine_table[indice] le compilateur compilerais le code comme si la table se trouverais en RAM et évidemment ça ne fonctionnerais pas.
Dans la fonction main il ne se passe rien à part les initialisations nécessaires. Tout le travail est fait par le périphérique PWM et l'ISR.
Donc la table est lue en boucle pour produire une onde sinusoïdale dont la fréquence est Fo=pas*(Fpwm/Ne)
où
Fo est la fréquence de l'onde sinusoïdale
Fpwm est la fréquence d'échantillonnage dans ce cas si 37500Hz
pas est la valeur d'incrément pour l'indice accédant la table.
Ne est le nombre d'échantillons dans la table ici 180.
pour pas=1 on a donc Fo=Fpwm/180=208Hz, en mettant pas=2 on double la fréquence et ainsi de suite. En théorie le pas maximal est de 90, mais bien avant d'atteindre cette valeur le procédé de numérisation produit un niveau de distortion inacceptable avec des sous-harmoniques.
On comprend qu'en augmentant le pas on réduit le nombre d'échantillons utilisés pour recréer l'onde sinuoïdale donc la distortion augmente. On peut le voir sur les captures d'écran oscilloscope suivantes:
Pour pas=1
pour pas=10
On voit bien que le sinuoïde est déformé.
Optimisation
Au départ j'avais écris l'ISR comme ceci:
Mais il s'est avéré que la sous-routine de l'opérateur '%' prenais 100 octets de plus, le code faisant 456 octets au lieu de 356. Sans oublier le temps d'exécution supplémentaire. Il y a une contrainte sur le temps d'exécution de l'ISR. Ce temps doit-être inférieur à la durée d'une période PWM.
la variante suivante prend 362 octets:
Notez que lorsque l'indice dépasse 179 je ne le ramène pas à zéro mais à la valeur de dépassement. En principe lorsqu'on numérise, l'échantilloneur n'est pas synchronisé sur le début du cycle. En pratique, j'ai essayé l'autre façon de faire et ça ne semble pas avoir d'influence significative sur le signal en sortie. Donc dans la version suivante chaque cycle est resynchronisé sur le début de la table.
Code source, version 2
Il n'est pas nécessaire que le pas soit un entier. Évidemment il faut que le tableau soit indexé par un entier mais un pas fractionnaire signifie simplement qu'au lieu d'avancer à vitesse régulière dans la table on progresse de façon irrégulière. on pourrait écrire le code de l'ISR comme suis:
On pourrait penser que cette avance irrégulière va créé de la distortion mais celle-ci est constituée d'éléments haute-fréquence que le filtre passe-bas élimine en grande partie. Le problème avec cette sous-routine viens plutôt de l'utilisation du type float. Sur un petit MCU comme le ATtiny13A, le coût aussi bien en terme d'espace programme utilisé qu'en terme de vitesse d'exécution serait prohibitif. Il faut se contenter de travailler avec des entiers et utiliser des multiplications et divisions qui sont des puissances de 2. C'est tout à fait possible comme nous allons le voir dans la version suivante du code source.
Dans cette version un potentiomètre est utilisé pour contrôler la fréquence de sortie du sinuoïde. Le voltage du potentiomètre est lu à toutes les 20 msec et la valeur du convertisseur est utilisée comme valeur de pas.
Dans la boucle while (1) la lecture du potentiomètre est démarrée en mettant à 1 le bit ADSC dans le registre ADCSRA. On surveille ce bit qui retombe à zéro lorsque la conversion est complétée. Le résultat se retrouve dans les registes ADCH et ADCL. je copie simplement cette valeur dans la variable pas en ajoutant 12 à ADCL pour une fréquence minimale de 10Hz, tandis que la fréquence maximale est d'environ 850Hz.
Observez comment le délais est créé entre chaque lecture du potentiomètre. On utilise le fait que l'ISR est déclenchée à intervalle régulier pour compter le temps. Dans la boucle while (1) on initialise la variable delais à 750 et on attends qu'elle revienne à zéro. Si on examine l'ISR on voit qu'à la fin si delais est plus grand que zéro sa valeur est décrémentée. l'ISR est déclenchée 37500 fois par seconde donc pour obtenir un délais de 20 msec, delais=.02*37500=750.
Notez que la classe de la variable delais est volatile. Si on ne la définie pas comme volatile. le compilateur va considéré que cette variable n'est jamais modifiée et donc va compiler while (delais); comme si c'était une boucle infinie. En effet comme cette variable est initialisée à 750 juste avant la boucle et n'est pas modififiée à l'intérieure de celle-ci, le compilateur optimise le code en considérant qu'il s'agit d'une boucle infinie et compile la seule instrcution rjump .-4. la classe volatile informe le compilateur que cette variable peut-être modifiée par une prodécure qui s'exécute en dehors du flux normal du programme (ISR) et ne la considère plus comme constante.
Si vous lisez les 2 versions du code, vous constaterez une différence au niveau de l'ISR et de la variable pas. Dans la première version pas était de type uint8_t et dans la seconde est de type unsigned int. De plus la variable somme aussi de type unsigned int a été ajoutée. A l'intérieur de l'ISR la valeur haute de somme est utilisée pour initialiser indice, Ce qui revient à une division entière de somme par 256.
Vous vous demandez peut-être quel est la raison d'être de ce changement. En fait c'est une façon d'éviter d'avoir à faire des calculs en virgule flottante, tout en préservant la partie fractionnaire de pas. /p>
Voyons ça en détail.
pas = Fo*180*256/37500
Fo est la fréquence du sinusoïde.
180 est le nombre d'échantillons dans la table.
37500 est la fréquence d'échantillonage du PWM.
La multiplication par 256 permet de conserver la fraction qui serait autrement perdue.
Mais l'indice d'accès à la table lui est un entier variant de 0 à 179. Au lieu d'incrémenter l'indice directement avec pas. On additionne pas à somme et on ne garde que le l'octet fort de la somme (arrondi à l'entier le plus prêt) pour indicer le tableau. Ce qui revient à dire: indice = (int)((somme+128)/256). On a multiplié pas par 256 mais on redivise par 256 pour créer l'indice. Ainsi l'indice n'est incrémenté que lorsque l'addition de pas à somme produit un débordement dans l'octet fort de somme.
L'octet fort se trouve à être la partie entière et l'octet faible la partie fractionnaire.
Conclusion
Si l'ATtiny13a possédait suffisamment de mémoire on pourrait lui faire jouer des fichiers WAV encodés en 8 bits non signés avec une fréquence d'échantillonage de 11025Hz. Mais comme il en manque on pourrait lui en donner en utilisant une eeprom à interface sérielle comme j'ai fait avec le PIC10F322 et une eeprom 24LC512 dans mon projet sonnette. Le PIC10F322 a un avantage cependant sur l'ATtiny13A. Son PWM a un résolution de 10 Bits au lieu de 8, ce qui m'a permis d'ajuster la fréquence pwm pour qu'elle concorde avec la fréquence du fichier WAV que j'ai utilisé. L'ATtiny13A a un autre mode FAST PWM, le mode 7. Dans le mode 7 OCR0A est utilisé comme limite supérieur pour le compteur TCNT0. C'est à dire que lorsque le compte dans TCNT0 atteint la valeur de OCR0A il retombe à zéro au lieu de continuer jusqu'à 255. Pour produite du PWM dans ce mode il faut utilisé le canal B. Ça permet d'ajuster la fréquence PWM mais la conséquence est que la résolution PWM diminue d'autant. Comme la résolution du PWM de l'ATtiny n'est que de 8 bits, si on veut jouer des fichiers WAV 8 bits il n'y a pas de jeux pour diminuer la résolution contrairement au PIC10F322. La seule autre façon d'ajuster la fréquence PWM sans perdre de résolution est de modifier la fréquence de l'oscillateur interne du MCU en modifiant la valeur dans le registre OSCCAL.
Dans cette série, je crois avoir présenté l'essentiel pour une introduction aux MCU ATtiny. Si vous avez lu cette série et bien compris vous devriez être en mesure maintenant de créer vos propre projets utilisant la famille de MCU ATtiny.
Aucun commentaire:
Publier un commentaire
Remarque : Seuls les membres de ce blogue sont autorisés à publier des commentaires.