lundi 28 janvier 2013

Introduction aux MCU atTiny, partie 7

gestion de la consommation

Comme les micro-controlleurs sont souvent utilisés dans des gadgets fonctionnnant sur piles, les fabriquants tentent de réduire la consommation électrique au minimum. Les petits micro-controlleurs comme l'atTiny13a lorsqu'ils sont en mode power down consomment tellement peut qu'il n'est pas nécessaire de débrancher les piles. Dans cette chronique on va examiner les différentes méthodes pour réduire la consommation électrique.

Comme démo j'ai repris le jeux pong 1-D que j'ai réalisé sur un atTiny13a. voici le schéma:


Les LEDs sont montées en Charlieplexing sur PB0-PB3 et les boutons sont lus en ADC sur PB4. PB5 garde sa fonction de RESET.

Il s'agit d'une rangée de 8 LEDs et d'un bouton à chaque extrémitée. Une LED allumée qui circule d'une extrémitée à l'autre tiens lieu de balle que les joueurs se renvoient en pressant leur bouton respectif. Plus un joueur attend que la balle soit prêt de lui pour la renvoyer plus elle retourne rapidement réduisant d'autant les chances de l'adversaire de la frapper. La partie se termine lorsqu'on joueur a accumulé 15 points. Le pointage est affiché en binaire sur la rangée de LED. Les 4 LEDs de gauche indiquent le pointage du joueur de ce joueur et les autres celui de droite.

Économie d'énergie

code source tennis.c

Je ne sais pas si vous avez remarqué mais les fabriquants donnent toujours la consommation d'énergie de leur MCU, en début de datasheet, au plus bas voltage de fonctionnement et à la fréquence de 1Mhz. Si on veut connaître la consommation à fréquence et voltage plus élevé il faut fouiller les données electrical characteristics à la fin du datasheet. Le fait est que la technologie CMOS a remplacé la technonologie NMOS du début 70's car elle consomme moins d'énergie. Sauf que la consommation augmente exponnentiellement avec la fréquence et le voltage.

Donc une des méthodes pour réduire la consommation d'un MCU est de le faire fonctionner à la plus basse fréquence que permet l'utilisation qu'on veut en faire. J'ai donc vérifier la consommation électrique du circuit à différentes fréquences et voltages. Voici le tableau des mesures obtenues:

Fosc courant (Vdd=3.3V) courant (Vdd=5V) commentaire
9.6Mhz 7,00mA 14,68mA sans désactivation périphériques
9.6Mhz 6,92mA 14,41mA avec désactivation périphériques
4.8Mhz 6,11mA 13.21mA
2.4Mhz 5,37mA 11,9mA
1.4Mhz 5,03mA 11,3mA

Au lignes 345 et 346 je désactive les comparateurs analogiques et les entrées numériques qui ne sont pas utilisées dans cette application, ce qui permet de réduire à consommation d'à peine 270uA. Le fait est que dans cette application c'est le courant fourni à la LED allumée (il n'y en a jamais plus d'une à la fois) qui domine la consommation électrique. Ce n'est donc pas de cette façon qu'on peut réduire la consommation de façon significative. Par contre on constate que la réduction de la fréquence de l'oscillateur Fosc a plus d'effet ainsi que la réduction du voltage.

Dans ce cas particulié l'intérêt principal du power management est d'éviter que les piles se retrouvent à plat si on oubli d'éteindre le gadget. C'est là qu'entre en jeux les différents modes de mise en sommeil du MCU. Le WDT de l'atTiny13A possède un mode de fonctionnement appellé interrupt mode dans le datasheet. Dans ce mode lorsque la minuterie du watchdog timer expire une routine d'interruption est appellée plutôt que de produise un RESET du MCU. Je me sert donc de cette fonctionnalité. Le diviseur du WDT est programmé à 1024 ce qui donne un délais d'environ 8 secondes. De plus la variable count_down est utilisée pour prolonger ce délais en multiples de 8 secondes. l'ISR décrémente le compteur count_down et lorsque ce compteur arrive à zéro le MCU est mis en mode power down. Dans ce mode il ne consomme pratiquement plus de courant car tous les oscillateurs sont mis à l'arrêt. Mon multimètre indique que la consommation électrique passe de 14ma à 0,01ma (10uA). Je ne me fis pas trop à cette dernière lecture car elle est à la limite de résolution de mon multimètre. La consommation en mode power down avec une alimentation de 5 volts est probablement inférieur à 10uA et si le circuit était alimenté en 3 volt serait moindre.

Dans la procédure main il y a 2 boucles while (1) imbriquées. Dans le cycle de la boucle intérieure le WDT est réinitialisé ainsi que la variable count_down. Si les joueurs sont inactifs plus de 16 secondes la variable count_down tombe à zéro et le MCU est mis en mode power down. Le bouton RESET joue le rôle de power on et redémarre le MCU en mode normal.

lundi 21 janvier 2013

Introduction aux MCU atTiny, partie 6

Travailler sous GNU/Linux

Dans cette chronique on va configurer les outils pour travailler avec les MCU AVR sous GNU/Linux. La version GNU/Linux que j'utilise est Ubuntu 12.04 mais ces instructions devraient-être valables sous d'autres distributions, considérant que beaucoup sont dérivées d'Ubuntu (Mint, Kubuntu, Xubuntu, etc).

L'installation est simple et rapide. Il suffit d'installer les 5 paquets suivants:

  1. gcc-avr, le compilateur 'C'
  2. binutils-avr, groupe de programmes utilitaires qui s'intallent dans /usr/bin et dont le nom commence par avr-.
  3. avr-libc, librairie de macros et fonctions de bases.
  4. avrdude, le logiciel qui contrôle le programmeur.
  5. geany, editeur de texte et plus (IDE), (optionel)

Vous pouvez utilisez le gestionnaire de paquets graphique mais personnellement je préfère utiliser apt-get, faire <CTRL>+<ALT>+<T> pour ouvrir la console de commandes. Les commandes sont les suivantes:


Vous devez être membre du groupe dialout pour qu'avrdude soit capable d'ouvrir le port du programmeur. user_name est votre nom de login.
Il n'y a pas de pilote à installer pour le programmeur Pololu. Ce dernier utilise le pilote cdc_acm qui vient avec le noyaux Linux. Lorsque vous brancherez le programmeur Pololu, 2 nouveaux périphériques /dev/ttyACM? apparaîtront. S'il n'y avait pas ce type de périphérique avant que vous ne branchiez le programmeur Pololu alors les nouveaux seront /dev/ttyACM0 pour le programmeur et /dev/ttyACM1 pour le port sériel de configuration du programmeur.

Geany

Personnellement j'utilise geany comme éditeur car il permet de compiler et programmer l'atTiny en 1 seule opération sans sortir de l'éditeur. On peut donc dire que c'est plus qu'un éditeur, c'est un IDE au même titre que Atmel studio. La syntaxe est colorée, la liste des fonctions et des macros apparait dans une fenêtre et on peut s'y rendre simplement en double-cliquant sur le nom. Comme dans Atmel studio, le résultat de la compilation apparaît dans une autre fenêtre en bas de l'éditeur. Lorsqu'un saisie le nom d'un symbole une liste de choix apparait comme dans Atmel studio. Il existe peut-être d'autres IDE qui travaillent aussi bien que celui-ci, je n'ai pas fait de recherche à ce sujet.

Compilation et programmation

Il faut créer un fichier Makefile pour le projet et le placer dans le répertoire du projet.

Si vous ne connaissez pas l'utilitaire make il faudrait l'étudier un mininum en commencant par la page man make. Pour vous simplifier la vie vous pouvez utiliser l'exemple çi-haut et simplement modifier les paramètres spécifique à votre projet. TINY est le numéro de modèle de MCU utilisé ici 13 (valide aussi pour 13a). TARGET est le nom du fichier 'C' à compiler sans l'extension, ici BLinkLED. Chaque fois que $(symbole) apparaît dans le texte une substitution est effectuée, par exemple ici -mmcu=attiny$(TINY) devient -mmcu=attiny13.

Pour compiler et flasher le MCU à partir de geany allez dans le menu construire (icône bloc de béton rouge) et choississez l'item Make all. Le résultat de l'opération s'affiche dans la fenêtre du bas. S'il n'y a pas d'erreur de compilation avrdude sera lancé automatiquement et le MCU programmé.
Voici le programme BlinkLED pour vous exercer.

Pour conclure lorsque vous branchez le programmeur Pololu dans le port USB et qu'il est relié correctement au MCU et que malgré tout la lumière rouge du programmeur clignote, ça peut vouloir dire que vous n'avez pas les droits d'ouverture du périphérique /dev/ttyACM0. Vous pouvez lancer quand même Make all et avrdude va afficher un message d'erreur s'il ne peut ouvrir le port. Si votre distribution GNU/Linux est Ubuntu ou un dérivé assurez-vous d'être membre du groupe dialout. Vous pouvez obtenir la liste de vos appartenances avec la commande groups dans la console de commandes. Je ne saurais dire si c'est le même groupe pour les autres distros. Réouvrez votre session après vous être ajouté au groupe dialout

mardi 15 janvier 2013

carte de voeux musicale

De nos jours les cartes de voeux musicales sont virtuelles, on les envoie via internet. Mais il y a un certain nombres d'années, je ne sais pas si ça existe encore ou si ça a été une passade, on pouvait acheter une carte de voeux qui jouait de la musique lorsqu'on l'ouvrait.

Voici un projet qui démontre toute la simplicité de ce type de gadget. Il s'agit de créer le circuit d'une telle carte de voeux. Pour le développement on utilise un PIC10F200 en format PDIP-8. Mais s'il nous prenais la fantaisie d'assembler réellement une telle carte il suffirait d'acheter un PIC au format DFN, 1 piézo et une pile au lithium CR2012. Le piezo speaker constitue l'élément le plus mince et la pile avec 1,2mm d'épaisseur le plus épais. C'est 3 éléments montés à plat s'insèreraient dans une carte de voeux sans que se soit trop apparent. Il s'agirait en plus de bricoler un commutateur plat qui mettrait la pile en contact avec l'alimentation du MCU seulement au moment de l'ouverture de la carte. Si vous avez le désir de fabriquer une telle carte je laisse au soins de votre imagination de défi de créer un tel commutateur.

matériel requis

circuit

code source


Le code occupe 127 instructions, même pas la moitié du maigre 256 instructions de ce MCU. Il reste de la place pour ajouter 128 notes à la mélodie...

Le TIMER est configuré pour être contrôlé par Fosc/4 avec un diviseur de 4 ce qui donne 4usec par tick. Les valeurs de la table scale sont donc les valeurs représentant la durée d'un demi-cycle pour chaque fréquence en multiple de 4usec. Les valeurs ont été calculée à l'aide du tableur d'openOffice et arrondi à l'entier le plus proche.

Pour la durée de la note il a fallu prendre un compteur 24 bits même si les valeurs de la table duration sont elle calculée en multiple de 8usec, la première valeur étant de 125000. 8usec parce qu'on ne soustrait qu'une seule fois la valeur de freq_dly à delay_cntr par cycle alors que freq_dly est compter 2 fois par cycle.

Notez que la macro delay_half_cycle réinitialise le timer en ajoutant la valeur freq_dly à la valeur qui est déjà dans le compteur plutôt que par une assignation. Ceci est pour tenir compte du fait que le temps continue à s'écouler entre le moment ou 1 demi-cycle se termine et ou on réinitialise le timer pour le demi-cycle suivant. Ceci cré une onde parfaitement carré malgré le temps d'exécution supplémentaire encouru à la fin du 2ième demi-cycle par l'exécution de la macro skip_timeout et du plus la fréquence est plus juste.

Il n'y a pas grand chose à ajouter à propos de ce code qui est en définitive des plus simple dans sa réalisation.

dimanche 13 janvier 2013

Introduction aux MCU ATtiny, partie 5

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

Le code est simple il suffit d'initaliser le périphérique PWM et de créer une ISR qui est déclenchée lorsque TCNT0 atteint la valeur du comparateur OCR0B. Cette ISR met le prochain échantillon de la table sine_table dans OCR0B et incrémente l'indice pour la prochaine valeur. Le programme utilise la variable pas plutôt qu'une constante pour incrémenter indice afin de pouvoir modifier la valeur de pas à l'exécution dans le but de changer la fréquence. Le programme utilise la canal PWM B dont la sortie est sur la broche 6. Le compteur TCNT0 est configuré pour fonctionner à la fréquence de 37500Hz, mode FAST PWM 3. Cette fréquence est la fréquence d'échantillonnage du convertisseur numérique/analogique.

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)

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.

l'initialisation du convertisseur A/N est simple. Le canal est sélection dans le regsitre ADMUX, le diviseur pour le signal d'horloge est sélectionné dans ADCSRA. J'ai utilisé le plus gros diviseur (128). Et pour activer le convertisseur il faut mettre le bit ADEN dans ADCSRA à 1.

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.

mercredi 9 janvier 2013

introduction aux MCU ATtiny, partie 4

Aujourd'hui je me suis un peut amusé dans l'électronique analogique car j'ai décidé que j'avais besoin d'un petit ampli audio pour la suite de ces chroniques car en plus d'une pulsation 3Hz j'ai ajouter une alarme audio double tonalité pour accompagner cette alarme visuelle. Voici le schéma de l'ampli.


L'entrée de l'ampli est branché sur la broche PORTB1. Même avec la résistance de 22K à l'entrée le son est suffisamment fort pour être entendu à la grandeur de la maison.

Interruption

L'un des concepts important de la programmation système et qu'il faut apprendre très tôt est celui d'interruption et c'est ce que nous allons étudier dans cette chronique.

Les micro-contrôleurs doivent répondrent à des événements extérieurs qui ne sont pas synchronisés avec leur horloge interne, donc qui peuvent survenir à n'importe quel moment dans l'exécution du programme. Il y a deux façon de répondre à ces événements, la première consiste à lire à intervalle régulier les entrées pour savoir quand l'une d'elle a changé d'état. C'est ce que fait la routine attend_pression_bouton présenté dans les chroniques précédentes. Mais cette façon de faire n'est pas très pratique pour plusieurs raisons. Premièrement elle demande beaucoup de temps CPU, temps pendant lequel les autres tâches sont misent en attendent. Si l'événement externe requiert une réponse rapide il faut vérifier l'entrée le plus fréquemment possible ce qui diminu d'autant le temps disponible pour les autres tâches. Cette technique dites de poling n'est vraiment pas efficace. Dans nos exemples précédent ça n'avait pas d'importance puisque le MCU n'avait aucune tâche à accomplir en attendant que le bouton soit enfoncé. Mais le plus souvent le MCU a plusieurs tâches concurrentes à accomplir et doit partager son temps le plus efficacement entre celles-ci.

