Lettura di molti tasti in real time contemporaneamente

Salve a tutti, avrei bisogno in un progetto che sto realizzando di leggere una 40ina di tasti anche contemporaneamente con ritardo praticamente nullo.
Ho sentito parlare della funzione millis(), ma in questo caso non so se riesco a risolvere con questa.
A livello di codice, voi cosa mi consigliereste?

Grazie.

P.S. I tasti saranno collegati tramite degli shift register SN74HC165, che credo metterò tutti in fila per l'output su un pin di arduino o al massimo farò due file usando due pin di arduino. Pensate che questo porti latenza? Il clock dovrebbe essere 16MHz anche per loro alla fine...

Prima parli di leggere 40 tasti "contemporaneamente" e poi li vuoi mettere su uno shift regsiter ? ? ? :o :o :o

Invece di leggere un tasto alla volta e far avanzare lo shift regsiter, prendi una MEGA usi 40 pin digitali e con solo 5 veloci letture di PINx (ogni lettura legge gli 8 bit di una porta contemporaneamente) leggi quasi "contemporaneamente" tutti e 40 i pin ... :wink:

Guglielmo

Ciao Guglielmo, dici che otterrei latenze eccessive (parlo di tasti che vengono rilevati in ritardo o non rilevati affatto) con gli shift register?
Inoltre che differenze ho con arduino mega a livello di microcontrollore e tutto ciò che a lui è legato rispetto ad arduino uno?

Apocalypse480:
Inoltre che differenze ho con arduino mega a livello di microcontrollore e tutto ciò che a lui è legato rispetto ad arduino uno?

Non entro troppo nei dettagli ma considera che una mega puoi vederla come una uno a livello di programmazione che però ha a disposizione molta più memoria e pin di I/O nonché più spazio per il programma (ovvero può contenere programmi più complessi e che quindi occupano più spazio) se dai un occhiata alle specifiche tecniche sul sito ti puoi are un idea migliore.
Non farti spaventare dalla dimensione della scheda rispetto alla uno, dopo aver prototipato in commercio trovi anche versioni di dimensioni più contenute

Apocalypse480:
Ciao Guglielmo, dici che otterrei latenze eccessive (parlo di tasti che vengono rilevati in ritardo o non rilevati affatto) con gli shift register?

Latenze eccessive dipende dalle specifiche, sicuramente usando uno shift per leggere 8 pulsanti ci metti sicuramente almeno 8 volte il tempo necessario a leggerle tutte assime. Se segui il consiglio di Guglielmo e lavori non con le digitalRead ma leggendo direttamente i registri collegati ai pin hai latenze irrisorie

Tutto sta in cosa rappresentano per te "ritardo praticamente nullo", "latenze eccessive" e "contemporaneamente".

Cambia tutto a seconda che "eccessivo" sia 1 us, 100 us, 1 ms, 10 ms o 100 ms. Tutti questi tempi sono grossomodo "istantanei" per i tempi di reattività umani, ma se hai vincoli più restringenti, specifica!

In ogni caso, millis() qua dubito c'entri qualcosa.

SukkoPera:
Tutto sta in cosa rappresentano per te "ritardo praticamente nullo", "latenze eccessive" e "contemporaneamente".

Cambia tutto a seconda che "eccessivo" sia 1 us, 100 us, 1 ms, 10 ms o 100 ms. Tutti questi tempi sono grossomodo "istantanei" per i tempi di reattività umani, ma se hai vincoli più restringenti, specifica!

In ogni caso, millis() qua dubito c'entri qualcosa.

Ho bisogno che vengano letti anche 10 tasti "contemporaneamente", ovvero che l'utente deve avvertire la sensazione del realtime alla pressione, quindi alla pressione di questi si verificheranno le azioni a loro legate ecc.
Quindi in pratica ho bisogno che magari mentre sto premendo un tasto e ancora non l'ho rilasciato possa premere altri tasti contemporaneamente ottenendo il risultato relativo.

Per quanto riguarda la funzione millis() l'avevo presa in considerazione per quanto riguarda i tempi di lettura dei tasti, per non sprecare tempo di cpu e rendere tutto più reattivo, in quanto più sono le istruzioni nel loop e più tempo ci vorrà per eseguirlo tutto, quindi tra il primo tasto e il quarantesimo passerà del tempo e siccome devo eseguire anche l'antirimbalzo via software per ogni pulsante le cose si complicano a livello di tempi.
Quindi anche in base alla gestione di questo avrei bisogno di una mano :slight_smile:

Apocalypse480:
… siccome devo eseguire anche l’antirimbalzo via software per ogni pulsante le cose si complicano a livello di tempi.

… ecco un’altra cosa da evitare! Il “debouncingsi fa hardware, bastano un paio di componenti per pin ed eviti di perdere tempo.

Guarda i possibili schemi allegati, se occorre (nel caso di pulsanti di bassa qulità) aumenta il valore della capacità.

Guglielmo

