[Anexo] LED Display & Matrix Keypad Driver
Manejo de drivers matriciales E/S por protocolo SPI.
El integrado TM1638
En la entrada anterior vimos cómo utilizar integrados para manejo de displays de cátodo común y escaneo de teclados por protocolo SPI, pero ¿es posible utilizar displays de ánodo común? La respuesta es SÍ. Veamos.
En la entrega anterior les comenté que decidimos utilizar este integrado por razones que analizaremos más adelante en detalle. Llegó el momento de abordar ese tema. Primero recordemos que el TM1638 soporta displays de 10 segmentos, de hasta 8 dígitos, siempre y cuando sean de cátodo común. Esto se verá representado en detalle en el siguiente circuito:
Como se ve en esa imagen, cada bloque de LEDs representa un dígito y su respectivo punto. En total tendríamos 4 dígitos de 8 segmentos (7 y el punto) y para encender, por ejemplo, el punto del primer dígito, SEG8 tendría que tener un estado alto (HIGH) y GRID1 un estado bajo (LOW). Esta instrucción tendría que ser cargada en el arreglo que habíamos hecho en la librería (display[]), para luego ser enviada al registro DRAM correspondiente.
Al profundizar sobre el funcionamiento de este dispositivo, he descubierto que trabaja en colector abierto, por lo que es posible controlar matrices de LEDs de hasta 8x8. Ahora bien, un array de 4 dígitos como el que se ve en la imagen que sigue es simplemente una matriz de 8x4 (8 ánodos y 4 cátodos comunes).
Considerando que este integrado puede controlar hasta 8 cátodos comunes, tranquilamente podemos invertir esa matriz quedando así una de 4x8 (4 ánodos comunes y 8 cátodos). Es por esta razón que elegimos este dispositivo en particular. Veamos entonces cómo sería la conexión cuando se tiene un display de 4 dígitos de Ánodo Común:
Una vez realizada la modificación correspondiente en cada conexión, sólo resta modificar el software, para lo cual, primeramente explicaremos la idea con un poco de detalle.
Observemos que en los registros de memoria que controlan los displays, tenemos 10 columnas (SEG1-SEG10) y 8 filas (GRID1-GRID8), según su Datasheet:
Considérese ahora la siguiente matriz de 14x8:
DRAM ADDRESS | Bit0 | Bit1 | Bit2 | Bit3 | Bit4 | Bit5 | Bit6 | Bit7 |
0x00 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x01 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x02 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x03 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x04 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x05 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x06 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x07 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x08 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x09 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x0A | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x0B | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x0C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x0D | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x0E | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0x0F | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Basándonos en la hoja de datos, si por ejemplo quisiéramos colocar HOLA en el display de 4 dígitos, las direcciones que nos interesan son:
0x00 primer dígito (a la izquierda)
0x02 segundo dígito
0x04 tercer dígito
0x06 cuarto dígito
Entonces, nuestro array para un display de Cátodo Común sería:
display{{0x76},{xx},{0x3F},{xx},{0x38},{xx},{0x77},{xx},{xx},{xx},{xx},{xx},{xx},{xx}}
*xx=Irrelevante.
Con esos datos, nuestra matriz modificada quedaría de la siguiente forma:
0 1 1 1 0 1 1 0 (H)
0 0 0 0 0 0 0 0
1 1 1 1 1 1 0 0 (O)
0 0 0 0 0 0 0 0
0 1 1 1 0 0 0 0 (L)
0 0 0 0 0 0 0 0
0 1 1 1 0 1 1 1 (A)
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
Ahora bien, para representarla en un display de ánodo común, hay que conectar los segmentos (SEG) a los dígitos (GRID) y los dígitos (GRID) a los segmentos (SEG), esto es:
S E G M E N T O S D Í G I T O S
D S
E
Í G
---->>> M
G E
N
I T
O
T S
O
S
Básicamente, nuestro objetivo es hacer por software una conversión de matrices. Esto se realiza mediante la siguiente función que deberá añadirse a la librería de la entrada previa:
/* To connect an anode display, simply exchange pins, connecting
SGx pins to common anodes and GRx pins to the segments:
______________________________________
| Common Cathode connection: |
| ------------------------- | ****Max number of displays:
| GRx Pin | 7 digits - 11 segments
| ________|_____________________ | or
| _|_ _|_ _|_ _|_ _|_ _|_ _|_ | 6 digits - 12 segments
| / \ / \ / \ / \ / \ / \ / \ |
| ¨|¨ ¨|¨ ¨|¨ ¨|¨ ¨|¨ ¨|¨ ¨|¨ | and 7x11 or 6x12 matrix of leds.
| SG1 SG2 ................... SG7 |
| |
***************************************
| Common Anode connection: | ****NOTICE that you can
| ------------------------- | connect only 7 segment displays
| SGx Pin | in this particular configuration.
| ________|_____________________ | If your displays has more than 7
| _|_ _|_ _|_ _|_ _|_ _|_ _|_ | segments, use the TM1638 instead.
| \ / \ / \ / \ / \ / \ / \ / |
| ¨|¨ ¨|¨ ¨|¨ ¨|¨ ¨|¨ ¨|¨ ¨|¨ |
| GR1 GR2 ................... GR7 |
| |
***************************************
____________ ____________
|DRAM ADDRESS| |DRAM ADDRESS|
| /¨¨¨¨¨¨¨¨¨¨ | /¨¨¨¨¨¨¨¨¨¨
| \/ 14 to 1 | \/ 7 to 1
v -> Segment -> v -> Digit ->
D 0a b 1 0 1 0 0 1 1 \ S 0b 1 1 1 1 1 1 1
1b ? ? t r q p n m | 1b h g f e d c b
i 2a c 0 1 1 0 1 0 1 | e 2b 0 0 1 0 1 0 1
3b ? ? X X X X X X | 3b & $ % Z Y X m
g 4a d 1 0 1 0 0 1 1 | g 4b 1 1 0 1 0 1 0
5b ? ? Y Y Y Y Y Y | 5b ) $ % Z Y X n
i 6a e 0 1 1 0 1 0 1 |\\\\\\\ m 6b 0 0 0 0 0 0 0
7b ? ? Z Z Z Z Z Z |/////// 7b ( $ % Z Y X p
t 8a f 1 0 1 0 0 1 1 | e 8b 1 1 1 1 1 1 1
9b ? ? % % % % % % | 9b / $ % Z Y X q
s Aa g 0 1 1 0 1 0 1 | n Ab 1 1 0 1 0 1 0
Bb ? ? $ $ $ $ $ $ | Bb ¬ $ % Z Y X r
| Ca h 1 1 1 0 1 0 1 | t Cb 1 0 1 0 1 0 1
v Db ? ? ~ ¬ / ( ) & / Db ~ $ % Z Y X t
MSB LSB MSB LSB
_^^__________ _^^_____________
|1 to 7 digits| |14 to 1 segments|
*************** ******************
*/
/*
The following function will make the conversions between arrays
in order to write the dRAM according with the common anode configuration:
*/
int to_anode(int* data){
int i,j,data_i[14],data_o[14];
// Stores the data into an int:
for(i=0;i<14;i++){
data_i[i] = data[i];
}
// Data rearrangement:
for(i=0;i<7;i++){
for(j=0;j<7;j++){
if(bit_test(data_i[2*i],j)){
bit_set(data_o[2*j],i);
}
else{
bit_clear(data_o[2*j],i);
}
if(bit_test(data_i[(2*i)+1],j)){
bit_set(data_o[(2*j)+1],i);
}
else{
bit_clear(data_o[(2*j)+1],i);
}
}
}
// Assign the eighth segment value (b7 of every int, stored on each pair
// DRAM address) to the eighth digit:
for(i=0;i<7;i++){
data_o[(2*i)+1]<<=data_o[(2*i)+1];
if(bit_test(data_i[(2*i)],7)){
bit_set(data_o[(2*i)+1],0);
}
else{
bit_clear(data_o[(2*i)+1],0);
}
}
// Return the result:
return data_o;
}
Añadida esa función a la librería, solamente basta con convertir el array con los valores asignados que le pasamos al dispositivo como si fuésemos a controlar un display de cátodo común, sólo que previo a pasarle el array a la función para que lo muestre, primero tendremos que transformar ese array usando:
int8 display[14] //array de 14 elementos de 8 bits
display[0] = 0x76; //coloco una H en el primer dígito
display[2] = 0x3F; //coloco una O en el segundo dígito
display[4] = 0x38; //coloco una L en el tercer dígito
display[6] = 0x77; //coloco una A en el cuarto dígito
display = to_anode(display); //transformo la variable
write_dram(display); //la muestro en el display
Luego pasaremos ese array normalmente a la DRAM del dispositivo con el comando:
O bien se puede hacer todo en un solo paso, esto es, asignar los valores al array como si fuera un display de cátodo común y realizar una composición de funciones de tipo g{f(x)} de este modo:
int8 display[14] //array de 14 elementos de 8 bits
display[0] = 0x76; //coloco una H en el primer dígito
display[2] = 0x3F; //coloco una O en el segundo dígito
display[4] = 0x38; //coloco una L en el tercer dígito
display[6] = 0x77; //coloco una A en el cuarto dígito
write_dram(to_anode(display)); //transformo la variable y la muestro en el display