Ciao a tutti
Per giocare un po' con i display con il 7219 ho pensato di fare un cronometro. Inizialmente pensavo di usare semplicemente millis(), ma poi ho preferito usare un timer, che mi permette anche di aggiornare il contatore del tempo nella ISR, quindi indipendentemente da qualunque altra cosa stia accadendo.
Ho usato un interrupt anche per la lettura dell'encoder, non per non perdere impulsi, ma per avere, anche qui, una lettura continua in qualunque stato del programma, senza doverlo leggere continuamente. Per il conteggio del tempo ho impostato il timer a 10ms (100Hz), con il solito calcolatore online:
https://www.arduinoslovakia.eu/application/timer-calculator
Per l'encoder, invece, ho usato un Pin Change Interrupt.
Appena acceso, dopo la presentazione, è pronto a partire alla prima pressione dell'encoder; alla seconda si mette in pausa e alla successiva riparte. Un altro pulsante azzera il conteggio. Al momento può azzerare e tenere azzerato anche durante il conteggio ripartendo subito dopo, ma forse sarebbe meglio farlo funzionare solo a conteggio fermo. Ci vuole poco...
Durante il conteggio, ruotando l'encoder e poi premendolo si può accedere all'impostazione Centesimi/Frame. Può essere utile per usarlo per sicronizzare riprese video (a 25 frame/secondo).
Quando il conteggio è fermo, invece, il menu è più ampio:
- prima c'è il Preset, con cui si può impostare il tempo all'avvio in ore, minuti e secondi;
- poi c'è Avanti/Indietro, con cui si può contare alla rovescia e al raggiungimento dello zero suona la note Do-La-Fa (Fa M).
- poi c'è ancora Centesimi/Frame.
Funziona molto bene e, dato che il conteggio avviene nella ISR, è stato facile non fermarlo mai se non richiesto.
Che ne dite? Come mi è venuto?
#include "LedControl.h"
#define DIN A0
#define CS A1
#define CLK A2
LedControl disp = LedControl(DIN,CLK,CS);
bool premuto=true;
int8_t selezione=0; // Valore selezionato nel menu.
volatile bool start=false; // true: conteggio in corso.
volatile bool fine=false; // true: tempo scaduto (conteggio all'indietro).
bool azzerato=false;
volatile uint32_t t100=0; // Tempo contato (nella ISR) in centesimi di secondo.
uint8_t secondi=0;
uint8_t minuti=0;
uint8_t ore=0;
volatile int8_t E; // Risultato della routine encoder(): 1, -1, 0.
volatile uint8_t S; // Lettura dei due valori dell'encoder.
volatile uint8_t So;// Lettura precedente dell'encoder.
volatile uint8_t X; // Usato in encoder() per evitare letture multiple.
int8_t preset_ore=0;
int8_t preset_minuti=0;
int8_t preset_secondi=0;
bool frame=false; // true:frame; false:centesimi.
volatile bool avanti=true; // false:conta indietro
// ----------------------- SETUP -----------------------
void setup ()
{
//Serial.begin(115200);
pinMode (6, INPUT_PULLUP); // Pulsante Start/Stop a Gnd con 100nF in parallelo.
pinMode (7, INPUT_PULLUP); // Pulsante Azzeramento.
pinMode (8, INPUT_PULLUP); // Encoder: A.
pinMode (9, INPUT_PULLUP); // Encoder: B.
pinMode (13, OUTPUT); // Cicalino.
// INTERRUPT PER L'ENCODER:
// Pin Change Interrupt Control Register (PCICR): PORTD=PCIE2; PORTC=PCIE1; PORTB=PCIE0;
// PCICR |= 0b00000001; // PORT B.
PCICR |= 1<<PCIE0;
// PORT B: PMSK0; D9 e D8 sono i bit 1 e 0:
// PCMSK0 |= 0b00000011;
PCMSK0 |= 1<<PCINT1 | 1<<PCINT0;
disp.shutdown (0,false); // Wakeup MAX7219.
disp.setIntensity (0,4); // Da 0 a 15.
disp.clearDisplay (0);
titolo_1();
//titolo_2();
delay(1500);
// azzera();
// INTERRUPT PER L'INCREMENTO OGNI 10ms (100Hz):
noInterrupts();
// Clear registers
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// 100 Hz (16000000/((624+1)*256))
OCR1A = 624;
// CTC
TCCR1B |= (1 << WGM12);
// Prescaler 256
TCCR1B |= (1 << CS12);
// Output Compare Match A Interrupt Enable
TIMSK1 |= (1 << OCIE1A);
interrupts();
Bip();
}
// ----------------------- LOOP -----------------------
void loop ()
{
if(!digitalRead(7)) // Pulsante di Reset.
{
if(avanti) azzera();
else t100 = (uint32_t)(((preset_ore*60)+preset_minuti)*60+preset_secondi)*100;
}
if(E) // Se l'encoder è stato ruotato:
{
selezione+=E;
if(selezione<1) selezione=0;
if(selezione>3) selezione=3;
if(start)
{
while(selezione==1 || selezione==2) selezione+=E; // Se sta contando, Preset e Avanti/indietro non devono apparire.
}
E=0;
visual_menu();
} // End if(E).
switch(selezione)
{
case 0: // ----- Funzionamento normale ----- //
scrivi_tempo();
if(!digitalRead(6))
{
if(!premuto) // Se il pulsante è stato premuto adesso:
{
premuto=true;
if(!start) // Se il cronometro è fermo, parte:
{
start=true;
Bip();
}
else // Se sta contando, si ferma:
{
start=false;
Biip();
}
}
}
else // Se start/stop non è premuto in questo momento:
{
premuto=false;
}
break;
case 1: // --------- Preset ---------- //
visual_menu();
if(!digitalRead(6)) // Se viene premuto il pulsante:
{
imposta_tempo();
}
break;
case 2: // ----- Avanti/Indietro ----- //
visual_menu();
if(!digitalRead(6)) // Se viene premuto il pulsante:
{
avanti_o_indietro();
}
break;
case 3: // ----- Centesimi/Frame ----- //
visual_menu();
if(!digitalRead(6)) // Se viene premuto il pulsante:
{
centesimi_o_frame();
}
break;
} // END switch.
if(start) // Se sta contando:
{
if(azzerato) // Se è stato appena azzerato:
{
Bip();
azzerato=false;
}
}
if(fine)
{
BipEnd();
fine=false;
}
}
// ----------------------- ISR t100, SUONI E AZZERAMENTO -----------------------
ISR(TIMER1_COMPA_vect)
{
if(start)
{
if(avanti) t100+=1;
else // Indietro.
{
if(t100) t100-=1;
else
{
start=false;
fine=true;
}
}
}
}
void Bip() {tone(13,2000, 70);}
void Biip() {tone(13,2000,200);}
void Biiip() {tone(13,2000,300);}
void BipEnd()
{
tone(13,2093,500); delay(500); // Do
tone(13,1760,500); delay(500); // La
tone(13,1397,1500); // Fa
}
void azzera()
{
if(t100) Biiip(); // Fa Biiip solo se non è già stato azzerato, cioè se t100 è diverso da zero.
azzerato=true;
scrivi_cifre(1,0); scrivi_cifre(2,0); scrivi_cifre(3,0); scrivi_cifre(4,0); // Scrive 00.00.00
while(!digitalRead(7)); // Per azzerare il tempo, attende che venga lasciato il pulsante.
t100=0;
}
// ----------------------- IMPOSTAZIONI -----------------------
void imposta_tempo()
{
disp.clearDisplay(0); // Spegne tutti i display.
scrivi_cifre(1, preset_ore);
while(!digitalRead(6)); // Aspetta che venga lasciato il pulsante.
while(digitalRead(6)) // Finché non viene premuto il pulsante:
{
if(E!=0)
{
preset_ore+=E;
if(preset_ore<0) preset_ore=99;
else if(preset_ore>99) preset_ore=0;
scrivi_cifre(1, preset_ore);
E=0;
}
}
scrivi_cifre(2, preset_minuti);
while(!digitalRead(6)); // Aspetta che venga lasciato il pulsante.
while(digitalRead(6)) // Finché non viene premuto il pulsante:
{
if(E!=0)
{
preset_minuti+=E;
if(preset_minuti<0) preset_minuti=59;
else if(preset_minuti>59) preset_minuti=0;
scrivi_cifre(2, preset_minuti);
E=0;
}
}
scrivi_cifre(3, preset_secondi);
while(!digitalRead(6)); // Aspetta che venga lasciato il pulsante.
while(digitalRead(6)) // Finché non viene premuto il pulsante:
{
if(E!=0)
{
preset_secondi+=E;
if(preset_secondi<0) preset_secondi=59;
else if(preset_secondi>59) preset_secondi=0;
scrivi_cifre(3, preset_secondi);
E=0;
}
}
t100=preset_ore;
t100=t100*60+preset_minuti;
t100=t100*60+preset_secondi;
t100*=100;
Bip();
selezione=0;
premuto=true; // Per non far partire o fermare il cronometro appena ritorna.
}
void visual_preset()
{
scrivi_cifre(1, preset_ore);
scrivi_cifre(2, preset_minuti);
scrivi_cifre(3, preset_secondi);
}
void avanti_o_indietro()
{
visual_av_ind();
while(!digitalRead(6)); // Aspetta che venga lasciato il pulsante.
while(digitalRead(6)) // Finché non viene premuto il pulsante.
{
if(E)
{
avanti=E+1; // E=-1:indietro; E=+1:avanti.
visual_av_ind();
E=0;
}
}
Bip();
selezione=0;
premuto=true; // Per non far partire o fermare il cronometro appena ritorna.
}
void centesimi_o_frame()
{
disp.clearDisplay (0); // Cancella il display.
visual_centesimi_o_frame();
while(!digitalRead(6)); // Aspetta che venga lasciato il pulsante.
while(digitalRead(6)) // Finché non viene premuto il pulsante.
{
if(E)
{
frame=E+1;
visual_centesimi_o_frame();
E=0;
}
}
Bip();
selezione=0;
premuto=true; // Per non far partire o fermare il cronometro appena ritorna.
}
// ----------------------- VISUALIZZAZIONI -----------------------
void visual_centesimi_o_frame()
{
if(frame)
{
disp.setRow (0,7,0x47); // F
disp.setRow (0,6,0x05); // r
disp.setRow (0,5,0x00); //
disp.setRow (0,4,0x00); //
}
else
{
disp.setRow (0,7,0x4E); // C
disp.setRow (0,6,0x6F); // e
disp.setRow (0,5,0x15); // n
disp.setRow (0,4,0x0F); // t
}
}
void scrivi_tempo()// Impiega in tutto 2,1~2,2ms. Chissà perché, le prime 6 chiamate da ogni start del cronometro
{ // impiegano circa 2200us; poi una poco meno e le successive 2132us!
uint32_t tempo=t100; // Tempo trascorso in centesimi di secondo.
uint8_t cent;
if(!frame) cent=tempo%100; // in centesimi di secondo.
else cent=(tempo%100)/4; // in frame (25f/s).
disp.setChar (0,0,(cent%10)+48,false); // Unità di centesimi.
disp.setChar (0,1,(cent/10)+48,false); // Decine di centesimi (decimi).
tempo/=100; // Tempo in secondi.
ore=tempo/3600;
minuti=((tempo%3600)/60)%60;
secondi=(tempo%3600)%60;
disp.setChar (0,7,(ore/10)+48,false); // Decine di ore.
disp.setChar (0,6,(ore%10)+48,true); // Ore.
disp.setChar (0,5,(minuti/10)+48,false); // Decine di minuti.
disp.setChar (0,4,(minuti%10)+48,true); // Unità di minuti.
disp.setChar (0,3,(secondi/10)+48,false); // Decine di secondi.
disp.setChar (0,2,(secondi%10)+48,true); // Unità di secondi.
}
void scrivi_cifre(uint8_t gruppo, uint8_t valore) // gruppo= 1:ore; 2:minuti; 3:secondi.
{
uint8_t p_unita = (3-gruppo)*2 +2;
disp.setChar (0, p_unita+1,(valore/10)+48,false); // Decine.
if(gruppo!=4) disp.setChar (0, p_unita,(valore%10)+48,true); // Unità.
else disp.setChar (0, p_unita,(valore%10)+48,false); // Se scrivo la cifra dei centesimi, non deve avere il punto.
}
void visual_av_ind()
{
if(avanti)
{
disp.setRow (0,7,0x77); // A
disp.setRow (0,6,0x1C); // v
disp.setRow (0,5,0x7D); // a
disp.setRow (0,4,0x15); // n
disp.setRow (0,3,0x0F); // t
disp.setRow (0,2,0x10); // i
disp.setRow (0,1,0x00); //
disp.setRow (0,0,0x00); //
}
else
{
disp.setRow (0,7,0x30); // I
disp.setRow (0,6,0x15); // n
disp.setRow (0,5,0x3D); // d
disp.setRow (0,4,0x10); // i
disp.setRow (0,3,0x6F); // e
disp.setRow (0,2,0x0F); // t
disp.setRow (0,1,0x05); // r
disp.setRow (0,0,0x1D); // o
}
}
void visual_menu()
{
switch(selezione)
{
// case 0:
// disp.setRow (0,7,0x4F); // E
// disp.setRow (0,6,0x5B); // S
// disp.setRow (0,5,0x4E); // C
// disp.setRow (0,4,0x00); //
// disp.setRow (0,3,0x00); //
// disp.setRow (0,2,0x00); //
// disp.setRow (0,1,0x00); //
// disp.setRow (0,0,0x00); //
// break;
case 1:
disp.setRow (0,7,0x67); // P
disp.setRow (0,6,0x05); // r
disp.setRow (0,5,0x6F); // e
disp.setRow (0,4,0x5B); // s
disp.setRow (0,3,0x6F); // e
disp.setRow (0,2,0x0F); // t
disp.setRow (0,1,0x00); //
disp.setRow (0,0,0x00); //
break;
case 2:
disp.setRow (0,7,0x77); // A
disp.setRow (0,6,0x9C); // v.
disp.setRow (0,5,0x01); // -
disp.setRow (0,4,0x30); // I
disp.setRow (0,3,0x15); // n
disp.setRow (0,2,0xBD); // d.
disp.setRow (0,1,0x00); //
disp.setRow (0,0,0x00); //
break;
case 3:
disp.setRow (0,7,0x4E); // C
disp.setRow (0,6,0x6F); // e
disp.setRow (0,5,0x15); // n
disp.setRow (0,4,0x8F); // t.
disp.setRow (0,3,0x01); // -
disp.setRow (0,2,0x47); // F
disp.setRow (0,1,0x85); // r.
disp.setRow (0,0,0x00); //
break;
}
}
void titolo_1()
{ // 0 1 2 3 4 5 6
uint8_t testo[]={0x5B, 0x1D, 0x15, 0x1D, 0x05, 0x17, 0x4E};
for (int8_t x=-1; x<7; x++)
{
for (int8_t p=x; p>-1; p--)
{
disp.setRow (0,p,testo[p]);
}
delay(100);
}
for (int8_t x=-1; x<7; x++)
{
for (int8_t p=x; p>-1; p--)
{
disp.setChar (0,p,' ',false);
}
delay(100);
}
}
void titolo_2()
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
{ // 0:S 1:o 2:n 3:o 4:r 5:h 6:C
int8_t car[]={0, 0, 0, 0, 0, 0, 0, 0, 0x5B, 0x1D, 0x15, 0x1D, 0x05, 0x17, 0x4E, 0, 0, 0, 0, 0, 0, 0, 0};
for(int8_t x=15; x>-1; x--)
{
for(int8_t p=0; p<8; p++){disp.setRow (0, p, car[p+x]);}
delay(150);
}
}
//disp.setRow (0,7,0x4E); // C
//disp.setRow (0,6,0x17); // h
//disp.setRow (0,5,0x05); // r
//disp.setRow (0,4,0x1D); // o
//disp.setRow (0,3,0x15); // n
//disp.setRow (0,2,0x1D); // o
//disp.setRow (0,1,0x5B); // S
// ----------------------- ISR Encoder -----------------------
ISR (PCINT0_vect)
{
// bit AB
S=((PINB^0xFF) & 0b00000011); // Inverto i bit, perché l'encoder chiude a massa, e considero solo D9 e D8. S (Stato) è composta dai bit A e B.
S^=S>>1; // Faccio AB XOR A, ottenendo un valore decimale di Stato fra 0 e 3.
if (S!=So && S==0) X=0; // Se lo Stato è cambiato ed è andato a 0: X=0.
if (X==0) // Pronto a rilevare una rotazione, perché lo Stato è passato per lo 0.
{
if (So==1 && S==2) // E' passato da 1 a 2: rotazione oraria: E=+1.
{
E=1;
X=1; // Non potrà rilevare nuove rotazioni finchè S non passerà di nuovo per lo 0.
}
else if (So==3 && S==2) // E' passato da 3 a 2: rotazione antioraria: E=-1.
{
E=-1;
X=1; // Non potrà rilevare nuove rotazioni finchè S non passerà di nuovo per lo 0.
}
else if (!S) X=0;
So=S; // Aggiorno lo Stato precedente con quello corrente.
}
}
// ----------------------- NOTE -----------------------
/*
Fis.I/OPorta
1 - RS
2 0 PD0
3 1 PD1
4 2 PD2
5 3 PD3
6 4 PD4 Encoder: A (con INPUT_PULLUP).
--
11 5 PD5 Encoder: B (con INPUT_PULLUP).
12 6 PD6 Encoder: Pulsante (con INPUT_PULLUP) verso GND con 100nF in parallelo.
13 7 PD7 Pulsante Reset.
14 8 PB0
----------
15 9 PB1
16 10 PB2
17 11 PB3
18 12 PB4
19 13 PB5 Uscita per cicalino passivo.
--
23 14 PC0 A0 DIN |
24 15 PC1 A1 CS |Display 8x 7segmenti.
25 16 PC2 A2 CLK |
26 17 PC3 A3
27 18 PC4 A4 SDA
28 19 PC5 A5 SCL
29 20 A6 (solo in Arduino Nano)
Fis
9 PB6 XTAL 16MHz In : compensatore a GND (ho messo 22+10pF).
10 PB7 XTAL 16MHz Out: 22pF a GND.
21 ARef
7,20: Vcc
8,22: GND
*/
// ----------------------- VERSIONI -----------------------
/*
v0.0 21/2/23
v0.1 21/2/23 Comincia a funzionare.
v0.2 21/2/23 Premendo una prima volta, parte facendo Bip.
Premendo una seconda volta, va in pausa facendo Biip.
Premendo di nuovo, continua il conteggio facendo Bip.
Premendo l'azzeramento, azzera: se non sta già a zero fa Biiip, altrimenti rimane muto.
Se l'azzeramento è stato fatto mentre contava, poi riparte automaticamente facendo Bip.
v0.3 23/2/23 Ho aggiunto la variabile cent in scrivi_tempo().
Ho aggiunto la possibilità di visualizzare i frame anziché i centesimi di secondo.
Ho aggiunto un po' di commenti.
v0.6 24/2/23 Ho aggiunto il menu centesimi/frame.
v0.7temp 24/2/23 Ho aggiunto il menu avanti/indietro, ma ancora manca la logica per il conteggio indietro.
Funziona quasi... Ma alla rovescia, quando arriva a zero non se ne esce più!
v0.8temp 24/2/23 Leggo l'encoder con l'interrupt, così non devo chiamarlo in continuazione e posso saltare
ai menu ruotandolo.
v0.9temp 24/2/23 Divido il programma in schede (file) e vado avanti..
v0.10temp 25/2/23 Introduco la temporizzazione dei 10ms (centesimi di secondo) fatta con il Timer 1:
https://www.arduinoslovakia.eu/application/timer-calculator
Sostituisco la variabile "indietro" con la variabile "avanti".
Funziona il menu: Preset / Av.-Ind. / Cent-Fr.
v0.11temp1 26/2/23 Elimino t0, che non serve più per l'incremento periodico di t100 (lo faccio con un timer)
e t_premuto, che serviva per la pressione lunga (ora uso la rotazione dell'encoder).
Aggiungo a scrivi_cifre() la possibilità di scrivere il 4°gruppo (decimi e centesimi) per
l'azzeramente. Sul 4°gruppo non deve accendere il punto.
Aggiungo nel menu 0=ESC, per tornare indietro senza fare nulla.
v0.11temp6 1/3/23 Ho sistemato l'entrata e l'uscita dalle impostazioni.
Rimane da aggiustare il blocco quando il conteggio all'indietro arriva a 0.
v0.12temp1 2/3/23 Se sta contando, oltre a non dover apparire il menu Avanti/Indietro, non deve apparire ne-
anche Preset.
v0.12temp2 2/3/23 Aggiungo il titolo: ChronoS.
v1.0 2/3/23 Sistemo la fine del conteggio alla rovescia e aggiungo il suono.
Sembra funzionare tutto! :)
v1.1 2/3/23 Faccio il titolo animato.
v1.1a 4/3/23 Ho aggiunto titolo_2() e rinominato titolo() in titolo_1().
*/