Le but est de construire une interface permettant de transformer les données d'un nunchuk en tensions, et d'utiliser ces tensions pour piloter un synthétiseur analogique.
Nous pourrons disposer des fonctions suivantes
2 CV pour positions joystick X/Y
3 CV pour les 3 accéléromètres X/Y/Z
2 gates pour les 2 boutons C et Z
3 gates pour sur chacun des accélérometres s'ils dépassent un seuil réglable
réglage de ce seuil
5 trimmers pour les CV
5 slew-limiters pour les CV
quantizer commutables en demi-ton pour les CV
Il ne s'agit pas de remplacer le clavier (encore que ca soit possible) mais d'utiliser le nunchuk comme outil d'expression (en plus du clavier...) afin de gèrer des filtres, des modulations, des variations de pitches, une enveloppe, bref, tout ce qu'il est possible de gérer avec des tensions sur un synthétiseur analogique.
Si j'arrive à faire fonctionner tout ca, il est assez simple de rajouter ces mêmes fonctionnalités en midi et ce nouvel outil pourra être utilisé en complément des molettes de modulation et du pitchbend.
Certains se demandent ce que je raconte... Un nunchuk c'est ca.
Ce genre de manette n'était pas directement branchée sur une console de jeu mais sur la manette principale de la console de jeu. Le nunchuk permet au joueur d'avoir des controles additionnels. On a une manette principale dans une main, par exemple la main gauche, et le nunchuk est dans l'autre main (la droite donc). Le nunchuk est relié à la manette principale avec un fil, celui qu'on voit sur la photo.
Au final, c'est la manette principale qui communique toutes les informations (les siennes et celles du nunchuk) à la console de jeu via bluetooth.
On peut jouer : soit avec la manette principale toute seule, soit la manette principale ET le nunchuk (Le nunchuk ne peut être utilisé seul)
Un nuchuck à 3 type de controles :
un joystick analogique en X/Y
3 accéléromètres en 3D : X/Y/Z
2 switches (on-off) C et Z
Lorsqu'il recoit une trame de demande d'information, le nunchuk transmet les données suivantes :
position du joystick en X : 8bits
position du joystick en Y : 8bits
accéléromètre axe des X : 10 bits
accéléromètre axe des Y : 10 bits
accéléromètre axe des Z : 10 bits
bouton C : 1 bits
bouton Z : 1 bits
Pour récupérer les données, les analyser et au final les transformer en tension via des DAC, nous allons utiliser un arduino nano.
Je prends un arduino Nano plutôt qu'un arduino UNO pour les raisons suivantes :
les versions chinoises de Nano coutent trois fois rien, quelques $
ils fonctionnent très bien
le Nano est au pas de 2.54mm sur toute sa longueur et surtout c'est symétrique ce qui n'est pas le cas de l'arduino uno.
le Nano est complètement compatible avec le UNO au niveau programmation avec 2 ports analogiques A6 et A7 supplémentaires
Branchement sur l'arduino
Un nunchuk se branche initialement sur une manette de jeu de Wii. Cette manette de jeu est alimentée en 3V via 2 pile R6 et cette alimentation sert aussi pour le nunchuk, donc en utilisation standard le nunchuk est alimenté en 3V. Ce que confirme d'ailleurs le datasheet du chips utilisé dans le nunchuk : un chip LIS3L02AL. Datasheet disponible avec le lien suivant.
Le datasheet du LIS3L02AL recommande une alimentation de 2.4 à 3.6V. Donc nous utiliserons la sortie régulée 3.3V de l'arduino pour alimenter le chip. Vous aller me dire c'est bien mais il faudrait aussi que les pins SDA et SCL soit en 3.3V, et il conviendrait de prendre un arduino nano en 3.3V plutot qu'un modèle 5V.
Oui c'est vrai j'ai rien à dire c'est ce que je pense aussi...
Mais... Il semblerait par contre que le chip qui gère la partie I2C soit plus conciliant et d'après les remarques utilisateurs ils n'y aurait pas de soucis à brancher SDA et SCL sur 5V, le nunchuk serait "5V tolerant"... Apparemment il a toujours pas cramé après plusieurs heures de branchement sur du I2C 5V... Mais bon, sur le principe ca reste étonnant.
Pour communiquer, le nunchuk utilise le bus I2C, 2 fils sont nécessaires (SDA et SCL). Ces 2 fils seront reliés sur le bus I2C de l'arduino, soit respectivement les ports A4 et A5.
Ces 2 broches peuvent alimenter des tas d'autres circuits sur le même bus, comme des eeproms par exemple ou des écrans LCD...
Donc, c'est simple nous avons 4 fils mais la prise concue par Nintendo est quelque peu propriétaire.
Alors vous avez quelques possibilités suivant votre courage ou vos moyens...
Le breakout du riche, pil poil le bon format et on récupère les datas sur un petit connecteur.
Breakout un peu moins riche, mais on a une connection male et femelle
Encore moins riche, on a que la partie centrale
Moi j'ai utilisé mon propre connecteur. Un truc costaud et pas une prise midi parce qu'il ne faut pas confondre avec le reste des équipements.
Grosso modo vous pouvez prendre n'importe quoi, voire même rien du tout et souder les fils directement sur le circuit imprimé où sera brancher l'arduino nano...
Le but est donc de récupérer les données sur le bus I2C. Il est à noter que si vous démontez la prise d'un nunchuk, suivant le modèle (officiel ou chinois), les couleurs de fils ne seront peut être pas identiques.
Voici le branchement standard d'un nunchuk et la couleur probable des fils. Blanc pour la masse (j'ai mis en gris pour une question de visibilité, mais n'importe comment c'est la position des pins qui importe)
Gnd : Masse 0V
VCC : +3.3V
SDA : data bus I2C sur pin A4
SDL : clock bus I2C sur pin A5
Principe du montage
Le nunchuk se contente d'envoyer (sur demande) les données dans l'arduino.
L'arduino de son coté envoie des requêtes au nunchuk et recoit en retour les précieuses informations de positionnement du joystick, des boutons et des accélérateurs. Ces informations seront filtrées, transformées, mises en forme pour pouvoir programmer des DAC.
Nous utiliserons ici 5 DAC et ceux-ci vont générer une tension entre 0 et 5V sur les données suivantes :
Axe X joystick
Axe Y joystick
Accéléromètre axe X
Accéléromètre axe Y
Accéléromètre axe Z
Les DAC utilisés seront des MCP4921. Ces DAC sont très rapides, ce sont des modèles 12 bits et ils fonctionnent sur le bus SPI.
Je n'utilise pas la fonction ShiftOut() pour programmer les données sur le bus SPI, cette fonction est trop lente. Je n'utilise pas non plus la librairie SPI, je travaille directement avec les registres, c'est simple et rapide, nous en reparlerons un peu plus loin...
Les DAC s'alimentent en 5V et la tension de référence peut être interne ou externe (on peut par exemple piloter la tension de référence via un autre DAC, ou un potentiomètre)
Voici la liste des pins :
VDD : +5V
CS : chip select (pour activer la programmation du DAC)
SCK : horloge
SDI : data
LDAC : Latch -> relié a la masse
VRef : Tension de référence (de 0 à +5V)
AVss : masse alimentation
VOUTA : sortie du DAC
Afin de lisser un peu les tensions nous les ferons passer par un slew limiter. Le slew limiter permet, par le biais d'un potentiomètre, de passer d'une tension à une autre plus ou moins progressivement. Cela se fait facilement avec 2 ampli-op et un condensateur qui se charge (et se décharge...) plus ou moins rapidement en fonction de la position d'un potentiometre. On a 5 DAC, il faudra installer 5 slew limiter (soit 10 ampli-op).
Pour les ampli-op j'utiliserai ici 4 chips LM324, pour un total de 16 ampli-op (4 par boitier)
VCC- sera relié à la masse et VCC+ à l'alimentation +9V.
Voici le comportement du joystick lorsqu'on le manipule très rapidement, on voit que les fronts sont très raides.
Avec le slew limiter activé, les fronts sont moins violents et la courbe est plus lissée.
On installe le même système sur les accéléromètres.
Sans slew-limiter.
Avec le slew-limiter activé.
Les gates seront gérés par l'arduino lui même, pas de circuit en particulier, juste des bascules de 0 à 5V. Il y aura surement un filtrage logiciel pour le debouncing et une longueur de gate minimum, genre 5ms par exemple car j'ai pu voir par le passé certains équipements qui ne déclenchaient pas quand le gate était trop rapide.
Voici un exemple de gate avec le bouton C. (On voit le VMax a 7.8V, cela est du au fait que je mesure la tension après mes comparateurs, LM324 qui sont alimentés en 9V.)
Sur les gates des boutons C et Z, je commande 2 interrupteurs analogiques, ce qui me permettra de mettre hors ou en circuit des éléments externes au montage (une sorte de relais électronique commandé par mes gates).
L'exemple de l'oscilloscope est pas forcément très parlant. J'ai mis une tension de 8V continus sur l'entrée de l'interrupteur analogique qui est relié au bouton C. Quand je manoeuvre le bouton, on voit les pics de tension de 8V. J'aurai mieux fait de mettre un signal sinusoidale par exemple....
En tout cas on voit que le montage prend de la ronflette quand il n'est pas mis dans le boitier métallique et que l'interrupteur est ouvert... On est sur le calibre 100ms sur la base de temps et on compte 5 cycles par graduation, donc 20ms le cycle soit 50hz...
Les interrupteurs analogiques sont alimentés entre 0V et 9V, donc si l'interrupteur commande par exemple une tension sinusoidale dont l'amplitude est de -5V à +5V, vous ne verrez que les fronts positifs.
Concernant les gates des accéléromètres...
Ceux-ci sont déclenchés lorsque l'accéléromètre dépasse un certain seuil. Ce seuil est fixé par un potentiomètre et il est unique pour les 3 accéléromètres. Il s'agit juste d'une comparaison. Le potentiomètre est alimenté en 5V à ses bornes et le curseur est relié à une entrée analogique.
Du coup, à la mesure nous aurons une valeur de 0 à 1023 puisque les convertisseurs de l'arduino sont en 10 bits. Ca tombe bien puisque les valeurs renvoyées par les accéléromètres sont elles aussi en 10 bits. Si le potentiomètre est réglé pour renvoyer une tension de l'ordre de 4V, nous aurons une valeur de 800 environ renvoyée par le CAN. Si un des accéléromètres renvoie une valeur supérieure à ce seuil (800 donc...) le GATE correspondant est déclenché pendant 4 ms.
Voici ce que ca donne sur l'oscilloscope lorsque j'agite le nunchuk. Dés que le mouvement est rapide, l'accéléromètre correspondant à l'axe du mouvement va renvoyer des données supérieures au seuil ce qui va déclencher un gate (un pulse).
Les DAC seront alimentés en 5V, ils ne pourront pas fournir une tension de sortie supérieure à 5V.
Les interrupteurs analogiques seront pilotés en 5V, dans la théorie ils ne devraient pas non plus être alimentés au dela de 5V et du coup ils ne pourraient pas couper une tension supérieure à 5V. Cela dit on peut les commander si on a au moins les 2/3 de la tension d'alimentation dans la commande, donc en théorie, je devrais pouvoir les alimenter en 7.5V et les commander en 5V ca devrait encore fonctionner...
Les slew limiter utiliseront des LM324, ce sont des ampli-op rail 2 rail, ce qui sous entend qu'ils devraient pouvoir fournir des tensions en sortie relativement proches de leur tension d'alimentation. Pour un LM324, il serait difficile d'obtenir 5V en sortie avec seulement 5V d'alimentation. Les LM324 seront donc alimentés en 9V et les suiveurs de tension auront un bon comportement pour le range 0 à 5V.
Le schéma de principe du montage donnera quelque chose dans ce genre.
Entre-temps, les entrées Ext 1 et Ext 2 ont été remplacées par 2 interrupteurs pour gèrer des modes de jeu en demi-tons, une sorte de quantizer temps réel pour avoir un accord juste en sortie (je me base sur les références de tensions du standard Volt/octave).
Utilisation du nunchuk
On le tient dans la main. Ah bon ? Ca alors :)
Le pouce agit sur le joystick et permet un déplacement en X et Y.
L'index et le majeur sont utilisés pour la manoeuvre des boutons C et Z.
Les mouvements de la main permettent de bouger l'ensemble de la manette dans l'espace et la vitesse des déplacements est renvoyée par les 3 accéléromètres sur les axes X, Y et Z.
Voici une copie d'une page d'un document trouvé sur le net expliquant le fonctionnement des accéléromères et les valeurs renvoyées.
Valeurs renvoyées :
Les accéléromètres renvoient des valeurs de 0 à 1023 avec une position centrale d'environ 512.
Le joystick renvoie des valeurs de 0 à 255 pour chacun des axes.
Les boutons renvoient 0 ou 1
En fait, dans la "vraie" vie, concernant les valeurs en position "neutre", on a pas forcément 127 pour le joystick et pas forcément 512 pour les accéléromètres. Quant aux positions maximum, c'est un peu pareil. J'ai prévu une correction sur le joystick pour que la position max soit réajustée afin d'avoir toujours 0 au minimum et 255 au maximum, cela ré-équillibre un peu les choses. Pour les accéléromètres je n'ai pas appliqué de correction car il est difficile de trouver une position minimum, neutre et maximum.
Tensions générées :
CV joystick : 0 à 5V
Accéléromètres : 0 à 5V
Gates : 8V
Interrupteurs analogiques : un peu plus de 8V maximum
Le dialogue I2C
La manette officielle répond avec l'ID 0x52.
On doit d'abord initialiser le nunchuk avec la séquence suivante
Connexion sur ID 0x52
Envoi octet 0x40 : setting de l'addresse en mémoire
Envoi octet 0x00 : l'adresse est 0x00
Déconnexion
Arrivé à ce stade le nunchuk est prêt à recevoir des requêtes de la part de l'utilisateur.
Le type de requête est simple : on demande au nunchuk de retourner les 6 octets se trouvant en mémoire à partir de l'adresse 0x00. A chaque requête on doit valider par un handshake.
Donc on va avoir le schéma suivant :
Connexion sur ID 0x52
Demande de 6 octets : (l'adresse a été settée à 0x00 précédemment)
Réception des 6 octets
Déconnexion
Puis handshake :
Connexion sur ID 0x52
Envoi de l'octet 0x00
Déconnexion
Temporisation de 2ms
Voici comment sont formatés les données en mémoire (et aussi dans notre buffer de lecture).
Pour les valeurs X et Y du joystick (Adresses 0x00 et 0x01) on a un octet pour chaque axe ; c'est facile.
Pour les accélérometres en revanche, les octets entre les adresses 0x02 et 0x04 représentent les 8 bits de poids fort de la valeurs renvoyées (n'oubliez pas qu'il s'agit initialement d'une valeur sur 10 bits pour les accéléromètres). Les 2 bits manquants de chacun des accéléromètres se trouvent dans le dernier octet de datas à l'adresse 0x05.
Pour finir, la position des boutons C et Z est stockée sous la forme de bits.
Cela pourrait être fini pour la lecture des données, mais une petite subtilité a été ajoutée par nos amis de Nintendo. En effet, les valeurs sont encodées et pour les décoder il faut faire une opération XOR avec 0x17 et ensuite ajouter 0x17. Un peu étrange comme calcul mais c'est comme ca, c'est toujours utile de le savoir.
Si l'octet lu s'apelle par exemple my_byte, voici comment le décoder :
my_byte = (my_byte ^ 0x17) + 0x17;
Deuxième subtilité, les bits des interrupteurs sont inversés : quand un bouton est appuyé, le bit vaut 0. Si le bouton est relaché, le bit vaut 1.
Le montage et la construction
Le boitier est un modèle AH103-SW de chez Monacor.
Mon avis personnel : je vous conseille de prendre plus grand, la c'est vraiment serré la dedans... J'ai pris ce boitier parce que j'ai déja réalisé des tas de montages avec ce modèle mais là, j'avoue, c'est un peu juste.
Je vous laisse le soin de faire un joli circuit imprimé. Me concernant, et comme d'habitude pour aller au plus vite, j'ai fait un montage sur veroboard. Ce n'est pas très joli et pas vraiment optimisé niveau routage mais bon, ca fonctionne.
J'imprime une premiere fois la serigraphie pour faire les points de repères pour le percage.
Je ne vais pas m'étendre sur l'implentation des composants. C'est assez simple.
A l'extreme gauche et en bas le régulateur 5V (7805), il servira à alimenter l'arduino, l'optocoupleur, le midi-out et les DAC.
L'entrée +9V alimentera directement les LM324
Le CD4066 sera alimenté via un pont diviseur, pour avoir environ 7.5V d'alimentation, ce qui me permettra de le commander encore avec 5V.
Ensuite nous avons l'arduino nano avec en dessous l'optocoupleur.
Pour l'instant l'optocoupleur pour le midi in est prévu sur mon circuit mais ne me sert pas. Dans un premier temps j'ai prévu les sorties CV/Gate. Ensuite viendra le tour du midi out. Pour le midi in, si ca se trouve je ne m'en servirai jamais... Du coup, je n'en parle plus dans le reste de l'article et cet optocoupleur n'apparait pas pour l'instant sur le schéma...
Ensuite, une colonne de 5 DAC (2 pour X/Y joystick et 3 pour X/Y/Z des accéléromètres)
Viennent après les 4 LM324 : 10 ampli-op pour les 5 slew-limiters et 5 pour les gates, soit 15 au total sur les 16 disponibles.
Le dernier chips est l'interrupteur analogique CD4066 qui sera piloté par les boutons C/Z du nunchuk.
Rien de particulier à signaler pour le cablage si ce n'est le découplage des LM324 et de l'interrupteur que j'ai fait sous les chips directement entre les pattes d'alimentation avec des petits condensateurs de 100nF.
Voici le schéma du montage.
Quelques explications rapides.
L'alimentation se fait avec un bloc secteur 9V régulé, type de ceux utilisés pour les guitares électriques (tous mes montages sont en général alimentés à la base en 9V et je refais une régulation en interne si besoin)
Le +9V sert pour l'alimentation des 4 chips LM324
Un pont diviseur permet d'avoir une alimentation 7.5V pour le CD4066
Le 5V est fabriqué à partir de la source +9V via un 7805. Cette source +5V me sert à alimenter l'arduino, l'optocoupleur (qui sert à rien pour l'instant), les DAC et le midi-out.
J'utilise un régulateur interne de l'arduino pour le +3.3V (tension d'alimentation requise pour le nunchuk).
Le nunchuk a 4 fils : 2 pour son alimentation (+3.3V et masse) et 2 pour la clock et les datas sur le bus I2C (pins A4 et A5 de l'arduino)
Les DAC sont reliés sur le BUS SPI (pin 11 et 13), sont alimentés en +5V, la sélection des chips se fait avec les pins 9, A0, A1, A2 et A3.
Les sorties des 5 DAC passent chacune à travers un slew-limiter (2 ampli-op en suiveur de tension avec un condensateur entre les 2 qui lisse les variations de tensions dans le temps)
Les gates des boutons C et Z, vont respectivement sur 2 ampli-op montés en comparateur, ce qui me permet d'avoir environ 8V en sortie. Les pins de l'arduino sont les 4 et 5. Ces ampli-op commande aussi 2 interrupteurs analogiques du chips CD4066, pour pouvoir commander l'activation ou l'arrêt de sources de tensions externes.
Les gates des 3 accélérateurs (pins 6,7 et 8) sont cablées sur 3 ampli-op montés eux aussi en comparateurs.
2 interrupteurs mettent à la masse les pins 2 et 3 pour gérer les modes de demi-tons des joysticks et des accéléromètres. Les résistances de pull-up intégrées à l'arduino sont utilisées sur ces 2 pins.
Le midi-out est cablé sur la sortie Tx de l'arduino via 220 ohms (Le coté opposé est relié comme d'habitude au +5V via 220 ohms)
Un potentiomètre relié à ses extrémités sur le +5V permet de définir le seuil de déclenchement des gates pour les accéléromètres. Le curseur de ce potentiomètre est relié à la pin A7 de l'arduino.
Les pins VRef des 5 DACS ne sont pas reliées comme d'habitude au +5V mais sont chacunes cablés sur le curseur d'un potentiomètre, ce qui permet de faire varier la tension de référence de chacun des DACS indépendamment et de garder une précision de 12 bits même si on utilise des tensions de sorties faibles (en effet, la limitation de l'amplitude se fait via la tension VRef et pas par calcul.)
Le programme
J'utilise 2 macros bien pratique pour basculer les états des pins de l'arduino, plutot qu'utiliser la fonction digitalWrite() qui est assez lente. x représente le nom du port (PORTD par exemple) et y le numéro du bit (de 0 à 7).
On initialise les entrées sorties, rien de plus banal. A signaler tout de même l'activation des resistances de PULL_UP pour les pins reliées aux 2 interrupteurs.
Je n'utilise pas la librairie SPI, je passe directement par les registres. Nous devons initialiser le SPI comme il suit.
DDRB |= 0x2C; // OUTPUT Direction for SS, MOSI and SCK
SPCR = 0x50; // MASTER, SPI Enable
SPSR = 0x01; // SPI2X
Le reste du setup() n'est qu'initialisation de variables et de pins de l'arduino.
Lecture du nunchuk
Il faut d'abord initialiser une fois le nunchuk. Il faut donc démarrer l'I2C, choisir l'adresse du NUNCHUK et envoyer 2 octets d'initialisation qui sont 0x40 et 0x00.
La lecture des 6 octets se faire via la fonction nunchuck_read() qui se charge de lire les 6 octets, de les décoder et de les stocker dans le tableau buffer[]
//------------------------------
// Lecture des datas du nunchuck
//------------------------------
uint8_t nunchuck_read() {
cnt=0;
Wire.requestFrom (NUNCHUCK_ADDRESS, 6);
while (Wire.available () && cnt < 6) {
buffer[cnt++] = decode(Wire.read());
}
return cnt;
}
La fonction ParseAndDecode() va lire le buffer et renseigner les variables globales. Les valeurs des joysticks sont utilisables directement. Pour les boutons on a 2 bits et pour chaque accéléromètre il faut concaténer 2 bits de poids faibles et un octet pour les 8 bits de poids forts (pour faire 10bits...)
A noter l'utilisation de la fonction map() pour réaligner un peu le joystick. Sur le mien, les positions extremes ne descendent pas à zero et ne monte pas à 255, c'est un peu moins à chaque fois. J'ai noté les valeurs mini renvoyées par le joystick (vous pouvez debugger avec la console arduino j'envoie les valeurs dans le port série -- A enlever d'ailleurs pour utiliser le midi-out et accélérer encore la réactivité du nunchuk)
vjoy=map(joy_X_raw,JOY_X_MIN,JOY_X_MAX,0,255);
if (vjoy < 0)
vjoy=0;
if (vjoy >255)
vjoy=255;
joy_X=vjoy;
Gestion des DAC
La fonction writeDAC(uint8_t numdac, uint16_t datain) permet d'écrite les 12 bits de données dans un DAC. Les paramètres de la fonction sont le numéro du DAC et les données...
On utilise le SPI pour programmer les DAC. Les 5 DACs sont sur le même bus, mais chaque DAC possède une broche CS (Chips select) qui lorsqu'elle est à l'état bas avertit le DAC que les données du bus le concerne. Pour programmer un DAC, on commence donc par mettre sa pin CS à l'état bas
void writeDAC(uint8_t numdac, uint16_t datain) {
switch(numdac) {
case DAC_JOY_X:
CLR(PORTB,1); // LOW state pin 9
break;
case DAC_JOY_Y:
CLR(PORTC,0); // LOW state pin A0
break;
[...]
Ensuite le registre d'écriture d'un MCP4921 est un registre 16 bit dont voici la composition.
Les bits 12 à 15 sont les 4 bits qui permettent de changer quelques fonctionnalités sur le DAC.
bit 12 à 1 : rendre Enable la sortie
bit 13 à 1 : gain normal de 1
bit 14 à 1 : utiliser Vref
bit 15 à 0 : utiliser le DAC A
En partant de la gauche nous aurons les données suivantes (x sont les bits de l'échantillons à copier).
HighByte LowByte
0111xxxx -- xxxxxxxx
Pour setter les 4 premier bits à 0111, il nous faudra un masque de 0x70 en hexadécimal (01110000 en binaire).
Nous pouvons donc utiliser une variable de type int, (16 bits donc...), stocker notre donnée 12 bits à l'intérieur.
Puis l'écriture dans le bus SPI se fait en 2 fois, on écrit d'abord la partie haut de notre variable 16 bits et on va setter les 4 bits (numero 15 à 12) avec 0111 en utilisant un OU "|".
Puis on écrit la partie basse de notre variable.
L'écriture dans le bus SPI est simple, on copie l'octet à envoyer dans le registre SPDR, c'est tout.
Ensuite il faut attendre la fin de la copie et cela se fait en attendant que le bit SPIF soit à 1 dans le registre SPSR.
SPDR = highByte(datain) | 0x70; // Copie MSB + 4 bits programmation du DAC (buffered, 1x gain et active mode)
while (!(SPSR & (1<<SPIF))); // Attente de la copie
SPDR = lowByte(datain); // Copie du LSB
while (!(SPSR & (1<<SPIF))); // Attente de la copie
une fois les données copiées dans le DAC, on repasse à l'état haut la pin CS du DAC concerné.
switch(numdac) {
case DAC_JOY_X:
SET(PORTB,1); // HIGH State pin 9
break;
case DAC_JOY_Y:
SET(PORTC,0); // HIGH State pin A0
break;
[...]
Nous savons maintenant comment écrire dans les DAC et générer les tensions CV pour les 2 joysticks et les 3 accéléromètres
Gates des boutons C et Z
Il ne reste plus que les gates à gérer.
Les gates basiques sont ceux des boutons C et Z. Quand un bouton est appuyé on met une sortie à l'état haut. Quand on le relache, la sortie passera à l'état bas si le gate a duré au moins 4ms (en fait vous mettez ce que vous voulez comme délai, c'est un paramètre). Pendant ce délai, l'arduino continue de travailler. C'est facile à faire, on mémorise un timestamp quand le bouton est appuyé et si le bouton est relaché on compare l'horloge actuelle au timestamp de déclenchement, si on a plus que le délai, on peut mettre la sortie à l'état bas, sinon on ne fait rien et on sort, on regardera la prochaine fois si le délai est atteint.
Cela permet d'éviter de bloquer l'arduino pendant ce délai et empêcher la lecture du nunchuk ou la mise à jour d'autre sorties.
Voici le cheminement à suivre.
Si l'état précédent était bouton inactif et que le bouton est pressé, on rend l'état actif, on memorise le moment de l'appui et passe à l'état haut la sortie GATE
Si l'état précédent est bouton actif et que le bouton est relaché. On verifie si le délai est atteint.
Si le délai n'est pas atteint, on ne fait rien et on sort
Si il est atteint, on passe l'état du bouton a inactif, on met la sortie au niveau bas
Ce principe sera utilisé pour tous les GATES : les 2 des boutons et les 3 des accéléromètres...
// Test passage high bouton C
if (!last_state_button_C && C_button) { // Si etat precedent LOW et bouton HIGH
SET(PORTD,4); // gate-out button C (D4) HIGH
last_time_button_C=millis(); // Memorisation timestamp changement etat
last_state_button_C=1; // Changement etat du bouton C
}
[...]
// Test passage low bouton C
if (last_state_button_C && !C_button) { // Si etat precedent HIGH et bouton LOW
// On teste si le temps de gate est depassé
if (millis() > last_time_button_C + MIN_TTL_GATE ) {
CLR(PORTD,4); // gate-out button C (D4) LOW
last_state_button_C=0; // Changement etat du bouton C
}
}
[...]
Gates des accéléromètres
Le principe est de déclencher un gate lorsque l'accéléromètre dépasse un certain seuil.
Ce seuil est fixé par un potentiomètre de réglage de seuil et ce seuil est commun pour les 3 accéléromètres.
Les accéléromètres renvoient des valeurs de 10 bits. Une pin analogique renvoie après conversion une valeur de 10 bits elle aussi, il est fonc facile de faire une comparaison.
Par exemple : mettons que le réglage de seuil soit sur le 4/5 de sa course, on lira donc après une conversion la valeur de 800 environ. Ensuite, on lit en temps réel les accéléromètres comme d'habitude mais si un des accéléromètres dépasse la valeur de 800 on déclenchera le GATE qui lui est associé. C'est simple.
Si le gate est déclenché et que l'accéléromètre redescend sous le seuil, on passe le gate associé au niveau bas (après un délai, même principe que les boutons C et Z).
A noter que la lecture de l'entrée analogique du potentiomètre de seuil n'est pas systématique, elle n'est faite que si un compteur readPot_cnt atteint la valeur de CALC_POT_CNT. J'ai fait ca pour gratter encore un peu de temps machine car je n'ai pas besoin que la détection de changement sur ce potentiomètre soit très réactive. Actuellement, je crois que la valeur du potentiomètre n'est lue que toutes les 30 executions de la fonction readThreshold(). (J'utilise le même principe pour lire la position des 2 interrupteurs du quantizer.)
Mise à jour des gates
void maj_gates_accel(void) {
// Test si seuil depasse pour accel X
// et si etat gate a off
if ( acc_X > gate_threshold && !last_state_accel_X_gate ) {
SET(PORTD,6); // gate-out accel X (D6) HIGH
last_time_accel_X_gate=millis(); // Memorisation timestamp changement etat
last_state_accel_X_gate=1; // Changement etat du gate accel X
}
// Test passage accelerometre X sous le seuil
if (acc_X <=gate_threshold last_state_accel_X_gate) {
// On teste si le temps de gate est depassé
if (millis() > last_time_accel_X_gate + MIN_TTL_GATE ) {
CLR(PORTD,6); // gate-out button C (D6) LOW
last_state_accel_X_gate=0; // Changement etat gate accel X
}
}
Ecriture des CV et Quantizer
Le quantizer permet d'avoir une valeur de CV qui tombe sur une tension correspondant à un demi-ton juste. On se base sur un tableau de valeurs qui correspond aux 61 notes possibles (5V = 5 x 12 + 1 possibilités).
L'activation du quantizing pour le joystick et/ou pour les accélérateurs se fait avec 2 interrupteurs.
Voici le tableau des 61 valeurs. Celui-ci est stockée en mémoire FLASH plutot qu'en RAM. C'est le mot clé PROGMEM qui précise que le stockage des données se fera en mémoire FLASH.
static prog_uint16_t VOct[] PROGMEM = {
// Definition des valeurs DAC
// pour conversion midi vers CV
// Standard Volt/Octave
// (61 VALEURS)
0,68,137,205,273,341,410,478,546,614,683,751,
819,887,956,1024,1092,1160,1229,1297,1365,1433,1502,1570,
1638,1706,1775,1843,1911,1979,2048,2116,2184,2252,2321,2389,
2457,2525,2594,2662,2730,2798,2867,2935,3003,3071,3140,3208,
3276,3344,3413,3481,3549,3617,3686,3754,3822,3890,3959,4027,
4095,
};
On utilise la fonction map, pour restreindre la lecture d'un joytick ou d'un accelerometre et rester dans le range de 0 à 60 (soit 61 valeurs... Mais rien ne vous empêche par exemple de mettre 12 à 35 pour avoir juste 2 octaves, ou 12 et 47 pour 3 octaves...)
Exemple d'écriture CV pour le joystick. Si le quantizer n'est pas activé on écrit la valeur directement dans le DAC (valeur x 16 pour avoir 12 bits)
if (!quantize_joy) {
writeDAC(DAC_JOY_X,joy_X*16);
writeDAC(DAC_JOY_Y,joy_Y*16);
}
else {
// JOYSTICK X
v = map(joy_X*4, 30, 950, 0,60 ); // 61 notes maxi (0 a 60) en Volt/Octave
c = constrain(v, 0, 60);
writeDAC(DAC_JOY_X,pgm_read_word_near( VOct + c)); // Ecriture joystick X quantize
// JOYSTICK Y
v = map(joy_Y*4, 30, 950, 0,60 ); // 61 notes maxi (0 a 60) en Volt/Octave
c = constrain(v, 0, 60);
writeDAC(DAC_JOY_Y,pgm_read_word_near( VOct + c)); // Ecriture joystick Y quantize
}
Même chose pour les accéléromètres. Si le quantizer n'est pas activé on écrit la valeur directement dans le DAC (valeur x 4 pour avoir 12 bits)
if (!quantize_acc) {
writeDAC(DAC_ACC_X,acc_X*4);
writeDAC(DAC_ACC_Y,acc_Y*4);
writeDAC(DAC_ACC_Z,acc_Z*4);
}
else {
// ACCELEROMETER X
v = map(acc_X, 30, 950, 0,60 ); // 61 notes maxi (0 a 60) en Volt/Octave
c = constrain(v, 0, 60);
writeDAC(DAC_ACC_X,pgm_read_word_near( VOct + c)); // Ecriture accel X quantize
// ACCELEROMETER Y
v = map(acc_Y, 30, 950, 0,60 ); // 61 notes maxi (0 a 60) en Volt/Octave
c = constrain(v, 0, 60);
writeDAC(DAC_ACC_Y,pgm_read_word_near( VOct + c)); // Ecriture accel Y quantize
// ACCELEROMETER Z
v = map(acc_Z, 30, 950, 0,60 ); // 61 notes maxi (0 a 60) en Volt/Octave
c = constrain(v, 0, 60);
writeDAC(DAC_ACC_Z,pgm_read_word_near( VOct + c)); // Ecriture accel Z quantize
}
Le code
Dans le fichier zip, à l'intérieur du sous-répertoire nunchukfa, vous trouverez le code source du séquenceur.
Il me reste à faire la partie midi-out que je n'ai pas encore programmée. Je ne sais pas si je gérerai des note-on note-off ou des controleurs, ou du pitch-bend pour les CV
Je pourrai changer le fonctionnement du quantizer. Un bouton pour l'activer globalement ou pas, et l'autre pour choisir 1/2 ton ou bien que les notes blanches, voir un mode particulier.