debouncing_hw.pdf (22.8 KB)

gpb01:
... ecco un'altra cosa da evitare! Il "debouncing" si fa hardware, bastano un paio di componenti per pin ed eviti di perdere tempo.

Guarda i possibili schemi allegati, se occorre (nel caso di pulsanti di bassa qulità) aumenta il valore della capacità.

Guglielmo

La penso allo stesso modo tuo Guglielmo, ma dovrei mettermi a farmi il circuitino così per 40 volte?

Se vuoi avere l' effetto della istantanetá della pressione coll'effetto hai tempi stratosfeici in cui il controller deve reagire.
Puoi leggere tutti i tasti singolarmente con un digitalread() e anche fare il debouce via software. basta che non usi dei delay maggiori di 10ms per ciclo loop.

Ciao Uwe

uwefed:
Se vuoi avere l' effetto della istantanetá della pressione coll'effetto hai tempi stratosfeici in cui il controller deve reagire.
Puoi leggere tutti i tasti singolarmente con un digitalread() e anche fare il debouce via software. basta che non usi dei delay maggiori di 10ms per ciclo loop.

Ciao Uwe

Nel mio sketch ci saranno solo letture dei pulsanti e invio dei messaggi midi relativi via seriale, quindi non dovrei avere delay ulteriori a quelli del debouncing.

Per quanto riguarda l'uso degli shift register (che dovrebbero essere 6, magari 3 su un pin e 3 su un altro), comporterebbero un ritardo notevole nonostante la loro frequenza?
(Per ritardo notevole intendo un ritardo che si nota nelle operazioni svolte dall'utente sul dispositivo, che essendo un controller midi ha bisogno della massima reattività possibile all'input)

Se la cosa deve essere "istantanea" per la percezione umana, allora credo che basti rimanere entro qualche decina di millisecondi, che per un microcontrollore è tantissimo tempo. Potrebbe anche andare bene la soluzione dello shift register, in questo caso, ma bisognerebbe provare, non ci ho mai gestito così tanti tasti.

Concordo con gli altri che il debouncing sarebbe meglio farlo in hardware, ma visto che i tempi non sono poi così stringenti, forse si potrebbe riuscire a fare anche in software. Però devi uscirtene con un modo sensato di farlo per un numero così grande di tasti, non è banale.

Ribadisco inoltre che millis() non c'entra proprio niente: serve a introdurre ritardi controllati, mentre tu dovrai leggere i tasti il più in fretta possibile.

gpb01:
... ecco un'altra cosa da evitare! Il "debouncing" si fa hardware

Non sono d'accordo, mediante l'uso della libreria bounce2 ho ottenuto ottimi risultati in fatto di debouncing. Ci ho fatto una gestione di 10 tasti per un gioco di tipo asta, quindi un'applicazione dove il (quasi) real-time era fondamentale.

Non mi si fraintenda, sul fatto che il debounce hardware risolva alla radice il problema del rimbalzo dei contatti non ci piove. Ma dire che il debounce software è da evitare, perché poco performante, secondo la mia esperienza non è vero.
Se si hanno vincoli di spazio, e/o non si vuole replicare lo stesso circuito centinaia di volte, per me è meglio adottare la soluzione software che offre un buon compromesso.

@Apocalypse480 se valuti di fare il debounce di molti tasti via software, qua trovi un mio esempio con l'uso della suddetta libreria:

Bounce (and Bounce 2) Library Official Question Forum

Se poi usi un Arduino MEGA come ti hanno consigliato gli altri, ti semplifichi la vita.

Apocalypse480:
Nel mio sketch ci saranno solo letture dei pulsanti e invio dei messaggi midi relativi via seriale, quindi non dovrei avere delay ulteriori a quelli del debouncing.

Come fai il deboucing? entrata per entrata?

Ciao Uwe

uwefed:
Come fai il deboucing? entrata per entrata?

Ciao Uwe

Esattamente

@krypton18 darò un'occhiata a quella libreria :slight_smile:

Usando la funzione shiftIn standard, cinque shift register in serie vengono letti in meno di un millisecondo (e fino a 40 volte più velocemente se si usa la scrittura/lettura diretta sulle porte).

In passato ho dovuto leggere 96 ingressi usando 12 shift register in serie, ciascuno con il suo debounce software. Dovevo contare impulsi casuali della durata di 100ms che potevano arrivare anche tutti assieme (la contemporaneità era il tormentone di chi mi aveva commissionato il lavoro, che non riusciva a capacitarsi di come fosse possibile gestirla, ma in effetti era un dirigente e non un tecnico).

Il tutto era ottenuto con alcuni array (stati, tempi), dove l'indice rappresentava l'ingresso desiderato. Quindi per fare il debounce bastava un for per ciclare sull'indice e un'unica funzione debounce (ok, adesso lo sto dicendo in C, la versione originale era scritta in assembly Z80, ma l'asm Z80 in molte cose è più lento del C Arduino, nel mio caso l'intero ciclo di loop durava 6ms, cioè 166 letture al secondo).

