Interface nunchuk pour synthétiseur analogique





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
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.

manette-1.png


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)


principe-nunchuck.png


Un nuchuck à 3 type de controles : Lorsqu'il recoit une trame de demande d'information, le nunchuk transmet les données suivantes :
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.

nano.png


Je prends un arduino Nano plutôt qu'un arduino UNO pour les raisons suivantes :


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.

Datasheet du LIS3L02AL


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.

adaptateur-nun-3.png


Breakout un peu moins riche, mais on a une connection male et femelle

adaptateur-nun-1.png


Encore moins riche, on a que la partie centrale

adaptateur-nun-2.png


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.

cac4-male.png


cac4-femelle.png


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)

nunchuck-pinout.png




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 : 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)

DAC4921-1.png


Voici la liste des pins :
  1. VDD : +5V
  2. CS : chip select (pour activer la programmation du DAC)
  3. SCK : horloge
  4. SDI : data
  5. LDAC : Latch -> relié a la masse
  6. VRef : Tension de référence (de 0 à +5V)
  7. AVss : masse alimentation
  8. 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).

slew-limiter.png


Pour les ampli-op j'utiliserai ici 4 chips LM324, pour un total de 16 ampli-op (4 par boitier)

lm324.png


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.

scope-joy-normal.jpg


Avec le slew limiter activé, les fronts sont moins violents et la courbe est plus lissée.

scope-joy-slew.jpg


On installe le même système sur les accéléromètres.

Sans slew-limiter.

scope-accel-normal.jpg


Avec le slew-limiter activé.

scope-accel-slew.jpg


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.)

scope-button-gate.jpg


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...

scope-button-analogswitch.jpg


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).

scope-accel-gate.jpg


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.

nunchuck-interface-functions.png


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 :)

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.

accelerometres.png


Valeurs renvoyées :
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 :


Le dialogue I2C



La manette officielle répond avec l'ID 0x52.

On doit d'abord initialiser le nunchuk avec la séquence suivante 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 :
Puis handshake : Voici comment sont formatés les données en mémoire (et aussi dans notre buffer de lecture).

I2C-datas.png


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.

AH103DWbox.jpg


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.

20190602_184754.jpg


Je ne vais pas m'étendre sur l'implentation des composants. C'est assez simple.

20190602_184916Bis.jpg


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.

20190525_172417.jpg


Voici le schéma du montage.

nunchuckfa-schema.png


Quelques explications rapides.



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).

Macros

#define CLR(x,y) (x&=(~(1<<y))) #define SET(x,y) (x|=(1<<y))

Setup()

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.

pinMode(2, INPUT_PULLUP); // Entrée Quantizer Joystick) pinMode(3, INPUT_PULLUP); // Entrée Quantizer Accelerometre

On initialise l'I2C à 400Khz
TWBR=12;
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.
#define NUNCHUCK_ADDRESS 0x52 // Adresse I2C du nunchuk //--------------------------- // Initialisation du nunchuck //--------------------------- void init_nunchuck() { Wire.begin(); Wire.beginTransmission (NUNCHUCK_ADDRESS); Wire.write (0x40); Wire.write (0x00); Wire.endTransmission (); }
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 decode() est la suivante
//---------------------- // decode octet nunchuck //---------------------- uint8_t decode(uint8_t b) { return (b ^ 0x17) + 0x17; }
A chaque fin de transmission on doit envoyer un ack, c'est juste l'octet 0x00 qui est envoyé.
void ack_transmit() { Wire.beginTransmission (0x52); Wire.write (0x00); Wire.endTransmission (); }
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.

DAC4921-2.png



Les bits 12 à 15 sont les 4 bits qui permettent de changer quelques fonctionnalités sur le DAC.
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.
  1. 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
  2. Si l'état précédent est bouton actif et que le bouton est relaché. On verifie si le délai est atteint.
  3. Si le délai n'est pas atteint, on ne fait rien et on sort
  4. 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).

Lecture du seuil.
void readThreshold() { if (readPot_cnt++ > CALC_POT_CNT) { readPot_cnt=0; gate_threshold=analogRead(A7); } }
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.
void readQuantize() { if (readSwitches_cnt++ > CALC_SWITCHES_CNT) { readSwitches_cnt=0; quantize_joy = bitRead(PIND,2); quantize_acc = bitRead(PIND,3); } }

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.

L'archive avec le code


Reste à faire