Accueil > electronique, hack > Leçon au plus près du bit : streamer audio sur ATtiny 15L

Leçon au plus près du bit : streamer audio sur ATtiny 15L

binary_wave

Vous connaissez Arduino? Non vraiment ?
Mais si, on en voit tout les jours sur hackaday. Des platines toutes faites avec un microcontrôleur AVR et quelques périphériques prêts à l’emploi.
La guerre dans cette gamme de micro contrôleurs a fait rage pendant des années entre Microchip et ATMEL. Après des débats passionnés entre les utilisateurs et des articles comparatifs provenant des fabricants, il semblerait bien qu’ ATMEL ait remporté une manche avec le phénomène Arduino.
Arduino, une(des) plateforme(s) peu onéreuse(s), rassemble une communauté qui partage code et connaissances.
Au niveau logiciel, tout a été fait pour faciliter le développement à l’aide de sktech, ajoutant une couche d’abstraction supplémentaire au code enfoui. Des librairies nombreuses ont aussi vu le jour. Ainsi, il est aisé de parler le SPI, d’utiliser un UART, d’accéder à un format de fichier FAT et même de jouer de la musique avec un Arduino. C’est bien tout ça, c’est sûr. Mais n’est-ce pas un peu dommage ?
Où est l’aspect éducatif et surtout didactique de la démarche ? Pire, il y a des cas où les limitations imposées par cette abstraction logicielle vous empêcheront de réaliser votre projet. Des cas où les tâches critiques rendront la notion de temps réel primordiale.
Aussi, le projet que je vous présente sera basé sur un ATtiny15L un tout petit microcontrôleur RISC 8 bits de chez ATMEL. On ne peut le programmer qu’en assembleur. Même pas en C crieront certains !! C’est pourtant la meilleure approche (et pour moi la plus ludique) de faire connaissance avec l’univers passionnant des micrôcontroleurs et de parfaire sa culture numérique.

schem_principeComme le montre le shéma ci-contre, le projet est le suivant. Le PC joue un fichier son (ou récupère un flux audio sur internet c’est au choix), l’envoie sur le microcontrôleur via une liaison série. Enfin, le microcontrôleur joue ce son sur une sortie audio. C’est en quelque sorte une carte son minimaliste
D’un point de vue didactique cela nous amènera à nous pencher sur la numérisation du son, les contraintes temps réels de la liaison PC-microcontrôleur et la conversion analogique numérique.
On trouve pas mal de projets similaires, à la différence que dans nombre des cas, le montage est autonome en stockant les données musicales sur de la mémoire flash (via une liason SPI entre la Flash et l’AVR). En procédant de la sorte, on reste détaché des contraintes temps réels avec la liaison au PC. J’ai donc choisi une voie différente, ce qui de fait évite des composants supplémentaires (mémoire flash et surtout programmateur pour celle-ci). De plus cela pimente un peu le jeu.

Avant de commencer

Le documents nécessaires concernant le uc sont:
– le datasheet de l’ATtiny15L
– l’AVR ASSEMBLER User Guide de chez Atmel
Le code est compilé avec avra qui existe aussi en paquet pour debian squeeze et wheezy. L’avantage de ce compilateur c’est que le code est pleinement compatible avec l’assembleur d’AVR studio donc avec les instructions décrites dans les docs d’Atmel.
Le programmateur « in situ » (ISP) utilisé est un clône chinois d’un ATVRISP2 acheté il y a quelques années sur Ebay. Il fonctionne parfaitement avec avrdude empaqueté pour debian.
La connexion série entre le PC et l’AVR s’établit sur des niveaux de tension TTL 3.3 V grâce à un convertisseur USB / Série de type FTDI dont le driver est natif sous linux.

Numérisation du son

wav
Une onde sonore telle que représentée sur le schéma est caractérisée par son amplitude et sa fréquence. La numérisation du son consiste à donner une valeur numérique de l’amplitude (en vert sur le schéma) lors d’un échantillonnage à intervalle de temps constant (en bleu).
Si on rejoue par la suite les différentes valeurs numériques précédemment enregistrées avec un intervalle de temps correspondant à l’échantillonnage, on obtient un signal dégradé mais néanmoins semblable à l’original. Des valeurs d’amplitudes numériques élevées ainsi qu’un fréquence d’échantillonnage importante sont garantes de la fidélité du signal. Dans notre cas, le minimalisme du matériel retenu nous impose les choses suivantes (nous verrons ensuite pourquoi):
l’échantillonnage sera fait avec une fréquence de 8 Khz et les valeurs d’amplitudes seront sur 8 bits (donc de 0 à 255). Pour les même raisons, nous nous limiterons à 1 seul canal (mono).
Il est sûr que notre petite carte son 8 bits ne nous permettra pas une qualité d’écoute optimale mais rappellera avec une certaine nostalgie les premiers sons de l’aire numérique😉