C'est dans ces situations qu'on a besoin des interruptions. Une interruption comme son nom l'indique est un mécanisme par lequel le déroulement d'un programme est interrompu pour répondre à un événement qui s'est produit. Les sources d'interruptions peuvent-être interne ou externe. Par exemple une minuterie interne au MCU peut déclencher une interruption lorsqu'elle expire. Mais une interruption peut aussi être déclenchée lorsqu'une broche d'entrée a changée d'état, il s'agit alors d'un événement externe.

Dans un cas comme dans l'autre le MCU doit interrompre ce qu'il est en train de faire pour réponde à l'interruption. Les interruptions sont répondues par des routines spécifiques qui ne s'éxécutent pas dans le déroulement normal du programme mais seulement lorsqu'un événement qui les concernes c'est produit. On les appelle sans surprise les routines de service d'interruption, en anglais interrupt service routine, abrégé ISR.

Donc une interruption se produit et le MCU interrompt l'exécution normmale du programme. Mais ça ne peut se faire n'importe comment. D'abord il faut converser la valeur du pointeur d'instruction car lorsque l'ISR sera répondue il faudra revenir à l'endroit où le programme était rendu avant l'interruption. De plus tous les registres utilisés par la routine d'interruption doivent-être préservés. Toutes ces donnés sont conservées sur la pile. La même pile qui est utlisée pour conserver les adresses de retour lorqu'une qu'une sous-routine est appellée.

Ce n'est pas tout, le MCU doit savoir où se trouve la routine qui sert tel ou tel interruption. C'est le rôle de la table des vecteurs d'interruptions. Pour les MCU ATtiny cette table se trouve à l'adresse zéro de la mémoire programme. Chaque source d'interruption a un numéro qui correspond à un index dans cette table. Et le vecteur lui-même dans cette table est simplement une instruction de saut vers l'ISR requise. Pour le code d'aujourd'hui j'ai créé une ISR pour répondre au débordement du compteur TCNT0. Chaque fois que ce compteur retombe de 255 à 0 l'interruption no. 3 est déclenchée. voici de quoi à l'air la table des vecteurs et le code assembleur de la routine ISR.

Comme on peut le voir l'entrée 3 de la table contient une instruction rjmp .+86 qui conduit à l'adresse effective 0x5e qui est le début de l'ISR pour l'overflow du timer0, c'est à dire du compteur TCNT0. Si on examine cette routine on constate que les premières instructions push consiste en l'empilement des registres r0 et r1. L'ISR va se servir de ces 2 registres donc elle en préserve le contenu en l'envoyant sur la pile et va les restaurer avant de quitter avec les instructions pop. Cette routine est très longue et pourtant elle ne fait que basculer la valeur d'une variable (adresses 0x6c à 0x74). Il n'y a que 4 instructions pour le code effectif toutes les autres ne servent qu'à préserver et restaurer le contexte du progamme qui a été interrompu.

On va maintenant regarder comment on cré une interruption en 'C'.
J'ai modifié plusieurs choses par rapport au code d'hier mais on va se concentrer sur la création de la routine de service d'interruption. J'avais besoin de cette routine car lorsque la lumière est en clignotement urgence 3HZ je l'accompagne d'un signal audio double tonalité, 500hz et 750hz. La fréquence bascule en même temps que l'état de la LED. Je devais donc savoir quand la LED change d'état. En mode 3Hz j'ai configuré le PWM en mode bascule. C'est à dire que la sortie change d'état à chaque fois que le comparateur confirme que OCR0A==TCNT0. De plus J'ai configuré le mode 7, c'est à dire que le compteur TCNT0 retombe à zéro en même temps qu'il atteint la valeur de OCR0A. Cette configuration me permet d'obtenir une onde carré à la sortie de PORTB0. Donc lorsque TCNT0 retombe à zéro l'interruption est déclenchée. Cette interruption ne fait que basculer l'indicateur booléen bascule_tone qui est utilisé par la routine alarme() et lui permet de savoir qu'elle fréquence faire entendre.

Si vous regardez au début du code j'ai ajouté la ligne #include <avr/interrupt.h>
Ce fichier défini les macros nécessaires à la création des routines d'interruptions. Plus bas vous apercevez la déclaration de la routine elle-même.

La macro ISR sert justement à déclarer les fonctions qui servent de routine de service d'interruption. En argument de la macro on donne un identifiant indiquant de qu'elle interruption il s'agit. Ces identifiants sont définis dans le fichier avr/io.h. Pour voir le contenu d'un fichier inclus, cliquez avec le bouton droit de la souris sur le fichier et choisissez l'item goto implementation dans le menu surgissant.


Comme vous le voyez cette ISR ne fait vraiment pas grand chose. En passant notez que la variable bascule_tone doit-être une variable globale pour être accessible aussi bien par l'ISR que par la fonction alarme().

Autre point à notez dans ce programme, c'est le changement de la fréquence de l'oscillateur système. Pour obtenir une fréquence suffisamment basse pour générer un PWM de 3HZ j'ai du diviser la fréquence de l'oscillateur par 16, ce qui fait que le core du MCU durant le mode alarme fonctionne à 600Khz au lieu de 9,6Mhz. J'en ai tenu compte lorsque j'appelle les fonctions _delay_ms() et _delay_us() en divisant les valeurs de délais par 16. La fréquence de l'oscillateur principal est remise à 9,6Mhz avant de quitter le mode alarme.

Voilà ce sera tout pour cette chronique. Le code prend maintenant 41% de l'espace programme. Et si on ajoutait une nouvelle fonction! Par exemple la lumière pourrait signaler un appel de détresse en envoyant le code morse international S.O.S.: ...---...

mardi 8 janvier 2013

Introduction aux MCU ATtiny, partie 3

Un coup d'oeil dans les coulisses

Avant de continuer avec la présentation du code pour aujourd'hui on va d'abord jeter un coup d'oeil dans les coulisses. Atmel Studio et GCC nous rendent la vie plus facile en générant du code supplémentaire pour nous. Pour voir le code généré par GCC il faut ouvrir le fichier listing produit par le compilateur. Heureusement il n'est pas nécessaire de chercher bien loin. Il se trouve dans la fenêtre solution explorer dans output files Double-cliquez dessus pour l'ouvrir.

Pourquoi ce petit programme occupe-t-il 192 octets de flash? Moins de 90 aurait du suffire!. Si on regarde le listing on constate que GCC a initialisé 10 vecteurs d'interruptions. Mais notre programme n'utilise aucune interruption donc notre code pourrait commencé à l'adresse 0. 20 octets perdus.
La routine __ctors_end occupe 8 octets, elle initialise le registre R1 comme générateur de constante avec la valeur '0'. Ensuite elle initialise le pointeur de pile à l'adresse 0x9F. Rien à reprocher à cette procédure sauf qu'elle pourrait-être installée à l'adresse 0x00 plutôt que 0x14.
La routine __do_copy_data copie les variables globales initialisées au début de la mémoire RAM à partir de la mémoire flash. Mais notre programme n'utilise pas de variables globales initialisées. 22 octets perdus.
La routine __do_clear_bss initialise à zéro toutes le reste de la RAM c'est à dire celle qui n'a pas été initialisée par __do_copy_data. Notre programme n'utilise aucune variable RAM, cette routine est donc aussi inutile. 40 octets de perdus.
Pour le reste il n'y a rien à reprocher au code que nous avons écris et qui a été compilé efficacement. Ça fait quand même 82 octets perdus par rapport à ce qu'on aurais obtenu si on avait programmé en assembleur.

Mais c'est le prix à payer pour se rendre la vie plus facile par la programmation en 'C' plutôt qu'en assembleur.

Bug dans le code d'hier

Retour sur le code d'hier, il y avait un bug et la seule raison pour laquelle ça fonctionnait est du à une particularité des AVR. Le bug est le suivant, comme je programme rarement en 'C' j'avais oublié que l'opérateur '-' a précédence sur l'opérateur '<<'. La ligne:

DDRB &= (0xFF - 1<<BOUTON); // met broche BOUTON en entrée
aurait du être:
DDRB &= (0xFF- (1<<BOUTON)); // met broche BOUTON en entrée.

La conséquence de cette erreur est que la broche PORTB0 que j'avais configurée en sortie dans une instruction précédente était revenue en mode entrée. Lorsqu'une broche est en mode entrée sur les AVR et qu'on écris un '1' sur cette broche ça active le pullup. Le courant passant à travers le pullup était suffisant pour commuter le transistor en conduction et allumer la LED. Lorsque le programme remettait '0' sur la broche le pullup était retiré et le transistor cessait de conduire. Ainsi le bug est passé inaperçu. Jusqu'à ce que je veuille utiliser le PWM et que je me gratte la tête en me demandant: pourquoi ça ne fonctionne pas?

Selon ma logique les opérateurs '<<' et '>>' devraient avoir la précédence sur les opérateurs '+' et '-' parce que se sont des opérateurs de multiplication et division par puissance de 2. Mais il semble que Kernighan et Ritchie, les auteurs du language 'C', non pas la même logique que moi.

Le code modifié pour aujourd'hui

Comme je l'ai expliqué à la fin de la chronique précédente il s'agit d'ajouter 4 niveaux d'intensité à la LED en utilisant un périphérique PWM. Tous les ATtiny même le plus petit ATtiny4 ont des périphériques PWM implantés. l'ATtiny13a possèdes 2 canaux PWM fonctionnants sur la même minuterie. Nous n'en utiliserons qu'un seul. Puisque pour cette application la fréquence PWM pourrait être aussi basse que 70hz sans que l'oeil ne percoive de scintillement on aurait tout aussi bien put le faire entièrement en software. Allons-y quand même pour la version hardware.

Modulation en largeur d'impulsion

Puisque cette série de chroniques est une introduction et que les périphériques Pulse Width Modulation (PWM) sont des plus utilisés sur les microcontrôleurs je vais expliquer ce que c'est, comment ça fonctionne et à quoi ça sert.

Les sorties numériques des MCU, comme leur nom l'indique, n'ont que 2 niveaux de tensions, soit la sortie est à 0 volt, soit elle est à Vdd, Vdd étant la tension d'alimentation du MCU. Donc si on commute cette sortie entre ces 2 niveaux on obtient une onde rectangulaire. La modulation PWM ou en français Modulation en largeur d'impulsion consiste à faire varier le rapport de durée entre la partie haute et la partie basse de l'onde. Par exemple si la partie haute a la même durée que la partie basse on obtient une onde carrée. En anglais le rapport entre ces 2 alternances du cycle s'appelle le duty cycle que reverso traduit par rapport cyclique. Donc si la partie haute dure 25% la partie basse durera 75% du cycle.

Presque tous les MCU ont un périphérique PWM. Voici comment ça fonctionne dans sa forme la plus simple. Un compteur binaire appellé timer reçoit un signal d'horloge qui incrémente le compte à chaque tick. Dans le cas du ATtiny13a ce compteur s'appelle TCNT0 et c'est un compteur 8 bits. Il peut donc compter de 0 à 255. Lorsqu'il arrive à 255 il retombe à zéro et le cycle recommence. De plus il faut un registre qui lui sert à déterminer le rapport cyclique. Dans le cas du ATtiny13a il y en a 2 parce qu'il y a 2 cannaux PWM, OCR0A et OCR0B. Supposons qu'on utilise OCR0A et qu'on met la valeur 0x7F dans ce registre. A chaque tick de l'horloge qui alimente TCNT0 la valeur de TCNT0 est comparée à celle de OCR0A et si TCNT0 égale OCR0A l'état de la sortie est inversée. Donc supposons qu'au départ TCNT0 a la valeur zéro et que la sortie est à 1. lorsque le compte dans TCNT0 atteindra 0x7F la sortie passera à zéro et le restera jusqu'à ce que TCNT0 débute un nouveau cycle. Le rapport cyclique de la sortie dépend donc de la valeur de OCR0A. Si ce dernier contient zéro la sortie sera toujours à 0 mais si la valeur de OCR0A=0xFF la sortie sera toujours à 1. On peut donc faire varier le rapport cyclique de 0 à 100% par incrément de 1/256.

Maintenant à quoi ça peut servir. Si presque tous les MCU en ont au moins 1 c'est que justement c'est très utile. On peut s'en servir pour positionner un servo-moteur, contrôler la vitesse d'un moteur en rotation continue, dans les circuits régulateurs ou convertisseurs de tension. Comme convertisseur digital/analogique ou encore pour contrôler l'intensité d'une LED comme nous allons faire.

Si on veut que la LED est une intensité de 75% il suffit de mettre le rapport cyclique à 75% donc on mettra dans OCR0A la valeur int(75/100*256). Il suffit que la période du cycle soit supérieur à 70Hz pour que l'oeil ne se rende pas compte du clignotement de la LED. En fait à partir d'environ 20Hz l'oeil ne perçoit déjà plus un clignotement mais un scintillement. A 60Hz on peut percevoir encore un léger scintillement si on observe du coin de l'oeil. A 70hz il n'y a vraiment plus de scintillement perçu même si le rapport cyclique est très bas, on perçois une LED toujours allumée mais à faible intensité.
Voici une capture d'écran prise à l'oscilloscope de la sortie PWM sur PB0. A gauche le rapport cyclique est de 12% et à droite de 50%.

code source

Voilà ce que notre programme va faire, il va faire clignoter la LED très rapidement et on va faire varier le rapport cyclique de 100% en diminuant la valeur de OCR0A de moitié à chaque étape. J'ai testé plusieurs variantes, voici celle que j'ai retenu.

notes sur l'optimisation du code

Le compilateur a beau être optimisant le programmeur doit aussi faire ça part. Voici quelques commentaires à ce sujet.

Cette variante utilise 222 octets de mémoire flash et aucune mémoire RAM. J'ai constaté que lorsqu'on défini une variable globale (variable en dehors des fonctions), cette variable est stockée dans la RAM plutôt que dans la banque de registres ce qui a des conséquences sur la taille du code et la vitesse d'accès à cette variable. Donc autant que possible on défini les variables à l'intérieur des functions. De toute façon c'est une bonne pratique en programmation de réduire les variables globales au mininum. Autre considération, notez que la variable rapport_cyclique est de type uint8_t. Les variables de type int sont de 16 bits donc occupent 2 octets et de plus augmente la taille du code car les AVR étant des processeurs 8 bits doivent manipuler les entiers 16 bits en 2 opérations plutôt qu'une seule. Deux instructions pour l'affectation d'une valeur. Deux instructions pour la copie d'une variable, etc. Donc encore une fois en autant que c'est possible utilisez les types int8_t ou uint8_t.

Pourquoi est-ce que j'ai utilisé une variable rapport_cyclique au lieu de manipuler la valeur directement dans le registre OCR0A? J'ai essayé les deux variantes. La version avec variable réduit le code de 6 instructions. En effet la variable étant enregistrée directement dans la banque de registres il n'est pas nécessaire d'aller la cherchée pour manipuler sa valeur, tandis que pour OCR0A il faut une instruction in pour transférer son contenu dans un registre avant toute manipulation et un out pour retourner le résultat dan OCR0A.

J'ai lu quelque part qu'il était plus efficace de créer une boucle infinie en utilisant l'instruction:

for (;;){}
plutôt que:
while (1){}
C'est faux le compilateur GCC est en mesure de reconaître que dans les 2 cas il s'agit d'une boucle infinie et les compile de la même manière par une instruction rjmp à la fin du bloc d'instructions.

Qu'avons nous appris

  • Ce qu'est un périphérique PWM et comment l'utiliser dans sa version la plus simple, ce qui est suffisant pour une introduction.
  • Dans les expresssions il vaut mieux mettre plus de parenthèses pour grouper les sous-expressions que moins. Les parenthèses ne coûtent rien à la compilation et ça évite les bugs du à une erreur d'interprétation des précédences des opérateurs.
  • Le programmeur même s'il progamme en 'C' doit lire le datasheet et bien comprendre le fonctionnement du micro-processeur sur lequel il travaille. Chaque détail compte.
  • Le programmeur ne peut compter sur le compileur seul pour optimiser le code, là aussi il doit faire sa part.

prochaine étape

On va ajouter une fonction de clignotement à 3Hz pleine intensité. Ça va nécessité une modification à la fonction attend_pression_bouton() car la lumière va entrée dans ce mode seulement si le bouton est maintenu enfoncé plus de 2 secondes.

dimanche 6 janvier 2013

introduction aux MCU ATtiny, partie 2

Maintenant que Atmel studio est installé ainsi que le programmmeur Pololu USB AVR, on peut commencer. tous les documents concernant cette série de chroniques seront enregistrés ici. Mais avant de programmer un MCU il faut évidemment étudier sa strcuture interne. Le core AVR 8 est très différent de celui des PIC mais il ressemble à celui de la majorité des microcontrolleur moderne, que ce soit les MSP430, ARM, MIPS en ce sens qu'il utilise un bloc de registres appellé en anglais general purpose register file alors que les PIC n'ont que le registre W. Toutes les opérations arithmétiques et logiques utilisent le contenu d'au moins 1 de ces registres comme argument et le résultat est retourné dans l'un de ces registres. Cependant comme les PIC l'ARV core est basé sur une architecture Harvard, c'est à dire que la mémoire programme est séparée de la mémoire des données. Le register file du AVR8 contient 32 registres de 8 bits pour la majorité des ATtiny sauf pour les petits ATtiny4,5,9 10 qui n'ont que 16 registres. Voici une représentation interne du AVR core 8.


À gauche c'est la structure général et à droite c'est la représentation du register file avec ces 32 registres. Notez que les 6 registres R26 à R31 ont une fonction spéciale. Utilisés par paire ils forment 3 registres d'indexation de 16 bits, X, Y et Z. C'est l'équivalent des registres FSR des PIC mais en plus puissant. D'abord parce qu'ils ont 16 bits il peuvent pointés jusqu'à 64K de RAM et deuxièmement parce qu'ils permmettent des modes d'adressage indexé, indexé relatif et incrémentation automatique. Chez les MCU PIC on ne retrouve ce genre de fonctionnalité que dans les familles enhanced mid-range (PIC12F1xxxx/PIC16F1xxxx) et extended (PIC18Fxxxx).

Ces registres peuvent aussi être utilisés comme registres d'usage général mais ce n'est pas recommendé à moins que vous n'ayez pas besoin de 3 registres d'indexation.

Autre différence avec les PIC la pile des retours est en RAM et non un mémoire dédiée comme c'est le cas avec les PIC. La dimension de la pile est donc limité seulement par la mémoire RAM disponible et de plus le pointeur de pile peut-être manipulé et on dispose d'instructions push et pop. Cette architecture facilite grandement la création d'un compilateur pour les languages de haut niveau comme le 'C' et permet donc un compilation plus facilement optimisée.

En ce qui concerne l'accès au périphérique c'est le même principe que sur tous les MCU que je connais, c'est à dire que dans l'espace d'adressage RAM il y a des registres spéciaux qui contrôlent la configuration des périphériques. Rien pour être dépaysé. Comme on va programmer en 'C' l'accès aux registres spéciaux se fera par affectation à des variables. C'est à dire que chaque SFR (Special Function Register) a un nom symbolique et que lorsqu'on veut écrire dans un registre on utilise simplement une affectation du genre:
CLKPR = 0 ; // désactivation du diviseur
et pour lire un SFR on fait comme si c'était une variable:
if (PORTB & 1<<4 == 1<<4)... // est-ce que le bit 4 du PORT B est à 1?

Modification au circuit

J'ai légèrement modifié le circuit présenté en partie 1:

Création d'un nouveau projet

J'ai préparé un document au format OpenOffice 3 expliquant les étapes pour la création d'un nouveau projet dans Atmel studio.

Lorsque vous branchez le programmeur Pololu au port USB si le pilote est installé correctement, dans le gestionnaire de périphérique de Windows vous verrez 2 nouveaux ports COM. Celui qui nous intéresse s'appelle Pololu USB AVR programmer Programming Port (COMx). Notez le nom du port COM. Il faut configuré Atmel studio pour l'utilisation de ce PORT.

Dans la barre de menu d'Atmel studio vous verrez l'item tools et dans le menu déroulant de celui-ci il y a add STK500.... Cliquez dessus, sélectionnez le port de votre programmeur Pololu et faite apply.

Maintenant si vous suivez les instructions du document pour créer un nouveau projet GCC vous devriez vous retrouver face à la fenêtre de l'éditeur avec le squelette de l'application en place. On peut maintenant écrire le code.

Pour cette première version de notre application ce sera très simple, on va utiliser le bouton pour allumer et éteindre le LED. A chaque pression du bouton l'état du LED va basculer.

code source
J'ai commenté le code pour que chaque étape soit limpide.

compilation et programmation du MCU

Pour compiler le code pressez la touche F7 ou encore cliquez l'icône build solution ou encore allez dans le menu build et cliquer le premier item. Le résultat s'affiche dans le fenêtre output en bas de l'éditeur. Cliquer sur un message d'erreur nous ramène à la ligne en faute dans le texte.

Lorsqu'il n'y a plus d'erreur on programme l'ATtiny en utilisant l'icône device programming qui est représenté par un C.I. avec un éclair par dessus. On peut aussi aller dans le menu tools.


Dans la fenêtre device programming
  1. Sélectionnez votre programmeur dans la liste déroulante sous tool
  2. cliquez apply
  3. cliquez read sous device signature
  4. allez dans memories et sélectionnez le fichier .hex qui correspond à votre programme.
  5. finalement cliquez sur program. L'état de l'avancement apparait plus bas dans la fenêtre. Si tout est ok il n'y a rien d'autre à faire.

ATTENTION!

Ne faites pas ça. Si vous programmez le fusible RSTDISBL le programmeur Pololu ne pourra plus utiliser ce chip. Seul un programmeur haute-tension peut reprogrammer un chip dont le RESET a été désactivé. C'est pour ça que j'ai modifié le circuuit et mis le bouton sur PORTB3 plutôt que PORTB5.

Ce que j'ai appris

Voici ce que j'ai appris dans cette deuxième étape.

  • Ne pas programmer RSTDISBL quand on utilise un programmeur basse tension comme le Pololu USB AVR. OOPS!
  • Comment désactivé le Watchdog timer
  • Un AVR ce n'est pas un PIC pour connaître l'état d'une entrée on ne lit pas le registre PORTx mais le registre PINx.
  • #include <interrupt.h> ne fonctionne pas. Le compilateur ne trouve pas le fichier... C'est pourquoi j'ai réécris les macros, plutôt que de perdre mon temps à chercher la solution. A voir plus tard.
  • Trop de clicks pour flasher le MCU. Dans MPLABX il suffit d'un seul.
  • Même si on cré un nouveau projet, le fichier HEX qui est indiqué dans device programming, section memories est le dernier sélectionné dans un projet précédent. ARGH!!

prochaine étape

Contrôler l'intensité du LED en utilisant le périphérique PWM.

  1. LED éteint on pèse sur le bouton, le LED allume à 100%
  2. on repèse l'intensité baisse à 75%
  3. A chaque pression l'intensité devra diminuner de 25% jusqu'à l'instinction.

jeudi 3 janvier 2013

Introduction aux MCU ATtiny, partie 1

Cette chronique est la première d'une série consacrée à un tutoriel d'introduction au microcontrôleurs ATtiny d'Atmel. Dans cette première partie je vais décrire les logiciels et le matériel nécessaire pour réaliser les expériences décrites dans cette série. En fait il va s'agir d'un seul projet auquel je vais ajouter des fonctionnalités à chaqe chronique. Toute la progammation va se faire en C.

Environnement de travail

L'environnement de travail sera le même que celui que j'ai utilisé pour ma chronique intitulée premiers pas avec MCU Atmel AVR.

Logiciels

Bien sur vous pouvez utiliser un autre programmeur que celui de Pololu. Mais celui de Pololu a l'avantage de pouvoir être contrôlé directement à partir d'Atmel studio.

Liste de matériel requis

Les liens vers les sites de Digikey et Robotshop ne sont que des suggestions.

Schéma électronique

Voici le montage qui sera utilisé pour toute la série.

installation d'Atmel Studio

Je n'ai rien trouvé en français sur l'installation d'Atmel studio 6.0 seulement ce vidéo en anglais produit par Atmel. En fait il y a plusieurs vidéo sur l'utilisation du studio. Je n'ai pas l'intention de faire une présentation sur la configuration et l'installation de l'environnement de travail. Vous devrez vous débrouiller par vos propre moyens pour ça.

Dans la prochaine crhonique je vais présenter le core AVR 8 et la première expérience que nous ferons avec l'ATtiny13A.