Display 7 segmenti via BCD

Quella libreria se non sbaglio usa il digital2 come ingresso con interrupt, quindi saranno da rivedere le assegnazioni delle uscite di Arduino. Non mi è ancora chiaro il formato dati in uscita.

Il GPS sarebbe bello, ma indoor lontano da vista cielo non credo funzioni (anche se a me che sono in mansarda sembra che qualcosa sul cellulare arrivi... grazie Dataman per avermi dato l'idea su cos'altro spendere :D )

Il DCF77 d'altra parte non ha meno problemi, l'antenna va collocata lontano da cavi elettrici, alimentatori, trasformatori, monitor, televisori, lampadine elettroniche (LED/CFL), variatori di luce, e ogni altra cosa che possa generare campi magnetici, inoltre la barretta di ferrite va orientata lungo l'asse est-ovest (va trovato empiricamente l'orientamento migliore), e il segnale arriva comunque "a sprazzi" e pieno di disturbi, in sostanza si riceve l'ora solo poche volte al giorno, tutto il resto del tempo l'orologio deve comunque avanzare per i fatti suoi.

Per il modulo non so, chiedevo appunto per sapere se e dove avevi trovato qualcosa a parte il solito modulo della Conrad.

Ma un normalissimo modulo RTC con DS3231 da pochissimi euro non va bene?

Eh ma come lo gestisco che ho tutto occupato?

Tutto occupato??? I 4 pulsanti e un LED potrebbero stare insieme… :slight_smile:

Pulsantiera con LED.jpg

ricki158: Eh ma come lo gestisco che ho tutto occupato?

Usa un piedino analogico e metti i pulsanti in una scala di resistenze.

Purtroppo ho già fatto il PCB. Magari in una versione 2.0, così l' RTC lo gestisco sui due ultimi pin analogici via i2c. Magari però apro un topic del progetto, senza parlarne qua. Così se devo rifare il PCB ne approfitto per chiedere informazioni su come mi conviene gestire la parte hardware, per esempio i Latch Enable di cui si parlava prima.

Io purtroppo sono alle prime armi per quanto riguarda la programmazione, o meglio in università non ho fatto molta programmazione di Arduino e i testi che ho trovato delle superiori non mi sono di aiuto. Insomma vi volevo ringraziare per le dritte e riproporre le mie scuse se magari porto alla frustrazione qualcuno qua dentro.

Detto questo volevo giungere alla conclusione del topic cercando di scrivere un numero intero di 6 cifre. Mi potreste spiegare come?

Il pcb è l’ultima cosa di cui occuparsi dopo aver testato per filo e per segno ogni cosa (software compreso), perché ci possono essere diverse modifiche da fare in corso d’opera.

La libreria del DCF ad esempio usa un pin di Arduino che tu vorresti usare come uscita, quindi in ogni caso va rifatto tutto (o va riscritta a mano la lettura della trama DCF, niente di insormontabile ma serve un po’di pratica con le macchine a stati finiti).

E non è detto che il DCF77 arrivi bene in quel punto della camera… l’antenna potrebbe richiedere una collocazione vicino al soffitto su una mensolina.

Con l’RTC indicato ci si toglie di mezzo questi problemi, e i pin per l’ex DCF diventano liberi.

Comunque per scrivere sei valori in multiplex su quei digit si potrebbe leggere un array di sei byte (qui chiamato tData):

#define  PIN_A0  2
#define  PIN_A1  3
#define  PIN_A2  4
#define  PIN_D0  5
#define  PIN_D1  6
#define  PIN_D2  7
#define  PIN_D3  8


uint32_t timeLoop = micros();
byte     tData[6] = {0, 0, 0, 0, 0, 0};  // posiz 0 = decine di ore, posiz 5 = unità secondi
byte     digit = 0;


void multiplex(){
    if (++digit > 6) digit = 1;         // digit cicla da 1 a 6
    byte value = tData[6 - digit];      // indice array cicla da 5 a 0
    PORTD = (PORTD & 3) | (digit << 2) | (value << 5);
    PORTB = (PORTB & 0xFE) | (value >> 3);
}


void setup(){
    pinMode(PIN_A0, OUTPUT);
    pinMode(PIN_A1, OUTPUT);
    pinMode(PIN_A2, OUTPUT);
    pinMode(PIN_D0, OUTPUT);
    pinMode(PIN_D1, OUTPUT);
    pinMode(PIN_D2, OUTPUT);
    pinMode(PIN_D3, OUTPUT);
}


void loop(){
    if(micros() - timeLoop > 3300){     // ogni 3.3 ms
        timeLoop += 3300;
        multiplex();                    // accende prossimo digit
    }
}

Se invece dell’array si volesse partire da tre variabili binarie ore minuti secondi, allora multiplex si potrebbe riscrivere così:

void multiplex(){
    byte value;
    if (++digit > 6) digit = 1;         // digit cicla da 1 a 6
    switch (digit)
    {
        case 1:  value = secondi % 10;  break;
        case 2:  value = secondi / 10;  break;
        case 3:  value = minuti % 10;   break;
        case 4:  value = minuti / 10;   break;
        case 5:  value = ore % 10;      break;
        case 6:  value = ore / 10;
    }
    PORTD = (PORTD & 3) | (digit << 2) | (value << 5);
    PORTB = (PORTB & 0xFE) | (value >> 3);
}

E se invece le variabili fossero in formato BCD packed:

void multiplex(){
    byte value;
    if (++digit > 6) digit = 1;         // digit cicla da 1 a 6
    switch (digit)
    {
        case 1:  value = secondi & 0x0F;  break;
        case 2:  value = secondi >> 4;    break;
        case 3:  value = minuti & 0x0F;   break;
        case 4:  value = minuti >> 4;     break;
        case 5:  value = ore & 0x0F;      break;
        case 6:  value = ore >> 4;
    }
    PORTD = (PORTD & 3) | (digit << 2) | (value << 5);
    PORTB = (PORTB & 0xFE) | (value >> 3);
}

Come mai nel primo codice hai riscritto le definizioni dei pin e risettati in modalità output?
Io ho inserito le tue righe nel codice come lo pensavo io:

/* ATMega 328P Standalone */
/* PORTD raggruppa in 1 byte di 8 bit i pin digitali da 0 a 7. Ricordiamo che i pin 0 e 1 sono rispettivamente Rx e Tx. L'ultimo bit corrisponde al pin digitale 0. */
/* PORTB raggruppa in 1 byte di 8 bit i pin digitali da 8 a 13. L'ultimo bit corrisponde al pin digitale 8. */
/* PORTC raggruppa in 1 byte di 8 bit i pin analogici da 0 a 5. L'ultimo bit corrisponde al pin analogico 0. */

#define LED_SEC 9;              /* 4 led blu che segnano i secondi. sono collegati al digit6 prima a sinistra delle ore. */
#define LED_DCF77ERROR 10;      /* led rosso che si accende in caso di errore del DCF77 o dati incorretti. */
#define LED_ALARM 11;           /* led di inserimento dell'allarme. */
#define BUZZER 12;              /* cicalina di allarme. */
#define LED_STATUS 13;          /* led di stato come su scheda ArduinoUNO. */

uint32_t TIME_LOOP = micros();
byte DATE[6] = {0, 0, 0, 0, 0, 0};        /* posizione 0 = decine giorno, posizione 5 = unità anno. */
byte TIME[6] = {0, 0, 0, 0, 0, 0};        /* posizione 0 = decine ore, posizione 5 = unità secondi. */
byte DIGIT=0;                   /* numero della cifra per il multiplexing, da 1 a 6, digit1 prima a destra dei secondi, digit6 prima a sinistra delle ore. */

void setup() {
  
  DDRD = DDRD | B11111100;      /* richiamo il registro della porta D (pin digitali da 0 a 7) e setto i pin dal 2 al 7 in modalità output, senza cambiare i pin Tx e Rx per sicurezza. */
  DDRB = DDRB | B11111111;      /* richiamo il registro della porta B (pin digitali da 8 a 13) e setto tutti i pin in modalità output. */
  DDRC = DDRC | B00000000;      /* richiamo il registro della porta C (pin analogici da 0 a 5) e setto tutti i pin in modalità input. */
}

void multiplex(){
  if (++DIGIT > 6) DIGIT = 1;         // digit cicla da 1 a 6
    byte VALUE = TIME[6 - DIGIT];      // indice array cicla da 5 a 0
    PORTD = (PORTD & 3) | (DIGIT << 2) | (value << 5);
    PORTB = (PORTB & 0xFE) | (VALUE >> 3);
}

void loop() {

   if(micros() - TIME_LOOP > 3300){
      TIME_LOOP += 3300;
      multiplex();              /* accende la prossima cifra */
   }
}

Appena mi arrivano i 6 display 7 segmenti blu ad anodo comune che ho ordinato, provo a vedere come va. Nel frattempo studio il codice per capire cosa hai scritto.
Il passo successivo, se tutto funziona, sarà quello di accendere i 4 LED da 3 mm blu che ho inserito per fare un flash allo scattare del secondo. Normalmente non è un problema ma in questa configurazione ho qualche perplessità. Potrei semplicemente dire che quando ricevo l’impulso dal DCF77 il LED_SEC deve andare basso per delay(250) e andare alto per delay(750).

Senti una cosa, apro allora nella sezione progetti l’orologio e ne parliamo lì di moduli DCF77, RTC e hardware?

ricki158:
Come mai nel primo codice hai riscritto le definizioni dei pin e risettati in modalità output?

Perché come detto nel post #24 c’erano interferenze con nomi “interni”. Il settaggio diretto del modo di funzionamento porte attraverso i registri DDRx potrebbe avere effetti collaterali sui pin assegnati alle periferiche. Il compilato effettua già dei settaggi invisibili all’utilizzatore, non è come partire con un controllore totalmente resettato, perciò i settaggi li farei nel modo “tradizionale” con pinMode(). L’unica variazione sarà quella per trasformare gli ingressi analogici in comuni ingressi digitali.

ho inserito le tue righe nel codice come lo pensavo io

Dalvo i DDRx su cui non ho esperienza, dovrebbe funzionare.

Potrei semplicemente dire che quando ricevo l’impulso dal DCF77 il LED_SEC deve andare basso per delay(250) e andare alto per delay(750).

Solo che ovviamente non si può usare delay, il programma non deve mai bloccarsi per più di tre millisecondi, altrimenti le cifre si spengono o tremolano (questa è la difficoltà aggiuntiva introdotta dal multiplex software, che obbliga a scomporre l’intera logica in brevi frammenti eseguiti molto velocemente).

Sul mio orologio DCF ci sono tre LED:

  • giallo: impulsi grezzi (pilotato direttamente dall’hardware della sezione ricevente, in questo modo si vedono visivamente tutti i disturbi o se la ricezione è buona)
  • verde: sync (se acceso indica due trame consecutive ricevute correttamente)
  • rosso: error (un flash ad ogni condizione di errore rilevata dal software… non indispendabile, ma già che c’era l’ho usato, inoltre è acceso fisso fino alla prima ricezione corretta, e ritorna acceso fisso dopo sei ore di mancata ricezione)

Claudio_F: Perché come detto nel post #24 c'erano interferenze con nomi "interni". Il settaggio diretto del modo di funzionamento porte attraverso i registri DDRx potrebbe avere effetti collaterali sui pin assegnati alle periferiche. Il compilato effettua già dei settaggi invisibili all'utilizzatore, non è come partire con un controllore totalmente resettato, perciò i settaggi li farei nel modo "tradizionale" con pinMode(). L'unica variazione sarà quella per trasformare gli ingressi analogici in comuni ingressi digitali. Dalvo i DDRx su cui non ho esperienza, dovrebbe funzionare.

Avevo capito che utilizzando il "metodo delle porte" riuscivo ad essere sufficientemente veloce. Il rinominare le variabili perché creavano conflitti ok, ma se già riesco ad arrivare ai pin attraverso il richiamo PORTx, non ha senso mettere le definizioni con porta e numero del pin. I registri DDRx invece ho trovato così com'è la spiegazione sulla parte di reference del sito.

Solo che ovviamente non si può usare delay, il programma non deve mai bloccarsi per più di tre millisecondi, altrimenti le cifre si spengono o tremolano (questa è la difficoltà aggiuntiva introdotta dal multiplex software, che obbliga a scomporre l'intera logica in brevi frammenti eseguiti molto velocemente).

Sul mio orologio DCF ci sono tre LED: - giallo: impulsi grezzi (pilotato direttamente dall'hardware della sezione ricevente, in questo modo si vedono visivamente tutti i disturbi o se la ricezione è buona) - verde: sync (se acceso indica due trame consecutive ricevute correttamente) - rosso: error (un flash ad ogni condizione di errore rilevata dal software... non indispendabile, ma già che c'era l'ho usato, inoltre è acceso fisso fino alla prima ricezione corretta, e ritorna acceso fisso dopo sei ore di mancata ricezione)

Forse non mi sono spiegato bene. LED_SEC praticamente mi pilota i 4 led blu da 3 mm che separano le ore dai minuti e i minuti dai secondi e dovrebbero accendersi allo scoccare del secondo, pensavo di farli accendere per 250mS e poi per 750mS tenerli spenti nell'attesa del nuovo secondo. Siccome attraverso il multiplexing (sono collegati assieme alla cifra delle decine delle ore) comunque si accendono, credo di poter scegliere in maniera separata quanto tempo possono rimanere accesi e quanto tempo possono rimanere spenti, e fare questa funzione ogni volta che la cifra delle unità dei secondi cambia, o meglio quando la cella 5 dell'array TIME varia.

ricki158: Avevo capito che utilizzando il "metodo delle porte" riuscivo ad essere sufficientemente veloce.

Si, il mio dubbio si riferiva solo alla parte di settaggio nella funzione setup (dove la velocità non conta).

Siccome attraverso il multiplexing (sono collegati assieme alla cifra delle decine delle ore) comunque si accendono, credo di poter scegliere in maniera separata quanto tempo possono rimanere accesi e quanto tempo possono rimanere spenti

Certo, ma per realizzare le temporizzazioni non si può usare delay in nessun punto del programma (altrimenti il multiplex si interrompe). I tempi in questo caso vanno realizzati usando millis (o contando i cicli di multiplex), cosa che vale anche per l'orario che deve avanzare anche in assenza del segnale DCF.

Io lo sapevo fare con delay(), con millis() devo imparare! Cerco qualcosa sul web per capire meglio.

Comunque cambiando argomento: che libri mi consigliereste per programmare decentemente in Arduino?

Ad esempio, accendere il LED on board sul digital pin 13 per 1.2 secondi quando la variabile signal viene settata a true:

bool     signal = false;                // comando accensione LED
uint32_t time;                          // tempo inizio accensione LED
byte     stat = 0;                      // stato processo

Switch eseguito continuamente dentro loop, pilota il LED per 1.2 secondi ma non blocca il loop:

switch (stat){
    case 0:                             // attesa comando
        if (signal){
            signal = false;             // reset comando
            time = millis();            // acquisisce tempo attuale
            digitalWrite(13, 1);        // accende LED on board
            stat = 1;                   // passa a stato 1
        }
        break;
    case 1:                             // attende 1.2 secondi
        if (millis() - time >= 1200){   // se timeout
            digitalWrite(13, 0);        // spegne LED on board
            stat = 0;                   // torna a stato 0
        }
}

Se invece si volesse agganciare lo switch al periodo del multiplex di 3.3 ms:

bool     signal = false;                // comando accensione LED
uint16_t counter;                       // contatore cicli
byte     stat = 0;                      // stato processo
switch (stat){
    case 0:                             // attesa comando
        if (signal){
            signal = false;             // reset comando
            counter = 364;              // imposta 1.2012 sec
            digitalWrite(13, 1);        // accende LED on board
            stat = 1;                   // passa a stato 1
        }
    case 1:                             // attende 1.2 secondi
        if (!--counter){                // se timeout (counter==0)
            digitalWrite(13, 0);        // spegne LED on board
            stat = 0;                   // torna a stato 0
        }

}

Visualizzando i secondi lascerei perdere i led blu intermittenti. Se proprio vuoi farli lampeggiare, fallo al 50%. In un orologio a nixie anch'io volevo aggiungere i punti lampeggianti, poi non li ho messi.

Io pensavo più a un codice del genere:

#define LED_SEC 9;              /* 4 led blu che segnano i secondi. sono collegati al digit6 prima a sinistra delle ore. */

int LED_SEC_STATE = LOW;
long LED_SEC_ON_TIME = 250;              /* millisecondi dell'accensione LED_SEC */
long LED_SEC_OFF_TIME = 750;             /* millisecondi dello spegnimento LED_SEC */
unsigned long previousMillis = 0;        /* salverà l'ultimo tempo in cui LED_SEC è stato aggiornato */

void setup(){
  DDRB = DDRB | B11111111;      /* richiamo il registro della porta B (pin digitali da 8 a 13) e setto tutti i pin in modalità output. */
}

void blink_LED_SEC() {
  unsigned long currentMillis = millis();

  if((LED_SEC_STATE == HIGH) && (currentMillis - previousMillis >= LED_SEC_ON_TIME)) {
    LED_SEC_STATE = LOW;                      /* spegni i LED */
    previousMillis = currentMillis;           /* ricorda il tempo */
    digitalWrite(LED_SEC, LED_SEC_STATE);     /* aggiorna lo stato del pin */
  }
  else if ((LED_SEC_STATE == LOW) && (currentMillis - previousMillis >= LED_SEC_OFF_TIME)) {
    LED_SEC_STATE = HIGH;                     /* accendi i LED */
    previousMillis = currentMillis;           /* ricorda il tempo */
    digitalWrite(LED_SEC, LED_SEC_STATE);     /* aggiorna lo stato del pin */
  }
}

void loop(){
    blink_LED_SEC()
}

Ho ancora qualche difficoltà a capire i pezzi di codice come ad esempio currentMillis - previousMillis, probabilmente perché non capisco esattamente ocme funziona la funzione millis() (e quindi anche la funziona micros() )

Il codice mi sembra giusto. Per non accumulare un piccolo ritardo ad ogni aggiornamento di previousMillis aggiornerei la variabile in questo modo:

void blink_LED_SEC() {
  unsigned long elapsed = millis() - previousMillis;

  if((LED_SEC_STATE == HIGH) && (elapsed >= LED_SEC_ON_TIME)) {
    LED_SEC_STATE = LOW;                      /* spegni i LED */
    previousMillis += LED_SEC_ON_TIME;        /* ricorda il tempo */
    digitalWrite(LED_SEC, LED_SEC_STATE);     /* aggiorna lo stato del pin */
  }
  else if ((LED_SEC_STATE == LOW) && (elapsed >= LED_SEC_OFF_TIME)) {
    LED_SEC_STATE = HIGH;                     /* accendi i LED */
    previousMillis += LED_SEC_OFF_TIME;       /* ricorda il tempo */
    digitalWrite(LED_SEC, LED_SEC_STATE);     /* aggiorna lo stato del pin */
  }
}

Con lo switch sarebbe praticamente identico:

void blink_LED_SEC() {
  unsigned long elapsed = millis() - previousMillis;

  switch (LED_SEC_STATE)
  {
    case HIGH:
        if(elapsed >= LED_SEC_ON_TIME){
            LED_SEC_STATE = LOW;
            previousMillis += LED_SEC_ON_TIME;    /* ricorda il tempo */
            digitalWrite(LED_SEC, LED_SEC_STATE); /* aggiorna lo stato del pin */
        }
        break;

    case LOW:
        if(elapsed >= LED_SEC_OFF_TIME){
            LED_SEC_STATE = HIGH;
            previousMillis += LED_SEC_OFF_TIME;   /* ricorda il tempo */
            digitalWrite(LED_SEC, LED_SEC_STATE); /* aggiorna lo stato del pin */
        }
  }
}

...lo switch permette forse di gestire in modo più chiaro processi che hanno più di due stati.

Invece una nota riguardo la definizione delle variabili di lavoro, questa versione usa 5 byte di RAM contro i 14 del tuo codice:

byte LED_SEC_STATE = LOW;
#define LED_SEC_ON_TIME  250              /* millisecondi dell'accensione LED_SEC */
#define LED_SEC_OFF_TIME 750              /* millisecondi dello spegnimento LED_SEC */
unsigned long previousMillis = millis();  /* salverà l'ultimo tempo in cui LED_SEC è stato aggiornato */

Inutile definire come variabili dei valori che non sono usati come variabili ma come costanti numeriche nel programma. Valori che non superano 255 possono essere contenuti in un byte anziché in un int (che occupa 2 byte).

Per il resto millis() ritorna semplicemente il valore di un contatore a 32 bit incrementato ogni millisecondo (tramite interrupt che funzionano in background), quindi millis() - previousMillis ritorna i millisecondi trascorsi dall'ultimo valore di previousMillis.

Nei miei sketch ho sempre usato "int", non sono mai arrivato a un livello di programmazione così alto. Grazie mille per le dritte utilissime! Piano piano sto conoscendo sempre più alcune strategie per fare delle funzioni particolari. Fino a questo momento ho fatto veramente cose basilari.

Grazie ancora per la pazienza!

Comunque adesso rileggevo il codice. Switch ... Case lo so usare. Volevo una delucidazione sull'uso di millis(). Praticamente io previousMillis = millis() mi fa cominciare il conteggio dei milliseocndi da quando si accende il processore. Mentre la variabile elapsed = millis() - previousMillis parte a contare i millisecondi da quando parte la funziona blink_LED_SEC con millis() al quale viene tolto il tempo previousMillis che è il tempo di accensione. Ma una cosa, se previousMillis mi conta i millisecondi dall'accensione e elapsed mi conta la differenza tra quando parte la funzione e l'accensione, questa variabile sarà una costante.

Adesso, se elapsed è più grande del tempo o uguale a LED_SEC_OFF_TIME (parto da LED_SEC_STATE = LOW), allora cambio la variabile LED_SEC_STATE e la porto su HIGH, a previousMillis aggiungo LED_SEC_OFF_TIME (quindi x + 750) e infine cambio lo stato del pin. Nel caso invece in cui LED_SEC_STATE = HIGH fa la stessa cosa praticamente variando naturalmente le variabili e aggiungendo x + 750 + 250.

Non mi è chiaro però come fa a temporizzare.

ricki158: Non mi è chiaro però come fa a temporizzare

In quel codice la funzione blink led viene eseguita qualche diecimila volte al secondo, per cui elapsed aumenta costantemente fino a raggiungere le soglie. I momenti in cui "si fa qualcosa" sono decisi dal momento in cui si verificano le condizioni. La maggior parte del tempo il programma rimane li a ciclare a vuoto testando le condizioni.

Scusami, ma continuo a non capire. Sono una frana!

Io uso nomi più brevi e facilmente leggibili per le variabili. Per il tempo, ad esempio, uso t0, t1, t2; per i valori precedenti di altre variabili aggiungo una o , ad esempio P e Po per lo stato attuale e precedente del pulsante. All'inizio del codice, dove definisco le variabili, descrivo anche a che cosa servono.

Per le azioni cicliche temporizzate, ad esempio far lampeggiare un LED, per non interrompere l'esecuzione del programma puoi scrivere:

if(millis()%1000>500) digitalWrite(pin,1); else digitalWrite(pin,0);

Così facendo, però, ogni volta che passa fa digitalWrite anche se non serve perché scrive la stessa cosa. Per evitare che ciò accada:

if(millis()%1000>500) {if(LED==0) {digitalWrite(pin,1); LED=1;}} else if(LED==1) {digitalWrite(pin,0); LED=0;}

cioè: se il resto di millis() diviso 1000 è maggiore di 500: se il LED è spento, accendilo e prendi nota che l'hai acceso; se non è maggiore di 500: se il LED è acceso, spegnilo e prendi nota che l'hai acceso.

Anziché usare la variabile LED potresti anche leggere ogni volta lo stato della porta, ma devi farlo con PORT, altrimenti digitalRead rallenta inutilmente il ciclo. Naturalmente, anche se per semplicità ho adottato il digitalWrite, il PORT sarebbe stato più veloce.

Datman: if(millis()%1000>500) digitalWrite(pin,1); else digitalWrite(pin,0);

In termine di costo di cicli, qual è più economico, tra un modulo e una comparazione.