rapidité de lecture matrices 5x64

Bonsoir à tous,

J'ai besoin de quelques conseils concernant un projet MIDI qui nécessite de lire 5 matrices de 8x8 matrices à diodes dont l'accès se fait sur des connecteurs IDC de 16 contacts. (ces connecteurs me sont imposés).
La plupart du temps, une seule nappe n'est active à la fois, mais c'est pas prévisible.

Évidement la vitesse de scan est super importante. et c'est surtout sur ce problème que j'aimerai avoir vos conseils.

J'ai pensé à plusieurs solutions à base d'arduino pro mini 5V (que j'aime bien :))

La 1ère, lecture en série :

  • avec 1 seul arduino, TX en MIDI OUT
  • 1x 74HC595 Les 8 sorties sont communes au 5 nappes
  • 5x 74HC165 chainés à la lecture, chacun lisant les 8 broches de lecture sur chaque nappe.

La 2ème solution, je parallélise :

  • 1 arduino par nappe
  • 1 arduino "Master" qui récupère les ordres des 5 autres en UART (grace à SoftwareSerial) en 115200 bauds, et TX en MIDI OUT

Ou autrement ? Genre un composant 8 ou 16 bits plus rapide à lire qu'un registre à décalage ?

Merci d'avance pour votre aide

Évidement la vitesse de scan est super importante. et c'est surtout sur ce problème que j'aimerai avoir vos conseils.

c'est quoi super importante? en dessus de quelle valeur c'est un pb?

C'est compliqué de répondre à cette question.

Chaque nappe contrôlant un instrument, je dirai que si on enfonce les 10 premiers boutons de l’instrument de la première nappe et en même temps les 10 derniers de la 5ème nappe, je voudrais que les 20 sonnent "en même temps".
Après ce "en même temps" reste relatif, car ça reste du MIDI à 31250 bauds

Donc ma réponse est : tant que les 20 sons semblent commencer en même temps pour le cas cité au dessus, c'est ok.

Donc vous avez un truc comme ça 5 fois (avec des touches)?

et vous voulez une boucle qui lise toutes les valeurs de chacun des boutons pour ensuite envoyer des messages MIDI vers 5 instruments?

J'ai bien 5 fois cette matrice (il manque les interrupteurs sur le schémas), mais je voudrai juste savoir comment lire ces matrices le plus rapidement possible, ou de façon la plus optimisée avec un Arduino pro mini.

Le reste ne me posera pas de problème.

Édit : par exemple, j'ai lu qu'il était possible de lire l'état de 8 bits d'un coup si on a 1 port complet de disponible sur l'arduini. Évidemment, avec un pro mini, je ne dispose pas de 5 ports... Peut être existe t'il des composants qui permettent ça. Les registres à décalage le permettent pas puisqu'il faut décaler chaque bit... Mais peut être que mon problème n'en est pas un, et qu'un micro pro à 16MHz va assez vite pour scanner et détecter les changements de 320 entrées suffisamment de fois / seconde pour rester imperceptible ?

je prendrai 5x 74HC165 dont je mettrais les data sur 5 pins différentes du même PINx

je mettrais 8 pins allant vers les colonnes pour scanner (éventuellement en passant par un optocoupleur pour ne pas avoir de pb de tension) et un bout de code qui allume une à une les rangées, vous "clockez" et "latchez" dans le 74HC165 et vous lisez les 8 bits l'un après l'autre mais comme les 5 sorties sont connectées sur le même PORT, vous pouvez faire cela en une seule lecture de registre PINx

ne pas faire de digitalRead ni de digitalWrite, passez par les manipulations des registres adéquats. c'est bcp bcp plus rapide.

L'arduino pro mini 5V a 13 pins numériques et 6 analogiques - donc tout ce qu'il vous faut

Lire 8 bits d'un coup 5 fois ou 5 bits d'un coup 8 fois ça revient la même chose puisque vous n'aurez pas un Register qui rassemble les 8 bits d'un coup et qu'il faudrait faire 2 lectures.

L'approche de lire 5 bits d'un coup consiste à lire chaque clavier en parallèle et serialiser les touches de chaque clavier. L'approche de lire 8 bits d'un coup c'est plutôt de lire toute une matrice d'abord et ensuite passer la suivante.

À moins de générer les ordres midi la volée - l'une ou l'autre approche est identique, vous lisez tout puis bâtissez les bonnes commandes.

J-M-L > je note déjà cette solution

pepe > Le problème c'est que les diodes sont placées dans l'autre sens, je suis donc obligé d'utiliser 5x 74HC165 et m'oblige donc à trouver 8 sorties et non 8 entrées.

Vous me proposez finalement tous les 2 la même logique, lire les ports en direct.

Donc, les pin A0 à A3 sont utilisables aussi en sortie digitales je devrais donc pouvoir utiliser :

  • PC0 PC1 PC2 PC3 et PD4 PD5 PD6 PD7 en sorties

  • PD2 PD3 pour CLK et LATCH sur les 74HC165

  • PB0 pour lire le 74HC165

Il reste donc les 2 ports série

Me mettre à l'assembleur me semble un peu optimiste. Si j'arrive à gérer ça avec les ports en C ça sera déjà un grand pas pour moi qui ne me suis jamais détaché du framework arduino...

L'arduino Pro Mini 328 5V

sans soudure complexe seul 4 entrées analogiques sont dispo effectivement:

Il vous faut 8 pins pour les 8 rangs je prendrais D4-D7 et A0-A3 mais personnellement je préfèrerai utiliser seulement 3 pins pour commander un demultiplexeur 74HC238 (éventuellement une 4ème pour les enable --> prendre A0-A3).

il vous faut 5 pins de lecture des 5 74HC165. Si vous n'utilisez pas l'interface SPI, je serai tenté de prendre le port B (digital pin 8 à 13 - les bits 6 et 7 ne sont pas utilisés dans le registre - pins du crystal)

prendre les pins 8,9,10,11,12 - ça vous laisse la LED branchée sur 13 pour faire un affichage visuel de fonctionnement par exemple

Si vous n'avez pas besoin des interruptions les pins 2 et 3 peuvent commander les 74HC165 mais si vous avez retenu l'option 74HC238 alors vous pouvez conserver les pins d'interruption pour autre chose.

il n'y a pas besoin de passer en assembleur, utilisez simplement les commandes directes par manipulation des registres des ports.

DDRB - Data Direction Register - définit si entrée (0) ou sortie (1) mettre B00100000 (D13=out, D8-D12 in)
PORTB - Data Register - pour mettre à HIGH ou LOW. on ne touche pas (ou pour faire clignoter D13)
PINB - INput Register - pour lire la valeur des D8-D13 (et en ignorant D13)

le compilateur C++ fera un bon boulot au global pour optimiser vos 2 boucles imbriquées. il faut être un peu smart sur la gestion de la mémoire pour être efficace.

Bon, j'ai commencé à faire quelques tests avec la lib arduino que je retirerai ensuite. Je pense que j'ai un problème de pull-up ou pull-down. Mais je commence à me prendre la tête...

  1. Dans un 1er programme j'ai ça :
int l = 12; //lecture entrées
int s = 4;  //sortie

void setup() {
    Serial.begin(115200); //DEBUG

    pinMode(l, INPUT);
    digitalWrite(l, HIGH); //pull-up 

    pinMode(s, OUTPUT);

    delay(2000);
}

void loop(){
    int res = 0; 

    digitalWrite(s, LOW);
    res = digitalRead(l); 
    digitalWrite(s, HIGH);

    Serial.print(!res);
    Serial.println("");

    delay(100);
}

J'ai connecté la pin 12 de l'arduino dans la broche 1 de la 1ère nappe et la pin 4 de l'arduino sur la broche 2 de la nappe.

Lorsque j'actionne la première note, j'obtiens bien 1 ou 0 quand je lâche.

  1. Je passe à l'intégration du 74HC165 pour les entrées (voir le schémas joint) :

Le programme de test :

int LATCH = 2;
int CLOCK = 3;
int DATA = 8;

void setup() {
    Serial.begin(115200);

    pinMode(LATCH, OUTPUT);
    pinMode(CLOCK, OUTPUT);
    pinMode(DATA, INPUT);

    digitalWrite(CLOCK,LOW);
    digitalWrite(LATCH,LOW);

    //Sorties
    int s = 4;
    pinMode(s, OUTPUT);
    digitalWrite(s, HIGH);

}

void loop() {

    digitalWrite(LATCH,HIGH);

    byte a_temp = shiftInFixed(DATA,CLOCK);

    for (byte i = 0; i < 8; ++i) {
        Serial.print(getBitLowF(a_temp, i));
    }
    //*/
    Serial.println("");
    delay(100);

}

byte shiftInFixed(byte dataPin, byte clockPin) {
    byte value = 0;

    for (byte i = 0; i < 8; ++i) {
        value = value |  (digitalRead(dataPin) << i);
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
    }
    digitalWrite(LATCH,LOW);

    return value;
}

// retourne la valeur de l'octet
byte getOctetValLowF(byte val, int pos)
{
    return val & (1 <<  pos);
}

// retourne l'état du bit sous forme d'un boolean
byte getBitLowF(byte val, int pos)
{
    return getOctetValLowF(val, pos) ? 1 : 0;
}

En branchant la pin 6 du 74HC165 sur la masse ou le 5v j'obtiens bien le bon résultat.. 01111111 ou 1111111

Par contre, si je branche cette même broche sur la pin2 de la nappe. J'obtiens quelque chose d'aléatoire ce qui me fait penser à un problème de patte folle.... J'ai essayé de voir où je pourrai mettre une resistance de pull-up ou pull-down, mais je n'arrive à rien.

Bon, problème de pull-down réglé... J'étais persuadé d'avoir fait ce branchement un moment donné sans que ça fonctionne... Mais bon :slight_smile:

OK ça avance!

au fait maintenant (depuis un moment) on peut remplacer ça

pinMode(l, INPUT);
digitalWrite(l, HIGH); //pull-up

par pinMode(l, INPUT_PULLUP);

et pour vos fonctions de manipulations de bit, il en existe déjà dans la librairie arduino, cf bitSet() et autres bit(), bitRead(), bitWrite(), bitClear()

C'est vrai, j'avais déjà vu pour le pull-up, mauvais réflexe. Concernant les fonctions de manipulation des bits, je les écris moi même pour le moment pour que ça rentre, parce que j'ai vraiment beaucoup de mal avec ça...

Lorsque je serais à l'aise, j'essayerai de me passer entièrement de la lib arduino. Il me semble qu'il existe les équivalents (comme _BV) aussi dans la avr-gcc.

oui - c'est bien de pratiquer :slight_smile:

je serai tenté cependant pour maximiser la performance de réfléchir au traitement réel à effectuer sur les bits que vous recevez.

Comment allez vous représenter en mémoire les 5 matrices de 64 boutons?

ça pourrait être 5 x 8 octets par exemple - cette représentation a l'avantage d'être compacte. Ils vous en faut deux pour conserver la version d'avant afin de bien générer les messages noteOn et noteOff.

Ensuite ça reporte le travail d'analyse lorsque vous générez le Midi - comparer les bits et balancer le message

l'autre option c'est un grand tableau de 320 (5*64) octets, un par touche. en théorie il vous en faut deux mais vous pourriez décider que les bits du bas sont à 1 si la touche est enfoncée en ce moment et les 4 bits du haut les données précédentes. comme ça un seul tableau suffit et la bascule de l'ancienne valeur se fait par un simple << 4 avant de mettre avec un | ou logique la nouvelle valeur dans les 4 bits du bas. vous savez ainsi que si vous avez :

0000 0000 = 0x00 la touche n'est pas appuyée et ne l'était pas avant -> pas de changement
0000 1111 = 0x0F la touche est appuyée et ne l'était pas avant -> noteOn
1111 0000 = 0xF0 la touche n'est pas appuyée et l'était avant --> noteOff
1111 1111 = 0xFF la touche est appuyée et l'était avant -> pas de changement

ce qui permet de faire un switch case en comparant à des constantes dans une boucle pour envoyer les messages ou de les envoyer pendant la lecture de la matrice

Enfin attention à la saturation du buffer de port série

Histoire de vous donner plus d'informations, j'avais déjà terminé ce projet il y a quelques années. Je n'avais que 3 instruments à lire et j'avais utilisé un arduino mega. j'ai bien utilisé la méthode "du grand tableau" à ce moment là.

Ca fonctionne pas mal, mais des utilisateurs ont remarqué une latence, ce qui me dérange aussi énormément...

J'ai un peu de temps en ce moment pour revoir ce projet et l'améliorer, passer à 5 matrices.

Faire 2 tableaux de 320 octets me semblait alors pas une bonne idée.

Du coup, je suis partis sur 5 tableaux de 8 octets car la majeure partie du temps certains octets ne changent pas, il est donc plus rapide (je crois) de comparer 2 octets que de comparer chacun des bits le composant. Je réduits ainsi le nombre calculs puisqu'il n'y a plus qu'à comparer les bits si les l'octet d'avant et d'après sont différents.

en théorie il vous en faut deux mais vous pourriez décider que les bits du bas sont à 1 si la touche est enfoncée en ce moment et les 4 bits du haut les données précédentes. comme ça un seul tableau suffit et la bascule de l'ancienne valeur se fait par un simple << 4 avant de mettre avec un | ou logique la nouvelle valeur dans les 4 bits du bas. vous savez ainsi que si vous avez :

0000 0000 = 0x00 la touche n'est pas appuyée et ne l'était pas avant -> pas de changement
0000 1111 = 0x0F la touche est appuyée et ne l'était pas avant -> noteOn
1111 0000 = 0xF0 la touche n'est pas appuyée et l'était avant --> noteOff
1111 1111 = 0xFF la touche est appuyée et l'était avant -> pas de changement

ce qui permet de faire un switch case en comparant à des constantes dans une boucle pour envoyer les messages ou de les envoyer pendant la lecture de la matrice

Alors, je sens que quelque chose me plait dans cette méthode qui me semble être quelque chose d'assez sympa et optimisé, mais j'arrive pas vraiment à tout saisir.

Quelle serait la méthode des deux qui serait la plus rapide ?

à mon avis la seconde méthode mais ça se fait aux dépens de la mémoire - 320 octets au lieu de 80 mais maintenant si la mémoire n'est pas un pb, autant ne pas se priver.

Qu'avez vous pas compris dans l'explication?

En fait je crois que je viens de comprendre.
Etant donné qu'on utilise 1 octet complet pour chaque note, au lieu d'utiliser 2 tableaux de 320 notes, on va diviser l'octet en 2.
Les 4 plus forts pour l'ancien état , les 4 bits coté faibles pour le nouvel état.

J'ai quelques doutes sur la façon de traiter la donnée avec les opérateurs bitwise :

Comment enregistre t'on l'état de la nouvelle variable ?

etat = (etat << 4) | 0x0F si le signal est activé,

etat = (etat << 4) | 0x00 si ce n'est pas le cas ?

(Par contre, il va falloir que je fasse un PCB pour continuer, si non je vais pas m'en sortir...il y a beaucoup de trop de fils :/)

Oui c'est ça

Avec un petit dessin pour comprendre mieux :slight_smile:

là je ne mets que 0 ou 1 dans les 4 bits au lieux de 0 ou F comme je disais au dessus. ça revient au même sur le principe.

M[index]   = (M[index  ] << 4) | (PINB & 1  ?  1 : 0)
M[index+1] = (M[index+1] << 4) | (PINB & 2  ?  1 : 0)
M[index+2] = (M[index+2] << 4) | (PINB & 4  ?  1 : 0)
M[index+3] = (M[index+3] << 4) | (PINB & 8  ?  1 : 0)
M[index+4] = (M[index+4] << 4) | (PINB & 16 ?  1 : 0)

je ne sais pas si vous connaissez l'opérateur ternaire (condition ? siVrai  : siFaux)

PINB & [color=blue]2[/color] ?  [color=red]1[/color] : [color=limegreen]0[/color] ça veut dire si (PINB maské avec 2) est vrai (différent de 0) alors retourner 1 sinon retourner 0. (donc si le second bit de PINB est actif, je retourne 1 sinon 0)


(en haute résolution)

Merci pour toutes ces infos. Avant d'aller plus loin, je vais devoir attendre les circuits imprimés que j'ai commandé, je pourrais alors jouer plus facilement avec le programme. Parce là, j'ai branché que quelques broches sur 2 matrices, y a des fils de partout et ça fini par m'embrouiller. Donc, retour à tout ça d'ici quelque semaines.

Merci :slight_smile:

heu ??? pourquoi t'as supprimé tous tes posts ? :confused: