Parte 3bis: il codice

Iniziamo ora ad analizzare il codice dell’orologio. Il codice è costituito da una serie di funzioni e procedure elementari, che vengono chiamate da funzioni e procedure più complesse. Partiamo commentando la procedura che gestisce il caricamento dei dati nei registri a scorrimento.

In precedenza abbiamo già detto che per eseguire la scrittura  nei registri a scorrimento è necessario manipolare tre segnali: il LATCH che viene portato sullo stato logico basso per iniziare la comunicazione, il DATA che assume i valori logici dei bit da immettere, il CLOCK che avvisa il registro che sulla linea DATA è pronto un nuovo dato da acquisire.

#define LATCH_PIN 8
#define CLOCK_PIN 12
#define DATA_PIN 11

void regWrite(int n,boolean datas[]){
  digitalWrite(LATCH_PIN,LOW);
  for(int i=0; i<n;i++) { 
    digitalWrite(CLOCK_PIN, LOW);
    digitalWrite(DATA_PIN, datas[i]);
    digitalWrite(CLOCK_PIN, HIGH); 
  }
  digitalWrite(LATCH_PIN,HIGH);
}

Dati alla procedura il numero di dati e un array contenente gli stati dei pin, la funzione porta il segnale LATCH a livello basso, e fornisce gli impulsi di clock necessari, scorrendo l’array dei bit da caricare. Si potrebbe ridurre lo spazio occupato dall’array stipando tutti i dati in un tipo a 16 bit, ma disponiamo spazio a sufficienza da poterci permettere questa comodità progettuale. La procedura prima di terminare la sua esecuzione riporta il segnale LATCH a livello logico alto.

Questa funzione, regWrite, viene chiamata nel codice dalla funzione che crea l’array di dati booleani, dati in ingresso la cifra da accendere e la valvola su cui accenderla.

void tubeWrite(int tube,int value){
 boolean valvole[16]={0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0};
 valvole[tube]=1;
 valvole[value+6]=1;
 regWrite(16,valvole);
 delayMicroseconds(600);  
 blank();
}

L’array di 16 bit è costituito da 6+10 dati. I primi 6 dati rappresentano le 6 nixie (numerate da 0 a 5), gli ultimi 10 dati rappresentano le 10 cifre (da 0 a 9). Sostituiremo un 1(valore logico HIGH) a uno zero in corrispondenza della nixie da accendere, e un altro 1 in corrispondenza della cifra da mostrare. In questo modo i dati caricati nel registro andranno ad attivare i transistor che permetteranno di visualizzare la cifra value sul tubo tube. A questo punto passiamo il numero di dati e l’array di dati alla funzione regWrite. Prima di concludere l’esecuzione di questo codice chiamiamo la funzione blank(), che è molto simile alla funzione regWrite(). A differenza di regWrite, blank inserisce nei registri 16 zeri, in modo che tutti i tubi vengano spenti. Questo è necessario per diminuire i cosiddetti fenomeni di ghosting. Il ghosting è un fenomeno che si verifica sulle nixie che vengono multiplexate e consiste nell’accensione non desiderata di cifre “fantasma” su tubi che dovrebbero essere spenti. Questo fenomeno può essere diminuito riducendo il tempo di accensione della cifra.

Fenomeno di ghosting: sul tubo a sinistra, con anodo non collegato, si riscontra la parziale accensione della cifra 3, che è accesa sul tubo di destra. I tubi hanno i catodi in comune. – foto dell’autore

L’ultima funzione da approfondire è una piccola funzione che ci restituisce il valore logico Vero se è necessario aggiornare l’orario(ovvero se sono le 4 di notte, orario scelto perché probabilmente quello in cui un orologio non viene guardato dall’utente.)

boolean shouldUpdate(){
  if(digits[5]==4&&digits[4]==0&&digits[3]==0
      &&digits[2]==0&&digits[1]==0&&digits[0]==0){
        return true;
      }
   else
    return false; 
  }

La funzione è molto semplice e contiene soltato un’istruizione IF, nella cui condizione viene controllato se nella cifra dell’unità delle ore è scritto il numero 4, e in tutte le altre il valore zero, cioè l’orario 04:00:00. Siamo sicuri che questa condizione si verifichi esattamente una volta al giorno perché all’interno della funzione di update è presente un delay di 2 secondi. Al ritorno dalla funzione nel codice nel loop principale saranno quindi almeno le 04:00:02, e la condizione non sarà verificata di nuovo prima del giorno successivo.

Conosciamo ora tutte le funzioni strumentali al corretto funzionamento dell’orologio, vediamo quindi il loop, ovvero la funzione principale di codice, che viene ininterrottamente eseguita dal microcontrollore. A questa funzione è affidato il multiplexing dei tubi e i controlli sull’aggiornamento dell’orario.

void loop() {
    if(millis()-lastmillis>1000){
      lastmillis=millis();
      if(shouldUpdate()||manualupdate){
        manualupdate=false;
        rtcUpdate();
      }
      readTime();
    }    
    tubeWrite(0,digits[4]);
    tubeWrite(1,digits[5]);
    tubeWrite(2,digits[2]);
    tubeWrite(3,digits[3]);
    tubeWrite(4,digits[0]);
    tubeWrite(5,digits[1]); 
}

Il primo if, insieme all’operazione lastmillis=millis() ci permette di eseguire il codice all’interno dell’if una volta al secondo, in modo da non dover leggere l’orario dall’RTC se questo non è cambiato. Così facendo evitiamo di sprecare cicli di clock e quindi potenza di calcolo, per una funzione che non è necessaria. Seguono le sei chiamate di tubeWrite(), che permettono di visualizzare le cifre nei vari tubi.

Vediamo per finire la funzione setup(), che viene eseguita all’accensione dell’orologio e predispone il microcontrollore al funzionamento.

void setup() {
  Wire.begin();
   
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2);

  while(!Fishino.reset()){
  }
  
  Fishino.setMode(STATION_MODE);
  
  while(!Fishino.begin(MY_SSID, MY_PASS))
  {
    delay(2000);
  }

  Fishino.staStartDHCP();

  while(Fishino.status() != STATION_GOT_IP)
  {
    delay(500);
  }
    
  Udp.begin(localPort);

  readTime();
}

In questa porzione di codice inizializziamo Wire, che serve a gestire il bus i2c, il protocollo SPI che è utilizzato per il modulo wifi, il modulo wifi stesso in modalità stazione, la connessione a una rete, il client DHCP, e il protocollo UDP. La funzione si conclude con una lettura dell’orario attuale dall’RTC.

Il codice integrale sarà disponibile a progetto completo, insieme allo schema e al PCB.

CC BY-NC-SA 4.0 This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *