Gestione 4 tasti in multiplex di 7seg x 4 katodo comune

Come da titolo, ho 4 display 7 segmenti connessi in multiplex, quindi i 4 catodi vengono attivati
ogni 2ms. Visto che sono 4 catodi cioè un nibble per semplificare scrivo cosa accade al nibble di seguito.

0001 durata 2ms
0010 durata 2ms
0100 durata 2ms
1000 durata 2ms
e si riparte daccapo no.

Il nibble viene scritto in uscita su un porta.
Tramite il circuito allegato ho connesso 4 pulsanti al 644A.
Kout va al pin PC2 posto come ingresso.
Mentre PWR, DWN, UP, e SET sono connessi ognuno al pin di comando del transistor npn a cui è connesso un catodo del digit.

Quindi in sostanza PC2 ha la pull-down è diventa HIGH quando un pulsante viene premuto sempre che il catodo sia acceso.

Per decodificare tutti è 4 tasti quindi devo salvare in un byte alla posizione del bit idx del catodo acceso la lettura da PC2.

idx con 4 catodi va da 0 a 3 e riparte da 0.

Ogni volta che idx == 3 metto il dato accumulato in buffer circolare non overwrited.
Questo lo faccio in una ISR che viene eseguita ogni 2ms, pertanto ogni 8ms ho un dato
da inserire nel buffer.

Fuori dalla isr ora mi serve gestire lo stream di tasti contenuto nel buffer.
Precedentemente ho usato un codice che anche se funziona non è mantenibile e ho notato che e anche più
lento del sistema con buffer key circolare e consume key. Tuttavia ancora non ho trovato una buona strategia
per gestire lo stream. I tasti possono essere premuti tutti insieme, possono essere premuti per x tempo o y tempo
e il codice eseguito di conseguenza sarà diverso. Quindi tutti i tasti multifunzione.

Avete qualche idea, strategia, o link per gestire questo stream.

PS: Il buffer è grande solo 5 tasti, potrebbe arrivare anche a 10 o 15, ma oltre finisce per avere effetto
di ritardo nell'uso, cioè se lasci il pulsante ci sono ancora dati nel buffer da processare.
PS2: lo so non è semplice.

Ciao.

schermata1.png

Non ho ben capito il tuo problema, se cioè ha difficoltà nella gestione della lettura oppure se nella gestione del dato letto...

Presuppongo però che sia la seconda, per cui diciamo come farei io.
Allora, partiamo da un presupposto. Eliminiamo per semplificare la questione del nibble e facciamo che salvo il dato in un byte, così se poi i pulsanti crescono di numero, posso gestirne di più mantenendo lo stesso meccanismo di calcolo sui byte. Il numero dei pulsanti lo indico con p. Nel tuo caso, p=4.

Mi faccio pertanto un array di x elementi che chiamo indicativamente readings (letture), dove metterò le letture fatte. Questo buffer ha un puntatore che chiamo per comodità ptr. Ptr punta sempre all'ultima cella libera. Quindi inizio con ptr=-1, così so che per ora il buffer è vuoto.
Ora, ogni volta che la ISR viene chiamata, va a leggere un pin e memorizza lo stato in un bit di un byte tampone, byte_temp. Quando ho letto lo stato di tutti i "p" pulsanti, salvo il byte tampone nell'ultima cella libera del buffer readings con readings[++prtr]=byte_temp.

Adesso dal programma principale io io faccio una funzione che chiamerò mettiamo readBuffer e che mi restituirà la prima lettura memorizzata nel buffer (readings[0]). Ovviamente se ptr==-1 allora restituisco un valore nullo perché significa che il buffer è vuoto. Se leggo qualcosa, decremento di 1 ptr e poi scalo il buffer in modo da avere sulla cella 0 sempre la lettura più vecchia.

Se vuoi avere il tempo di una pressione, non dovrai fare altro che controllare se per più letture successive il bit corrispondente a quel pulsante è settato, segno che l'utente ha tenuto il pulsante premuto per 8ms*num_letture.

Sostanzialmente il codice attuale che ho fa quello che hai descritto tu, te lo mostro.

