L’ ITIS Mattei di Recanati ha di recente partecipato al nostro concorso “Aggiungi una funzione al tuo Dotklok” con una soluzione che si è classificata al secondo posto a causa di un non perfetto funzionamento delle modifiche realizzate. Tuttavia, in considerazione dell’impegno dei ragazzi e degli insegnanti, abbiamo concesso loro la possibilità di rivedere quanto inviato a suo tempo, correggere gli errori e… portare a casa (o meglio, a scuola) una stampante 3Drag che abbiamo deciso di regalare loro visto l’impegno profuso e la qualità del lavoro svolto. Ecco dunque la proposta dei ragazzi di Recanati descritta da loro stessi: 

Classe_4B_Informatica

La soluzione che segue è stata ideata, sviluppata e realizzata dai ragazzi della 4°B dell’indirizzo “Informatica e Telecomunicazioni” dell’Istituto di Istruzione Superiore “E.Mattei” di Recanati.
La modifica da loro implementata prevede una consistente trasformazione  dell’hardware ed importanti interventi al firmware.

Per quanto riguarda l’hardware i ragazzi hanno scelto di sostituire la scheda, per così dire, customizzata, del DotKlok, con Arduino UNO R3 ed uno shield progettato e realizzato all’interno dell’Istituto.

L’idea è di rendere il DotKlok una piattaforma di sviluppo adatta ai ragazzi di informatica che vogliono avvicinarsi al mondo dei microcontrollori e di Arduino nello specifico, quindi è necessario tornare alla programmazione via USB che meglio si presta ai molteplici caricamenti di firmware.

A livello software è stata implementata una modalità di visualizzazione dell’orologio che permette di vedere contemporaneamente l’ora del giorno e quella scolastica in corso, nonché di attivare l’uscita su relay per far suonare una campanella al cambio d’ora.

L’hardware
Lo shield realizzato dai ragazzi, completa adeguatamente lo strumento di sviluppo riunendo i componenti  principali del Dotklok e inserendo ulteriori possibilità di implementazione.

Si tratta infatti di un PCB che ospita il chip Real Time Clock, il classico integrato DS1307 della Dallas Semiconductor e  la relativa batteria tampone, un header 16 pin per collegare il flat della matrice led e due relay 3022 della Finder da 5V 2A, pilotati ognuno da due transistor NPN di tipo Darlington, i  BC517. Questi relay permettono di pilotare una uscita di potenza (max 125V AC 2A) come una campanella pur essendo miniaturizzati, tanto da poter essere alloggiati su di uno zoccolo standard (passo 2,54) da 16pin. Il loro consumo in termini di corrente nella fase di attivazione è di soli 40mA. Due indicatori a led per segnalare l’attivazione dei relay e una morsettiera per il collegamento dei carichi di potenza, completano il PCB.

Abbiamo quindi realizzato lo shield che vedete in foto e di cui alleghiamo i file Eagle Cadsoft. Il PCB realizzato è un prototipo mono faccia che presenta più di un ponte filo e un ingombro leggermente superiore ad una soluzione su doppia faccia, ma ha l’indubbio vantaggio di poter essere realizzato facilmente da chiunque abbia un minimo di dimestichezza con lo sviluppo circuitale. 

 

Shield_LatoComponenti

Shield lato componenti

 

Shield_LatoRame

Shield lato saldature

Dalle foto è ben visibile in alto il connettore da 16 pin (8×2) per il flat di collegamento alla matrice led della Sure Electronics. Su tale connettore il pin 1 di riferimento è indicato dal pallino nero. Sul lato opposto a questo connettore, troviamo quello per le uscite di potenza diviso in due blocchi uno per relay.

Viste di fronte: il centrale delle tre connessioni è il comune, a sinistra troviamo il contatto NC ed a destra l’NA. Ovviamente ognuno di questi connettori si riferisce al realy posto in corrispondenza dietro di esso. I Finder, come già accennato, sono miniaturizzati e possono essere agevolmente montati su di un DIL 16 pin.

Il chip DS1307 della Dallas trova posto su uno zoccolo 8 pin con il pin uno connesso al quarzo e quindi di facile individuazione. Mentre la batteria tampone ha il suo alloggiamento specifico e va inserita con il più verso l’alto.

I restanti componenti sono i transistor e le resistenze necessarie a pilotare i relay ed i led. Da notare i due diodi di ricircolo posti in parallelo alle bobine dei relay per prevenire le extracorrenti dovute all’attivazione e disattivazione delle bobine dei relay stessi.

Per quanto riguarda i pulsanti di selezione e controllo, i pin di riferimento sono indicati dalle frecce e fanno riferimento alla dichiarazione dei button all’inizio dello sketch principale, per tanto il loro ordine è facilmente modificabile:

// Button mapping

Button b1 = Button(6,PULLUP);

Button b2 = Button(5,PULLUP);

Button b3 = Button(8,PULLUP);

Button b4 = Button(7,PULLUP);

Button b5 = Button(4,PULLUP);

Button buttons[] = { b1, b2, b3, b4, b5 };

Dallo schema si può notare una soluzione tecnica interessante che prevede il collegamento dei pin SDA ed SCL del DS1307 ai pin omonimi di Arduino UNO R3 posti sul connettore del Digital I/O dopo il pin ARef invece che sul connettore Analog In ai pin A4 ed A5. Questa soluzione, infatti, è compatibile con la pinnatura di Arduino Mega R3 consentendo di montare lo shield su entrambe le schede senza bisogno di modifiche.

Il Software

L’ide su cui è stato sviluppato il software è l’1.0.2 per cui sono state opportunamente modificate le librerie RTClib.cpp e Button.h per gestire anche la retro compatibilità, inserendo queste righe di codice sia nella RTClib che nella Button:

 

#if (ARDUINO >= 100)
 #include <Arduino.h> // capital A so it is error prone on case-sensitive filesystems
#else
 #include <WProgram.h>
#endif
 
mentre per la RTClib, che utilizza anche la Wire, è stato necessario inserire anche queste altre righe per mantenere la retro compatibilità:
 
#if (ARDUINO >= 100)
 
uint8_t RTC_DS1307::isrunning(void) {
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(i);  
  Wire.endTransmission();
 
  Wire.requestFrom(DS1307_ADDRESS, 1);
  uint8_t ss = Wire.read();
  return !(ss>>7);
}
 
void RTC_DS1307::adjust(const DateTime& dt) {
    Wire.beginTransmission(DS1307_ADDRESS);
    Wire.write(i);
    Wire.write(bin2bcd(dt.second()));
    Wire.write(bin2bcd(dt.minute()));
    Wire.write(bin2bcd(dt.hour()));
    Wire.write(bin2bcd(0));
    Wire.write(bin2bcd(dt.day()));
    Wire.write(bin2bcd(dt.month()));
    Wire.write(bin2bcd(dt.year() - 2000));
    Wire.write(i);
    Wire.endTransmission();
}
 
DateTime RTC_DS1307::now() {
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(i);  
  Wire.endTransmission();
 
  Wire.requestFrom(DS1307_ADDRESS, 7);
  uint8_t ss = bcd2bin(Wire.read() & 0x7F);
  uint8_t mm = bcd2bin(Wire.read());
  uint8_t hh = bcd2bin(Wire.read());
  Wire.read();
  uint8_t d = bcd2bin(Wire.read());
  uint8_t m = bcd2bin(Wire.read());
  uint16_t y = bcd2bin(Wire.read()) + 2000;
 
  return DateTime (y, m, d, hh, mm, ss);
}
 
#else
 
uint8_t RTC_DS1307::isrunning(void) {
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.send(i);   
  Wire.endTransmission();
 
  Wire.requestFrom(DS1307_ADDRESS, 1);
  uint8_t ss = Wire.receive();
  return !(ss>>7);
}
 

 
 void RTC_DS1307::adjust(const DateTime& dt) {
     Wire.beginTransmission(DS1307_ADDRESS);
     Wire.send(i);
     Wire.send(bin2bcd(dt.second()));
     Wire.send(bin2bcd(dt.minute()));
     Wire.send(bin2bcd(dt.hour()));
     Wire.send(bin2bcd(0));
     Wire.send(bin2bcd(dt.day()));
     Wire.send(bin2bcd(dt.month()));
     Wire.send(bin2bcd(dt.year() - 2000));
     Wire.send(i);
     Wire.endTransmission();
 }
  
 DateTime RTC_DS1307::now() {
   Wire.beginTransmission(DS1307_ADDRESS);
   Wire.send(i);   
   Wire.endTransmission();
  
   Wire.requestFrom(DS1307_ADDRESS, 7);
   uint8_t ss = bcd2bin(Wire.receive() & 0x7F);
   uint8_t mm = bcd2bin(Wire.receive());
   uint8_t hh = bcd2bin(Wire.receive());
   Wire.receive();
   uint8_t d = bcd2bin(Wire.receive());
   uint8_t m = bcd2bin(Wire.receive());
   uint16_t y = bcd2bin(Wire.receive()) + 2000;
  
   return DateTime (y, m, d, hh, mm, ss);
 }
  
 #endif

 

Sistemate le librerie è stata la volta della creazione dell’animazione “school_time” che i ragazzi hanno concepito per  mostrare contemporaneamente l’ora del giorno a numeri ed una scritta scorrevole dell’ora scolastica in corso (Vedi Foto).

Set_Foto

Per ottenere che la matrice fosse separata in due parti, una scorrevole ed una fissa, sono state reimplementate alcune funzioni.

Nello specifico è stata scritta all’interno del file ht1632 la funzione “scroll_half_text(char *string)” che permette di far scorrere del testo nella metà superiore del display. Per far questo viene chiamata all’interno del file principale, la funzione “shift_half_display_left()” che appunto shifta la sola porzione superiore del display con il seguente codice:

void shift_half_display_left() {
  for(int x=0; x<X_MAX; x++){
    for(int y=0; y<=(Y_MAX/2)-1; y++){
      display[x][y]=display[x+1][y];
    }
  }
  for(int y=0; y<=(Y_MAX/2)-1; y++){
    display[X_MAX][y]=0;
  }
}

 

I primi due cicli for nidificati scorrono la matrice verso destra, mentre il terzo ciclo azzera la colonna più a destra. Dopo aver shiftato la matrice, la funzione update_display(); provvede ad aggiornarne la visualizzazione sul display.

Qui di seguito riportiamo il codice dell’animazione “school_time” scritta dai ragazzi:

// school_time
// show the hour lesson litteral and time in 3x5 font
 
void school_time(){
 
  boolean power_up=true;
 
  byte pend_x;
  byte pend_y;
  byte hour;
  int p = 0;
 
  // display init
  ht1632_clear();
  clear_display_buffer();
 
  plot(11,10,1);        //PLOT DEI SOLI MARCATORI D'ORA-MINUTI
  plot(11,12,1);
  display[11][10]=1;    //Inseriti anche in matrice
  display[11][12]=1;
 
      //                  0          1           2           3           4           5        6             7        8
  char* Orari[] ={"Entrata","Prima Ora","Seconda Ora","Terza Ora","Quarta Ora","Quinta Ora","Sesta Ora","Uscita","Intervallo"};
  char OrariodaPlot[12];
  char OrarioPrecedente[12];
  strcpy(OrarioPrecedente, OrariodaPlot);   // copy string source to destination
  //boolean suonato= false;
 
  int this_animation=animation;
  /* TIME LOOP */
  do{
 
    time_now=RTC.now();
  if(DEBUG) Serial.println(time_now.hour());
  switch(time_now.hour()) {   //SWITCH DI SCELTA DELLA STRINGA GIUSTA
      case 0:
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 7:
        p=0;
        for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[0][p]!='\0'){
          OrariodaPlot[p]=Orari[0][p++];
          //  if(DEBUG) {Serial.print(OrariodaPlot);
          //  delay(500);}
        }
        OrariodaPlot[p+1]='\0';
         // if(DEBUG) Serial.println(OrariodaPlot);
         break;
      case 8:
        if (time_now.minute() > 14) {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[1][p]!='\0'){
          OrariodaPlot[p]=Orari[1][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        else {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
         }
        while(Orari[0][p]!='\0'){
          OrariodaPlot[p]=Orari[0][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
       /* if (time_now.minute() == 15){
          if (!suonato){
            digitalWrite(campana, HIGH);
            delay(2000);
            suonato=true;
          }*/
        break;
      case 9:
        if (time_now.minute() > 14) {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[2][p]!='\0'){
          OrariodaPlot[p]=Orari[2][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        else {
          p=0;
          for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[1][p]!='\0'){
          OrariodaPlot[p]=Orari[1][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        break;
      case 10:
        if (time_now.minute() > 14) {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[3][p]!='\0'){
          OrariodaPlot[p]=Orari[3][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        else {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[2][p]!='\0'){
          OrariodaPlot[p]=Orari[2][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        break;
      case 11:
        if (time_now.minute() > 30) {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[4][p]!='\0'){
          OrariodaPlot[p]=Orari[4][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        else if (time_now.minute() > 14) {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[8][p]!='\0'){
          OrariodaPlot[p]=Orari[8][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
          }
        else
        {         
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[3][p]!='\0'){
          OrariodaPlot[p]=Orari[3][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot); 
        }
        break;
      case 12:
        if (time_now.minute() > 30) { 
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[5][p]!='\0'){
          OrariodaPlot[p]=Orari[5][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        else {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[4][p]!='\0'){
          OrariodaPlot[p]=Orari[4][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        break;
      case 13:
        if(time_now.minute() > 30) {
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[6][p]!='\0'){
          OrariodaPlot[p]=Orari[6][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        else {         
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[5][p]!='\0'){
          OrariodaPlot[p]=Orari[5][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
        }
        break;
      case 14:
      case 15:
      case 16:
      case 17:
      case 18:
      case 19:
      case 20:
      case 21:
      case 22:
      case 23:
         p=0;
         for (int i = 0; i<12; i++) {
          OrariodaPlot[i]='\0';
        }
        while(Orari[7][p]!='\0'){
          OrariodaPlot[p]=Orari[7][p++];
        }
        OrariodaPlot[p+1]='\0';
        // if(DEBUG) Serial.println(OrariodaPlot);
      break;
  } // end switch(time_now.hour)
   
    if(strcmp(OrariodaPlot,OrarioPrecedente) != 0){
      ringCampana = millis();
      relay1 = true;
      digitalWrite(campana, HIGH);
      strcpy(OrarioPrecedente, OrariodaPlot);   // copy string 2 to 1
    }
    if(relay1) if (millis() - ringCampana >= ringingTime){
      digitalWrite(campana, LOW);
      relay1 = false;   
    }
   
    /* UPDATE DISPLAY */
 
    // check if a second has passed
    if( ( time_now.second() != time_prev.second() ) || power_up ) {
     
      if( !power_up && midnight_random() )  return;
      power_up=false;
      if(twelve_hour){
        hour=time_now.hour();
        if(hour>12)  hour-=12;
        if(hour==0)  hour=12;
        if(hour<10)    putchar_3x5(4,9,' ');
        else           putchar_3x5(4,9,hour/10);
        putchar_3x5(7,9,hour%10);
      }
      else{
        putchar_3x5(4,9,time_now.hour()/10);
        putchar_3x5(7,9,time_now.hour()%10);
      }
      putchar_3x5(13,9,time_now.minute()/10);
      putchar_3x5(16,9,time_now.minute()%10);
     
      time_prev = time_now;
     
       scroll_half_text(OrariodaPlot);          
    } // end if prev_sec != now_sec
   
    /* CHECK BUTTONS (return from to main loop if necessary) */
    if(animation != this_animation) return; // animation was changed in scroll_text()
 
    //if( change_animation() )  return;
    if(DEBUG) Serial.print("false");
    while( PAUSE && b5.isPressed( )); // pause mode for photos
   
  }while(1); // end time loop
 
} // end fuction school_time
  

Essenzialmente l’animazione dopo aver dichiarato un vettore di stringhe contenente le ore scolastiche, sceglie quale stampare a video per mezzo di una switch case che controlla che ore sono e che copia la stringa corretta dal vettore Orari[] ad una stringa di appoggio OrariodaPlot[12] che verrà passata alla funzione scroll_half_text() per essere visualizzata sul display. La stringa OrarioPrecedente[12] serve per controllare se è cambiata l’ora di lezione ed in tal caso attivare la campanella:

if(strcmp(OrariodaPlot,OrarioPrecedente) != 0){
      ringCampana = millis();
      relay1 = true;
      digitalWrite(campana, HIGH);
      strcpy(OrarioPrecedente, OrariodaPlot);   // copy string 2 to 1
    }
 
 
if(relay1) if (millis() - ringCampana >= ringingTime){
      digitalWrite(campana, LOW);
      relay1 = false;   
    }

 

Il pezzo di codice relativo al secondo if,  controlla per quanto tempo suona la campanella attraverso alcune costanti e variabili globali definite all’interno del file principale dotklok_ISMattei_1_4_1:

const int campana = 12;
const int campana2 = 13;
unsigned long ringCampana = millis();
unsigned long ringingTime = 1000;
boolean relay1=false;

 

per l’esattezza la variabile ringTime rappresenta la durata e ringCampana il momento in cui inizia a suonare. In fine relay1 è utilizzata per conoscere lo stato del relay: attivo o disattivo.

Il progetto futuro, al quale stanno lavorando i ragazzi, è quello di integrare un modulo wi-fi o ethernet in modo da poter passare via rete le informazioni da visualizzare: per esempio potremmo avere dei display piazzati al di fuori dei laboratori che visualizzano la classe che occupa in quel momento il laboratorio e permetta con i relativi pulsanti di verificare se sarà disponibile nelle ore successive.

Il progetto è stato voluto e coordinato da Pierangelo D’Annunzio , la realizzazione è stata affidata alla classe 4B di Informatica e Telecomunicazione.

I principali accorgimenti software sono dello studente Alessandro Cameli; la supervisione della parte hardware è del Prof. Mauro Arcangeli che ha risolto le parti più complesse del PCB, mentre indispensabile è stato il contributo del A.T. Massimo Cappellacci per le realizzazioni pratiche. La cura dei file, le foto, lo sviluppo del modello 3D in SketchUp ed ogni altro errore o imprecisione riscontrata è da ascriversi al coordinatore.

File di progetto disponibili:

  • Sketch Arduino – Tutti i file di programmazione del firmware comprese le librerie ritoccate.
  • PCB Eagle – I file di schema e board per Eagle Cadsoft comprese le librerie personalizzate.
  • Modello 3D – I file di Google SketchUp del modello 3D e i relativi modelli dei componenti da inserire nella cartella di Eagle necessari all’ULP “eagleUp_export.ulp” per generare il modello finale.
  • Immagini – Tutte le immagini del prototipo e delle modifiche.

Complimenti vivissimi a tutti: in questi giorni abbiamo inviato la stampante 3D all’ITIS Mattei con la quale, tra l’altro, la classe potrà partecipare al nuovo Concorso “Stampa e Vinci“:

Stampa_Vinci