martes, 15 de junio de 2021

LED Display & Matrix Keypad Driver

 Manejo de drivers matriciales E/S por protocolo SPI. 
El integrado TM1638


Existen en la actualidad diversos fabricantes de drivers matriciales para diferentes aplicaciones en lo referente a periféricos de entrada o salida, como teclados, displays de siete segmentos, etc. En esta entrada nos enfocaremos en el TM1638 por razones que más adelante analizaremos en detalle.



i. Funcionamiento.

Este dispositivo desarrollado por Titan Microelectronics utiliza tecnología CMOS y es capaz de soportar hasta 8 displays de 10 segmentos como salida y además posee un key-scanner interno para un teclado matricial de hasta 24 teclas en 3 columnas (Kx) de 8 filas (KSy) y es operado como esclavo mediante la interfaz SPI haciendo uso de cualquier microcontrolador del mercado. Veamos a continuación su disposición de pines.



Pin #Nombre Caract Descripción
1 a 3K1 - K3EntradaConexión a teclado (Filas)
4 y 15VCC/VDDAlim (+)VCC es la alimentación de la lógica y VDD la alimentación de la matriz
5 a 12SEG1/KS1 - SEG8/KS8SalidaConexión a segmentos de display/Conexión a teclado (Columnas)
13 y 14SEG9 y SEG10SalidaConexión a segmentos de display
16, 17, 19-24GRID1 - GRID8SalidaConexión a cátodo común de display
18 y 25GNDAlim (-)Ground/Tierra/Aterrizaje/(-)
26DIOE/SData Input + Data Output pin
27CLKEntradaSeñal de entrada de reloj
28STBEntradaStrobe/¬SS/Slave-Select



ii. Circuito de prueba.

En este apartado construiremos un circuito de prueba cuya finalidad práctica consiste en el manejo de este integrado (e integrados similares, tales como el PT6961, ETK6207, ...). Mientras espero el lote de TM1638, probaremos con el PT6961 cuyo Datasheet es similar, así como también sus direcciones de lectura/escritura.



Obsérvese que en el simulador no hay STB/SS pin del TM1637 (utilizaremos este en la simulación del software, ya que no tengo las librerías de los que utilizo), sin embargo, de existir físicamente en el suyo, deberá declararlo posteriormente en su firmware. Asimismo sólo hay un pin para el bus de datos, por lo que deberá colocar un resistor de 1K para conectarlo, como se muestra en el circuito.  


iii. Firmware.


En este apartado desarrollaremos una librería para el integrado y también el firmware del microcontrolador. El compilador utilizado es PIC C, pero esta librería puede adaptarse a otros compiladores. Comencemos.


a) Librería.


En esta librería controlaremos displays de 8 segmentos de cátodo común.

    
      
/************************************************************\
|                 LED DISPLAY DRIVER LIBRARY   	             |
|                 --------------------------                 |
| Author: Farias, Ezequiel                                   |
| Release date: MAY 18 2010                                  |
|------------------------------------------------------------|
|                                                            |
\.***********************************************************/

#ifndef SS_PIN
	#define SS_PIN PIN_C2
#endif

#ifndef DISPLAY_MODE_7
	#define DISPLAY_MODE 0x02		//6 digits - 12 segments
#else
	#define DISPLAY_MODE 0x03		//7 digits - 11 segments
#endif

#define NORM_INC_W 0x40
#define NORM_INC_R 0x42
#define NORM_FIX_W 0x44
// where COMMAND 2 is: | 0| 1| 0| 0|b3|b2|b1|b0|
//                                   |  |  |__|_________________________________
//                                   |  |     | Data Write & Read Mode Settings |
//                                   |  |     |---------------------------------|
//                                   |  |     | 00: Write Data to Display Mode  |
//                                   |  |     | 10: Read Key Data               |
//                                   |  |      *********************************
//                                   |  |      __________________________________________________
//                                   |  |_____|  Address Increment Mode Settings (Display Mode)  |
//                                   |        |--------------------------------------------------|
//                                   |        | 0: Increment Address after Data has been Written |
//                                   |        | 1: Fixed Address                                 |
//                                   |         **************************************************
//                                   |         __________________________
//                                   |________|      Mode Settings:      |
//                                            |--------------------------|
//                                            | 0: Normal Operation Mode |
//                                            | 1: Test Mode             |
//                                             **************************

#define SET_ADDRESS_INIT 0xC0

// Where COMMAND3 = | 1| 1| 0| 0|b3|b2|b1|b0|
//                               \_________/
//                                    |_______Address from 0x00 to 0x0D (0 to D)
//                                            Use the 14 bytes of the Display RAM
//                                            according with your display type.

//       COMMAND4 = | 1| 0| 0| 0|b3|b2|b1|b0|
//                                | \______/
//                                |     |    ____________________________
//                                |     |___| Dimming Quantity Settings: |
//                                |         |----------------------------|
//                                |         | 000: Pulse Width = 1/16    |
//                                |         | 001: Pulse Width = 2/16    |
//                                |         | 010: Pulse Width = 4/16    |
//                                |         | 011: Pulse Width = 10/16   |
//                                |         | 100: Pulse Width = 11/16   |
//                                |         | 101: Pulse Width = 12/16   |
//                                |         | 110: Pulse Width = 13/16   |
//                                |         | 111: Pulse Width = 14/16   |
//                                |          ****************************
//                                |         __________________________
//                                |________|     Display Settings:    |
//                                         |--------------------------|
//                                         | 0: Display OFF           |
//                                         | 1: Display ON            |
//                                          **************************

// ---------Functions ------------


//--------------------------------------------------------------------------------
// This function simply rotate the bits, in order to send LSB firstly, that way
// we can use more uC's which haven't that option or is not supported:
int mirror(int x){
   int j,data;
   for(j=0;j<8;j++){
      if(bit_test(x,j)){
         bit_set(data,7-j);
      }
      else{
         bit_clear(data,7-j);
      }
   }
   return data;
}


//-------------------------------------------------------------------------
// This function initialize the display:

void PT6961_init(){
	output_high(SS_PIN);
	int i,data;
	delay_ms(200);	 //time to initialize chip
	data = mirror(NORM_INC_W);
	output_low(SS_PIN);
	for(i=0;i<14;i++){
		spi_write(0x00);
	}

	data = mirror(SET_ADDRESS_INIT);
	output_low(SS_PIN);
	spi_write(data);
	output_high(SS_PIN);

	output_high(SS_PIN);
	data = mirror(DISPLAY_MODE);
	output_low(SS_PIN);
	spi_write(data);
	output_high(SS_PIN);

	output_low(SS_PIN);
	spi_write(mirror(0x80));
	output_high(SS_PIN);

	output_high(SS_PIN);
	data = mirror(DISPLAY_MODE);
	output_low(SS_PIN);
	spi_write(data);
	output_high(SS_PIN);

	output_low(SS_PIN);
	spi_write(mirror(0x8A));
	output_high(SS_PIN);
}


//-------------------------------------------------------------------------
// This function reads all the keys:

void read_keys(int* keys){
	int h;
	output_low(SS_PIN);
	spi_write(mirror(NORM_INC_R));
	for(h = 0; h<5; h++){
		keys[h] = spi_read(0xFF);
	}
	output_high(SS_PIN);
}


//-------------------------------------------------------------------------
// This function sends an entire array to all 14 DRAM address:

void write_dram(int *data){
	int j,aux;
	signed int i;
	output_low(SS_PIN);
	spi_write(mirror(0x40));
	output_high(SS_PIN);

	output_low(SS_PIN);
	spi_write(mirror(0xC0));
	output_high(SS_PIN);

	output_low(SS_PIN);
	for(j=0;j<7;j++){
		for(i=1;i>=0;i--){
			spi_write(mirror(data[(2*j)+i]));
		}
	}
	output_high(SS_PIN);
	aux = mirror(DISPLAY_MODE);
	output_low(SS_PIN);
	spi_write(aux);
	output_high(SS_PIN);

	output_low(SS_PIN);
	spi_write(mirror(0x8A));
	output_high(SS_PIN);
}