// chiama questa funzione periodicamente almeno ogni 4ms
void dled_update()
{
    // idx is index cathode, idx range 0 to 3
    static uint8_t idx = 0;
    // 1 byte

    static uint8_t current_key_code = 0;

    if (idx > (DLED_COLUMNS -1))
        idx = 0;

    // read idx digit value from buffer and write this value to segment out of portb
    PORTB = dled_bit_buff[idx];

    RESET_OUT_CATHODE();
    SWITCH_ON_CATHODE(idx);

    _asm_nop(); // there is need, otherwise current_key_code is always zero
    // read PC2 bit value and save it in current_key_code to bit position (NMAX_CHATHODE - 1)
    set_bit_to(current_key_code, (NMAX_CATHODE - idx), (PINC & _BV(PC2)));
    // this code run each time which idx == 3
    // Note that idx count range value is 0-3 and restart from zero.
    if (idx == (DLED_COLUMNS - 1))
        // store current_key_code accumulate in key buffer
        key_buffer_store(current_key_code);

    idx++;

Come vedi in current_key_code c'è il risultato della scansione dello stato delle 4 uscite catodi lette attraverso
il pin PC2. DLED_COLUMNS - 1 vale 3, se idx == 3 salvo current_key_code nel keybuffer, nota che il salvataggio avviene anche se current_key_code = 0, cioè nessun tasto premuto.

Il codice è di una velocità spettacolare, se tengo piggiato UP o DWN il conteggio del display in 8-9 secondi passa da -30.0 a +30.0 a step di 0.1. Ora però i pulsanti UP DWN devono funzionare così:
1 pressione per x tempo (es 500ms) non ha alcun effetto se non rilascio il pulsante.
Se il pulsante rimane premuto per più di 500ms si attiva l'autorepeat che mi permette di modificare i parametri
alla velocità della luce. Ho pure un problema ad integrare il tutto nella macchina a stati usata dall'algoritmo, es se premo per ~5 sec il tasto Set entro in nella lista dei parametri comuni e posso scorrerli con up e dwn, ma qui non serve la velocità della luce, anzi è da evitare. Se dopo essere entrati in parametri comuni pigio Set viene mostrato il valore del parametro e con up e dwn mosso modificare il valore e qui serve la ipervelocità descritta prima. Se adesso pigio nuovamente Set non mostra più il valore del parametro ma torna indietro alla lista dei parametri.

Possibili alternative:
leggo dal key buffer e conto le occorrenze di tutti i key code ritornari, nel senso incremento una variabile ogni volta che il key code è 16, se quello successivo e ancora 16 incremento altrimenti ....? Questo conteggio lo faccio fuori dalla isr, quindi se ci fosse un traffico su seriale non bufferizzata la lettura dal key buffer rallenta come pure il conteggio.

Lavoro nella ISR e uso una struttura key che contiene i campi time, key_code e ...., forse status (es. PRESSED o RELEASED). La key buffer allora salva puntatori a struct e.... boh. Qualcosa di simile è nel codice vecchio funzionante, ma lento e poco mantenibile, confuso eh... insomma e da migliorare. Tra l'altro con il vecchio codice la isr è più impegnativa e poco chiara, così e breve chiara e concisa.

Intanto devo vedere come escludere/includere l'ipervelocità in modo elegante.

Chissà magari esiste già del codice pronto per gestire un flusso di key, magari nelle api di GNU, da cui prendere spunto.

PS:il 644A viaggia a 20MHZ, poi da portare a 8MHZ con l'oscillatore interno. Non dovrei avere problemi di velocità, perché così l'ipervelocità è davvero iper.

Ciao, scusa la lunghezza.

Io farei una semplice funzione da richiamare all'occorrenza. Anzi, potresti farne un paio.
La prima, semplice, che chiamerei buttonPressed, ti dice quale pulsante è stato premuto contando x stati ad 1 del bit corrispondente a quel pulsante.
L'altra funzione, simile, che chiamerei longButtonPress, restituisce (quando chiamata) se un pulsante è stato premuto per un lungo tempo. Anche qui, come sopra, conti le occorrenze ma a differenza della precedente,invece di restituire un qualunque pulsante premuto per x cicli, vai a controllare nello specifico un determinato pulsante e conti almeno 60 occorrenze, dato che 60*8ms=480 ms.

Però farei tutto al di fuori della ISR, per lasciare la ISR più snella possibile e non far fare ad ogni ciclo dei calcoli inutili, nel senso che il tuo codice non dovrà controllare solo i pulsanti, no?

Alla fine ho fatto come dicevi tu, semplice funzione che ritorna il valore dei tasti premuti.

Con l'occasione ho messo su una macchina a stati interessante, ma che ancora devo testare.

typedef void (*fnc_PTR)();

fnc_PTR machine_fnc[10];

typedef enum {
    DefaultEnter,
    Default,
    DefaultExit,
    CommonParameterEnter,
    CommonParameter,
    CommonParameterExit,
    EditParameterEnter,
    EditParameter,
    EditParameterExit

} machine_states_t;

machine_states_t               ms0_current_state;

void
default_state()
{
    //printf("cstate: Default\n");
    display_sprint("22.2", 1);
    ms_display = DefaultExit;
}

void
default_state_exit()
{
    //printf("cstate: DefaultExit\n");
    display_sprint("22.2", 1);
    m0_current_state = DefaultEnter;
}
void default_state_enter()
{
    // default_state_enter
    if (...)
         m0_current_state = DefaultEnter;   
    else 
        m0_current_state = ??;    // esce da default_state_enter e torna al while e prenota la prossima funzione
} 
int
main(void)
{
    m0_current_state = DefaultEnter;
    machine_fnc[DefaultEnter] = default_state_enter;
    machine_fnc[Default] = default_state;
    machine_fnc[DefaultExit] = default_state_exit;
    machine_fnc[CommonParameterEnter] = common_parameter_enter;
    machine_fnc[CommonParameter] = common_parameter;

    while (1)
    {
        machine_fnc[m0_current_state]();   // call state function
    }

}

In sostanza l'unica funzione che certamente viene eseguita è default_state_enter() e questa come le altre decidono a chi passare la palla.

Che ne pensi?
È nuda e cruda ma è velocissima.

Ciao.

Bel codice, mi piace :wink: