Word Clock – Orologio “parlante” open source

Progetto completo open source

Progetto completo di un World Clock realizzato con il PIC18F4620, completamente open source. Inizialmente verrà mostrato l'hardware di un orologio che usa le parole al posto dei numeri. Successivamente si descriverà il software con le routine che possono essere utili più in generale. Tutto il progetto può anche essere visto come un esercizio, con diversi spunti da prendere, e ce ne sono molti, dalla gestione della comunicazione I2C alla definizione di una macchina a stati finiti per gestire procedure Time-Safe attraverso interrupt.

Ed infine si proseguirà con la descrizione dell'assemblaggio, del tutto in una cornice, nel vero senso della parola, adatta.
 

Introduzione

Molti maker hanno in mente un progetto che ritengono abbastanza importante per iniziare a lavorarci. Un progetto interessante, divertente ma non abbastanza "professionale". Con questa idea in mente però continuano ad accumulare informazioni e componenti in un cassetto per usarli “un giorno”.

In un periodo di lavoro (quello vero non l'hobby) molto intenso, ho sentito il bisogno di liberare la mente dalla routine giornaliera divagando su qualcosa di semplice ma allo stesso tempo stimolante, utile ma divertente. Era il momento giusto per aprire il cassetto e tirare fuori il progetto messo da parte per troppo tempo.

Provate a cercare con Google "word clock". Troverete un enorme quantità di siti come questo [1]. Molti basati su Arduino, perfettamente descritti e pronti per funzionare ... per chi si accontenta. Non stavo cercando una soluzione rapida e semplice. Il mio obiettivo è il progetto non il risultato.

Quindi, dopo tutto, anche un progetto apparentemente semplice da realizzare, in realtà contiene molti spunti con soluzioni hardware o routine software da ri-utilizzare anche in altre occasioni.

Board

La fase più divertente del progetto: la progettazione dell'hardware, recuperando tutti i componenti raccolti negli anni.

SandwichP

La realizzazione del pannello che conterrà la matrice di LED per visualizzare le parole.

YouTubePreview

Da un vecchio acquisto cumulativo presso un negozio on-line ho messo da parte 500 LED bianchi ad alta luminosità.

Da un acquisto ancora più vecchio ho alcuni fogli di PVC espanso 20x20cm.

Utilizzando Adobe Illustrator ho realizzato le maschere trasparenti con le lettere e ho iniziato quindi a sperimentare la stampa con diversi tipi di stampa, sia a getto d'inchiostro che laser.

SW

Un'altra fase divertente del progetto è la scrittura del software. Usando le giuste tecniche tutti i pezzi lavoreranno all'unisono, come vedremo in seguito.

ribba-cornice

Alla fine occorre assemblare tutti i pezzi insieme e finalmente vedremo come far diventare questo orologio un pezzo di arredamento che non sfiguri anche in un ambiente diverso da un laboratorio.

Hardware

Schemi e spiegazioni

Matrice LED

come multiplexare righe e colonne

Molti dei circuiti che si trovano in Rete usano alcune serie di LED collegati insieme per comporre le parole con poche linee di I/O del microcontrollore. Per questo progetto volevo invece avere un circuito di uso generale, utilizzabile per diverse lingue. Questo richiede ovviamente una matrice di LED pilotata da I/O con una scansione X-Y ad una velocità sufficiente. Per usare lingue diverse basta semplicemente modificare il software e la maschera delle lettere.

DisplayRear

LED istallati e connessi sul panello (retro).

Display

Questa è una classica configurazione a matrice [2]. Multiplexando i segnali in modo corretto è possibile illuminare ogni LED singolarmente.

Driver

I LED ad alta luminosità non possono essere pilotati in una configurazione a matrice 10 x 12 direttamente tramite le porte I/O del microcontrollore. C'è bisogno di alcuni driver, sia di tipo high-side che low-side. I driver high-side sono utilizzati per le colonne, quelli low-side per le righe. Le colonne (12) sono pilotate in parallelo ad ogni scansione delle righe (10). Questo permette di avere un refresh completo del quadro ogni 1/10 del tempo totale. Applicando la scansione sulle colonne si avrebbe invece un tempo per ognuna di 1/12 del tempo totale. Questo consente ad ogni LED di rimanere acceso per il 10% del ciclo di lavoro, invece del 8,3%, con un certo aumento della luminosità risultante a causa dell'operazione di multiplex, il tutto associato ad una gestione più semplice.

Drivers

Schema elettrico equivalente del circuito di pilotaggio dei LED.

WordClockItaMatrix

Come avviene l'indirizzamento di ogni singola lettera. Ad esempio: per illuminare la parola "SONO" è necessario attivare le colonne ABCD nel momento esatto in cui la fila 0 è abilitata.

Dopo un po' di prove pratiche sono emerse alcune correzioni da apportare al software di controllo che permettono un notevole risparmio sulla complessità dell'hardware.

Utilizzando un ulteriore timer disponibile nel PIC18F è possibile attuare la regolazione della luminosità PWM senza complicare il circuito. All'inizio avevo pensato di ottenere una regolazione della luminosità tramite il classico PWM ma, in questo modo avrei avuto bisogno di ulteriori circuiti di potenza per alimentare le colonne in comune con una corrente uguale alla somma delle singole correnti.

Ho pensato invece di ottenere una “parzializzazione” del tempo di accensione usando un secondo timer con una notevole semplificazione hardware a scapito di una complessità del software solo leggermente maggiore. Il timer TMR3 è utilizzato per pilotare il multiplexing della matrice, commutando le righe in successione e illuminando le colonne corrispondenti alla parola da visualizzare. Nella Routine relativa a questo timer (che lavora ad interrupt) è avviato il timer TMR1 in modalità one-shot con un periodo pre-calcolato secondo il duty-cycle desiderato. Quando TMR1 va in overflow spegne tutte le colonne. In poche parole: con TMR3 si accende con TMR1 si spegne .

Ad esempio: TMR3 periodo = 1 ms (in modo da avere un frame-rate con periodo da 10ms=100Hz), se TMR1 = 500us il ciclo di lavoro è del 50%, se il periodo è 1ms il duty cycle è al 100%, sempre con una velocità di scansione di 100Hz.

In ogni caso, è meglio fermare la luminosità massima al 99% per evitare sovrapposizioni con la successiva fila nella scansione provocando un "effetto ombra" a causa del ritardo di spegnimento dei driver.

PWMmatrice

La sincronizzazione dei segnali è molto importante, anche con una matrice di 10 x 12. Su una velocità di scansione a 100Hz anche gestire i microsecondi può portare a criticità. Ci si deve prendere cura anche dei tempi di ritardo dei driver. L'UDN2981 (usato per pilotare le colonne sul lato alto) ha un ritardo di spegnimento più grande di 3 microsecondi. Il LN2003 (sulle righe, dal lato basso) è dieci volte inferiore. Con un MCU a 10Mips è sufficiente spostare il codice che porta a ON o a OFF anche solo pochi cicli dopo per provocare una sovrapposizione dei segnali. Pochi microsecondi di accensione accendono, anche se solo debolmente, i LED ad alta luminosità. Bisogna stare molto attenti alla “dead band” tra le diverse commutazioni.

LogicAnalyzer

L'utilizzo di un analizzatore logico ha facilitato molto la comprensione di tutto il comportamento del circuito.

RowsSignals

Questi sono i segnali che scandiscono le 10 righe. Mantenendo ognuno alto per 1ms si ottiene un quadro completo ogni 1ms x 10 = 10ms = 100Hz.

Column

I segnali relativi alle colonne

A0 e A1 sono i primi due segnali di riga. Gli altri sono legati alle colonne da A a L. Sono ON per 0.5ms per ottenere un duty cycle del 50%. In questo esempio, tutta la riga 0 è illuminata a metà.

DeadBand

In quest'immagine una riga ed una colonna con una zona morta di 100ns. Dopo alcuni altri test, ho deciso di aumentarla.

Quanta corrente serve per accendere i LED?

I LED ad alta luminosità bianchi richiedono 20mA per funzionare correttamente. Con un multiplexing su 10 righe, questi sono ON per 1/10 di tutto il periodo di frame. Grazie alla persistenza della retina, una scansione a 100Hz viene vista come una luce continua, ma la luminosità equivalente è minore . In teoria dovrebbero essere pilotati con una corrente di 10 volte superiore per avere la luminosità corretta. Ma, nella pratica, niente è perfettamente lineare.

LedDs

Analizzando le caratteristiche di un generico LED bianco ad alta luminosità, possiamo vedere che, aumentando di 2,5 volte la corrente (50mA) abbiamo solamente un raddoppio dell'intensità luminosa. Con 4x solo 2,7 volte la luminosità. Come si poteva supporre, il comportamento non lineare rende impossibile raggiungere le (teoricamente) necessarie 10 volte.

Una buona scelta per evitare una morte prematura dei LED, potrebbe essere una corrente di 50mA.

LuminosityVSForwardCurrent

Intensità luminosa relativa in funzione della corrente diretta di un LED generico bianco.

Leggendo la scheda tecnica [3] si può notare una corrente diretta massima If= 100 mA con un 10% di duty cycle @ 1kHz. Calcolando il miglior compromesso con una buona luminosità, una corrente non molto elevata per i LED e la capacità massima dei driver, si arriva ad un più ragionevole valore pratico di 40mA. Considerando anche la caduta di tensione di entrambi i driver, tale corrente si ottiene inserendo un resistore da 2.2Ohm con un'alimentazione di 6.5V.

I calcoli precedenti sono stati fatti per ottenere il miglior compromesso tra luminosità massima e gestione dell'hardware. Nella pratica, per evitare una luminosità fastidiosa in una stanza buia e allo stesso tempo una buona visibilità in un luogo assolato, la luminosità (cioè: il duty cycle del PWM) viene regolata automaticamente leggendo la luce ambiente tramite una fotoresistenza.

MCU

PIC18F4620

Per gestire gli I/O e le diverse periferiche con la giusta velocità e, soprattutto, con l'esatta temporizzazione, è necessario avere il pieno controllo sull'hardware. Il solito Arduino è molto comodo da usare tramite il suo IDE che nasconde tutte le noiose configurazioni delle periferiche ma, proprio per questo, non è adatto a pilotare 10 file di LED con una scansione a 100Hz (quindi una temporizzazione di 1ms) mentre scambia i dati sul bus I2C con il Real Time Clock e in seriale con il modulo WiFly.

E allora?

MCU

Un PIC18F4620 a 10 MIPS è più che adatto allo scopo.

Usando nel modo corretto e con le giuste priorità le periferiche e i relativi interrupt, è possibile ottenere delle temporizzazioni molto precise e costanti.

MCU

Lo schema elettrico della parte relativa al PIC18F4620.

Il livello del pin TX della seriale verso il WiFly è adattato da 5.5V a 3.3V tramite un partitore.

PIC18F

Il ben noto e sperimentato PIC18F4620 a 10 MIPS [4] è il giusto compromesso tra potenza e complessità.

Quasi tutti i pin di questa versione a 40 pin sono utilizzati per pilotare le righe e le colonne, il bus I2C, RX e TX seriale, il quarzo oscillatore, LED di heart-beat e l'ICSP.

*------Port A

* RA0=LDR An.

* RA1 = Row 0

* RA2 = Row 1

* RA3 = Row 2

* RA4 = Row 3

* RA5 = Row 4

*------Port B

* RB0 = INT 0

* RB1 = Row 7

* RB2 = Row 8

* RB3 = Row 9

* RB4 = _____

* RB5 = LED y

*------Port C

* RC0 = Row 5

* RC1 = Row 6

* RC2 =______

* RC3 = I2C C

* RC4 = I2C D

* RC5 = Col L

* RC6 = -TX--

* RC7 = -RX--

*------Port D

* RD0 = Col A

* RD1 = Col B

* RD2 = Col C

* RD3 = Col D

* RD4 = Col E

* RD5 = Col F

* RD6 = Col G

* RD7 = Col H

*------Port E

* RE0 = Col I

* RE1 = Col J

* RE2 = Col K

Lo Sparkfun I2C Real Time Clock [5] è usato per mantenere il tempo di sistema. Questo ha un'uscita a 1Hz (SQW) usato per avviare la lettura di ore, minuti e i secondi via bus I2C ogni secondo da parte dell'MCU.

RTC

RTC

Per correggere la deriva a lungo termine del RTC, il tempo è sincronizzato tramite Internet per mezzo del modulo Sparkfun WiFLy [6], con il riferimento di tempo campione dell'INRIM [7] di Torino, quello che una volta si chiamava Istituto Elettrotecnico Nazionale (IEN).

WiFly

WiFly

La tensione prelevata da un classico alimentatore a parete (da 9 a 36V) viene abbassata e stabilizzata a 6.5V, con un altissimo rendimento, attraverso uno switching regulator [8]. Due regolatori lineari sono utilizzati poi per fornire al modulo WiFly la 3.3V e al PIC18F4620 la 5V.

Pwr

PwrSupply

Tutti i componenti trovano posto in modo molto compatto su una “millefori”, collegati tra loro tramite filo per wire-wrap.

Board

Un test completo della matrice per la visualizzazione di alcuni modelli dinamici e anche alcuni caratteri aggiungendo al codice un generatore di caratteri 5x7.

YouTubePreviewV

In questo video [9] il duty cycle è fissato al 50%.

LINKS capitolo Hardware:

[1] http://www.elektronika.ba/841/word-clock/

[2] http://www.appelsiini.net/2011/how-does-led-matrix-work

[3] http://www.guiott.com/WC/OVLEW1CB9-TT-datasheet-5394461.pdf (White High-Intensity LED Lamp (5 mm, 15°Viewing Angle)

[4] http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en010304

[5] https://www.sparkfun.com/products/12708

[6] https://www.sparkfun.com/products/retired/9333

[7] http://www.inrim.it/n/index.php

[8] http://it.rs-online.com/web/p/regolatori-switching/6664372/

[9] https://www.youtube.com/watch?v=olsO2BnUBeQ&feature=youtu.be

 

Software

Come far parlare tra loro periferiche lente e veloci

Dopo aver visto la realizzazione dal punto di vista Hardware, passiamo alla descrizione del Software.

Il primo problema da affrontare è quello di visualizzare le parole relative alle ore in modo stabile e senza sfarfallio. Per avere un aggiornamento ogni 1 ms, bisogna porre un'attenzione particolare perché i diversi task non interferiscano tra loro. Questo richiede un programma completamente “interrupt driven” assegnando la giusta priorità a ciascuna attività.

Vediamo intanto come comunicare con il modulo Real Time Clock in I2C per aggiornare l'orologio con i giusti valori.

RTC

Le procedure I2C sono state sviluppate per essere integrate in un programma completamente interrupt driven, non usano quindi polling o cicli di attesa. L'header standard i2c.h è usato solo per alcune definizioni e per l'inizializzazione. Analizzando il codice sorgente della libreria I2C si può notare come in alcuni casi ci siano dei loop di attesa per l'esecuzione di alcune operazioni della periferica SSP che, ovviamente, non sono compatibili con una struttura in tempo reale.

Un altro compito da eseguire parallelamente alle procedure “time critical”, è la comunicazione seriale con il modulo WiFly, che scambia i dati a 9600 bps. Per evitare blocchi del programma a causa di un bus così lento, tutte le procedure di basso livello devono essere pilotate in interrupt dal modulo USART, utilizzando una macchina a stati finiti (FSM) che gira in modo asincrono con il flusso del programma.

WiFly

La FSM è sviluppata in modo generico in modo da essere utilizzabile per qualsiasi scambio dati senza dover riscrivere il codice, ma solo cambiando una semplice struttura.

Driver

Dopo un'attenta analisi delle problematiche elettriche connesse alla scansione della matrice LED, analizziamo il software che pilota direttamente le porte di I / O.

Procedure I2C

Tutte le procedure critiche sono state riscritte in modalità time-safe utilizzando gli interrupt SSP (Synchronous Serial Peripheral), gli indicatori di stato e le variabili globali in una macchina a stati finiti.

Una traccia utile per usare questo tipo di tecnica è riportata nel documento Microchip AN736 [1].

La comunicazione I2C avviene attraverso diversi livelli.

Nel ciclo principale i flag sono controllati iniziando dal basso. L'intero ciclo viene ripetuto per ciascun dispositivo, se necessario, con la scansione dei buffer riempiti da procedure specifiche per ognuno dei dispositivo.

I2cFlow

Macchina a Stati Finiti [2]

  1. L'evento I2C è comunicato dalla periferica SSP attraverso interrupt. La procedura I2cLowService esegue la sequenza I2C per scambiare un singolo byte, azzera l'evento I2C o il flag di collisione del bus e, una volta scambiato tutto il buffer per quel dispositivo specifico, ripristina I2cBusyFlag per abilitare la routine di livello superiore.

  2. I2cBusyFlag è impostato da I2cHighService e resettato da I2cLowService.

  3. La routine specifica per il dispositivo ha riempito il buffer, impostato i flag I2cHighService e inizializzato la sequenza I2cLowService. Questo per lo scambio di ogni singolo byte. Ad ogni byte scambiato il contatore viene diminuito. Quando il contatore TX è = 0 inizia la ricezione. Quando entrambi i contatori sono = 0 si può iniziare con il dispositivo successivo, se necessario.

  4. Tutti i buffer dei dispositivi sono stati utilizzati, al ciclo successivo riparte dal primo dispositivo.

  5. E' pronto per controllare il dispositivo successivo al ciclo seguente.

Livello applicativo [3]

Ogni dispositivo I2C ha un proprio buffer (4 byte per RX e 4 byte per TX). La routine specifica del dispositivo riempie il buffer con i byte per lo scambio e imposta i flag secondo le azioni da eseguire:

I2C [I2cDevPtr] .Flag.Rx

I2C [I2cDevPtr] .Flag.Tx

309 void I2cData (unsigned char DevPtr, unsigned char TxBytes,

310                 unsigned char TX1, unsigned char TX2, unsigned char TX3,

311                 unsigned char TX4, unsigned char RxBytes)

312 {// Exchange data with I2C device

313     I2c[DevPtr].Flag.Tx = TxBytes; // n bytes to TX

314     I2c[DevPtr].TxBuff[0] = TX1 ; // first byte

315     I2c[DevPtr].TxBuff[1] = TX2 ; // second byte

316     I2c[DevPtr].TxBuff[2] = TX3 ; // third byte

317     I2c[DevPtr].TxBuff[3] = TX4 ; // fourth byte

318     I2c[DevPtr].Flag.Rx = RxBytes; // n bytes to RX

319     I2c[DevPtr].Done = 0; // reset flag, wait for execution

320 }

Livello superiore (I2cHigh) [4]

La procedura ad alto livello viene eseguita se il dispositivo indirizzato ha qualcosa da scambiare:

I2C [I2cDevPtr] .Flag.Rx! = 0 || I2C [I2cDevPtr] .Flag.Tx! = 0

e se la routine livello inferiore è disponibile:

I2cBusyFlag = 0.

Scambia i byte attraverso la routine di livello più basso e imposta il I2cBusyFlag.

239 void I2cHighService (void)

240 {

241     /*

242      initialize I2C sequence resetting flags, counters and buffers

243      kick start

244      all the following procedures will be triggered by interrupts

245      */

246     Ptr.Tx = 0; // reset TX buffer pointer

247     Ptr.Rx = 0; // reset RX buffer pointer

248     I2c[I2cDevPtr].RxBuff[0] = 0; // reset RX buffer

249     I2c[I2cDevPtr].RxBuff[1] = 0; // reset RX buffer

250     I2cEventFlag = 0;

251     I2cBusCollFlag = 0;

252     I2cBusyFlag = 1;

253     I2cStat = START; // FSM pointer

254

255     StartI2C(); // start with first status

256

257 }

 

Livello inferiore (I2cLow) [5]

La procedura a basso livello è la FSM propriamente detta. Questa è eseguita se la Interrupt Service Routine ha messo a 1 l'I2cEventFlag.

-START->ADRW-> se c'è qualcosa da trasmettere TX

altrimenti Rx

-WRITE byte[0]->...WRITE byte[n]->

-RSTART->ADRR->

-READ byte[0]->ACK...READ byte[n]NACK->

-STOP

-FINE

118 void I2cLowService (void)

119 {

120     I2cEventFlag=0; // wait for next interrupt

121

122     if (I2cBusCollFlag || SSPCON2bits.ACKSTAT)

123     { // Collision on bus or NACK -> sequence aborted

124         I2cBusCollFlag = 0; // error status reset

125         I2cStat = FINE; // current status = FINE

126         StopI2C(); // sends stop

127         // it doesn't reset RX & TX counter to retry on next round

128     }

129

130     else

131     {

132     // execute scheduled sequence for each byte in the selected record

133

134         switch (I2cStat)

135         {

136             case (STRT): // START sequence

137                 if (I2c[I2cDevPtr].Flag.Tx > 0)// something to send?

138                 {

139                     I2cStat = WRITE; // next status

140                     SSPBUF = I2cAdr[I2cDevPtr]; // update flag: R/W = write

141                 }

142                 else // if no bytes to send it has some byte to receive

143                 {

144                     I2cStat = READ; // next status

145                     SSPBUF = I2cAdr[I2cDevPtr]+1;// update flag: R/W = read

146                 }

147             break;

148

149

150             case (WRITE):

151                 /* sends Nth byte

152                  all bytes sent?

153                  all bytes received?

154                  otherwise stops sequences

155                 */

156                 SSPBUF = I2c[I2cDevPtr].TxBuff[Ptr.Tx]; // TX first byte

157                 Ptr.Tx ++; // points to next byte

158                 if (Ptr.Tx >= I2c[I2cDevPtr].Flag.Tx) // all bytes sent?

159                 {

160                     if (I2c[I2cDevPtr].Flag.Rx > 0) // all bytes received?

161                     {

162                         I2cStat = RSTART; // starts RX sequence

163                     }

164                     else // nothing to receive

165                     {

166                         I2cStat = STP;

167                     }

168                 }

169                 else // still something to TX

170                 {

171                     I2cStat = WRITE; // TX next byte(s)

172                 }

173             break;

174

175

176             case (READ):

177                 SSPCON2bits.RCEN = 1; // starts RX sequence

178                 Ptr.Rx ++; // next byte Rx

179                 if (Ptr.Rx >= I2c[I2cDevPtr].Flag.Rx) // all bytes received?

180                 {

181                     I2cStat = NACK; // YES, send NACK (Rx over)

182                 }

183                 else

184                 {

185                     I2cStat = ACK; // NO, send ACK (Rx proceed)

186                 }

187             break;

188

189

190             case (ACK):

191                 I2c[I2cDevPtr].RxBuff[Ptr.Rx-1] = SSPBUF; // store Nth byte received

192                 I2cStat = READ; // more bytes

193                 AckI2C();

194             break;

195

196

197             case (NACK):

198                 I2c[I2cDevPtr].RxBuff[Ptr.Rx-1] = SSPBUF; // store Nth byte received

199                 I2cStat = STP; // last byte received

200                 NotAckI2C();

201             break;

202

203

204             case (RSTART):

205                 // reinit bus without release

206                 I2cStat = ADRR; // next status = start RX

207                 RestartI2C();

208             break;

209

210

211             case (ADRR):

212                 I2cStat = READ; // next status = RX

213                 SSPBUF = I2cAdr[I2cDevPtr]+1;// update flag: R/W = read

214             break;

215

216

217             case (STP):

218                 I2cStat = FINE; // next status

219                 I2c[I2cDevPtr].Flag.Rx = 0; // no more bytes to RX or TX

220                 I2c[I2cDevPtr].Flag.Tx = 0; // high level procedures can start again

221                 StopI2C(); // send stop

222             break;

223

224

225             case (FINE):

226                 I2cBusyFlag = 0; // I2C comm over

227                 I2c[I2cDevPtr].Done = 1; // procedure complete for this device

228             break;

229

230

231             default:

232             break;

233

234         } // end switch

235     } // end else

236

237 }

Interrupt Service Routine [6]

L'intera macchina a stati finiti è pilotata da interrupt della periferica SSP

255 // High priority interrupt vector

256 void interrupt high_isr (void)

257 {

258 .......

293

294     if (PIR1bits.SSPIF) // an I2C event is over

295     {

296         PIR1bits.SSPIF = 0; // reset I2c interrupt flag

297         I2cEventFlag = 1; // I2cLowService will be executed

298     }

299

300     if (PIR2bits.BCLIF) // I2c bus collision

301     {

302         PIR2bits.BCLIF = 0; // interrupt flag reset

303         I2cEventFlag = 1; // execute I2cLowService

304         I2cBusCollFlag = 1; // a collision occurred

305     }

306 }

L'intero progetto MPLABX C per il PIC18F4620 è disponibile come open source in GitHub repository [7]

Una procedura di comunicazione seriale multiuso

Per essere pilotato il WiFly ha bisogno di una serie di comandi molto semplici, come ben spiegato nella scheda tecnica [8] e di seguito in alcuni esempi di questa pagina [9]. Il protocollo è semplice ma lento, non strutturato e differisce caso per caso, sia dal punto di vista dei tempi sia della sintassi. Ancora una volta una macchina a stati finiti asincrona schedulata da interrupt è perfetta per essere integrata con altre procedure time-critical. Ma non volevo scrivere un programma "hard-coded" per ogni diversa sequenza di comandi. Il mio obiettivo era quello di realizzare un generica procedura polivalente applicabile per diverse operazioni Wifly e riutilizzabile anche in situazioni diverse.

Per essere compatibile con altre procedure critiche, la sequenza di comunicazione deve essere sviluppata su più strati. Il livello superiore inizia con il TX o l'RX di una stringa e tutto deve funzionare autonomamente pilotato solo tramite interrupt della periferica USART. La schedulazione della sequenza della procedura deve essere gestita da una FSM facilmente configurabile. Questo è possibile tramite un uso intensivo dei puntatori.

Definizione della struttura [10]

Per ogni sequenza dobbiamo definire una struttura con alcuni campi definiti.

Il primo campo è il codice di operazione da eseguire.

Il secondo è la stringa da trasmettere o che deve essere ricevuta come terminatore durante la ricezione.

Il terzo è un puntatore a una funzione da richiamare alla fine di ogni passo della FSM.

33 typedef char (*FsmCallbackFunc)(void); // pointer to function

34 .....

50 struct FsmTable * FsmStructActive;

51

52 struct FsmTable

53 {// Scheduler for Finite State Machine

54     unsigned char Stat; // status index

55     const unsigned char * Str; // string to send or receive

56     FsmCallbackFunc pCallback; // function to call after RX or TX is over

57 };

58

59 enum Fsm{FsmRx, FsmTx, FsmDo, FsmEnd};

Programmazione della FSM [11]

In questo esempio, quando la stringa relativa all'orario è completamente ricevuta, deve essere decodificata. Nei passaggi precedenti chiama semplicemente una callback fittizia che non fa nulla.

68 struct FsmTable ReadTimeFsm[] =

69 {/* scheduler used for comm protocol with WiFLy

70     this reads time from http://www.inrim.it

71     */

72     {FsmTx, "$$$", (FsmCallbackFunc)CommFsmIdle},

73     {FsmRx, "CMD", (FsmCallbackFunc)CommFsmIdle},

74     {FsmTx, "open\r\n", (FsmCallbackFunc)CommFsmIdle},

75     {FsmRx, "*CLOS*", (FsmCallbackFunc)TimeDecode},

76     {FsmDo, "", (FsmCallbackFunc)CommFsmWriteRTC},

77     {FsmEnd, "", (FsmCallbackFunc)CommFsmClear}

78 };

Un altro esempio di una sequenza che invia solo un carriage return/line feed e attende la stringa "EXIT" senza chiamare alcuna altra funzione.

61 struct FsmTable ExitCmdFsm[] =

62 {// scheduler used to exit from command mode

63     {FsmTx, "exit\r\n", (FsmCallbackFunc)CommFsmIdle},

64     {FsmRx, "EXIT", (FsmCallbackFunc)CommFsmIdle},

65     {FsmEnd, "", (FsmCallbackFunc)CommFsmIdle}

66 };

FSM start [12]

Dopo aver definito le operazioni da eseguire nella FSM, dobbiamo chiamare solo l'avvio della FSM puntando alla struttura desiderata.

Naturalmente dobbiamo anche costruire le procedure necessarie per richiamarle.

Dopo di che la macchina viene autonomamente pilotata solo tramite l'interrupt USART o TMR1 timeout in caso di errore di comunicazione.

Un flag dice alla procedura di livello applicativo se la FSM è libera di eseguire un'altra sequenza.

72 void StartCommFsmSched(struct FsmTable * FsmStruct)

73 {// initialize the FSM

74     FsmIndx = 0;

75     CommFsmFlag = 1; // kick off the FSM

76     TxFsmFlag = 0;

77     RxFsmFlag = 0;

78     FsmStructActive = FsmStruct;

79     CommFsmDoneFlag = 1; // the FSM is started

80 }

Finite State Machine [13]

La macchina a stati finiti legge tutte le informazioni definite nella struttura ed esegue le operazioni indicate.

82 void CommFsmSched(struct FsmTable * FsmStruct)

83 {//Communication Finite State Machine

84     unsigned char FsmStat;

85

86     FsmStat = FsmStruct[FsmIndx].Stat;

87

88     switch (FsmStat)

89     {

90          case FsmRx:

91             if(RxFsmFlag==0)

92             {// the first time is called

93                 StartRx(FsmStruct[FsmIndx].Str);

94             }

95             else

96             {// after terminator string has been received

97                 // execute the callback function

98                 CommFsmState = (* FsmStruct[FsmIndx].pCallback);

99                 (* CommFsmState) () ;

100                FsmIndx++;

101                RxFsmFlag = 0;

102            }

103         break;

104

105         case FsmTx:

106         // transmit the string and then call the callback function

107

108         if(TxFsmFlag==0)

109         {// the first time is called

110             StartTx(FsmStruct[FsmIndx].Str);

111         }

112         else

113         {// after all the buffer is transmitted

114             // execute the final function

115             CommFsmState = (* FsmStruct[FsmIndx].pCallback);

116             (* CommFsmState) () ;

117             FsmIndx++;

118             TxFsmFlag = 0;

119         }

120

121         break;

122

123         case FsmDo:

124             CommFsmState = (* FsmStruct[FsmIndx].pCallback);

125             (* CommFsmState) () ;

126             FsmIndx++;

127         break;

128

129

130         case FsmEnd:

131             CommFsmState = (* FsmStruct[FsmIndx].pCallback);

132             (* CommFsmState) () ;

133             CommFsmFlag = 0;

134             CommFsmDoneFlag = 0; // the FSM is over

135         break;

136

137         default:

138             CommFsmFlag = 0;

139         break;

140     }

141 }

L'intero progetto MPLABX C per il PIC18F4620 è disponibile come open source in GitHub repository [7]

Scansione della Matrice. Procedure e tecniche.

Per pilotare la matrice abbiamo bisogno di 10 porte I/O del PIC18F4620 per le righe e 12 per le colonne, cioè 22 pin occupati dei 40 disponibili in questa versione della MCU.

WordClockItaMatrix

Le righe, da 0 a 9, sono selezionate una dopo l'altra con periodo di 1 ms, con un periodo complessivo di 10ms. Il frame rate di 100Hz è adatto ad avere una visualizzazione libera da sfarfallio.

Le colonne, da A a L, sono selezionate in parallelo quando la riga corrispondente deve essere illuminata.

1006 typedef union {

1007     struct {

1008             unsigned LATA0 :1;

1009             unsigned LATA1 :1;

1010             unsigned LATA2 :1;

1011             unsigned LATA3 :1;

1012             unsigned LATA4 :1;

1013             unsigned LATA5 :1;

1014             unsigned LATA6 :1;

1015             unsigned LATA7 :1;

1016     };

1017 ......

1048     } LATAbits_t;

1049 extern volatile LATAbits_t LATAbits @ 0xF89;

Qui un esempio del file di intestazione del PIC che mostra come le porte sono mnemonicamente definite. L'esempio è per PORT A.

Mnemonici [14]

La maggior parte degli I/O di tutte le porte sono utilizzati per pilotare le righe e le colonne. Per avere una denominazione più mnemonica ho ulteriormente definito le porte. Oltre alle definizioni per le righe.

56 #define Row0 LATAbits.LATA1

57 #define Row1 LATAbits.LATA2

58 #define Row2 LATAbits.LATA3

59 #define Row3 LATAbits.LATA4

60 #define Row4 LATAbits.LATA5

61 #define Row5 LATCbits.LATC0

62 #define Row6 LATCbits.LATC1

63 #define Row7 LATBbits.LATB1

64 #define Row8 LATBbits.LATB2

65 #define Row9 LATBbits.LATB3

Righe [15]

Le righe sono pilotate in modo sequenziale, una a una ogni 1 ms, indirizzate tramite gli mnemonici definiti.

32 void SetRow(unsigned char Row)

33 {// from 0 to 9, enable one row at a time to scan matrix

34     switch (Row)

35     {

36         case 0:

37             Row0 = ON;

38         break;

39

40         case 1:

41             Row1 = ON;

42         break;

43

44         case 2:

45             Row2 = ON;

46         break;

47

48         case 3:

49             Row3 = ON;

50         break;

51

52         case 4:

53             Row4 = ON;

54         break;

55

56         case 5:

57             Row5 = ON;

58         break;

59

60         case 6:

61             Row6 = ON;

62         break;

63

64         case 7:

65             Row7 = ON;

66         break;

67

68         case 8:

69             Row8 = ON;

70         break;

71

72         case 9:

73             Row9 = ON;

74         break;

75

76         default:

77             SetRowOff();

78         break;

79     }

80 }

Per le colonne è un po' più complicato. Tutte e 12 devono essere impostate nel ciclo di 1 ms. Al fine di risparmiare tempo prezioso sono stati utilizzati alcuni trucchi.

Definizione delle parole sulla matrice [16]

La matrice è stata definita con un array in cui l'indice definisce la variabile a 16 bit corrispondente a ciascuna riga e i bit di ciascuna variabile definiscono la singola colonna dei LED, come riportato in questo foglio [17], e traslata nel codice con la procedura WordSetting () .

170 void WordSetting()

171 {// set the matrix with the words according to the new time

172     int i;

173

174     for(i=0; i<=MAXROW; i++)

175     {// reset the matrix

176         Matrix[i]=0;

177     }

178

179     if(Min <= 30)

180     {// after half hour the word is "to" the next hour, before is "after"

181         Matrix[6]=0b000000011110; // E

182     }

183     else

184     {

185         Hour++;

186         Matrix[7]=0b100000000000; // MENO

187     }

188

189     switch(Min / 5)

190     {

191         case 1:

192         case 11:

193             Matrix[8] = Matrix[8] | 0b000001111110;// CINQUE

194         break;

195

196         case 2:

197         case 10:

198             Matrix[9] = Matrix[9] | 0b111100000000;// DIECI

199         break;

200

201         case 3:

202         case 9:

203             Matrix[7] = Matrix[7] | 0b001101111110;// UN QUARTO

204         break;

205

206         case 4:

207         case 8:

208             Matrix[8] = Matrix[8] | 0b111110000000;// VENTI

209         break;

210

211         case 5:

212         case 7:

213             Matrix[8] = Matrix[8] | 0b111111111110;// VENTICINQUE

214         break;

215

216         case 6:

217             Matrix[9] = Matrix[9] | 0b000000111110;// MEZZA

218         break;

219

220         default:

221         break;

222     }

223

224     if(!TimeSync)// light up points only if RTC time is in sync with INRIM

225     {

226         switch(Min % 5)

227         {

228             case 1:

229                 Matrix[0] = Matrix[0] | 0b000000000001;// first point

230             break;

231

232             case 2:

233                 Matrix[0] = Matrix[0] | 0b000000000001;// first point

234                 Matrix[1] = Matrix[1] | 0b000000000001;// add second point

235             break;

236

237             case 3:

238                 Matrix[0] = Matrix[0] | 0b000000000001;// first point

239                 Matrix[1] = Matrix[1] | 0b000000000001;// add second point

240                 Matrix[2] = Matrix[2] | 0b000000000001;// add third point

241             break;

242

243             case 4:

244                 Matrix[0] = Matrix[0] | 0b000000000001;// first point

245                 Matrix[1] = Matrix[1] | 0b000000000001;// add second point

246                 Matrix[2] = Matrix[2] | 0b000000000001;// add third point

247                 Matrix[3] = Matrix[3] | 0b000000000001;// add fourth point

248             break;

249

250             default:

251             break;

252         }

253     }

254

255     Hour = Hour % 12;

256

257     if(Hour == 1)

258     {

259         Matrix[1] = Matrix[1] | 0b101000000000;// E' L'

260     }

261     else

262     {

263         Matrix[0] = Matrix[0] | 0b111101101110;// SONO LE ORE

264     }

265

266     switch(Hour)

267     {

268         case 1:

269             Matrix[1] = Matrix[1] | 0b000111000000;// UNA

270         break;

271

272         case 2:

273             Matrix[1] = Matrix[1] | 0b000000011000;// DUE

274         break;

275

276         case 3:

277             Matrix[2] = Matrix[2] | 0b111000000000;// TRE

278         break;

279

280         case 4:

281             Matrix[5] = Matrix[5] | 0b111111100000;// QUATTRO

282         break;

283

284         case 5:

285             Matrix[6] = Matrix[6] | 0b111111000000;// CINQUE

286         break;

287

288         case 6:

289             Matrix[5] = Matrix[5] | 0b000000001110;// SEI

290         break;

291

292         case 7:

293             Matrix[4] = Matrix[4] | 0b000000111110;// SETTE

294         break;

295

296         case 8:

297             Matrix[2] = Matrix[2] | 0b000111100000;// OTTO

298         break;

299

300         case 9:

301             Matrix[2] = Matrix[2] | 0b000000011110;// NOVE

302         break;

303

304         case 10:

305             Matrix[3] = Matrix[3] | 0b111110000000;// DIECI

306         break;

307

308         case 11:

309             Matrix[3] = Matrix[3] | 0b000001111110;// UNDICI

310         break;

311

312         case 0:

313             Matrix[4] = Matrix[4] | 0b111111000000;// DODICI

314         break;

315

316         default:

317         break;

318     }

319

320     SetColB();

321 }

 

Pre-setting per le colonne [18]

La traslazione (verso sinistra) sarebbe troppo pesante per essere eseguita ogni 1 ms, perché richiederebbe molte operazioni di mascheratura per impostare ciascuna porta.

Per questo motivo viene eseguita solo ad ogni cambio di visualizzazione, cioè ogni minuto e il risultato è memorizzato nel buffer in una matrice di int senza segno. Questo spreca più memoria (un int per ogni bit), ma permette un grosso risparmio di tempo.

82 void SetColB(void)

83 {// pre-fill the matrix with bytes instead of bits.

84  // This wastes memory but saves a lot of time during runtime matrix scan

85     char Col;

86     char Row;

87     unsigned int Mask;

88

89     for(Row = MINROW; Row <= MAXROW; Row++)

90     {

91         for(Col = MinCol; Col <= MaxCol; Col++)

92         {

93             Mask=BitMask[Col] & Matrix[Row];

94

95             if(Mask)

96             {

97                 MatrixB[Row][MaxCol-Col] = ON;

98             }

99             else

100            {

101                MatrixB[Row][MaxCol-Col] = OFF;

102            }

103         }

104     }

105 }

Impostazione dinamica delle colonne [19]

Nel modo descritto in precedenza la conversione viene eseguita in 36μS invece di centinaia di μS. In questo modo si spreca solo una piccola percentuale del periodo di 1 ms, consentendo la regolazione dei LED attraverso la riduzione del duty cycle.

107 void SetCol(unsigned char Row)

108 {/* from 'A' to 'L', enable all the column to light all the LEDs interested

109     the byte matrix is pre-computed when LEDs to display change (every minute)

110     this saves a lot of time during 1ms column scan

111

112     * RD0 = Column A

113     * RD1 = Column B

114     * RD2 = Column C

115     * RD3 = Column D

116     * RD4 = Column E

117     * RD5 = Column F

118     * RD6 = Column G

119     * RD7 = Column H

120     *

121     * RE0 = Column I

122     * RE1 = Column J

123     * RE2 = Column K

124     *

125     * RC5 = Column L

126     */

127

128     ColA = MatrixB[Row][11];

129     ColB = MatrixB[Row][10];

130     ColC = MatrixB[Row][9];

131     ColD = MatrixB[Row][8];

132     ColE = MatrixB[Row][7];

133     ColF = MatrixB[Row][6];

134     ColG = MatrixB[Row][5];

135     ColH = MatrixB[Row][4];

136

137     LATD = Dbits.Port;

138

139     ColI = MatrixB[Row][3];

140     ColJ = MatrixB[Row][2];

141     ColK = MatrixB[Row][1];

142

143     LATE = Ebits.Port;

144

145     ColL = MatrixB[Row][0];

146

147     LATC = PORTC | Cbits.Port;

148 }

Al fine di risparmiare tempo durante le procedure eseguite più spesso, i valori come BitMask [20] e DutyTab [21] sono pre-calcolati all'avvio o in zone meno critiche del programma.

ISR a bassa priorità [22]

La luminosità ambiente viene letta dalla fotocellula attraverso periferiche ADC.

182 // Low priority interrupt vector

183 void interrupt low_priority low_isr (void)

184 {

185 .......

244

245     if (PIR1bits.ADIF)

246     {// A new AD value has been read

247         PIR1bits.ADIF=0; // reset interrupt flag

248         DutyCycle=(ADRESH*LightIndx)>>8;//only the highest 8 bits are read

249         //>>8 to convert back index to int

250     }

251

252 }

Definizione del duty cycle del PWM [23]

Viene poi convertita in tempo con l'aiuto dei valori DutyTab[21].

In questo modo il TMR1 spegne le colonne attenuando la luminosità dei LED secondo il duty cycle calcolato.

160 void SetTimer1(unsigned char DutyCycle)

161 {

162     //set both High and Low registers for Timer1

163     int Count = DutyTab[DutyCycle];

164     TMR1H = Count >> 8; // byte High

165     TMR1L = Count; // byte Low

166     T1CONbits.TMR1ON=1;

167 }

Matrix test [24]

Questa Procedura viene compilata solo quando necessario per testare tutto l'hardware e le funzionalità di matrice.

323 void TestMatrix()

324 {

325     static unsigned char Xcol;

326     static unsigned char Yrow;

327     int i;

328     static int j = 0;

329     static int k = 0;

330

331

332     for(i=MINROW; i<=MAXROW; i++)

333     {// reset the matrix

334         Matrix[i]=0;

335     }

336

337     switch(j)

338     {

339         case 0: // char display

340             WriteMatrixChar('G', 'O', 7, 1);

341             TestTime = 5000;

342             j++;

343         break;

344

345         case 1: // dot by dot

346             Matrix[Yrow]=0b0000100000000000 >> Xcol;

347             if((++Xcol)>MaxCol)

348             {

349                 Xcol = MinCol;

350                 if((++Yrow)>MAXROW)

351                 {

352                     Yrow=MINROW;

353                     j++;

354                 }

355             }

356             TestTime = 100;

357         break;

358

359         case 2: // row by row

360             Matrix[Yrow]=0b0000111111111111;

361             if((++Yrow)>MAXROW)

362             {

363                 Yrow=MINROW;

364                 j++;

365             }

366             TestTime = 300;

367         break;

368

369         case 3: //column by column

370             Matrix[0]=0b0000100000000000 >> Xcol;

371             Matrix[1]=0b0000100000000000 >> Xcol;

372             Matrix[2]=0b0000100000000000 >> Xcol;

373             Matrix[3]=0b0000100000000000 >> Xcol;

374             Matrix[4]=0b0000100000000000 >> Xcol;

375             Matrix[5]=0b0000100000000000 >> Xcol;

376             Matrix[6]=0b0000100000000000 >> Xcol;

377             Matrix[7]=0b0000100000000000 >> Xcol;

378             Matrix[8]=0b0000100000000000 >> Xcol;

379             Matrix[9]=0b0000100000000000 >> Xcol;

380             if((++Xcol)>MaxCol)

381             {

382                 Xcol = MinCol;

383                 j++;

384             }

385             TestTime = 300;

386         break;

387

388         case 4: //diagonal left

389             Matrix[0]=0b0000100000000000 >> Xcol;

390             Matrix[1]=0b0000010000000000 >> Xcol;

391             Matrix[2]=0b0000001000000000 >> Xcol;

392             Matrix[3]=0b0000000100000000 >> Xcol;

393             Matrix[4]=0b0000000010000000 >> Xcol;

394             Matrix[5]=0b0000000001000000 >> Xcol;

395             Matrix[6]=0b0000000000100000 >> Xcol;

396             Matrix[7]=0b0000000000010000 >> Xcol;

397             Matrix[8]=0b1000000000001000 >> Xcol;

398             Matrix[9]=0b0100000000000100 >> Xcol;

399             if((++Xcol)>MaxCol)

400             {

401                 Xcol = MinCol;

402                 j++;

403             }

404             TestTime = 300;

405         break;

406

407         case 5: //diagonal right

408             Matrix[0]=0b0000000000000100 << Xcol;

409             Matrix[1]=0b0000000000001000 << Xcol;

410             Matrix[2]=0b0000000000010000 << Xcol;

411             Matrix[3]=0b0000000000100000 << Xcol;

412             Matrix[4]=0b0000000001000000 << Xcol;

413             Matrix[5]=0b0000000010000000 << Xcol;

414             Matrix[6]=0b0000000100000000 << Xcol;

415             Matrix[7]=0b0000001000000000 << Xcol;

416             Matrix[8]=0b0000010000000001 << Xcol;

417             Matrix[9]=0b0000100000000010 << Xcol;

418             if((++Xcol)>MaxCol)

419             {

420                 Xcol = MinCol;

421                 j++;

422             }

423             TestTime = 300;

424         break;

425

426         case 6: //all LEDs on

427             if(k%2)

428             {

429                 Matrix[0]=0XFFFF;

430                 Matrix[1]=0XFFFF;

431                 Matrix[2]=0XFFFF;

432                 Matrix[3]=0XFFFF;

433                 Matrix[4]=0XFFFF;

434                 Matrix[5]=0XFFFF;

435                 Matrix[6]=0XFFFF;

436                 Matrix[7]=0XFFFF;

437                 Matrix[8]=0XFFFF;

438                 Matrix[9]=0XFFFF;

439             }

440             else

441             {

442                 Matrix[0]=0;

443                 Matrix[1]=0;

444                 Matrix[2]=0;

445                 Matrix[3]=0;

446                 Matrix[4]=0;

447                 Matrix[5]=0;

448                 Matrix[6]=0;

449                 Matrix[7]=0;

450                 Matrix[8]=0;

451                 Matrix[9]=0;

452             }

453

454             if((++k)>8)

455             {

456                 k = 0;

457                 j++;

458             }

459             TestTime = 1000;

460         break;

461

462         default:

463             j=0;

464             TestTime = 300;

465         break;

466     }

467

468     // Matrix[0]=0b0000111111111111;

469

470     SetColB();

471 }

Questa comprende anche la visualizzazione dei caratteri utilizzando un font 7x5 [25].

Con i metodi utilizzati per pilotare la matrice è stato veramente facile usando una delle tante descrizioni di font disponibili in rete. L'unico problema è stato quello di scambiare righe e colonne perché normalmente si usa una scansione a colonne e non per righe. Questa operazione è stata eseguita con questo foglio di lavoro [26].

Un video che mostra come funziona questo test è visibile in [27].

L'intero progetto MPLABX C per il PIC18F4620 è disponibile come open source in GitHub repository [7]

LINKS capitolo Software:

[1] http://ww1.microchip.com/downloads/en/AppNotes/00736a.pdf

[2] https://github.com/guiott/wordclock/blob/master/myi2c.c#L259

[3] https://github.com/guiott/wordclock/blob/master/myi2c.c#L309

[4] https://github.com/guiott/wordclock/blob/master/myi2c.c#L239

[5] https://github.com/guiott/wordclock/blob/master/myi2c.c#L118

[6] https://github.com/guiott/wordclock/blob/master/WordClock.c#L255

[7] https://github.com/guiott/wordclock

[8] http://ww1.microchip.com/downloads/en/DeviceDoc/50002230A.pdf

[9] https://code.google.com/p/wifly-wonderland/wiki/WiFly_comandi_utili

[10] https://github.com/guiott/wordclock/blob/master/comm.h#L33

[11] https://github.com/guiott/wordclock/blob/master/comm.h#L68

[12] https://github.com/guiott/wordclock/blob/master/comm.c#L72

[13] https://github.com/guiott/wordclock/blob/master/comm.c#L82

[14] https://github.com/guiott/wordclock/blob/master/matrix.h#L56

[15] https://github.com/guiott/wordclock/blob/master/matrix.c#L32

[16] https://github.com/guiott/wordclock/blob/master/matrix.c#L170

[17] http://www.guiott.com/WC/WordClock.xlsx

[18] https://github.com/guiott/wordclock/blob/master/matrix.c#L82

[19] https://github.com/guiott/wordclock/blob/master/matrix.c#L107

[20] https://github.com/guiott/wordclock/blob/master/matrix.c#L5

[21] https://github.com/guiott/wordclock/blob/master/matrix.h#L137

[22] https://github.com/guiott/wordclock/blob/master/WordClock.c#L183

[23] https://github.com/guiott/wordclock/blob/master/WordClock.c#L160

[24] https://github.com/guiott/wordclock/blob/master/matrix.c#L323

[25] https://github.com/guiott/wordclock/blob/master/Font8x5.h#L18

[26] http://www.guiott.com/WC/Font5x8.xlsx

[27] https://www.youtube.com/watch?v=IOzSpM-XEm4&feature=youtu.be

Assemblaggio

Collegamenti e millefori
In questa ultima parte del progetto Word Clock, si descrivono i passaggi per l'assemblaggio di tutte le componenti per dare una “veste” all'orologio, inserendolo in una cornice tale da poterlo anche destinare ad ambienti diversi dal laboratorio, nel quale anche le schede a vista sono accettate, o meglio, benvenute...

 

Board

La figura mostra la scheda finale nella versione prototipale, realizzata utilizzando la pluri-collaudata millefori. La scomodità del wiring è compensata dalla rapidità con cui si possono realizzare dei circuiti senza dover necessariamente realizzare il circuito stampato. Per molti invece, la realizzazione dello stampato stesso rappresenta una fase quasi ludica. Si può realizzare con tecniche casalinghe o con sistemi altamente professionali. Oggi entrambe le possibilità sono alla portata anche dell'hobbista con poche disponibilità economiche. Il circuito stampato si può realizzare in ambiente casalingo con tecniche semplici e con l'”acido”, come “una volta”. Si può poi passare al bromografo (ma anche questo è facile da realizzare in casa con materiale di recupero [1]). Oppure si può realizzare lo sbroglio con uno dei tanti programmi di CAD dedicati agli stampati e poi inviarli ai service specializzati, che oramai anche con spese abbordabili realizzano PCB (Printed Circuit Board) alla portata di tutti.

 

Board

Per chi fosse interessato alla realizzazione del circuito ecco il layout con la disposizione dei componenti. Anche se non è stato realizzato il PCB, si è usato ugualmente un CAD specifico per ottimizzare la distribuzione dei fili da saldare.

 

Wiring

Per i collegamenti sono necessari pochi attrezzi, ma più sono professionali, più diventa facile realizzare quello che si vede nelle immagini riportate di seguito.

millefori

Un buon filo tipo quello da Wire-Wrap (metodo “antico” che non richiede neanche l'uso del saldatore [2]), aiuta molto. Se si usa il saldatore con fili di scarsa qualità, anche solo avvicinandolo, si squaglia letteralmente buona parte della protezione del collegamento di rame, con le relative conseguenze di corti nascosti dietro ogni collegamento. Anche una buona “spella fili” e un'affilata tronchesina, aiutano molto. Per il resto, occorre solo tanta pazienza e un po' di metodo, ma anche questo fa parte del viaggio e può avere il suo fascino.

 

Wires1

Wires3

Wires2

 

Dalle differenti viste delle immagini precedenti si può facilmente immaginare che occorre un minimo di precisione e metodo per poter arrivare ad un circuito funzionante. I vari “ponticelli”, in altre parole, le sovrapposizioni dei fili, sono molto frequenti, quindi come dicevamo è molto importante che non ci siano parti di filo scoperte, perché il corto è dietro l'angolo. Le saldature devono essere pulite e ben fatte, anche in questo caso per evitare corti tra pin vicini. A meno di quelle volute, come dall'immagine si può notare, che in alcuni casi, invece di usare dei fili spellati per collegamenti adiacenti, si usa estendere la saldatura anche su più fori. Anche qui, una buona scheda millefori, aiuta, rispetto ad una molto economica, dove le piazzole non ben definite o ben fatte portano a continui sollevamenti delle piazzole stesse (come facilmente accade se si insiste troppo col saldatore).
Dalle varie immagini si può notare che tutti gli integrati sono montati con zoccoli (o con l'equivalente di Strip femmina, a passo 2,54, sul tipo di questo [3]). Questo per due motivi, sia per poter sostituire facilmente l'eventuale componente che si guasta, ma, soprattutto, per evitare di portare direttamente il saldatore su componenti un po' più “sensibili” alla temperatura. In ogni caso come per le piazzole, ma, soprattutto, per i componenti, è importante avere una buona tecnica nel saldare. Per non bruciare piazzole o componenti o, al contrario per non fare “saldature fredde”, cioè quelle saldature fatte o troppo in fretta o su superfici non ben pulite che comportano facili falsi contatti o, nel caso peggiore, sconnessioni. Anche soffiare sulla saldatura, attività comune, sarebbe non consigliato, perché aumenta la possibilità che lo stagno non si distribuisca bene o non si raffreddi correttamente.

Una volta realizzato il circuito e debitamente testato, si passa, sempre per chi vuole cimentarsi, alla realizzazione del contenitore.
In questo caso le scelte sono veramente infinite, di seguito vediamo una delle possibili soluzioni. Anche qui, come per il circuito stampato, per molti, come me, la realizzazione ha la sua parte di fascino.

 

Pannello

In questa parte dell'articolo si descrivono in modo prevalentemente “pittorico” le varie fasi per la realizzazione del contenitore.
Partiamo dal pannello che ospiterà i LED e dalla maschera necessaria per rappresentare i caratteri. Il tutto con una buona visibilità, a seguito di una buona diffusione della luce bilanciando una sufficiente luminosità con una buona “leggibilità” dei caratteri.

 

WC01

I componenti di base utilizzati per realizzare il pannello per la matrice di LED, sono:

  • Due fogli 20x20cm di PVC espanso,
  • la maschera delle lettere,
  • e alcuni fogli di diffusione.

Bulino

Una stampa dalla griglia per le lettere è utilizzata come maschera di foratura per segnare i punti da perforare. Righello e matita, oppure altre forme più “moderne”, a discrezione personale.

 

Preforatura
Incidendo con un perno, usato come guida per aiutare la successiva foratura.
Guidando il trapano con un puntatore laser, aumenta la precisione del trapano stesso. Ottenendo un pattern, un modello, molto regolare, come mostrato nelle figure seguenti.

Anche in questo caso, come si può notare dalle immagini che seguono, il puntatore laser è rigorosamente home-made. Da buon maker ovviamente si adottano tecniche di “hackeraggio” di altri attrezzi o dispositivi più o meno comuni.

Laser01
Sempre con la dovuta attenzione, nell'usare dispositivi che possano danneggiare la vista o altro.
Lo stesso vale per tutti gli attrezzi mostrati in questo articolo. Si declina ogni responsabilità per un uso improprio degli stessi, sia per eventuali danni a cose o a persone.

Laser03

 

 

Laser05

Ovviamente se si dispone di un buon trapano a colonna o le attrezzature riportate nelle immagini, il tutto diventa “un gioco da ragazzi”. Senza supporto a colonna o fissaggi adeguati, risulta in generale molto più complesso. Come si diceva, buoni strumenti, consentono una buona realizzazione in tempi minori. Ma, non è detto che con la manualità non si riesca a sopperire quello che gli attrezzi sono in grado di fornire.
Il laboratorio non nasce necessariamente già tutto attrezzato, ma lo può diventare piano piano a seconda delle disponibilità e delle intenzioni personali.

Ovviamente, neanche a dirlo, se si ha la possibilità di avere nelle vicinanze un buon laboratorio di un amico o, ancora meglio, un buon FabLab attrezzato...(un buon esempio fra tutti  a Roma [4]).

Proseguiamo con le forature...

Buchi5mm

Nell'immagine sono rappresentati i fori da 5mm, adatti per alloggiare i LED nel pannello.

DisplayFront

I LED (110 per la matrice + 4 agli angoli per i conteggi parziali) montati sul panello posteriore.

SandedLeds

I LED sono levigati in punta, per ottenere una migliore diffusione della luce, altrimenti molto concentrata.

MascheraEsterna

Il Pannello frontale con i fori più grandi che agiscono come maschera.

 

Sandwich

Il Pannello frontale (grossi buchi = mascherina per la luce) montato sulla parte superiore del pannello posteriore con i LED.

Assemblaggio Finale

ribba-cornice

E' stata utilizzata una cornice 25x25cm commerciale, con sufficiente profondità per contenere tutto [5].

WC03

La maschera per le Lettere.

 

WC04
La maschera per le lettere speculare per migliorare il nero. Raddoppiato e a specchio su carta trasparente. Il lato con l'inchiostro è all'interno per essere protetto dai graffi.

WordClockItaBOLD

La maschera finale con un diverso font di caratteri (alta risoluzione).

Cornice

Una vista interna del telaio con la maschera nera per adattare le dimensioni.

CornicePannello

Un altro passaggio nell'assemblaggio.

CorniceAssemblata

E quindi, l'assemblaggio finale.

InnerBoard

Un immagine con la scheda montata sul pannello posteriore.

Back

E quindi il Pannello posteriore con il connettore per la programmazione del PIC (la porta ICSP) ed il GPIO9 del WiFly (Per avviare il WiFly come AP, collegarcisi con un browser qualsiasi e collegarsi ad una rete WiFi esistente).

BackICD3

Per la programmazione del PIC è stato usato il Programmatore ufficiale della MicroChip, l'ICD3, connesso alla porta ICSP, come accennato. Oltre all'ottima qualità come programmatore consente anche il Debugging, molto utile nella maggior parte delle situazioni e per la fase iniziale di avvio del progetto, quando i Bug o le tarature da fare sono molto frequenti.

Sequence
Una sequenza (con un GIF animato) che mostra come i diversi strati sono messi uno sopra l'altro, a partire dai fogli di diffusione della luce e quindi le diverse maschere con le lettere per ottenere un sufficiente contrasto.

Assembled1

Per finire il pannello in lavorazione installato all'interno del telaio.
E per chiudere un breve video per mostrare il funzionamento reale [6].

 

 

LINKS capitolo Assemblaggio:
[1] http://guiott.com/bromografo/bromografo.html
[2] http://en.wikipedia.org/wiki/Wire_wrap
[3] http://www.robot-italy.com/it/female-strip-rounded-0-1-pitch-36pin.html
[4] https://www.fablabs.io/fablabspqwork
[5] http://www.ikea.com/it/it/catalog/products/00078051/
[6] https://www.youtube.com/watch?v=IOzSpM-XEm4&feature=youtu.be

Allegati

Allegati

File Eagle

Registrati gratuitamente all'Embedded IOT

Registrati gratis all'EmbeddedIOT

Una risposta

  1. Maurizio Di Paolo Emilio Maurizio 20 gennaio 2016

Scrivi un commento