Ci sono diversi tipi di debounce: all'attacco, all'attacco e al rilascio, al rilascio. Il primo e l'ultimo non proteggono da eventuali spike, l'altro è un filtro passa basso, insensibile agli spike, ma introduce inevitabilmente un piccolo ritardo nel riconoscimento pari al tempo di debounce (che normalmente si aggira su qualche decina di millisecondi).

Se si vuole la massima reattività all' "attacco", e si è ragionevolmente sicuri che i pulsanti non saranno soggetti a scariche ESD e che i collegamenti non capteranno disturbi impulsivi, si può usare un debounce al rilascio. Rimane il fatto che il debounce, di qualsiasi tipo, introduce una pausa minima necessaria tra due pressioni consecutive.

Rimangono da vedere gli array. Servirà un array di byte da 5 elementi per contenere gli ingressi (da estrarre bit per bit). Per ogni ingresso servirà come minimo un byte di stato e un unsigned int per contare i millisecondi. Si può usare millis (castata a 16 bit per risparmiare memoria) per misurare i singoli tempi fino a 65 secondi. Quindi un array di byte da 40 elementi per gli stati, e un altro da 40 unsigned int per i tempi (e siamo a 125 byte di memoria dati occupata).

Claudio_FF:
Usando la funzione shiftIn standard, cinque shift register in serie vengono letti in meno di un millisecondo (e fino a 40 volte più velocemente se si usa la scrittura/lettura diretta sulle porte).

In passato ho dovuto leggere 96 ingressi usando 12 shift register in serie, ciascuno con il suo debounce software. Dovevo contare impulsi casuali della durata di 100ms che potevano arrivare anche tutti assieme (la contemporaneità era il tormentone di chi mi aveva commissionato il lavoro, che non riusciva a capacitarsi di come fosse possibile gestirla, ma in effetti era un dirigente e non un tecnico).

Il tutto era ottenuto con alcuni array (stati, tempi), dove l’indice rappresentava l’ingresso desiderato. Quindi per fare il debounce bastava un for per ciclare sull’indice e un’unica funzione debounce (ok, adesso lo sto dicendo in C, la versione originale era scritta in assembly Z80, ma l’asm Z80 in molte cose è più lento del C Arduino, nel mio caso l’intero ciclo di loop durava 6ms, cioè 166 letture al secondo).

Ci sono diversi tipi di debounce: all’attacco, all’attacco e al rilascio, al rilascio. Il primo e l’ultimo non proteggono da eventuali spike, l’altro è un filtro passa basso, insensibile agli spike, ma introduce inevitabilmente un piccolo ritardo nel riconoscimento pari al tempo di debounce (che normalmente si aggira su qualche decina di millisecondi).

Se si vuole la massima reattività all’ “attacco”, e si è ragionevolmente sicuri che i pulsanti non saranno soggetti a scariche ESD e che i collegamenti non capteranno disturbi impulsivi, si può usare un debounce al rilascio. Rimane il fatto che il debounce, di qualsiasi tipo, introduce una pausa minima necessaria tra due pressioni consecutive.

Rimangono da vedere gli array. Servirà un array di byte da 5 elementi per contenere gli ingressi (da estrarre bit per bit). Per ogni ingresso servirà come minimo un byte di stato e un unsigned int per contare i millisecondi. Si può usare millis (castata a 16 bit per risparmiare memoria) per misurare i singoli tempi fino a 65 secondi. Quindi un array di byte da 40 elementi per gli stati, e un altro da 40 unsigned int per i tempi (e siamo a 125 byte di memoria dati occupata).

Interessantissimo!
Una cosa sola non ho capito, ovvero nel byte di stato cosa andrà messo.
Di questo vorrei conferma: in pratica hai due array che allo stesso indice fanno corrispondere tasto premuto e in base a quello calcoli se è passato il tempo in cui si possono verificare rimbalzi?

Apparte questo, ho abbozzato questo codice:

unsigned int time;
int button;

void setup(){
  for(int s=3;s<=45;s++){
    pinMode(s,INPUT);
  }
}

void loop(){
  for(int i=3;i<=45;i++){
    time=millis();
    control(i);
  }
}
void control(int n){time=millis();
control(n);
  if (millis()-time>10){
    button=digitalRead(n);
    if (button==1){
      sendMIDI(n);
    }
  }
}

void sendMIDI(int bpressed){
  Serial.write();
  Serial.write();
  Serial.write();
}

Ancora mancano delle strutture dati per poter contenere i messaggi esadecimali del MIDI associati ad ogni bottone (tipo un array che in base al pulsante premuto usato come indice mi fa mandare il relativo messaggio MIDI).
Questo sketch non prevede ancora l’uso degli shift register, ma un pin digitale per ogni tasto come se stessi usando un arduino mega.
Come concetto secondo voi è efficiente?