PWM logicielle sur Atmel AVR Attiny2313

Travaillant sur un projet de présentoire lumineux, j’ai eu besoin de disposer de plus que les quelques canaux de PWM que n’en proposent généralement les microcontrôleurs. En particulier les petits AVR comme l’Attiny2313. La seule solution restante était donc de se passer de ces fonctionnalités et d’implémenter la PWM en logiciel de manière à pouvoir piloter un minimum de 6 leds via les port E/S standards.

Le principe de fonctionnement est simple. En réglant l’un des timers de manière à obtenir le maximum de tops d’horloge possible, la plus haute fréquence donc, on peut implémenter, sous la forme d’un compteur et d’une poignée de comparaisons une PWM acceptable. Le début du code est tout ce qu’il y a de plus classique :
.device attiny2313

.def T1 = r1
.def T2 = r2
.def temp = r16
.def truc = r17
.def cpt = r18
.def savestatus = r20
.def zlow = r21
.def zhigh = r22
.def pwm1 = r19
.def pwm2 = r23
.def pwm3 = r24
.def pwm4 = r25
.def pwm5 = r26
.def pwm6 = r27

.org 0x0000
rjmp RESET
.org 0x000D
rjmp TIM0A_CMP
.org 0x0013
Vient ensuite la routine d’interruption correspondant à la comparaison de timer. Ici on trouve cpt, le compteur. Il est incrémenté à chaque interruption puis sa valeur est comparée aux variables réglant le PWM pour chaque sortie. Tant que le compteur est en dessus de la valeur d’une variable la sortie correspondante est active. Si le compteur est au delà, la sortie est à la masse. Notez qu’il faut penser à sauvegarder le registre d’état car les comparaisons successives risquent de perturber les drapeaux et donc de nuire au bon fonctionnement du reste du code :
TIM0A_CMP:
in savestatus,SREG
inc cpt

cp cpt,pwm1
brlo lowrouge
cbi PORTB,PB4
rjmp vert
lowrouge:
sbi PORTB,PB4
vert:
cp cpt,pwm2
brlo lowvert
cbi PORTB,PB3
rjmp bleu
lowvert:
sbi PORTB,PB3
bleu:
cp cpt,pwm3
brlo lowbleu
cbi PORTB,PB2
rjmp uv
lowbleu:
sbi PORTB,PB2
uv:
cp cpt,pwm4
brlo lowuv
cbi PORTB,PB1
rjmp blanc
lowuv:
sbi PORTB,PB1
blanc:
cp cpt,pwm5
brlo lowblanc
cbi PORTB,PB0
rjmp jaune
lowblanc:
sbi PORTB,PB0
jaune:
cp cpt,pwm6
brlo lowjaune
cbi PORTB,PB5
rjmp fin
lowjaune:
sbi PORTB,PB5
sbi PORTB,PB5
fin:
out SREG,savestatus
reti
On définit également deux petites routines de retardement :
mini:
clr T1
hop:
dec T1
brne hop
dec temp
brne hop
ret

micro:
dec temp
brne micro
ret
Enfin, on arrive au reste où il faut, dans l’ordre, définir la pile, utiliser l’octet de calibration propre à chaque AVR, placer les ports en sortie, définir l’effacement du timer en comparaison, le prescaling, le niveau de référence pour la comparaison et le déclenchement d’une interruption en comparaison :
RESET:
ldi temp, 0x70
out SPL, temp

ldi temp,0x52
out OSCCAL,temp
ldi temp,0xf0
rcall mini

sbi DDRB,PB0
sbi DDRB,PB1
sbi DDRB,PB2
sbi DDRB,PB3
sbi DDRB,PB4
sbi DDRB,PB5

ldi temp,(1<<WGM01)
out TCCR0A,temp

ldi temp,(1<<CS00)
out TCCR0B,temp

ldi temp,0x02
out OCR0A,temp

ldi temp,(1<<OCIE0A)
out TIMSK,temp

sei
Arrive ensuite la boucle principale où l’on pioche dans la mémoire flash les valeurs des variables pwm*. Cette boucle sera interrompue par les interruptions du timer. Cela manque de précision mais est largement suffisant pour faire pulser des leds proprement :
loop: ldi ZH, high(tbpwm1*2)
ldi ZL, low(tbpwm1*2)
add ZL, zlow
adc ZH, zhigh
lpm
mov pwm1,r0

ldi ZH, high(tbpwm2*2)
ldi ZL, low(tbpwm2*2)
add ZL, zlow
adc ZH, zhigh
lpm
mov pwm2,r0

ldi ZH, high(tbpwm3*2)
ldi ZL, low(tbpwm3*2)
add ZL, zlow
adc ZH, zhigh
lpm
mov pwm3,r0

ldi ZH, high(tbpwm4*2)
ldi ZL, low(tbpwm4*2)
add ZL, zlow
adc ZH, zhigh
lpm
mov pwm4,r0

ldi ZH, high(tbpwm5*2)
ldi ZL, low(tbpwm5*2)
add ZL, zlow
adc ZH, zhigh
lpm
mov pwm5,r0

ldi ZH, high(tbpwm6*2)
ldi ZL, low(tbpwm6*2)
add ZL, zlow
adc ZH, zhigh
lpm
mov pwm6,r0

inc zlow
inc zlow

ldi temp,0x50
rcall micro
rcall mini

rjmp LOOP
Enfin, pour terminer, nous avons les valeurs stockées. Ici 6 sinus identiques légèrement décalés les uns par rapport aux autres :
tbpwm1:
.db 128,131,134,137,140,144,147,150,153,156,159,162,165,168,171,174
.db 177,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216
.db 218,220,222,224,226,228,230,232,234,235,237,239,240,241,243,244
.db 245,246,248,249,250,250,251,252,253,253,254,254,254,254,254,254
.db 254,254,254,254,254,254,254,253,253,252,251,250,250,249,248,246
.db 245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220
.db 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179
.db 177,174,171,168,165,162,159,156,153,150,147,144,140,137,134,131
.db 128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82
.db 79,77,74,71,68,65,63,60,57,55,52,50,47,45,43,40
.db 38,36,34,32,30,28,26,24,22,21,19,17,16,15,13,12
.db 11,10,8,7,6,6,5,4,4,4,4,4,4,4,4,4
.db 4,4,4,4,4,4,4,4,4,4,5,6,6,7,8,10
.db 11,12,13,15,16,17,19,21,22,24,26,28,30,32,34,36
.db 38,40,43,45,47,50,52,55,57,60,63,65,68,71,74,77
.db 79,82,85,88,91,94,97,100,103,106,109,112,116,119,122,125

tbpwm2:
.db 11,12,13,15,16,17,19,21,22,24,26,28,30,32,34,36
.db 38,40,43,45,47,50,52,55,57,60,63,65,68,71,74,77
.db 79,82,85,88,91,94,97,100,103,106,109,112,116,119,122,125
.db 128,131,134,137,140,144,147,150,153,156,159,162,165,168,171,174
.db 177,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216
.db 218,220,222,224,226,228,230,232,234,235,237,239,240,241,243,244
.db 245,246,248,249,250,250,251,252,253,253,254,254,254,254,254,254
.db 254,254,254,254,254,254,254,253,253,252,251,250,250,249,248,246
.db 245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220
.db 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179
.db 177,174,171,168,165,162,159,156,153,150,147,144,140,137,134,131
.db 128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82
.db 79,77,74,71,68,65,63,60,57,55,52,50,47,45,43,40
.db 38,36,34,32,30,28,26,24,22,21,19,17,16,15,13,12
.db 11,10,8,7,6,6,5,4,4,4,4,4,4,4,4,4
.db 4,4,4,4,4,4,4,4,4,4,5,6,6,7,8,10

tbpwm3:
.db 38,36,34,32,30,28,26,24,22,21,19,17,16,15,13,12
.db 11,10,8,7,6,6,5,4,4,4,4,4,4,4,4,4
.db 4,4,4,4,4,4,4,4,4,4,5,6,6,7,8,10
.db 11,12,13,15,16,17,19,21,22,24,26,28,30,32,34,36
.db 38,40,43,45,47,50,52,55,57,60,63,65,68,71,74,77
.db 79,82,85,88,91,94,97,100,103,106,109,112,116,119,122,125
.db 128,131,134,137,140,144,147,150,153,156,159,162,165,168,171,174
.db 177,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216
.db 218,220,222,224,226,228,230,232,234,235,237,239,240,241,243,244
.db 245,246,248,249,250,250,251,252,253,253,254,254,254,254,254,254
.db 254,254,254,254,254,254,254,253,253,252,251,250,250,249,248,246
.db 245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220
.db 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179
.db 177,174,171,168,165,162,159,156,153,150,147,144,140,137,134,131
.db 128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82
.db 79,77,74,71,68,65,63,60,57,55,52,50,47,45,43,40

