Controleur midi usb arduino : Différence entre versions

De fablabo
Aller à : navigation, rechercher
(Programmation)
(Sans Tableau)
Ligne 122 : Ligne 122 :
 
Dans ce cas-là, le schéma de câblage ressemble à ça:<br/>
 
Dans ce cas-là, le schéma de câblage ressemble à ça:<br/>
 
[[file:SansTableau.png|700px]]
 
[[file:SansTableau.png|700px]]
 +
 +
'''attention se code est en cours de rédaction il n'est pas utilisable pour le moment'''
 
<pre>
 
<pre>
 
//version 2.1: l'intrus (sans tableau)
 
//version 2.1: l'intrus (sans tableau)

Version du 25 juin 2020 à 07:46


Controleur midi usb arduino (accessible aux débutants)



Contexte

Le projet démarre par la nécessité de produire son controleur-midi, ne pas avoir besoin de l'acheter, pouvoir le modifier/réparer, et l’envie de pouvoir brancher à peu près n'importe quel capteur en entrée midi (capteur de température à la place d’un fader, ...).
Il nous semblait important aussi, d'avoir un matériel nativement reconnu comme un instrument midi par l'ordinateur sur lequel on le branche en usb.
En gros on va utiliser des composants, et quelques lignes de code qu’on va écrire à l’intérieur, bien bien mélanger et ça va faire un contrôleur midi usb.

Choix des composants

Les cartes

[Nous utilisons parfois puces pour parler des microcontrôleurs]

Il existe plein de cartes arduino différentes. Les modèles qui peuvent être utilisé pour ce projets sont les Uno, Méga, Léonardo et Micro. On peut les classer en deux groupes : les Micro et Léonardo qui fonctionnent a peu près de la même manière (avec une seule puce*) et les Uno et Méga qui ont une autre façon de fonctionner (avec deux puces*). Nous traiterons ici uniquement des Méga et Uno puisque c’est celles que nous avons utilisées.
Pour nous, dire qu'une arduino uno ou mega suffit, reste incomplet, il vous faut pour ce projet vérifier que les carte ont les microcontrôleur suivants:

La arduino uno doit posséder:
  • atmega16u2 (ici 1 images atmega16u2 sous deux formes)
  • atmega328

AtmegaUno16u2.png

La arduino méga doit posséder:
  • atmega16u2
  • atmega2560

Mega16u2.png
Qu’importe leur forme : soudée ou détachable, ce qui compte c’est que les deux puces citées soient présentent !

Attention !! : certains fabricants remplacent la 16u2 par un CH340, les cartes sont alors parfois moins chères, mais on ne peut pas les transformer en périphérique MIDI. Les Firmware des CH340 sont propriétaires, merci pour ce cadeau empoisonné !


UnoCh340.png MegaCH340.png exemple de carte uno et méga avec des CH340 (qu'il ne faut pas choisir, pour ce projet):



Les capteurs

Les entrées de notre contrôleur midi sont:

  • des boutons poussoires,
  • des faders (10kohm),
  • des potentiomètres (10kohm)

Les boutons poussoires sont remplaçables par toute sorte d’interrupteur(magnétique, bille de mercure, ...) et les potentiomètres et faders sont remplaçables par toute sorte de capteurs (capteur d'humidité, photorésistances, ...) dont la résistance maximal est proche de 10kohm et la minimale de 0(sinon le fonctionnement du capteur ne sera pas linéaire).

La boite

Toutes les boîtes sont permises! (vous pouvez aussi simplement souder les composants sur la plaque de circuit imprimé et l'utiliser tel quel)
Attention à l'épaisseur, c'est bien de voir large: 5 cm nous paraissent le minimum pour pouvoir accueillir la carte, les fils et les soudures.
Nous c'est un livre dont le titre est l'intrus d'où le nom de notre contrôleur, et ça a été une galère à évider... et il n'est finalement pas assez haut pour tous les fils.

Principe de fonctionnement

Pour ce contrôleur midi, nous avons utilisé une arduino Méga pour la simple raison qu’elle a 16 entrées analogiques, mais c’est possible de le faire avec une Uno, en remplaçant atmega 2560 par atmega328 dans le texte en utilisant des multiplexeurs.
L’information des capteurs (nos boutons, faders, etc.) est récupérée par l’atmega2560 qui contient le code qu’on a écrit qui transforme l’information des capteurs en message midi. Elle envoie à l’atmega16u2 le message midi pour qu’il le transmette à l’ordinateur.

On ne sait pas si l’atmega16u2 est une interface simple entre l’atmega2560 et l’ordinateur ou si elle modifie le message, si vous le savez, vous pouvez nous le dire.

Le message MIDI

[vous pouvez passer cette rubrique et juste utiliser la fonction qu'on a écrite ou cette bibliothèque [1] si ça vous intéresse pas trop]

Le langage MIDI est un protocole de communication spécialement créer pour la communication musicale numérisée, il peut être utilisé pour toute sorte d'utilisation, y compris non musicale, par exemple pour contrôler des jeux de lumière.
Les messages sont constitués de 3 octets (24 bits). Le premier bit du premier octet est toujours à 1 et le premier bit des deuxième et troisième octets est toujours à 0 afin de systématiquement identifier le début du message lorsqu'il y a de l'information en continu. Pour simplifier la lecture, l'écriture et la compréhension du message MIDI, on préfère l'écrire en hexadécimal.

le premier octet

Il se découpe en deux partie de 4 bits chacune.
Il commence toujours par 1. Les trois bits suivant code la nature du message (control change(CC), note on, note off, ...)
Les quatre bits suivants codent le canal (chanel en anglais). Cela peut jouer si plusieurs instruments MIDI sont connectés en même temps.

le deuxième octet

Il code l'identité du message. Selon la nature du message:

  • note on/note off: il indique quelle note.
  • control change (CC): il indique quel contrôle.

Comme il commence par 0, il y a 128 possibilités différentes (de 0 à 127), donc max 128 notes différentes ou 128 CC différents. Il existe des conventions mais nous ne les avons pas prises en compte. Cela n'a pas d'incidence dans notre cas, puisque c'est un contrôleur MIDI, mais cela en aurait eu si c'était un instrument MIDI.

le troisième octet

Suivant la nature:

  • note ON: vélocité de la note. Attention: Si on le mets à 0, cela équivaut à un signal note OFF.
  • note OFF: Cela n'a pas d'incidence.
  • control change: il code sa valeur.

Pareil que pour le deuxième octet, puisqu'il commence par 0, il peut prendre 128 valeurs différentes (de 0 à 127).

Câblage

Savoir quoi brancher sur quoi est assez simple en théorie, c'est beaucoup moins évident dans la pratique...
En général avec trois ou quatre fils, ça passe tranquille, une dizaine ça se complique. Pour nous avec la boite qu'on avait choisie (un livre de 23cm x 18cm x 3.5cm), et 8 faders, 8 potentiomètres et 16 boutons, ça a très vite été bien galère.
Dans l'idée, il faut bien différencier les entrées numériques, des entrées analogiques.
Le but ici, est vous expliquer comment câbler. Vous devez adapter le concept à votre contrôleur midi à vous.

les entrées analogiques

Dans cet équipe, on retrouve toute les résistances variables: photorésistance, potentiomètres, faders, etc.
Les potentiomètre et fader ont généralement 3 pattes, les deux extrémités sont à brancher respectivement sur le 5v et le GND, alors que la patte du milieu doit être branchée sur une entrée analogique de l'Arduino. Il est très important (pour grandement se simplifier la programmation) de brancher tous les faders et potentiomètres dans le même sens.
Certains faders ont 6 pattes, ce sont des faders double pistes, composés de 2X3 pattes, pour notre projet la deuxième piste ne va pas nous servir. On le branche donc comme expliqué ci-dessus et on laisse trois broches libres.
Les photorésistances se branchent entre le 5v et une entrée analogique de l'Adruino.

MegaCablageExemple.png

les entrées numériques

Ici, c'est l'équipe des interrupteurs, nous avons utiliser des bouton poussoir classiques, mais il est aussi possible d'utiliser des switchs (il me semble qu'en français on dit interrupteur à bascule).
Ils se branchent entre le GND et l'entrée numérique, sans résistance car nous utilisons la résistance interne à l'Arduino appelée résistance pull-up. Attention cela a un impact sur la programmation (cela inverse l'information en la rendant contre intuitive). Si vous ne comprenez pas trop ce passage, on l'explique dans la partie programmation.

MegaCablageExempleNumerique.png

Programmation

Dans cette partie, on va essayer d’être claires, pour que ça vous paraisse simple, mais si vous n'avez jamais écrit un programme, la programmation, c'est pas simple et il est possible que ça vous paraisse obscur, très obscur. On ne peut que vivement vous conseiller de faire un tutoriel de programmation de l'Arduino (celui-là par exemple [2]).
Surtout ne lâchez pas l'affaire. Vous êtes une dorade qui vient de faire l'acquisition d'un dîner succulent. Malheureusement, il y a un hameçon dans votre repas, au bout de cet hameçon, un fil puis une canne à pêche, puis un bateau. Il va falloir tirer pour casser, soit le fil, soit la canne, soit le bateau, parce que ce repas c'est le vôtre, y'a pas moyen de négocier et accessoirement aussi que vous n'êtes le repas de personne. C'est pas vous qui allez vous faire grailler par la programmation, c'est la programmation qui va vous servir de casse croûte.

Informations générales

On peut générer 1024 informations différentes avec les entrées analogiques de l'Arduino, de 0 à 1023.
On peut générer 2 informations différentes avec les entré numérique de l’Arduino, soit 0 soit 1, on peut aussi parler de l'état haut ("HIGHT" en anglais), et de l'état bas ("LOW" en anglais).


Sans Tableau

Pour simplifier la compréhension du programme, nous allons d'abord expliquer une version avec un seul potentiomètre et un seul bouton, ce qui signifie que nous n'aurons pas de tableau d'entrée donc pas de boucle de lecture pour ces fameux tableaux.
Dans ce cas-là, le schéma de câblage ressemble à ça:
SansTableau.png

attention se code est en cours de rédaction il n'est pas utilisable pour le moment

//version 2.1: l'intrus (sans tableau)

//Fait le 9/5/2018 par Al²
//re-fait le 5/5/2020 par Al²: version visant à modifier le bug de la sensibilité des cc


// _____ A définir ____

//sensibilité: 2, ça a l'air bien
int sensibilite = 2;



// _________ A ne pas modifier _____________

// déclaration des variables



        // déclaration des variables pour le bouton    
int digitalInBouton = 2;
noteBouton = 20;

boolean EtatBoutonAct = 0;
boolean EtatBoutonPrec = 0;
int i;



        // déclaration des variables pour le potentiomètre
        
int const pot = A0;

// attribution Control change Midi en hexadecimal
int Cc =0x10;

// valeur initiale
int potVal;

int j;


#define midiChannel (byte) 0 // canal 1


void setup() {
  Serial.begin (31250); // attention vitesse de transmission MIDI = 31250, Moniteur Série = 9600

  // initialisation des broches
    pinMode (digitalInBouton, INPUT_PULLUP);
    
  
  // initialisation des valeur du potentiomètre

   potVal=0;

}

void loop() {
  // Le bouton
  
  EtatBoutonAct  = digitalRead (digitalInBouton); // lecture du bouton
  
  //Détection des changements d'état des boutons
  //De cette manière on évite de "spamer" les messages midi
  //On envoie un message MIDI, uniquement lors du changement d'état

  if (!EtatBoutonAct &&  EtatBoutonAnt) { 
    sendMessage (0x90, noteBouton, 127); // envoie noteON
  }
  if (EtatBoutonAct && !EtatBoutonAnt) {
    sendMessage (0x80, noteBouton, 127); // envoie noteOFF
  }

EtatBoutonAnt = EtatBoutonAct;
}

  // Le potentiomètre
    
       potVal = rafraichir (pot , potVal, Cc);
       //delay (1);
}

  
int rafraichir (int const pot, int potVal, int Cc){
   if (potVal + sensibilite < analogRead (pot) || potVal - sensibilite > analogRead (pot)){ 
    potVal = analogRead (pot);
    int Val = potVal/8;
    
   

   sendMessage(0xB0, Cc, Val);   //envoie valeur non-clippée
  }
  
  return potVal; 
}



  // Fonctions communes
void sendMessage (byte cmd, byte ch, byte val){
  cmd= cmd|byte (midiChannel); 
  Serial.write (cmd);
  Serial.write (ch);
  Serial.write (val);
}

/*
 // version débuggage, penser à changer la vitesse (9600 ou 31250)
 void sendMessage (int cmd, int cc, int val){
  cmd= cmd|byte (midiChannel); 
  Serial.print("cmd : ");
  Serial.print (cmd, HEX);
  Serial.print(" ");
  Serial.print("cc : ");
  Serial.print (cc, HEX);
  Serial.print(" ");
  Serial.print("val : ");
  Serial.print (val, DEC);
  Serial.println ("");
}
*/

Avec Tableau

MegaFinale.png
Dans notre programme nous avons rajouté un fonctionement qui bloque a

//version 2.1: l'intrus

//Fait le 9/5/2018 par Al² 
//re-fait le 5/5/2020 par Al²: version visant à modifier le bug de la sensibilité des cc
//re-re-fait le 23/05/2020 par Al² pour Jonas: ajout du bouton "enjaillement-contrôlé"

// _____ A définir ____
// Nombre d'entrées analogiques:
int Ent_Analog = 16;
// nombre de boutons
int const nb = 16;
//sensibilité: 2, ça a l'air bien
int sensibilite = 2;
//max du bouton enjaillement-contrôlé
int enjCont = 100;



// _________ A ne pas modifier _____________

// déclaration des variables



        // déclaration des variables pour les boutons    
// tableau 1             |brocheBouton|note en HEX|
int bouton [16][2] = {      38,           20,
                            39,           21,
                            40,           22,
                            41,           23,
                            42,           24,
                            43,           25,
                            44,           26,
                            45,           27,
                            46,           28,
                            47,           29,
                            48,           30,
                            49,           31,
                            50,           32,
                            51,           33,
                            52,           34,
                            53,           35};


// tableau 2              |etat du bouton actuel | etat du bouton antérieur |
boolean EtatBouton[16][2]={         0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0,
                                    0,                        0};
int i;

        // déclaration du bouton enjaillement-contrôlé

int const Switch = 12;
boolean switchEtat;


        // déclaration des variables pour les potentiomètres
        
// définition broches            potentiomètres                         faders       (de droite a gauche)
int const pot [16]=        {A0,A1,A2,A3,A4,A5,A6,A7           ,A8,A9,A10,A11,A12,A13,A14,A15};

// attribution control change Midi
int Cc  [16]={0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17         ,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F};

// valeur initiale
int potVal [16];

int j;


#define midiChannel (byte) 0 // canal 1


void setup() {
  Serial.begin (31250); // attention vitesse de transmission MIDI = 31250, Moniteur Série = 9600

  // initialisation des broches
  for (i=0;i<nb;i++){
    pinMode (bouton [i][0], INPUT_PULLUP);
    }
  // initialisation du switch
  pinMode (Switch, INPUT_PULLUP);
  
  // initialisation des valeur du potentiomètre
  for (int i = 0; i<16 ; i++) {
   potVal[i]=0;
}
}

void loop() {
  // Les boutons
  for (i=0;i<nb;i++){
  EtatBouton [i][0] = digitalRead (bouton [i][0]); // lecture des boutons
  
  // detection des changements d'état des boutons
  if (!EtatBouton [i][0] &&  EtatBouton [i][1]) { // 
    sendMessage (0x90, bouton [i][1] , 127); // envoie noteON
  }
  if (EtatBouton [i][0] && !EtatBouton [i][1]) {
    sendMessage (0x80, bouton [i][1], 127); // envoie noteOFF
  }

EtatBouton [i][1] = EtatBouton [i][0];
}

  // Les potentiomètres
    for (int j = 0; j<Ent_Analog ; j++) {
       potVal [j] = rafraichir (pot[j], potVal[j], Cc[j], j);
       //delay (1);
    }
}

  
int rafraichir (int const pot, int potVal, int Cc, int i){
   if (potVal + sensibilite < analogRead (pot) || potVal - sensibilite > analogRead (pot)){ 
    potVal = analogRead (pot);
    int Val = potVal/8;
    
    if (i > 7){   //  version 2.1 : pour  les faders uniquement (clipper à la valeur choisie quand le switch est à 1)
                  // 7 parce que les faders commencent à A8 (donc sont stocké dans les dernières cases du tableau

      //vérification du bouton d'enjaillement-contrôlé
      switchEtat = digitalRead(Switch);
    }
    else switchEtat = 0;
    
    if (Val>enjCont && switchEtat){
        sendMessage(0xB0, Cc, enjCont); //envoie valeur clippée
    }
    else sendMessage(0xB0, Cc, Val);   //envoie valeur non-clippée
  }
  
  return potVal; // version 1: else return analogRead(pot); repare le 5/5/2020 
}



  // Fonctions communes
void sendMessage (byte cmd, byte ch, byte val){
  cmd= cmd|byte (midiChannel); 
  Serial.write (cmd);
  Serial.write (ch);
  Serial.write (val);
}

/*
 // version débuggage, penser à changer la vitesse (9600 ou 31250)
 void sendMessage (int cmd, int cc, int val){
  cmd= cmd|byte (midiChannel); 
  Serial.print("cmd : ");
  Serial.print (cmd, HEX);
  Serial.print(" ");
  Serial.print("cc : ");
  Serial.print (cc, HEX);
  Serial.print(" ");
  Serial.print("val : ");
  Serial.print (val, DEC);
  Serial.println ("");
}
*/

Débuggage

Inverser le 5v et le GND en branchant les potentiomètres et faders, inverse les bornes (0-1024 ou 1024-0), ce qui a un impact sur la manière dont on se comporte le contrôleur avec les logiciels. Il faut donc faire attention de les brancher tous dans le même sens. Basiquement, "il fonctionne à l'envers".

Programation de l'atmega16u2

Dans cette section , il va s'agir d'installer le logiciel qui pourra programmer l'atmega16u2 en utilisant son mode dfu. Suivant que vous possediez window mac os ou linux les étapes seront un peu differente.
Attention, nous avons linux sur nos ordinateur, et nous n'allons pas expliquer la procédure avec Windows ou mac.
Il nous semble que c'est possible sous windows avec le logiciel "flip" d'atmel que vous trouverez sur leur site.
Sur mac, débrouillez vous, il est nous semble néanmoins que dfu-programmer exist aussi sur mac, essayer d'adapter les commande linux, et bonne chance!!

dfu-programmer

Donc avec votre machine sous Linux, nous on a une Debian et une Ubuntu xfce, et sa fonctionne sur les deux système de la même manière, vous devez installer dfu-programmer.
dfu-programmer est le logiciel que nous allons utiliser pour programmer l'atmega16u2 en branchant l’Arduino à l'ordinateur de manière tous à fait habituel.
C'est un logiciel qui ne peut s'utiliser qu'en ligne de commande via le terminal. Pour l'installer, il faut ouvrir un terminal Pour updater et upgrader:

sudo apt update
sudo apt upgrade


Pour installer dfu-programmer:

sudo apt install dfu-programmer


Pour vérifier que notre versions de dfu-programmer prent en charge l'atmega16u2:

 dfu-programmer --targets

se qui devrait faire apparaitre quelque chose comme ça:

targets:
    at89c51snd1c       at89c51snd2c       at89c5130          at89c5131       
    at89c5132          at90usb1287        at90usb1286        at90usb1287-4k  
    at90usb1286-4k     at90usb647         at90usb646         at90usb162      
    at90usb82          atmega32u6         atmega32u4         atmega32u2      
    atmega16u4         atmega16u2         atmega8u2          at32uc3a0128    
    at32uc3a1128       at32uc3a0256       at32uc3a1256       at32uc3a0512    
    at32uc3a1512       at32uc3a0512es     at32uc3a1512es     at32uc3a364     
    at32uc3a364s       at32uc3a3128       at32uc3a3128s      at32uc3a3256    
    at32uc3a3256s      at32uc3a4256s      at32uc3b064        at32uc3b164     
    at32uc3b0128       at32uc3b1128       at32uc3b0256       at32uc3b1256    
    at32uc3b0256es     at32uc3b1256es     at32uc3b0512       at32uc3b1512    
    at32uc3c064        at32uc3c0128       at32uc3c0256       at32uc3c0512    
    at32uc3c164        at32uc3c1128       at32uc3c1256       at32uc3c1512    
    at32uc3c264        at32uc3c2128       at32uc3c2256       at32uc3c2512    
    atxmega64a1u       atxmega128a1u      atxmega64a3u       atxmega128a3u   
    atxmega192a3u      atxmega256a3u      atxmega16a4u       atxmega32a4u    
    atxmega64a4u       atxmega128a4u      atxmega256a3bu     atxmega64b1     
    atxmega128b1       atxmega64b3        atxmega128b3       atxmega64c3     
    atxmega128c3       atxmega256c3       atxmega384c3    

sur mon terminal "l'atmega16u2" se trouve bien sur la cinquième lignes et la deuxième colonnes.
A partir de maintenant vous etes près pour la prochaine étape: passer ll'atmega16u2 en dfu mode.

reprogrammer l'atmega16u2