//-------------------------------------------------------------------------
// This function clears the display:

void cls(){
	int x;
	output_low(SS_PIN);
	spi_write(mirror(0x40));
	output_high(SS_PIN);
	output_low(SS_PIN);
	for(x=0;x<14;x++){
		spi_write(0x00);
	}
	output_high(SS_PIN);
	output_low(SS_PIN);
	spi_write(mirror(0x8A));
	output_high(SS_PIN);
}



b) Explicación.

De esta librería nos interesan 4 funciones: cls(), write_dram(int *data), read_keys(int keys) y PT6961_Init(). 

cls() = Borra el display.

write_dram(int *data) = Escribe en las direcciones DRAM del dispositivo el array dado como argumento.

read_keys(int keys) = Lee las teclas que fueron presionadas y guarda esa lectura en otro array.

PT6961_Init() = Inicializa el display, ajustando los parámetros iniciales necesarios.

En la sección que sigue haremos un ejemplo de uso de esta librería en el que quedará detallado el uso cada una de esas funciones y las definiciones previas.


c) Firmware.

El microcontrolador elegido es el PIC16F876A, con un cristal de 8MHz.



#include <16F876A.h>
#FUSES HS NOLVP NOBROWNOUT NOPUT
#USE delay(Clock=8M)

// 7 Digits - 11 Segments (Common Cathode) or 11 Digits - 7 Segments (Common Anode)
#define DISPLAY_MODE_7 TRUE
#define SS_PIN PIN_C2
#include 

#byte SSPBUF = 0x211  // SPI Buffer Address Register

// Modes:
#define SPI_MODE_0  (SPI_L_TO_H | SPI_XMIT_L_TO_H) 
#define SPI_MODE_1  (SPI_L_TO_H) 
#define SPI_MODE_2  (SPI_H_TO_L) 
#define SPI_MODE_3  (SPI_H_TO_L | SPI_XMIT_L_TO_H) 

#use SPI(MASTER,MODE=0,SPI1)

int display[14],keys[5],button;

void main(){
   output_high(SS_PIN);
   spi_init(TRUE);
   PT6961_init();
   //This is the array that we'll pass to the function later.
   display[0] = 0x76;      // First grid     H
   display[1] = 0x00;
   display[2] = 0x3F;      // Second grid    O
   display[3] = 0x00;
   display[4] = 0x38;      // Third grid     L
   display[5] = 0x00;
   display[6] = 0x77;      // Fourth grid    A
   display[7] = 0x00;
   display[8] = 0x00;
   display[9] = 0x00;
   display[10] = 0x00;
   display[11] = 0x00;
   display[12] = 0x00;
   display[13] = 0x00;
   write_dram(display);		//write the values of the "display" array on the DRAM
   delay_ms(2000);
   cls();					//clears the display
   /*
   
   0 = 0x3F
   1 = 0x06
   2 = 0x5B
   3 = 0x4F
   4 = 0x66
   5 = 0x6D
   6 = 0x7D
   7 = 0x07
   8 = 0x7F
   9 = 0x6F
   A = 0x77
   b = 0x7C
   C = 0x39
   d = 0x5E
   E = 0x79
   F = 0x71
   
   
   */
   
   /* ** KEY MAP: ******
    ____________________________________________________________________________________________________
   |            |          |          |          |          |          |          |          |          |
   |            |    B7    |    B6    |    B5    |    B4    |    B3    |    B2    |    B1    |    B0    |
   |____________|__________|__________|__________|__________|__________|__________|__________|__________|
   |            |          |          |          |          |          |          |          |          |
   |   Byte 1   |    XX    |    XX    |  SG2/K3  |  SG2/K2  |  SG2/K1  |  SG1/K3  |  SG1/K2  |  SG1/K1  |
   |____________|__________|__________|__________|__________|__________|__________|__________|__________|
   |            |          |          |          |          |          |          |          |          |
   |   Byte 2   |    XX    |    XX    |  SG4/K3  |  SG4/K2  |  SG4/K1  |  SG3/K3  |  SG3/K2  |  SG3/K1  |
   |____________|__________|__________|__________|__________|__________|__________|__________|__________|
   |            |          |          |          |          |          |          |          |          |
   |   Byte 3   |    XX    |    XX    |  SG6/K3  |  SG6/K2  |  SG6/K1  |  SG5/K3  |  SG5/K2  |  SG5/K1  |
   |____________|__________|__________|__________|__________|__________|__________|__________|__________|
   |            |          |          |          |          |          |          |          |          |
   |   Byte 4   |    XX    |    XX    |  SG8/K3  |  SG8/K2  |  SG8/K1  |  SG7/K3  |  SG7/K2  |  SG7/K1  |
   |____________|__________|__________|__________|__________|__________|__________|__________|__________|
   |            |          |          |          |          |          |          |          |          |
   |   Byte 5   |    XX    |    XX    |  SG0/K3  |  SG0/K2  |  SG0/K1  |  SG9/K3  |  SG9/K2  |  SG9/K1  |
   |____________|__________|__________|__________|__________|__________|__________|__________|__________|
   
   
   */
   
   while(true){
      while(input(PIN_B0)){
         read_keys(keys);		//we store the reading results on the "keys" array
         if((keys[0] | keys[1] | keys[2] | keys[3] | keys[4]) != 0x00){
            // if some button was pressed, scan it into the array, then show it on the display.
            if(keys[0] != 0x00){
               button = mirror(keys[0]);
               if(button == 0x01){
                  display[2] = 0x06;
                  display[6] = 0x06;
               }
               if(button == 0x02){
                  display[2] = 0x5B;
                  display[6] = 0x06;
               }
               if(button == 0x08){
                  display[2] = 0x06;
                  display[6] = 0x5B;
               }
               if(button == 0x10){
                  display[2] = 0x5B;
                  display[6] = 0x5B;
               }
            }
            // 5 6 7 8
            if(keys[1] != 0x00){
               button = mirror(keys[1]);
               if(button == 0x01){
                  display[2] = 0x06;
                  display[6] = 0x4F;
               }
               if(button == 0x02){
                  display[2] = 0x5B;
                  display[6] = 0x4F;
               }
               if(button == 0x08){
                  display[2] = 0x06;
                  display[6] = 0x66;
               }
               if(button == 0x10){
                  display[2] = 0x5B;
                  display[6] = 0x66;
               }
            }
            // 9 10 11 12
            if(keys[2] != 0x00){
               button = mirror(keys[2]);
               if(button == 0x01){
                  display[2] = 0x06;
                  display[6] = 0x6D;
               }
               if(button == 0x02){
                  display[2] = 0x5B;
                  display[6] = 0x6D;
               }
               if(button == 0x08){
                  display[2] = 0x06;
                  display[6] = 0x7D;
               }
               if(button == 0x10){
                  display[2] = 0x5B;
                  display[6] = 0x7D;
               }
            }
            display[0] = 0x38;      // First grid	"L"
            display[4] = 0x7C;      // Third grid	"b"
            write_dram(display);
            delay_ms(500);
            cls();
         }
      }
   }
}
    
    



Una vez finalizada la compilación, se procede a cargar el firmware al microcontrolador. El programa comienza, el display se ilumina mostrando un mensaje de bienvenida (HOLA), luego queda a la espera escaneando el teclado.



Si se pulsa uno de los de la fila superior mostrará L1bX  y si se pulsa uno de los de la fila inferior, mostrará L2bX, donde X es el número de botón que se ha pulsado, del 1 al 6, de izquierda a derecha, respectivamente.




No hay comentarios:

Publicar un comentario