MicroPython : Piloter le générateur de signal AD9833

Bonjour à tous,

En prévision d'un projet "Penny Organ" sous MicroPython, je me suis penché sur le support de l'AD9833 sous MicroPython.

Générateur de signal
L'AD9833 est un générateur de signal sinusoïdal, triangulaire et carré (clock)
Générateur de signal AD9833 - source MCHobby
Ce composant, relativement populaire pour les projets "Générateur de signal" avec Arduino (voir ce projet de Cezar Chirila sur All About Circuits).

En effet, l'AD9833 est capable de produire un signal de:
  • 0 à 12.5 MHz (sinus, triangle, clock/rectangle)
  • Décalage de phase de 0 à 2π (avec une valeur de 0 à 4096).
  • Une tension de 0.6Vpp (peak to peak)
L'AD9833 dispose de fonctionnalité intéressantes comme:
  1. La possibilité de suspendre la génération du signal avec l'instruction reset. C'est un nom de fonctionnalité bizarre pour suspendre un signal!
  2. La mise à disposition de deux registres de fréquences Freq0 et Freq1... il suffit de configurer le registre avec la fréquence souhaitée puis d'indiquer quel registre doit être utilisé pour produire le signal de sortie.
Faire de l'audio
La bonne nouvelle, c'est qu'en restant dans une gamme de fréquence audible (0 à 21000 Hertz), il est possible de générer des notes de musiques.
Par exemple, le LA est à 440 Hertz.

C'était justement le propos du projet "Penny Organ" (voir ci-dessous) dont je suis tombé sous le charme tellement le son est beau surtout en polyphonie.


Avant d'en faire une version MicroPython, il faudra d'abord pouvoir contrôler un AD9833 sous MicroPython... ce qui vient d'être achever.

Câbler un AD9833 sur MicroPython Pyboard
AD9833 sur MicroPython - source: ESP8266-uPy GitHub
Alors il y a plusieurs choses à savoir:
  1. l'AD9833 utilise un protocole avec un broche DAT et CLK. Nous sommes donc face à une transmission de donnée unidirectionnelle sur bus SPI. Seule la broche MOSI de la Pyboard sera utilisée (MOSI: Master Out, Slave In).
  2. La broche FNC de l'AD9833 agit comme un ChipSelect (/SS) permettant de débuter une transaction SPI sur le générateur en plaçant le signal au niveau bas. S'il y a plusieurs AD9833 sur le bus, il faudra une broche FNC différente pour chaque AD9833 (afin de pouvoir les activer indépendamment les un des autres).
  3. Le bus SPI doit être configuré en mode 2!
    Donc avec la polarité à 1 et la phase à 0.
Bien que pas forcement nécessaire, la vitesse du bus à été réduite au débit symbolique de 9600 baud. Il faut avouer que j'ai eu de nombreux problèmes de parasitage et que diminuer la vitesse du bus peu vraiment simplifier le débogage de situation complexe.

Mille milliards de parasites!
Quand on travail avec des signaux analogiques (même avec un générateur de signal aussi rudimentaire que l'AD9833), les choses peuvent rapidement ingérable!
J'ai faillit devenir fou: parfois cela marche, parfois pas. Tantôt un signal généré à la bonne fréquence indiquée, d'autres fois, il faut répéter l'ordre plusieurs fois pour que cela finisse par fonctionner!

Ne cherchez pas plus loin... vous êtes face à un problème de parasitage et le premier coupable sera certainement le breadboard que vous utilisez (contact imparfait, crosstalking et effet d'antenne).
En rebranchant l'AD9833 sur le bus SPI avec des câbles très court je me suis débarrassé de tout mes problèmes en une seule fois.  

Bibliothèque AD9833 pour MicroPython
La bibliothèque ad9833.py est disponible sur le dépôt sur le lien suivant:
Exemple d'utilisation
Le code d'exemple suivant initialise le bus SPI correctement, la broche de commande ChipSelect (heuu FNC sur l'AD9833).

from machine import Pin, SPI

from ad9833 import *
# SPI doit être initialisé en mode 2 -> Polarity=1, Phase=0
# Y8 = MOSI --> DAT
# Y6 = SCK  --> CLK
# Y5 = /SS  --> FUNC

# Phase et polarity SONT ABSOLUMENT REQUIS pour mettre le bus SPI en mode 2!
# Ne pas hésiter à réduire le débit à 9600 bauds avec baudrate=9600
spi = SPI(2,  baudrate=4800, polarity=1, phase=0)
ssel = Pin( "Y5", Pin.OUT, value=1) # use /ss

# mclk est l'horloge source du générateur AD9833
gen = AD9833( spi, ssel) # 25 MhZ par défaut

# Initialiser l'AD9833 avec onde sinusoïdale à 1.3KHz, 
# pas de décalage de phase sur les deux registres de fréquence
# et rester sur le générateur FREQ0
frequency0 = 1300 #  1.3 Khz
frequency1 = 50 #  50 Hz
phase      = 0 # No phase shift (0..4095)

# Suspendre la sortie
gen.reset = True

# Configurer Freq0
gen.select_register(0)
gen.mode = MODE_SINE
gen.freq = frequency0
gen.phase = phase

# Configurer Freq1
gen.select_register(1)
gen.freq = frequency1
gen.phase = phase


# Activater la sortie sur Freq0
gen.select_register(0)
gen.reset = False

# Une sinusoïde à 1.3 KHz doit être visible sur la sortie
# La tension de sortie doit être de 0.6 Vpp (de pic à pic)


Pour être "Plateforme Agnostique", c'est le code appelant (script principale) qui doit fournir les bus et broches nécessaires au fonctionnement du pilote. Comme tous les pilotes exploitent l'API machine, ceux-ci sont plus facilement utilisable entre différentes plateforme MicroPython à l'autre

Un peu de musique
Le but étant quand même de faire de la musique avec un AD9833, un deuxième script de test notes.py est mis à disposition sur le dépôt.

from machine import Pin, SPI
from time import sleep

from ad9833 import *


# Note (NomEuropéen, CodeImperial,Fréquence)
NOTES = [('DO' ,'C',264),('RE','D',297),('MI','E',330),('FA','F',352),
   ('SOL','G',396),('LA','A',440),('SI','B',495),('DO','C',528) ]

# SPI bus in mode 2!
spi = SPI(2,  baudrate=9600, polarity=1, phase=0)
ssel = Pin( "Y5", Pin.OUT, value=1) # use /ss

# générateur de fréquence
gen = AD9833( spi, ssel) # fréquence par défaut à 25 MhZ

def play_note( freq ):
 """ Jouer une note à la fréquence indiquée """
 global gen
 gen.select_register(0)
 gen.mode  = MODE_SINE
 gen.freq  = freq
 gen.phase = 0 # No phase shift (0..4095)

for name, name2, freq in NOTES:
 # Suspendre la sortie (plus de signal
 gen.reset = True
 sleep( 1 )

 print( "Jouer %s à %s Hz" % (name,freq) )
 play_note( freq )

 # Activer la sortie
 gen.reset = False

 sleep( 2 )

# Arrêter la dernière note jouée
gen.reset = True
print( "That's all folks!")

Ressources

Aucun commentaire