tbpwm4:
.db 177,174,171,168,165,162,159,156,153,150,147,144,140,137,134,131
.db 128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82
.db 79,77,74,71,68,65,63,60,57,55,52,50,47,45,43,40
.db 38,36,34,32,30,28,26,24,22,21,19,17,16,15,13,12
.db 11,10,8,7,6,6,5,4,4,4,4,4,4,4,4,4
.db 4,4,4,4,4,4,4,4,4,4,5,6,6,7,8,10
.db 11,12,13,15,16,17,19,21,22,24,26,28,30,32,34,36
.db 38,40,43,45,47,50,52,55,57,60,63,65,68,71,74,77
.db 79,82,85,88,91,94,97,100,103,106,109,112,116,119,122,125
.db 128,131,134,137,140,144,147,150,153,156,159,162,165,168,171,174
.db 177,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216
.db 218,220,222,224,226,228,230,232,234,235,237,239,240,241,243,244
.db 245,246,248,249,250,250,251,252,253,253,254,254,254,254,254,254
.db 254,254,254,254,254,254,254,253,253,252,251,250,250,249,248,246
.db 245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220
.db 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179

tbpwm5:
.db 245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220
.db 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179
.db 177,174,171,168,165,162,159,156,153,150,147,144,140,137,134,131
.db 128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82
.db 79,77,74,71,68,65,63,60,57,55,52,50,47,45,43,40
.db 38,36,34,32,30,28,26,24,22,21,19,17,16,15,13,12
.db 11,10,8,7,6,6,5,4,4,4,4,4,4,4,4,4
.db 4,4,4,4,4,4,4,4,4,4,5,6,6,7,8,10
.db 11,12,13,15,16,17,19,21,22,24,26,28,30,32,34,36
.db 38,40,43,45,47,50,52,55,57,60,63,65,68,71,74,77
.db 79,82,85,88,91,94,97,100,103,106,109,112,116,119,122,125
.db 128,131,134,137,140,144,147,150,153,156,159,162,165,168,171,174
.db 177,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216
.db 218,220,222,224,226,228,230,232,234,235,237,239,240,241,243,244
.db 245,246,248,249,250,250,251,252,253,253,254,254,254,254,254,254
.db 254,254,254,254,254,254,254,253,253,252,251,250,250,249,248,246

tbpwm6:
.db 218,220,222,224,226,228,230,232,234,235,237,239,240,241,243,244
.db 245,246,248,249,250,250,251,252,253,253,254,254,254,254,254,254
.db 254,254,254,254,254,254,254,253,253,252,251,250,250,249,248,246
.db 245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220
.db 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179
.db 177,174,171,168,165,162,159,156,153,150,147,144,140,137,134,131
.db 128,125,122,119,116,112,109,106,103,100,97,94,91,88,85,82
.db 79,77,74,71,68,65,63,60,57,55,52,50,47,45,43,40
.db 38,36,34,32,30,28,26,24,22,21,19,17,16,15,13,12
.db 11,10,8,7,6,6,5,4,4,4,4,4,4,4,4,4
.db 4,4,4,4,4,4,4,4,4,4,5,6,6,7,8,10
.db 11,12,13,15,16,17,19,21,22,24,26,28,30,32,34,36
.db 38,40,43,45,47,50,52,55,57,60,63,65,68,71,74,77
.db 79,82,85,88,91,94,97,100,103,106,109,112,116,119,122,125
.db 128,131,134,137,140,144,147,150,153,156,159,162,165,168,171,174
.db 177,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216
Bien entendu, ce code n’est qu’une première ébauche. Il est possible de condenser le code de l’interruption et de la boucle principale pour économiser du temps et de la mémoire. De plus il faut prendre en considération que ce code occupe déjà quelques 85% de la mémoire flash d’un Attiny2313. En voulant utiliser plus de motifs ou plus de sorties, on se heurte à un problème d’espace. Il me faudra donc trouver un autre support de stockage, comme une carte MMC, pour pousser plus loin.

Attiny15, PWM, led qui pulse, Sinus et GNU bc

Le microcontrôleur AVR Attiny15 offre une fonctionnalité pour faire de la PWM (Pulse-width modulation) ou de modulation de largeur d’impulsions en bon français. Ceci permet, par exemple, de faire varier l’intensité lumineuse d’une Led ou la vitesse d’un moteur. Pour cela, on utilise dans le code de l’Atiny15 une valeur entre 0 et 255. Pour obtenir une fluctuation d’intensité qui donne l’impression que la Led pulse (comme sur les Mac en veille) il faut utiliser une série de valeurs sinusoïdale. C’est là que GNU bc intervient et se révèle sous sa vrai nature : bien plus qu’une calculette en ligne de commande, un véritable langage.

Nous avons besoin de 256 valeurs hexadécimales entre 0 et 255 qui soient répartie à la façon d’un sinus. Hors de question d’utiliser un simple compteur de 255 à 0 puis de 0 à 255. Nous n’obtiendrons pas une pulsation ainsi. Il nous fait un sinus. Ca, bc, nous le donne de bon coeur avec la fonction s(x). Il nous faut ensuite décaler le sinus sur x de pi/2 (1) et sur y de 1 pour ne pas avoir de valeurs négatives. Nous calculons donc :
((s(3.14159*(0.5+((2/256)*a)))+1)/2)*256)
Aaaaah ! Du calme, découpons :

a : c’est notre valeur de base entre 1 et 256
Nous avons besoins de deux sommets du sinus. Le premier est à pi/2 et le suivant à pi fois 2.5. A ces deux points sur x nous avons 1 sur y. Nous avons donc 2 fois pi sur x entre les deux sommets. 2 que nous découpons en 256 tranches. Le premier point est pi fois 0.5 plus 2/256 fois 1. Le dernier est pi fois 0.5 plus 2/256 fois 256. Donc : 3.14159*(0.5+((2/256)*a))
Nous n’oublions pas d’ajouter 1 au résultat pour « remonter » le sinus variant alors entre 0 et 1 en non plus -1 et 1.
Notre sinus fait 2 « de haut » on « ratatine » en divisant par 2

Enfin pour obtenir un sinus entre 0 et 255 on multiplie par 256 (l’arrondis se chargera du reste)

L’arrondis, justement. Il n’y a pas d’arrondis dans bc (enfin pas exactement). Nous pouvons jouer sur la variable scale permettant de définir le nombre de décimales significatives. Si nous utilisons scale=0 notre 3.14159 devient 3 et le calcul est complètement faussé. Donc….

Première démonstration de la puissance de bc. On créé simplement la fonction qui nous manque :
define i(x) {
auto s
s = scale
scale = 0
x /= 1
scale = s
return (x)
}
Fonction très simple prenant en argument une valeur et retournant la même divisée par 1. On change au passage la valeur de scale ce qui élimine les décimales. Résultat, après avoir enregistré cette fonction dans un fichier toto.bc :
$ echo « i(10/3) » | bc -l toto.bc
3
Bingo ! Et là vous vous dites, « il ne reste plus qu’à mettre une bonne couche de shell et » :
for ((i=1;i<=256;i+=1));
do echo « obase=16;i(((s(3.14159*(0.5+((2/256)*$i)))+1)/2)*256) » |
bc -l toto.bc;
done
Rappelons que obase permet de spécifier la base en sortie (16 = hexa).

Et bien non ! Si bc permet à l’utilisateur de créer des fonctions il doit bien être capable de supporter des boucles. Nous ajoutons donc, dans notre toto.bc :
obase=16

auto a
for(a=1;a<=256;a=a+1) {
i(((s(3.14159*(0.5+((2/256)*a)))+1)/2)*256)
}
quit
Et voilà. Il ne nous reste plus qu’à faire :
$ bc -l toto.bc
FF
FF
FF
FF
FF
FE
FE
FD
FC
FC
FB
FA
[…]
FB
FC
FC
FD
FE
FE
FF
FF
FF
FF
FF
FF
Voilà notre beau sinus pour notre Attiny15.

Note sur la PWM : la modulation de largeur d’impulsions est une technique pour faire varier l’intensité (Led) ou la vitesse (moteur) d’un composant qui ne peut pas être contrôlé en variant la tension ou le courant. Dans le cas d’une Led, elle a besoin de 10mA pour fonctionner (selon le modèle) et fait apparaître une tension à ses bornes (+/- 2 Volts selon le modèle). Pour faire varier l’intensité lumineuse, il faut la faire clignoter très rapidement, notre oeil fera le reste. Avec la PWM on fait clignoter la Led à une fréquence fixe et on joue sur le rapport cyclique. 100% d’intensité ce sont des « clignotements » toujours allumés. 0%, toujours éteint. 50% un « clignotement » sur deux est allumé. 10% c’est un sur 10, etc. La Led ne clignote jamais plus ou moins vite, on passe simplement des « tranches » à « éteint ». Avec un Attiny15 on règle le rapport cyclique avec une valeur entre 0 et 255 dans le registre OCR1A. Ce sont donc les valeurs de ce registre que nous avons calculé avec bc.

Stabilisation du quadruple afficheur 7 segments USB

Juste pour information, l’ajout d’une capa de 10 uF entre Vcc et GND est effectivement suffisant pour éviter les problèmes sur certaines machines (comme on pouvait s’en douter). Le montage semble maintenant stable et ne pose plus de problème, en tout cas depuis quelques 48 heures. J’ai également décidé de diffuser les sources du firmware dérivé des travaux de Dick Streefland pour son adaptateur USB SPI. En GPL donc.

Voici le tarball du firmware à copier directement dans les sources d’usbtiny. Attention, c’est du vite fait, du quick’n’dirty, du gruik coding de 10 minutes… Je ferai propre lorsque j’aurais un peu de temps devant moi. En attendant, il faudra vous satisfaire de ceci :

http://www.lefinnois.net/7seg.tar.gz

Tant qu’à faire, voici aussi le PDF du montage. La plupart des informations s’y trouve, sauf les résistances de 470 ou 330 permettant de relier le circuit du bas avec les 4511 et l’AVR et celui du haut avec les afficheurs à cathodes communes. Un coup d’oeil sur la photo et vous devriez comprendre :

Afficheur 4 fois 7 segments sur USB

Je poursuis mes essais concernant l’utilisation de l’Atmel AVR Attiny2313 cadencé à 12Mhz (quartz) permettant de gérer de manière entièrement soft l’USB. Voici un premier projet visant à remplacer un montage équivalent sur port parallèle : un notificateur/compteur de mail.

J’utilise désormais la technique du transfère de toner détaillées dans un autre billet. Cette fois, en lieu et place du fer à repasser j’ai utilisé une plastifieuse à document légèrement bidouillée. La température et la pression exercée sont constant mais quelques réglages devront encore être fait. Les pistes un peu épaisse ne sont pas parfaite en raison de la non uniformité du toner déposé par l’imprimante. Le problème ne se pose pas avec le fer à repasser car le toner fond bien plus et les imperfections se corrigent d’elles-même. Malheureusement, la pression du fer est totalement aléatoire et l’écrasement des pistes peut créer des faux contactes et autres bouchage de vias.

Avec des pistes épaisse les problèmes de recouvrement se pose mais cela reste acceptable. Un petit coup de lampe à leds permet de s’en assurer :

L’autre avantage du transfère de toner est de pouvoir très facilement s’occuper du marquage côté composants. Cela donne une touche plus pro :

Même de près :

Le montage ici présent utilise, bien entendu, le fameux Attiny2313 couplé à 4 décodeur BCD vers 7 segments 4511. On les voit ici, juste sous les afficheurs (oui, il y a une pétouille corrigée à la hache avec l’un des connecteurs PDIP):

Comme vous pouvez vous en douter le placement du module supérieur était une vrai partie de plaisir avec les 28 résistances devant s’insérer parfaitement dans le module inférieur. Etre Zen pour faire de l’électronique tu dois…

Au finale, le montage complet occupe un peu trop de place à mon goût mais je ne suis pas mécontent du routage finalement :

Les tests sont également concluant :

Malheureusement, après des essais sur plusieurs machines il semblerait qu’il y ai un problème de stabilité de l’alimentation. L’absence de condensateur entre le +5 et la masse n’est sans doute pas pour rien dans l’affaire. Je comprend mieux maintenant pourquoi le créateur d’usbtiny recommande d’activer la Brown Out Detection. Une autre solution est d’alimenter le montage en +5V stabilisé plutôt que via l’USB. Le montage pompe quelques 85mA, nous ne sommes vraiment par loin des 100mA que l’USB peut fournir sans négociation…

PS : Ah oui, j’oubliais. Je grave maintenant les circuits au persulfate de sodium. C’est plus pratique (transparent) et moins nocif et polluant que le perchlorure de fer. Un jour peut-être me laisserai-je tenter par le couple acide + eau oxygénée 30% (disponible chez Castorama). Mais je ne suis pas encore très chaud pour les dégagement gazeux dangereux..