Go Down

Topic: Cronometro TM1637 (Read 596 times) previous topic - next topic

photobyfs

Buongiorno,

come da titolo sto cercando di visualizzare un cronometro su un display TM1637.
Nonostante la poca esperienza, dopo un po' di riflessione ho scritto il codice, che a livello matematico funziona correttamente.
L'unico problema che riscontro è che la visualizzazione non è "fluida": spesso i secondi rallentano per poi avanzare rapidamente, addirittura saltando un numero! Come se il processore fosse troppo "impegnato" nello svolgere i calcoli, che non sembrano però così impegnativi!

Esempio:
invece di procedere con     45:12  45:13  45:14  45:15  45:16
visualizzo magari                 45:12  45:13  45:13  45:15  45:16

Come posso risolvere l'inconveniente?
Allego parte del codice, in quanto in realtà il cronometro è solo una piccola parte di un codice molto più strutturato.

Grazie

Code: [Select]

#include <TM1637Display.h>
#define CLK1 12
#define DIO1 11
#define CLK2 10
#define DIO2 9
TM1637Display display1(CLK1, DIO1);
TM1637Display display2(CLK2, DIO2);

unsigned long t_parz_a, t_parz_b, t_tot;
unsigned long t0_a = 0, t0_b = 0;


void setup() {
  display1.setBrightness(7, true);
  display2.setBrightness(7, true);
}


void loop() {

  t_parz_a = (millis() - t0_a) / 1000;               // [s]
  t_parz_b = (millis() - t0_b) / 1000;               // [s]
  t_tot = millis() / 1000;                           // [s]


                                                            // esempio 429 secondi              // esempio 43 secondi
  float numero = t_parz_a / 60.0;                           // numero = 429 / 60 = 7.15         // numero = 0.716
  int minuti = numero;                                      // minuti = 7                       // minuti = 0
  numero = (numero - minuti) * 60.0;                        // numero = 0.15 * 60 = 9.0         // numero = 43.0
  int secondi = numero;                                     // secondi = 9                      // secondi = 43

                                                            // esempio min = 32  sec = 27        // esempio min = 5  sec = 9
  float i = minuti / 10.0;                                  // i = 3.2                           // i = 0.5
  int minuti_dec = i;                                       // minuti_dec = 3                    // minuti_dec = 0
  i = (i - minuti_dec) * 10.0;                              // i = 2.0                           // i = 5.0
  int minuti_uni = i;                                       // minuti_uni = 2                    // minuti_uni = 5
  float j = secondi / 10.0;                                 // j = 2.7                           // j = 0.9
  int secondi_dec = j;                                      // secondi_dec = 2                   // secondi_dec = 0
  j = (j - secondi_dec) * 10.0;                             // j = 7.0                           // j = 9.0
  int secondi_uni = j;                                      // secondi_uni = 7                   // secondi_uni = 9

  int disp = minuti_dec * 1000 + minuti_uni * 100 + secondi_dec * 10 + secondi_uni;
  display1.showNumberDecEx(disp, 0b11100000, true);

}
-- photobyfs --

pgiagno

Non avendo lo sketch completo posso solo dire che non capisco la gestione di millis().

Un esempio di cronometro semplice al decimo di secondo potrebbe essere questo

Code: [Select]
unsigned long t_tot, t_init, t_clock;

void setup() {
  //..
  t_clock = 100;        //precisione del decimo di secondo
  t_init = millis();
}

void loop() {
  if (millis() - t_init >= t_clock) {
    t_init = millis();
    // Sono passati 100 millisecondi
    // Aggiorna il display
    //...
    // Fai quello che devi fare
    //...
  }
  // Se hai altro da fare, fallo qui, senza usare "delay();"
}


Ciao,
P.

Datman

#2
Jul 18, 2019, 12:45 pm Last Edit: Jul 18, 2019, 12:46 pm by Datman
Dovendo avere una risoluzione di 1 secondo, fai l'aggiornamento del display solo una volta al secondo:
Code: [Select]
unsigned long t;

if(millis()-t>999)
 {
 t=millis();
 display();
 }

Considera, però, che potrebbe anche essere il resto del programma a rallentare il loop!
Comunque, per i calcoli che fai non è necessario usare variabili float:
Code: [Select]
uint8_t minuti;
uint8_t secondi;

minuti=secondiTotali/60;
secondi=seconditotali%60;


Hi,I'm Gianluca from Roma.I play&work with electronics since I was16(1984).
After 25yrs of maintenance on cameras&video mixers,since 2013myJob is HDTVstudios design.
Since Jan2015 IPlayWith Arduino:bit.ly/2F3LPWP
Thanks 4 a Karma if U like my answer

photobyfs

#3
Jul 18, 2019, 02:10 pm Last Edit: Jul 18, 2019, 02:14 pm by photobyfs
Non avendo lo sketch completo posso solo dire che non capisco la gestione di millis().
Effettivamente non ho spiegato l'utilizzo della variabile t0: serve per azzerare il cronometro. In pratica alla pressione di un tasto, t0=millis() ed i secondi totali sono millis()-t0, quindi di fatto ripartono da zero!


Riporto il codice con la parte di azzeramento, per essere più chiaro.

Code: [Select]
#include <TM1637Display.h>
#define CLK1 12
#define DIO1 11
TM1637Display display(CLK1, DIO1);

unsigned long t_parz, t_tot;
unsigned long t0 = 0;

#define azzera 6

void setup() {
  display.setBrightness(7, true);
}


void loop() {

  t_parz = (millis() - t0) / 1000;                   // [s]
  t_tot = millis() / 1000;                           // [s]

  //                                                        // esempio 429 secondi              // esempio 43 secondi
  float numero = t_parz / 60.0;                             // numero = 429 / 60 = 7.15         // numero = 0.716
  int minuti = numero;                                      // minuti = 7                       // minuti = 0
  numero = (numero - minuti) * 60.0;                        // numero = 0.15 * 60 = 9.0         // numero = 43.0
  int secondi = numero;                                     // secondi = 9                      // secondi = 43

  //                                                        // esempio min = 32  sec = 27        // esempio min = 5  sec = 9
  float i = minuti / 10.0;                                  // i = 3.2                           // i = 0.5
  int minuti_dec = i;                                       // minuti_dec = 3                    // minuti_dec = 0
  i = (i - minuti_dec) * 10.0;                              // i = 2.0                           // i = 5.0
  int minuti_uni = i;                                       // minuti_uni = 2                    // minuti_uni = 5
  float j = secondi / 10.0;                                 // j = 2.7                           // j = 0.9
  int secondi_dec = j;                                      // secondi_dec = 2                   // secondi_dec = 0
  j = (j - secondi_dec) * 10.0;                             // j = 7.0                           // j = 9.0
  int secondi_uni = j;                                      // secondi_uni = 7                   // secondi_uni = 9

  int disp = minuti_dec * 1000 + minuti_uni * 100 + secondi_dec * 10 + secondi_uni;
  display.showNumberDecEx(disp, 0b11100000, true);

  if (digitalRead(azzera) == LOW) {
    t0 = millis();
  }
}



Quote
Comunque, per i calcoli che fai non è necessario usare variabili float
Non conosco il tipo di variabile che mi hai indicato! Dovendo utilizzare numeri con la virgola, sbaglio ad usare float?
Il mio scopo è, da un dato iniziale in secondi (es 4973) ottenere la visualizzazione in minuti:secondi, peraltro salvata in una sola variabile (nell'esempio 82:53, da inviare al display come un int 8253).


Quote
Dovendo avere una risoluzione di 1 secondo, fai l'aggiornamento del display solo una volta al secondo
Quindi nel mio codice invece di lasciare display.showNumberDecEx(disp, 0b11100000, true); libero nel loop, lo dovrei scrivere come segue?
Code: [Select]

if(millis()-t>999) {
 t=millis();
 display.showNumberDecEx(disp, 0b11100000, true);
}


-- photobyfs --

pgiagno

Provo a capire cosa vuoi fare
- Un cronometro che parte alla pressione di un pulsante A, visualizzando in tempo reale il passare del tempo
- Che si ferma, dando un tempo parziale, alla pressione di un pulsante B, continuando a contare il tempo in background
- Che riprende la visualizzazione corrente in tempo reale alla successiva pressione del pulsante B
. Che si ferma alla pressione di un pulsante C
- Che si azzera alla successiva pressione del pulsante C

È questo che vuoi fare? Qualcosa di più semplice? di più complicato?

Se sì, che precisione vuoi? Il secondo va bene?

Ciao,
P.

Datman

#5
Jul 18, 2019, 03:20 pm Last Edit: Jul 18, 2019, 03:35 pm by Datman
uint8_t è il tipo generico di una variabile a 8 bit senza segno con valore che può andare, quindi, da 0 a 255. Nel caso specifico di Arduino equivale a byte. Se vuoi metterci minuti e secondi, ti serve un unsigned int (tipo generico: uint16_t, cioè unsigned int 16 bit), che arriva a 65535.

Code: [Select]
int disp=100*(t_parz/60) +t_parz%60;
// Può arrivare a 255 minuti e 59 secondi (4 ore e quasi 16 min.).

Fai attenzione a fare la scrittura sul display una volta al secondo in sincronia con il calcolo, altrimenti puoi avere errori di un secondo:
Code: [Select]
if(millis()-t0>999)
  {
  t0=millis();
  t_parz++;
  int disp=100*(t_parz/60) +t_parz%60;
  display.showNumberDecEx(disp, 0b11100000, true);
  }
Tieni presente che la precisione del tuo cronometro dipende dalla precisione della frequenza di clock del microcontrollore che, in Arduino Uno, è prodotta da un risuonatore ceramico, meno preciso di un quarzo.
Per avere una precisione maggiore devi usare una scheda che abbia il quarzo oppure montare il microcontrollore su un circuito stampato o mille fori con un quarzo da 16Mhz e relativi condensatori da 22pF.
Hi,I'm Gianluca from Roma.I play&work with electronics since I was16(1984).
After 25yrs of maintenance on cameras&video mixers,since 2013myJob is HDTVstudios design.
Since Jan2015 IPlayWith Arduino:bit.ly/2F3LPWP
Thanks 4 a Karma if U like my answer

photobyfs

Quote
Provo a capire cosa vuoi fare
- Un cronometro che parte alla pressione di un pulsante A, visualizzando in tempo reale il passare del tempo
- Che si ferma, dando un tempo parziale, alla pressione di un pulsante B, continuando a contare il tempo in background
- Che riprende la visualizzazione corrente in tempo reale alla successiva pressione del pulsante B
. Che si ferma alla pressione di un pulsante C
- Che si azzera alla successiva pressione del pulsante C

È questo che vuoi fare? Qualcosa di più semplice? di più complicato?

Se sì, che precisione vuoi? Il secondo va bene?

Ciao,
P.
Sto cercando di creare un tripmaster per auto, che in pratica funziona come un contakm di bicicletta! Quindi assieme alla visualizzazione della velocità e della distanza percorsa mi interessa anche il tempo.
Fisicamente sarà realizzato con due schermi, uno per rappresentare le quantità TOTALI, ed uno le quantità PARZIALI, queste ultime suddivise in trip_a e trip_b.
Quindi a seconda delle posizioni degli switch visualizzerò distanza totale e distanza parziale (a o b), oppure tempo totale e tempo parziale (a o b).

In particolare il cronometro:
- si avvia all'accensione dell'apparecchio
- si azzera alla pressione di un tasto (solo il parziale, a o b che sia)

La parte dell distanza è relativamente semplice e già funziona. Ora sto ragionando sulla parte legata al cronometro. Il codice riportato funziona, ma va "a scatti" e non è bellissimo da vedere.
Non ho necessità di grande precisione, sarà utilizzato per 8 ore al massimo, tra l'altro i parziali verranno azzerati ogni pochi minuti, quindi la risoluzione del piccolo Arduino è più che sufficiente.

Tra l'altro ho notato un ulteriore problema: se nello schermo dei parziali visualizzo min:sec, e nello schermo dei totali visualizzo ore:min, i minuti non sono sincronizzati! E' per un problema di arrotondamento? Allego estratto del codice.

Grazie delle risposte.

Code: [Select]

  if (digitalRead(switch_1) == HIGH) {                          // visualizza i tempi su schermo 1 e 2

    //                                                          // esempio 4429 secondi
    float numero = t_tot / 3200.0;                              // numero = 4429 / 3200 = 1.23
    int ore = numero;                                           // ore = 1
    numero = (numero - ore) * 60.0;                             // numero = 0.23 * 60 = 13.8
    int minuti = numero;                                        // minuti = 13

    //                                                          // esempio ore = 1  min = 13
    float i = ore / 10.0;                                       // i = 0.1
    int ore_dec = i;                                            // ore_dec = 0
    i = (i - ore_dec) * 10.0;                                   // i = 1.0
    int ore_uni = i;                                            // ore_uni = 1
    float j = minuti / 10.0;                                    // j = 1.3
    int minuti_dec = j;                                         // minuti_dec = 1
    j = (j - minuti_dec) * 10.0;                                // j = 3.0
    int minuti_uni = j;                                         // minuti_uni = 3

    int disp = ore_dec * 1000 + ore_uni * 100 + minuti_dec * 10 + minuti_uni;
    display2.showNumberDecEx(disp, 0b11100000, true);

    if (digitalRead(switch_4) == LOW) {                         // visualizza t_parz_a su schermo 1

      //                                                        // esempio 429 secondi              // esempio 43 secondi
      float numero = t_parz_a / 60.0;                           // numero = 429 / 60 = 7.15         // numero = 0.716
      int minuti = numero;                                      // minuti = 7                       // minuti = 0
      numero = (numero - minuti) * 60.0;                        // numero = 0.15 * 60 = 9.0         // numero = 43.0
      int secondi = numero;                                     // secondi = 9                      // secondi = 43

      //                                                        // esempio min = 32  sec = 27        // esempio min = 5  sec = 9
      float i = minuti / 10.0;                                  // i = 3.2                           // i = 0.5
      int minuti_dec = i;                                       // minuti_dec = 3                    // minuti_dec = 0
      i = (i - minuti_dec) * 10.0;                              // i = 2.0                           // i = 5.0
      int minuti_uni = i;                                       // minuti_uni = 2                    // minuti_uni = 5
      float j = secondi / 10.0;                                 // j = 2.7                           // j = 0.9
      int secondi_dec = j;                                      // secondi_dec = 2                   // secondi_dec = 0
      j = (j - secondi_dec) * 10.0;                             // j = 7.0                           // j = 9.0
      int secondi_uni = j;                                      // secondi_uni = 7                   // secondi_uni = 9

      int disp = minuti_dec * 1000 + minuti_uni * 100 + secondi_dec * 10 + secondi_uni;
      display1.showNumberDecEx(disp, 0b11100000, true);
    }

    if (digitalRead(switch_4) == HIGH) {                        // visualizza t_parz_b su schermo 1

      float numero = t_parz_b / 60.0;
      int minuti = numero;
      numero = (numero - minuti) * 60.0;
      int secondi = numero;

      float i = minuti / 10.0;
      int minuti_dec = i;
      i = (i - minuti_dec) * 10.0;
      int minuti_uni = i;
      float j = secondi / 10.0;
      int secondi_dec = j;
      j = (j - secondi_dec) * 10.0;
      int secondi_uni = j;

      int disp = minuti_dec * 1000 + minuti_uni * 100 + secondi_dec * 10 + secondi_uni;
      display1.showNumberDecEx(disp, 0b11100000, true);
    }
  }


La logica che ho pensato è la seguente:
- se switch_1 è HIGH allora negli schermi visualizzo i tempi (altrimenti le distanze)
- in ogni caso sul primo schermo visualizzo i tempi totali
- poi discrimino per il secondo schermo se lo switch è su trip_a o trip_b
- in base a questo su quello schermo visualizzo il tempo parziale (che finchè non azzero corrisponderà in realtà al totale, a differenza della visualizzazione in min:sec piuttosto che in ore:sec)

Non capisco perchè nello schermo dei totali i minuti scattino con un certo anticipo (diversi secondi) rispetto allo schermo dei parziali!
Come dicevo presumo per un arrotondamento nei calcoli...boh!
-- photobyfs --

pgiagno

Penso che il problema sia nella gestione di millis().

Devi considerare millis() come un contatore che parte da 0 quando il programma va in esecuzione e continua a incrementarsi ogni millisecondo per circa 50 giorni, a meno che il programma non venga fatto ripartire. Non c'è altro modo di "azzerare" millis() se non con un reset

Quindi il passare del tempo viene sempre calcolato come differenza fra due valori.

Quindi il tempo t0_sec è dato da t0_sec = millis(), il passaggio di un secondo viene calcolato quando millis() - t0_sec = 1000. In realtà conviene scrivere millis() - t0_sec >= 1000 per sicurezza.

A questo punto la prima cosa da fare è "riarmare" t0_sec con un nuovo t0_sec = millis(). Ed è la prima istruzione da eseguire per avere la massima precisione che, come dice Datman, non è eccelsa, ma nel tuo caso più che sufficiente.

Se si vuole un calcolo del tempo trascorso dall'inizio, basta mettere t0_iniz = millis() al termine del setup e calcolare il tempo trascorso con t_elapsed = millis() - t0_iniz.

Conviene inoltre mantenere l'orario in termini di millisecondi, in una variabile unsigned long e decodificare ore, minuti e secondi al momento del display.

Come suggerisce Datman non è necessario usare il tipo float per i calcoli che vuoi fare tu, ma il tipo unsigned long. Il linguaggio C consente di ricavare ore, minuti e secondi in modo molto più diretto. Ad es:

Code: [Select]
  unsigned long numero;
  uint8_t ore, minuti, secondi;
  uint16_t disp;

  . . .

  //ad esempio "numero" contiene 12345 secondi da dividere in ore e minuti
  ore = numero / 3200; // ore = 3
  minuti = numero % 3200 / 60; // minuti = 45
  secondi = numero % 3200 % 60;  // secondi = 45

  //per visualizzare ore e minuti
  disp = ore * 100 + minuti; // disp = 300+45 = 345

  //per visualizzare minuti e secondi
  disp = minuti * 100 + secondi; // disp = 4500+45 = 4545

Ciao,
P.

Datman

Dice chiaramente che per "azzeramento" intende il porre a zero la differenza tra millis() e t0, ponendo t0=millis().
Hi,I'm Gianluca from Roma.I play&work with electronics since I was16(1984).
After 25yrs of maintenance on cameras&video mixers,since 2013myJob is HDTVstudios design.
Since Jan2015 IPlayWith Arduino:bit.ly/2F3LPWP
Thanks 4 a Karma if U like my answer

paolo311

Secondo me il problema degli "scatti" dipende dal fatto che il valore restituito da millis() si incrementa in maniera asincrona, mentre la visualizzazione dei valori è sincrona al loop.

Questo implica che la velocità di resfresh degli schermi dipende da quanto ci impiega il loop ad eseguire un ciclo completo. Se il requisito è che lo schermo debba "snocciolare" un secondo per volta, bisogna essere sicuri che il loop, nel caso peggiore, ci impieghi meno di un secondo ad essere eseguito; altrimenti il refresh dello schermo accumulerà ritardo fino a saltare un secondo, pur mostrando un valore esatto.

Per i minuti "diversi" devi verificare se l'intervallo mostrato è il medesimo, l'algoritmo di calcolo è lo stesso e i schemi vengono refreshati diciamo nello stesso istante; se una di queste 3 cose non avviene, è probabile che i due valori mostrati siano diversi.

Non so se mi sono capito.

Ciao
Paolo

Datman

Se non ti sei capito nemmeno tu, è un problema.
Hi,I'm Gianluca from Roma.I play&work with electronics since I was16(1984).
After 25yrs of maintenance on cameras&video mixers,since 2013myJob is HDTVstudios design.
Since Jan2015 IPlayWith Arduino:bit.ly/2F3LPWP
Thanks 4 a Karma if U like my answer

pgiagno

Come sempre succede è necessario l'INTERO sketch con la configurazione HW ed eventualmente lo schema elettrico.

Se questo non è possibile, mi fermo qui.

Ciao,
P.

gpb01

paolo311 ha ragione. Purtroppo la visulalizzazione corretta dei valori del tempo è sempre causa di problemi, salvo la funzione loop() non giri almeno il doppio più veloce della cifra significativa che si vuole visualizzare, altrimenti ... gli avanzamenti a scatti sono la norma.  :smiley-confuse:

Guglielmo
Search is Your friend ... or I am Your enemy !

Datman

Dato che parla di letture di velocità, potrebbe anche essere un pulseIn che rallenta enormemente il loop.
Hi,I'm Gianluca from Roma.I play&work with electronics since I was16(1984).
After 25yrs of maintenance on cameras&video mixers,since 2013myJob is HDTVstudios design.
Since Jan2015 IPlayWith Arduino:bit.ly/2F3LPWP
Thanks 4 a Karma if U like my answer

photobyfs

Come richiesto allego l'intero codice.

Per quanto riguarda i collegamenti elettrici, l'unico input esterno è l'impulso di velocità che leggo con un interupt (che per ora simulo con un pulsante e resistenza di pull-down ma in futuro sarà un semplice sensore hall collegato ad una ruota o albero di trasmissione). Il resto sono solo switch.

Come precisato, con il codice così come è scritto il primo prototipo funziona, solamente che il cronometro va a scatti ed i minuti tra le due visualizzazioni non "cambiano assieme".

Un ulteriore step sarebbe quello di visualizzare la velocità istantanea, ma devo capire come calcolare il tempo tra due impulsi successivi...ci devo pensare con calma viste le mie limitate conoscenze della materia.

Code: [Select]

#include <TM1637Display.h>
#define CLK1 12
#define DIO1 11
#define CLK2 10
#define DIO2 9
#define CLK3 8
#define DIO3 7
TM1637Display display1(CLK1, DIO1);
TM1637Display display2(CLK2, DIO2);
TM1637Display display3(CLK3, DIO3);

const byte switch_1 = 3;                // switch tra dist - time               ---->  lo switch avviene mandando a massa i pin per poter utilizzare la resistenza di pullup interna
const byte switch_2 = 13;               // switch tra actual - average
const byte switch_3 = 4;                // switch tra avanti - indietro
const byte switch_4 = 5;                // switch tra a - b
const byte azzera   = 6;                // azzeramento                          ---->  l'azzeramento avviene mandando a massa il pin per poter utilizzare la resistenza di pullup interna

float trip_a, trip_b, odo, v_act, v_med;
unsigned long t_parz_a, t_parz_b, t_tot;
unsigned long t0_a = 0, t0_b = 0;

volatile long a = 0;                    // a --> trip a
volatile long b = 0;                    // b --> trip b
volatile unsigned long c = 0;           // c --> odo
// volatile unsigned long t = 0, delta_t;

const float rap  = 11.0 / 43.0;         // 43 giri albero = 11 giri ruota  rapporto coppie coniche differenziale sj410
const float circ = 2.095;               // [m]
const float dist = circ * rap;          // PER VERIFICARE: misurare spazio noto e visualizzare la variabile a ---> dist = spazio/a
const int azzeraDelay = 250;

void contatore() {
  //delta_t = millis() - t;
  if (digitalRead(switch_3) == LOW) {      // marcia avanti
    a++;
    b++;
  }
  if (digitalRead(switch_3) == HIGH) {     // marcia indietro
    a--;
    b--;
  }
  c++;
  //t = millis();
}


void setup() {

  Serial.begin(9600);

  pinMode(switch_1, INPUT_PULLUP);
  pinMode(switch_2, INPUT_PULLUP);
  pinMode(switch_3, INPUT_PULLUP);
  pinMode(switch_4, INPUT_PULLUP);
  pinMode(azzera, INPUT_PULLUP);

  display1.setBrightness(7, true);
  display2.setBrightness(7, true);
  display3.setBrightness(7, true);

  attachInterrupt(0, contatore, RISING);             // interrupt 0 = pin D2   interrupt 1 = pin D3

}


...continua...
-- photobyfs --

Go Up