Liaison Série

Dans notre cas, elle sera unidirectionnelle du PC vers l’AVR. Sur les gammes supérieures de microcontôleurs, il existe des UART matérielles configurables par des registres. Sur l’Attiny15 ce n’est pas le cas il faudra donc le programmer (Bit bannging).
On choisira le mode suivant pour la communication: 1 bit de start, 8 bits de données et 1 bit de stop soit un total de 10 bits pour un octet de données transféré. Si l’échantillonnage est de 8 KHz, cela signifie que 8000 octets devront être traités en 1 seconde. La transmission série nous donnera (baudrate/10) octets par seconde.On doit donc avoir (baudrate/10) > 8000, soit baudrate > 80000.
En choisissant un baudrate usuel, le premier à remplir cette condition est bds=115200.

diag Pour rappel, le petit schéma ci contre: Le PC est prêt à envoyer donc le signale par un état bas (S). Puis, sont envoyées successivement les données avant qu’un état haut signale de stopper , c’est le bit de stop (P).
Sur l’adaptateur FTDI, un état bas est à 0 V (0 logique) tandis qu’un état haut est à 5V (1 logique). Les données sont envoyées selon une période que nous appellerons  TBit avec TBit=(1/baudrate).
Vu que l’on va travailler à haut débit (enfin « haut débit pour l’AVR ) la meilleure façon de procéder est d’attendre le bit de start puis d’attendre une demie période (on se positionne au milieu de S sur le schéma) avant de faire des mesures toutes les périodes jusqu’au bit de stop. On minimise ainsi le risque de louper une donnée en se permettant des erreurs de + ou – 1/2 période.
get_char

Dans le code qui suit, "cnt" est un registre initialisé à 9.
Le nombre de cycles pris par les instructions est indiqué en commentaire en bleu.

;
;=====================
GET_CHAR:
sbic PINB,PRx
rjmp GET_CHAR
;#
;# Attendre T1
;# Avec T1=1/2 TBit
;#
LOOP:

;#
;# Attendre T2
;# T2=Tbit-9 cycles
;#

clc ; (1)
sbic PINB,PRx ; (2 ou 3)
sec ; (1)
dec cnt ; (1)
breq END ; (1 ou 2)
ror Rx ; (1)
rjmp LOOP ;( 2)

END:
ret
;=====================
;

On voit dans le code que la détection d’un 1 logique ou d’un 0, prend 9 cycles d’horloge.
On a Tbit=(1/baudrate) secondes.
Soit « fq » la fréquence d’horloge de notre microcontrôleur.
On a donc Tbit=(fq/baudrate) cycles =>
T1=(fq/2xbaudrate) cycles
T2=[(fq/baudrate)-9] cycles
Pour l’ATtiny15, la féquence d’horloge fq=1,6 Mhz. Ce qui donne pour un baudrate=115200 bds
T1=6.94 cycles soit 7 nop
T2=4.89 cycles soit 5 nop
On voit bien ici tout l’intérêt de programmer en assembleur lors de cette tâche critique car on doit respecter au cycle d’horloge prêt le timing pour garantir l’intégrité de la transmission.
A ce sujet, dans le datasheet du uc, il est indiqué de fortes variations de l’oscillateur interne autour de la fréquence « fq » de 1.6 Mhz. Ces variations peuvent être dues à la stabilité ou au voltage de votre alimentation ainsi qu’à la température ambiante.
Pour cette raison, il est très important de rentrer la bonne valeur de configuration dans le registre OSCCAL dédié à un réglage fin de l’oscillateur.
En sortie d’usine, une calibration faite sous 5 V à 25 °C donne une valeur du registre OSCCAL qui est ensuite enregistrée dans les fusibles du uc.
Il faut donc lire cette valeur de fusible avec votre programmateur pour ensuite la réintégrer au début de votre programme. Par exemple dans mon cas, la valeur stockée dans les fusibles est 0x8F, ce qui nous donnera le code assembleur suivant:

RESET:
ldi r16,0x8F
out OSCCAL,r16

Bien sûr cela ne reste utile que si vous utilisez votre montage dans les mêmes conditions que celles prises pour la calibration d’usine (cad 5V stable, 25°C). Dans un cas contraire, il faudrait s’équiper d’un matériel de mesure précis (analyseur logique) afin de trouver la bonne valeur pour atteindre la fréquence fq.
Note: C’est cette possibilité de configurer l’oscillateur interne qui permet entre autre d’overclocker le uc et même de façon importante avec toutefois l’inconvénient dans ce cas de ne pas garantir une complète stabilité d’horloge.

Pour autant, tout n’est pas fini avec cette routine de réception d’octet GETCHAR.
Il faut pouvoir restituer les octets à une fréquence de 8000 Hz pour respecter le temps d’échantillonnage du son. Or, nous sommes en mesure de recevoir 1 octet toutes les [1/(baurate/10)] secondes soit à une fréquence de 11520 Hz
Une fois l’octet reçu, il faudra organiser sa conversion analogique avant de reboucler sur une nouvelle réception d’octet. Or, nous le verrons, tout cela ne prendra que quelques cycles d’horloges.
Comme pour le PC, le temps de latence sera quasi nul entre deux envois, on se retrouvera donc avec une fréquence proche 11520 Hz pour le son restitué; ce qui le rendrait complètement déformé et inaudible.
Du côté du uc:
si on temporise entre chaque réception d’octet, alors on perd des octets envoyés par le PC car lui,rien ne l’arrêtera.
Une bufférisation est impossible (pensez donc, au mieux 32 registres disponibles !!)
Du côté du PC plusieurs pistes:
La première est de contraindre le PC à envoyer ses octets en prenant un temps de pause entre chaque envoi. L’écart de fréquence étant de l’ordre de 3500 Hz cela nous donne un pas de 285 us. Cela me semble très difficile à obtenir sauf peut-être en utilisant un noyau temps réel (et encore !!). Donc on oublie.
La deuxième est de ralentir le débit de transmission de données afin de synchroniser l’émission côté PC avec la restitution côté uc. On s’approcherait donc d’un baudrate à 80 000 bauds. Bien que possible, cette solution n’a pas été retenue car je la trouve guère portable. En effet, tous les logiciels de communication série (et au-delà tous les OS) , ne supportent pas un débit fantaisiste. Donc on oublie aussi.
La troisième possibilité retenue est la mise en oeuvre d’un contrôle matériel du flux (Hardware Handshake) sur la liaison série. Voyons comment cela fonctionne et comment l’implémenter dans notre routine.
Sur un port série traditionnel et même sur les câbles adaptateurs USB, on trouve deux broches nommées RTS (Request To Send) et CTS (Clear To Send). Voilà comment s’interconnectent deux terminaux sur une liaison série 5 fils:
RTS/CTS
Ces deux signaux (RTS/CTS) supplémentaires servent à assurer le controle du flux de transmission. Ils permettent à l’équipement de réception de signaler qu’il n’est pas apte à recevoir d’autres données.
Voyons cela sur le chronogramme:
RTS_2
Protocole:
1) L’émetteur positionne RTS1 à 0 : c’est la requête d’émission, il demande à émettre
2) Le récepteur positionne CTS1 à 0 : il est prêt à recevoir.
3) L’émission commence
4) L’émetteur signale la fin d’émission ( il redemandera une émission en 6))
5) Le recepteur acquiesce, en repassant CTS1 à l’etat 1, une autorisation sera possible + tard
(8)
6) L’émetteur fait une nouvelle requête d’émission
7) elle n’est pas prise en compte tout de suite car le recepteur est occupé…)
8) Le recepteur est de nouveau prêt et acquièce.
9,10…) la suite est identique à 3,4 5

Dans notre cas, il suffira donc d’utiliser la broche CTS du PC hôte pour contraindre celui-ci à temporiser l’envoie de ces données. Aussi, l’envoi d’un nouvel octet sera asservi à l’appel de la routine GETCHAR permettant à loisir de temporiser la réception.
Voila au final à quoi ressemblera la routine GETCHAR:

;=====================
GET_CHAR:
cbi PINB,CTS ; mise à l'état bas de CTS: déblocage émission
sbic PINB,PRx
rjmp GET_CHAR
nop
nop
nop
nop
nop
nop
nop

LOOP:

nop
nop
nop
nop
nop

clc
sbic PINB,PRx
sec
dec cnt
breq END
ror Rx
rjmp LOOP

END:
sbi PORTB,CTS ; mise à l'état haut de CTS: blocage émission
ret
;=====================

Note:
Au niveau du uc, inutile de scruter l’état du port RTS du PC hôte. En effet, la vitesse de calcul de celui-ci est tellement plus élevée que le port RTS ne sera jamais détecté à l’état haut (sauf en cas de déconnexion)

Conversion analogique (DAC)

Celle-ci sera effectuée de façon conventionnelle par la sortie PWM de l’Attiny suivi d’un filtre RC passe-bas. Après une temporisation permettant de se caler sur la fréquence d’échantillonnage de 8 KHz, il suffira donc de basculer l’octet reçu par GETCHAR dans le registre OCRIA contrôlant de fait le rapport cyclique et donc, la tension de sortie.
Il existe de nombreuses documentations sur la modulation de largeur d’impulsion pour comprendre son fonctionnement.
Afin de faciliter le montage, la sortie audio attaque directement un jack que l’on branchera sur l’entrée ligne de n’importe quelle sono (vérifiez quand même votre montage avant !!).
Au final avec seulement trois composants le montage ressemblera à ça:
montage
Pour les valeurs de la capacité de découplage, une faible valeur sera suffisante (100 nF) car n’on a pas à se soucier de la partie amplification. Par contre, si d’aventure vous vouliez connecter directement un petit HP, il faudrait peut-être choisir une valeur plus élevée. Prenez aussi garde dans ce cas à ne pas trop tirer sur la sortie de l’ATtiny. Un petit transistor et une résistance supplémentaire peuvent faire aussi office d’amplificateur …

On voit qu’il reste encore deux broches de libres sur l’uc et les (seulement)268 Octets du code compilé permettent d’imaginer des add-ons😉 (nota: l’ATtiny dispose de 1K de flash)
La fréquence de coupure avec la résistance de 2.2 K sera de 700 Hz. Sachant que l’Attiny sera configuré avec une fréquence PWM de 150 Khz, on en déduit un ripple de 0.038 V. Je vous donne en lien cette page de calcul pour tester différentes valeurs. Une fois le circuit monté, il est d’ailleurs très intéressant de remplacer à la volée les valeurs de la résistance pour voir les effets sur le filtre.
Enfin, pour peaufiner ces réglages, une oreille bien affûtée vaut un bon oscillo.

Aspects logiciel

Vous trouverez l’archive contenant le source et le binaire du projet ici.
Pour l’envoi du fichier son depuis le PC on peut utiliser n’importe quel programme de terminal à condition que celui-ci permette de spécifier le contrôle du flux matériel dans ses options.
C’est le cas de Cutecom et de minicom tous deux packagés pour debian. Notez que si vous utilisez un de ces deux programmes, la configuration de votre port série restera figée sur ces réglages (baudrate=115200 et contrôle de flux « on »). Ainsi, vous pourrez par la suite vous permettre un simple
$: cat fichier_son.wav > /dev/ttyUSB0
ce qui a l’avantage d’être très pratique.
On peut aussi utiliser python de façon très simple (notez le paramètre rtscts=True pour le contrôle de flux):

#!/usr/bin/env python
import sys, os, serial
s = serial.Serial(port= »/dev/ttyUSB0″,baudrate=115200,rtscts=True);
f = open (sys.argv[1], »rb »);
s.write(f.read());
s.close;

Les fichiers « .wav » en question doivent être par contre dans le bon format; c’est à dire 1 canal, échantillonnés à 8 Khz et sur 8 bits.
Parmi les différents encodeurs disponibles (ffmpeg, mencoder, sox, vlc) seul vlc m’a vraiment apporté toute satisfaction. Il peut prendre en entrée n’importe quel type de fichier audio ou de stream et générer à la volée un fichier voir même écrire directement dans un pipe. Cette fonctionnalité est vraiment très pratique. Jugez plutôt:
On récupère un flux audio sur le net du type « htpp://mon_flux_audio »
On crée ensuite un pipe fifo
$:mkfifo pipe
Puis on encode à la volée le flux audio que l’on écrit dans le pipe:

$: cvlc --play-and-exit --sout #transcode{acodec=u8,channels=1,samplerate=8000}:std{access=file,mux=wav,dst=pipe} "http://mon_flux_audio"

Enfin , il suffit de lancer parallèlement la commande suivante pour le jouer « en live »:
$: cat pipe > /dev/ttyUSB0
Huuuummm, Linux🙂

Catégories :electronique, hack Étiquettes : , , , , , ,
  1. Aucun commentaire pour l’instant.
  1. No trackbacks yet.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :