Ciao a tutti
Armeggiando un po' sono riuscito a fare questo visualizzatore, utile in particolari applicazioni.
Va tutto alla velocità della luce , con uno switch/case che instrada l'esecuzione verso l'impostazione corrente. In questo modo posso fare il multiplexing, indispensabile con il 595, e l'intermittenza della cifra che si sta impostando.
Sia la rotazione dell'encoder che la pressione del pulsante sono rilevate mediante un Pin Change Interrupt, che incrementa lo stato a ogni pressione del pulsante.
La tabella dei caratteri è limitata ai numeri da 0 a 9 e alle lettere che mi servivano, ma è facile ampliarla con tutti i caratteri che servono. E' ancora più semplice per la negazione bit a bit che ho fatto, che non rende più necessario fare i calcoli con logica inversa.
Sarà utile per chi vuole capire come vanno usati i moduli con 8 display a 7 segmenti pilotati da HC595 (e comunque...: Lunga vita e prosperità al MAX7219! ).
#include <EEPROM.h>
volatile uint8_t stato=0; // 0:Riposo; 1:In/Out; 2...5:Le 4 cifre.
volatile bool puls_prec=true; // true = premuto.
volatile int16_t contatore=0; // Fino a oltre +/-32000. Ho previsto anche i valori negativi.
volatile uint8_t s=0; // Valore decimale dei bit.
volatile uint8_t sp=0; // Stato precedente di s.
volatile uint8_t x=0;
volatile int8_t E=0; // Encoder 1:Orario; -1:Antiorario.
uint8_t InOut=1; // 1:In; 0=Out.
int16_t contatorep=0; // Valore precedente.
bool acceso=false; // Intermittenza - true: adesso è acceso; false:adesso è spento.
uint32_t t_intermittenza=0;
uint8_t c[5]; // Sono le 4 cifre, per c da 1 a 5.
#define PULS_PREMUTO !((PINB^0xFF)&0b00000100) // Inverte il PINB con un XOR, ne prende solo il terzo bit e fa un casting invertente a bool con '!'.
/*
EEPROM:
0 InOut
1 c[1]
2 c[2]
3 c[3]
4 c[4]
*/
void Bip()
{
digitalWrite(7, HIGH);
delay(50);
digitalWrite(7,LOW);
}
void imposta_InOut()
{
if(E) // In.
{
InOut=1-InOut; // Alterna In/Out.
}
E=0;
if(InOut) // In
{
if(acceso)
{
disp_l(2, 'I');
disp_l(3, 'n');
}
else // Spento. Serve per mantenere costante il tempo impiegato
{ // e non alterare la luminosità delle altre cifre.
disp_l(2, ' ');
disp_l(3, ' ');
}
}
else // Out
{
if(acceso)
{
disp_l(1, 'O');
disp_l(2, 'u');
disp_l(3, 't');
}
else // Spento. Serve per mantenere costante il tempo impiegato
{ // e non alterare la luminosità delle altre cifre.
disp_l(1, ' ');
disp_l(2, ' ');
disp_l(3, ' ');
}
}
}
void imposta_cifre()
{
uint8_t n = stato-1; // Stato==2: 1a cifra.
if(E) // Se l'encoder è stato ruotato:
{
if(E==-1)
{
if(c[n]>0) c[n]-=1;
else if(c[n]==0) c[n]=9;
}
else // E==+1:
{
if (c[n]<9) c[n]+=1;
else if(c[n]==9) c[n]=0;
}
E=0;
}
if(acceso)
{
disp_n(n+4, c[n]);
disp_l(n+5, ' ');
}
else // Spento. Serve per non alterare la luminosità delle altre cifre.
{
disp_l(n+5, ' ');
}
}
ISR (PCINT0_vect)
{
if(!PULS_PREMUTO) // Pulsante non premuto:
{
puls_prec=false; // Non premuto.
}
if (PULS_PREMUTO && puls_prec==false) // Pulsante premuto:
{
puls_prec=true;
stato+=1;
if(stato>10) stato=0;
Bip();
}
else // E' stato ruotato l'encoder.
{
// bit AB
s=((PINB^0xFF) & 0b00000011); // Inverto i bit, perché l'encoder chiude a massa, e considero solo D9 e D8. S è composto dai bit A e B.
s^=s>>1; // Faccio AB XOR A, ottenendo un valore decimale fra 0 e 3.
if (s!=sp && s==0) x=0;
if (x==0)
{
if (sp==1 && s==2)
{
E=1;
x=1;
}
else if (sp==3 && s==2)
{
E=-1;
x=1;
}
else if (!s) x=0;
sp=s;
}
}
}
void setup()
{
pinMode( 4, OUTPUT); // DIO (Data)
pinMode( 5, OUTPUT); // RCK (Latch)
pinMode( 6, OUTPUT); // SCK (Clock)
pinMode( 7, OUTPUT); // Cicalino.
pinMode( 8, INPUT_PULLUP); // A encoder.
pinMode( 9, INPUT_PULLUP); // B encoder.
pinMode(10, INPUT_PULLUP); // Pulsante encoder verso massa con 100nF in parallelo.
InOut=EEPROM.read(0);
if(InOut>1) InOut=0;
c[1]=EEPROM.read(1);
if(c[1]>9) c[1]=0;
c[2]=EEPROM.read(2);
if(c[2]>9) c[2]=0;
c[3]=EEPROM.read(3);
if(c[3]>9) c[3]=0;
c[4]=EEPROM.read(4);
if(c[4]>9) c[4]=0;
// Pin Change Interrupt Control Register (PCICR): PORTD=PCIE2; PORTC=PCIE1; PORTB=PCIE0;
// PCICR |= 0b00000001; // PORT B.
PCICR |= 1<<PCIE0;
// PORT B: PMSK0; D10, D9 e D8 sono i bit 2, 1 e 0:
// PCMSK0 |= 0b00000111;
PCMSK0 |= 1<<PCINT2 | 1<<PCINT1 | 1<<PCINT0;
}
void loop()
{
if(millis()-t_intermittenza>300)
{
t_intermittenza=millis();
acceso=!acceso;
}
switch(stato)
{
case 0: // Riposo
break;
case 1: // Sel. In/Out.
imposta_InOut();
break;
case 2: // Salva InOut e imposta la 1a cifra.
EEPROM.update(0, InOut);
imposta_cifre();
break;
case 3: // Salva la 1a cifra e imposta la 2a.
EEPROM.update(1, c[1]);
imposta_cifre();
break;
case 4: // Salva la 2a cifra e imposta la 3a.
EEPROM.update(2, c[2]);
imposta_cifre();
break;
case 5: // Salva la 3a cifra e imposta la 4a.
EEPROM.update(3, c[3]);
imposta_cifre();
break;
case 6: // Salva la 4a cifra e azzera lo stato per eventualmente ricominciare.
EEPROM.update(4, c[4]);
stato=0;
break;
} // END switch/case.
if(stato!=1) // Se non sto regolando InOut, nel qual caso In o Out deve
{ // essere libero di lampeggiare, lo scrive stabilmente:
if(InOut)
{
disp_l(2, 'I');
disp_l(3, 'n');
}
else
{
disp_l(1, 'O');
disp_l(2, 'u');
disp_l(3, 't');
}
}
for(uint8_t cifra=1; cifra<5; cifra++)
{ // Se non è la cifra che sto regolando, la scrive stabilmente:
if(cifra != stato-1) disp_n(cifra+4, c[cifra]);
else disp_l(8, ' ');
}
}
#define DATA 4
#define LATCH 5
#define CLOCK 6
void disp_n(byte p, byte v)
{
// 16 32 64 128 1 2 4 8
switch(p)
{
case 1: p= 16; break;
case 2: p= 32; break;
case 3: p= 64; break;
case 4: p=128; break;
case 5: p= 1; break;
case 6: p= 2; break;
case 7: p= 4; break;
case 8: p= 8; break;
}
// ---1---
// 32 2
// --64---
// 16 4
// ---8--- 128
switch(v)
{
case 1: v= 6; break;
case 2: v= 91; break;
case 3: v= 79; break;
case 4: v=102; break;
case 5: v=109; break;
case 6: v=125; break;
case 7: v= 7; break;
case 8: v=127; break;
case 9: v=111; break;
case 0: v= 63; break;
}
v=~v; // ATTENZIONE! I display sono ad anodo comune, perciò i segmenti
// si accendono a livello basso! Facendo così, però, possiamo ragionare
// nei calcoli precedenti secondo una logica non inversa.
digitalWrite(LATCH, LOW);
shiftOut(DATA, CLOCK, MSBFIRST, v); // matrice per numero decimale
shiftOut(DATA, CLOCK, MSBFIRST, p); // Posizione particolare delle cifre
digitalWrite(LATCH, HIGH);
}
void disp_l(byte p, char l)
{
// 16 32 64 128 1 2 4 8
switch(p)
{
case 1: p= 16; break;
case 2: p= 32; break;
case 3: p= 64; break;
case 4: p=128; break;
case 5: p= 1; break;
case 6: p= 2; break;
case 7: p= 4; break;
case 8: p= 8; break;
}
// ---1---
// 32 2
// --64---
// 16 4
// ---8--- 128
switch(l)
{
case 'I': l= 6; break;
case 'n': l= 84; break;
case 'O': l= 63; break;
case 'u': l= 28; break;
case 't': l=120; break;
case ' ': l= 0; break;
}
l=~l; // ATTENZIONE! I display sono ad anodo comune, perciò i segmenti
// si accendono a livello basso! Facendo così, però, possiamo ragionare
// nei calcoli precedenti secondo una logica non inversa.
digitalWrite(LATCH, LOW);
shiftOut(DATA, CLOCK, MSBFIRST, l); // matrice per numero decimale
shiftOut(DATA, CLOCK, MSBFIRST, p); // Posizione particolare delle cifre
digitalWrite(LATCH, HIGH);
}
/*
0.2 10/5/23 Funziona
0.3 10/5/23 Ho messo la regolazione continua 9->0 senza fine corsa.
0.4 11/5/23 Faccio la funzione salva_visualizza_salta().
0.5 15/5/23 Versione per visualizzazione su moduli con 8 display con HC595.
0.6 15/5/23 Ho ridotto gli stzti a solo 6, togliendo quelli intermedi. Adesso
anche i calcoli delle posizioni sono più semplici, perché da una ci-
fra all'altra è uno stato.
0.7 15/5/23 Metto l'impostazione InOut in una funzione apposita: imposta_InOut.
Rinomino imposta_cifre() in imposta_cifre().
*/