Simulare un Dilplay M5450

Ciao,
sto cercando di integrare il mio vecchio allarme di casa basato sul micro ST6 e corredato di quadro sinottico a LED basato sul’M5450 con dei “sinottici” un po’ più evoluti.

(Sì, lo so dovrei rifare tutto con un sistema un po’ più moderno, ma sono vecchio anch’io … un passo alla volta! :confused: )

In sintesi si tratta di simulare un display a LED basato su un M5450: inserire un Arduino al posto del display per poterne prima catturare e poi elaborare opportunamente lo stato dei Led pilotati che formano il “sinottico” dei sensori perimetrali e volumetrici.

La comunicazione con l’M5450 prevede 36 cicli di clock su un filo corrispondenti a 1 start Bit e 35 Bit dati su un secondo filo.
nessun controllo di flusso ne hw ne sw:

|–| |—| |—| |—| |—| |—| |----|
Clock:| 1 || 2 || 3 || 4 || 5 || 6 |…| 36 |____

|----| |--------------| |-------|
Dati | || |___…ecc… | |

| START | Bit1 | Bit2 | Bit3 | Bit4 … | Bit35 |

I due segnali /fili Data e Clock che ricevo in ingresso nell’Arduino dall’ST6 (a dire il vero è infrapposto un “pilota” 74LS244) un sono “belli” puliti: a titolo di esempio il clock nell’immagine allegata.

Speravo di riuscire a leggere con Arduino correttamente le informazioni del sinottico con un codice più o meno così :

#define LcdDATA    11   
#define LcdCLOCK   13   

int OKDati;
int Sinottico_Spento;
int Dati_Nuovi[36];
unsigned long PrecedenteMillis = 0;

void setup() {
  // Configuro l'interrupt 
  attachInterrupt(digitalPinToInterrupt(LcdCLOCK), LeggiLED, RISING); // all'arrivo del primo fronte di salita del Clock lancio la sub "LeggiLED"
    
}

void LeggiLED(){
  detachInterrupt(digitalPinToInterrupt(LcdCLOCK)); 
  // disabilito l'interupt (non so se serva ma vorrei evitare che ogni fronte di salita chiami nuovamente l'interrupt)

        PrecedenteMillis = millis();  
        OKDati=1;
        Sinottico_Spento=0; 
        for (int i=1; i <= 36; i++) 
                 {
                  Dati_Nuovi[i]= digitalRead(LcdDATA); memorizzo il bit
                  while(digitalRead(LcdCLOCK)== HIGH) {if (millis()- PrecedenteMillis >200){ OKDati=0;break;}} 
                  // attendo la discesa del Clock

                  while(digitalRead(LcdCLOCK)== LOW)  {if (millis()- PrecedenteMillis >200){ OKDati=0;break;}} 
                  // attendo la salitadel Clock e ripeto il ciclo partendo dalla lettura del pin Dati
                  }

        for (int i=1; i <= 36; i++) 
                      {
                      Sinottico_Spento += Dati_Nuovi[i];
                      }

        if ((OKDati == 1) and (Sinottico_Spento!=0) and (Sinottico_Spento!=36)) 
            {
            Serial.print("Dati Validati!");
              // DATI RICEVUTI CORRETTAMENTE PROCEDO ALLA VALIDAZIONE
              
            }
  attachInterrupt(digitalPinToInterrupt(LcdCLOCK), LeggiLED, RISING); // rimetto a posto l'interrupt
}


void loop() {
 

}

(Soprassedendo sul sistema di uscita dai while che dovrebbe essere inutile se la sequenza dati non ha disturbi o errori)

In realtà però le info lette sono quasi “casuali” :disappointed_relieved: :disappointed_relieved: :disappointed_relieved:

A vostro avviso potrebbe essere un problema di temporizzazione del segnale?
Troppo veloce la comunicazione e troppo lento Arduino a leggere[ digitalRead(LcdDATA); ] ?
come vedete in allegato dovrei riuscire a completare un ciclo di lettura in meno di un millisecondo… troppo poco?

Nel caso sia un problema di velocità sapete se è possibile l’utilizzo della SPI per letture “bovine” configurando Arduino come “SLAVE” di una sequenza di 36 bit?
(forse modificando qualche libreria?)

Grazie fin d’ora di ogni spunto o consiglio!

:slight_smile:

Mi sa che hai esagerato un po' nella routine di interrupt! Nell'interrupt devi mettere solo dato=1; (dichiarata volatile byte). Il resto lo farai fuori. Inoltre, se vai a vedere il source code, scopri che digitalRead usa diverse macro e disabilita il pwm sul pin, quindi non è veloce come un PINx (dove x è la lettera della porta).

Dimenticati di fare quello che hai fatto nella ISR !

Le ISR DEVONO essere le più brevi possibili ... tipicamente si acquisisce un valore, si alza una flag e si esce immediatamente; sarà poi nel loop() il controllo ... se la flag è alzata si faranno le elaborazioni del caso e si riabbassa la flag.

Non solo, ma alcune cose che hai usato, in una ISR ... NON funzionano proprio, come ad esempio millis().

Devi ripensare il codice riducendo la ISR come ti ho detto e facendo tutto quello che devi fare altrove.

Guglielmo

Nella ISR l'interrupt viene già disattivato e, poi, riattivato all'uscita, quindi non serve farlo.

Grazie delle risposte!

Ho intuito il problema ma fatico a concretizzare una soluzione :confused: !

Se ho capito bene i problemi potrebbero essere di due tipi:

1) Gestione della routin di interrupt troppo pesante.

2) Tempi di lettura troppo lunghi se fatti con DigitalRead()

Partendo dal caso 1 (più comprensibile per i miei neuroni) la soluzione a vostro avviso potrebbe essere qualche cosa del genere?

#define LcdDATA    11   
#define LcdCLOCK   13   

int i=0;
int Letto_Nuovo_Bit=0;
int Dati_Nuovi[36];
int Dato_Nuovo=0;

void setup() 
{
  // Configuro l'interrupt 
  attachInterrupt(digitalPinToInterrupt(LcdCLOCK), LeggiLED, RISING); 
  // all'arrivo del fronte di salita del Clock lancio la sub "LeggiLED"
    
}

void LeggiLED()  // Routin di interupt
{
         Dato_Nuovo= digitalRead(LcdDATA); // Leggo l'ingresso DATI
         Letto_Nuovo_Bit=1; // segnalo la presenza di un nuovo bit in arrivo
}


void loop() 
{

      if (Letto_Nuovo_Bit==1)  // se sono in presenza di un nuovo bit in arrivo
      {
        Dati_Nuovi[i]= Dato_Nuovo;      // memorizzo il bit nell'array dei dati in Input
        Letto_Nuovo_Bit=0;                  // azzero la variabile "Bandiera" che indica bit in arrivo
        i=i+1;                                      // incremento il contatore del bit da leggere
      }
        
      if (i==36)                      // se ho letto 36 bit azzero il contatore e verifico se i dati letti hanno senso
         {
              Serial.print("Tutti i bit ricevuti procedo alla Verifica e validazione!");
                      // DATI RICEVUTI CORRETTAMENTE PROCEDO ALLA VALIDAZIONE
                      //..... 
          i=0;
         }

}

Nel caso 2) DigitalRead() troppo lento un po' mi stranisco ... un clock con un periodo di 1 ms dovrebbe significare 1 KHz e quindi 1KBit/s ... non pare velocità che possa impensierire il microprocessore. Mi pareva di aver letto che DigitalRead() viaggia sull'ordine dei 100KHz . Tuttavia tutto è possibile, nel caso allora dovrò passare a lavorare con il processore dell'ESP che dovrebbe essere la soluzione finale per creare un "sinottico Web" (per altro già fatto con altri "input" di casa).

Grazie!

TUTTE le varibili usate in una ISR DEVONO essere dichiarate con l'attributo "volatile" ...

Detto questo, non dovresti avere problemi a leggere 1 KHz con la digitalRead(), vero che è più pesante della lettura diretta del pin della porta, ma ad 1 KHz ancora c'arriva :D :D :D

Considera che hai varie cose che fanno perdere tempo:

  1. tempo di entrata nella ISR ≈ 2 o 3 μsec
  2. tempo di uscita da una ISR ≈ 2 o 3 μsec
  3. tempo di esecuzione delle istruzioni nella ISR.

... la digitalRead() impiega ≈ 4 μsec.

Quindi tra entrare, uscire e fare una digitalRead() vanno via ≈ 10 μsec + il tempo di assegnare il valore ad una variabile e vedi che ... ci stai tranquillamente.

Guglielmo

Mi sembra che manchi un sistema di sincronizzazione... Come fa a capire quale è il primo bit? Si deve resettare durante le pause tra un blocco e l'altro. Ad esempio, se non arrivano dati per 5ms, azzera i. Puoi anche mettere un rilevamento di dati incompleti, soprattutto per evitare errori all'accensione: se la pausa di sincronismo, quindi la fine del blocco, arriva prima del trentaseiesimo bit (quindi i si azzera prima di arrivare a 35, che è il trentaseiesimo elemento), scarta tutto il blocco.

Ma quale Arduino usi, per avere un Interrupt sul pin 13 ?

Grazie gpb01, sempre preciso e chiaro , introdurrò il prefisso volatile nelle variabile gestite dall’interupt!

@brunello22: era un mero esempio teorico, alla fine devo implementare tutto su un ESP

@ Datman: hai ragione! l’esempio è un “estratto minimo” da testare eventualmente sull’Arduino UNO pulito relativo a un sorgente ben più grosso

se supero il problema di lettura “hardware” dovrò inserire la parte di gestione della sincronizzazione!

Accennavo che si tratta di un “Sinottico Web” (consultabile come WebApp) dello stato dei sensori dell’allarme di casa mia.

La versione completa implementa su un ESP8266 un server SSL Https crittografato con chiave a 1024 bit con Basic Autentication e credenziali (login e password) di 16 caratteri in modo da risultare sufficientemente ostico anche ad un brutal force.

Una pagina web che si costruisce in modo dinamico (vedi allegato) e un sistema di segnalazione “push” ai cellulari della famiglia (con Pushover) in caso di situazioni anomale valutate con una serie di condizioni (si apre un balcone con la finestra chiusa o un volumetrico che segnala una presenza senza che sia entrato nessuno in casa attraverso una particolare sequenza di sensori perimetrali/Volumetrici …) con connessione a Server NTP per la gestione degli orari.

Tutto programmabile via OTA (visto che il micro è scomodo da raggiungere) e in grado di aprire una rete WiFi autonoma in caso di mancanza di internet e ripristinarsi al suo ritorno.

Un giochino insomma per passare le serate invernali che se ritenete condivido volentieri con la community!

orfmia: Grazie gpb01, sempre preciso e chiaro , introdurrò il prefisso volatile nelle variabile gestite dall'interupt!

Occhio che i valori che ti ho indicato sono, all'incirca, quelli per un ATmega328P a 16 MHz ... NON ho idea delle latenze e dei tempi di esecuzione su ESP8266 ::)

Guglielmo

Giusto! ha un clock da 160MhZ ma non si sa mai... A questo punto passo sull'ESP e faccio una prova brutale!

Test effettuato sul ESP8266 con il seguente Codice:

#define LcdDATA    14   // 14 = NodeMCU GPIO14 = Pin D5
void setup() {
    pinMode(LcdDATA, INPUT);      // imposta il pin come ingresso
    Serial.begin(115200);
}
void loop() {
Serial.print(micros());
Serial.print(" - ");
         for (int i=1; i <= 1000; i++) 
         {
          digitalRead(LcdDATA);
         }
Serial.println(micros());
}

che fa 1000 cicli di lettura (all’interno di un ciclo for che immagino introduca comunque un piccolo ritardo)

settando la frequenza di clock del micro a 80Mhz mi da questo risultato:

23:46:51.497 → 46606 - 47211

Quindi 47211-46606= 605 μsec per 1000 letture pari a 0,6 μsec a lettura.

portando il clock a 160Mhz
23:53:38.893 → 46152 - 46490

Quindi 46490-46152= 338 μsec per 1000 letture pari a 0,35 μsec a lettura, quasi il doppio.

tuttavia non è un risultato sconvolgente se paragonato al “vecchio” ATmega328P a 16 MHz che come dicevi tu ne metteva circa 4 μsec!

Comunque se non va la lettura non dovrebbe essere colpa dei “tempi” … domani provo a mettere in bella i vostri preziosi consigli e poi condivido i risultati.

Grazie ancora!
CIAO!