02/14/2014
Un LED cube 3x3x3 costruito completamente a mano!
I LED cube sono matrici LED a tre dimensioni in grado di illuminarsi nelle più complesse animazioni; ne esistono di numerosi tipi tra cui 3x3x3, 4x4x4 e addirittura 8x8x8. Alcuni sono comandati via PC, altri sono provvisti di microcontrollore e quindi completamente autonomi.
Nel mio caso ho dotato il cubo di microfono e amplificatore per lampeggiare a tempo di musica (una specie di VU meter tridimensionale). L'ho realizzato con un semplice PIC micro 16F84, un amplificatore MAX9812, microfono e pochi altri componenti.
La griglia LED è composta da 3 piani di 9 LED ciascuno per un totale di 27 LED (3x3x3). Il micro prevede solo 13 GPIO, ovviamente non sufficienti a pilotare tutti e 27 i LED singolarmente; ho quindi realizzato un indirizzamento del singolo LED secondo la coppia piano/colonna. In questo modo per accendere un LED è sufficiente specificare il piano (uno di tre, quindi 3 GPIO) e la colonna di afferenza (una di nove, quindi altri 9 GPIO) per un totale di 12 GPIO. Elettricamente ciò si traduce nel collegare tra loro i catodi dei LED di ogni piano e tutti gli anodi secondo le colonne.
Questa particolare configurazione purtroppo ci costringe ad accendere un solo piano alla volta: abilitare più di un piano contemporaneamente comporterebbe l'accensione di LED non desiderati. E' comunque possibile risolvere il problema avvicendando i 3 piani molto velocemente tra loro (tecnica display multiplexing): in sostanza il micro seleziona il primo piano, ne accende opportunatamente i nove LED, spegne il primo piano, accende il secondo, accende i nove LED del secondo piano, spenge il secondo piano, accende il terzo e così via in loop il più velocemente possibile. L'occhio umano non è in grado di percepire questo veloce cambiamento e la percezione finale è quella di avere tutti i LED accesi contemporaneamente. Nel mio caso ho programmato il multiplexing ad una frequenza di circa 100Hz ottenendo così un frame rate dell'intero pattern LED di circa 33,3Hz. Nell'immagine qui sotto mostro i collegamenti tra i GPIO del micro con alla griglia LED 3x3x3.
In aggiunta al multiplex, il micro alimenta i LED tramite PWM così da poterne modificare la luminosità. Il pic 16F84 non prevede hardware dedicato per il PWM ma è comunque possibile realizzarlo via software togglando molto velocemente on/off i GPIO opportuni.
Il cubo è in grado di mostrare fino ad 8 animazioni differenti, ognuna composta da un certo numero di frame (un frame è semplicemente una configurazione ON/OFF dei 27 LED). Le animazioni sono codificate nella flash del micro come semplici tabelle retwl di lookup.
Ho dotato il cubo di un microfono amplificatore integrati. In questo modo è in grado di mostrare le animazioni LED andando più o meno a tempo di musica. Il segnale analogico captato dal microfono viene amplificato, filtrato da un filtro RC passa basso e poi squadrato in un segnale TTL 0-5V. Il segnale così ottenuto passa al piedino RB0/INT del micro. Il filtro RC è dimensionato per far in modo che passino solo le basse frequenze ad esempio la batteria di una musica o simili. Ogni volta che il microfono capta un segnale a bassa frequenza il piedino RB0 viene portato a livello logico alto il che scatena un interrupt nel microcontrollore. Il micro quindi seleziona casualmente una animazione (dalle 8 disponibili) e la sulla griglia LED, un frame dopo l'altro (è possibile impostare da software la velocità delle animazioni).
Questo lo schema elettrico completo:
Il pic 16F84 è nella sua classica configurazione con oscillatore esterno da 4MHz e circuito di reset. I pin da RB1 a RB7 e RA2,RA3 indirizzano le 9 colonne LED tramite apposite resistenze limitatrici, i pin RA0,RA1 e RA4 attivano rispettivamente il piano alto, di mezzo e il basso tramite i 3 FET di potenza (2N7000). La sezione audio analogica è composta da microfono, integrato amplificatore MAX9812, filtro passa basso (R17,C7) e squadratore di segnale (i due BC547 a valle). Notare il pullup di 4.7K per il pin RA4: RA4 è l'unico pin del 16F84 ad essere open-collector, occorre quindi il pull-up.
Il MAX9812 non viene commercializzato nel classico package DIL ma solo su TSOP: essendo un package estremamente miniaturizzato ho dovuto realizzare una basetta adapter apposita (vedi foto in basso a destra) sulla quale saldare a parte l'integrato.
Il codice sorgente è scritto interamente in assembler in quanto sia per il multiplexing dei 3 layer che per il PWM occorre un timing particolarmente accurato, dell'ordine dei microsecondi. Il codice si compone di tre sezioni principali:
Al power on viene eseguita l'inizializzazione delle varie parti del micro (direzioni dei GPIO, timer interno, variabili di programma etc...). Immediatamente dopo il cubo entra nella idle routine nella quale parte la modalità glowing (tutti e 27 i LED rimangono accesi e si aumenta e diminuisce ciclicamente la luminosità. Il cubo permane in questa modalità fino a che non subentra un interrupt esterno (dal pin RB0) corrispondente ad un beat del segnale sonoro captato dal microfono (una musica, un battito di mani, etc...). L'interrupt viene immediatamente servito dalla routine ISR: viene impostata la luminosità massima e viene scelta una animazione casuale (fra 8 disponibili) che viene mostrata sulla griglia led. L'animazione è mostrata sulla griglia LED un frame dopo l'altro, cicliclamente. Ad ogni giro viene decrementata la luminosità di un fattore 1. Quando la luminosità raggiunge lo zero il micro esce dalla ISR e si riporta in modalità attesa (cubo glowing).
Ecco il flowchart completo del programma:
Il sorgente si compone di un singolo file .asm. Per la compilazione e programmazione del micro è sufficiente l'ambiente MPLab e apposito programmatore, ad esempio il pickit 3
Download codice sorgente: ledcube
Nella prima parte del codice indichiamo il micro che andremo ad utilizzare (16F84) e la base per le costanti (base 16, hex):
;directives PROCESSOR 16F84A RADIX HEX
passiamo poi a creare i define dei registri principali del micro
;defs PCL EQU 0x02 OPTREG EQU 0x01 TMR EQU 0x01 INTCON EQU 0x0B TMR0 EQU 0x01 TRISB EQU 0x06 TRISA EQU 0x05 PORTA EQU 0x05 PORTB EQU 0x06 STATUS EQU 0x03 FSR EQU 0x04 INDF EQU 0x00 Z EQU 2 ; Z flags GIE EQU 7 ; GIE bit INTF EQU 1 ; INTF flag
infine definiamo le variabili (parliamo sempre di locazioni a 8bit) del nostro programma, in particolare i 3 layer (LOWL, MIDL e HIGL) e il layer "extra" EXTL che andrà a contenere tutti i noni LED dei layer (useremo quindi solo 3 bit in EXTL). TIME sarà la velocità delle animazioni e BRIG il livello di luminosità:
LOWL EQU 0x20 ; bottom layer (8 bit pattern) MIDL EQU 0x21 ; middle layer (8 bit pattern) HIGL EQU 0x22 ; upper layer (8 bit pattern) EXTL EQU 0x23 ; extra layer (9th led for each layer) TIME EQU 0x24 ; current frame speed TCONT EQU 0x25 ; frame speed counter LSEL EQU 0x26 ; layer selector BCONT EQU 0x27 ; brightness counter BRIG EQU 0x28 ; brightness CURL EQU 0x29 ; current layer
La primissima istruzione (locazione 0x0000) è un semplice goto alla routine di inizializzazione. Occorre mettere il goto in quanto il micro richiede che all'indirizzo 0x0004 parta la routine di gestione interrupt.
ORG 0x0000 ; start from address 0x0000 goto Init
A questo punto osserviamo il codice di inizializzazione:
Init: ; initialization bsf STATUS,5 ; select RAM bank 1 movlw b'00000001' movwf TRISB movlw b'00000000' movwf TRISA bcf OPTREG,5 ; start the TMR module in counter mode bcf STATUS,5 ; select RAM bank 0 movlw 0x00 ; all leds off movwf PORTA movwf PORTB clrf LSEL ; no layer selected call IdleCube
molto semplicemente impostiamo tutti i GPIO come output (bit di TRISB e TRISA a zero) tranne il primo bit di PORTB (RB0) che ci servirà invece come ingresso per triggerare l'interrupt (RB0 è collegato all'amplificatore audio esterno). Avviamo il timer interno in modalità conteggio ciclico, resettiamo gli output ed infine saltiamo alla funzione IdleCube.
Passiamo ora ad analizzare la funzione IdleCube:
per prima cosa impostiamo il pattern "full 27 led" ossia tutti e 27 i led accesi. Da notare che i noni LED di ogni layer sono mappati rispettivamente nel primo, secondo e quinto bit di EXTL (RA0, RA1 e RA4): osservare lo schematico per vedere questa corrispondenza.
;******** IDLECUBE *********************************** ; ; The cube is glowing and ready to trigger... ; ;***************************************************** IdleCube: movlw b'11111111' movwf LOWL movlw b'11111111' movwf MIDL movlw b'11111111' movwf HIGL movlw b'00010011' movwf EXTL
Settiamo al minimo luminosità e velocità di animazione:
movlw 0x01 movwf BRIG ; smallest brightness movlw 0x02 movwf TIME ; smallest pattern duration
Abilitiamo poi l'interrupt esterno dal pin RB0
bcf INTCON,INTF bsf INTCON,4 ; enabling the interrupt from the RB0/INT pin bsf INTCON,GIE ; enabling the interrupt service
passiamo poi ciclicamente a fare fadein e fadeout del ful-27-led pattern:
IdleCube_fadein: ; fade in loop callShowCube incfsz BRIG goto IdleCube_fadein IdleCube_fadeout: ; fade out loop call ShowCube decfsz BRIG goto IdleCube_fadeout incf BRIG,1 goto IdleCube_fadein
Il micro rimane in questa condizione indefinitamente. Ecco il cubo durante la fase idle:
Non appena la sezione microfono/amplificatore emette un segnale sul pin RB0, viene triggerato un interrupt gestito immediatamente dal micro. Il micro inizia quindi ad eseguire il codice alla locazione 0x0004 (inizio della nostra routine ISR). La routine è abbastanza semplice e si compone di tre parti principali:
Per il valore random viene campionato il valore corrente del timer (un valore compreso tra 0 e 255) e viene messo in AND con la maschera 0x07 per prelevarne solo i 3 bit meno significativi. Questi 3 bit sono il nostro valore casuale 0-7 e vengono sommati al program counter per chiamare la funzione animazione corrispondente.
Interrupt: ; interrupt service routine bcf INTCON,INTF ; disable the interrupt service bsf INTCON,GIE movf TMR,0 ; pick a pattern at random andlw 0x07 addwf PCL,1 ; jump into jump table goto Pattern_treble goto Pattern_updown goto Pattern_twobars goto Pattern_buildframe goto Pattern_rotplane goto Pattern_rlplane goto Pattern_snake goto Pattern_fourface
Analizziamo ora la funzione principale, ossia ShowCube. ShowCube mostra sulla griglia led un singolo frame dell'animazione prescelta. Il frame è codificato nei 24 bit di LOWL, MIDL, HIGL e nei 3 bit aggiuntivi di EXTL per un totale di 27 bit (i 3x3x3 led della griglia). Il pattern completo è mostrato sulla griglia accendendo un layer alla volta ad una frequenza di circa 100Hz. I bit RA0, RA1 e RA4 abilitano il layer corrispondente (controllare lo schematico per le corrispondenze). Il singolo layer è mostrato invocando la funzione ShowLayer.
;******** ShowCube *********************************** ; ; Shows the cube pattern for 0.01 * TIME sec using ; brightness level BRIG. The refresh rate is 100Hz. ; ;***************************************************** ShowCube: ; setup counter movf TIME,0 movwf TCONT ; TCONT 0 decfsz TCONT,1 goto ShowCube_loop return
Per finire ecco la funzione ShowLayer utilizzata per illuminare un singolo layer della griglia. La funzione implementa un classico PWM per settare la luminosità del singoli LED ad un valore prescelto (variabile BRIG): per un tempo pari a (BRIG/255)% il layer rimane acceso, per un tempo pari a ((255-BRIG)/255)% il layer rimane spento. In sostanza la variabile BRIG codifica il duty cycle del PWM.
;******** ShowLayer ********************************** ; ; Shows the layer encoded in W and EXTL for x msec. ; (BRIG/255)% of the time the layer is ON, the ; remaining (255-BRIG)/255)% of the time the layer ; is OFF. (this is a PWM controlled brightness) ; ;***************************************************** ShowLayer: ; layer on for (255/BRIG)*x msec ; layer off for (255/(255-BRIG))*x msec ; where x depends on the osc used ; set the first seven leds (bit0 of PORTB is used to receive interrupts!) movwf PORTB ; PORTB ; set/clear the 8th led btfsc CURL,0 bsf PORTA,2 ; test the 9th bit of the current layer and set ; the 9th led accordingly (9th led = bit3 of PORTA) movf EXTL,0 andwf LSEL,0 ; W btfss STATUS,Z ; is the 9th bit clear (9th led off)? bsf PORTA,3 ; no, set the 9th bit ; set the 9th bit of the current layer and turn on ; the entire layer movf LSEL,0 xorwf PORTA,1 ; PORTA ; wait (BRIG/255)% movf BRIG,0 movwf BCONT ShowLayer_loop_layeron: decfsz BCONT goto ShowLayer_loop_layeron ; turn off the entire layer clrf PORTA ; wait ((255-BRIG)/255)% movf BRIG,0 movwf BCONT decf BCONT,1 ShowLayer_loop_layeroff: incfsz BCONT goto ShowLayer_loop_layeroff return
Ecco il cubo all'opera: