Neste tutorial é apresentada a implementação de um programa para a leitura um teclado matricial de 4 linhas e 4 colunas. Primeiramente é implementado o código com o uso da técnica de varredura, após por interrupção e em seguida o uso de um Bean presente no kinetis.
1. Introdução
O teclado utilizado neste tutorial é semelhante ao mostrado na fig. 1, encontrado em diversas aplicações comerciais e industriais, como caixas eletrônicos e IHM (Interfaces Homem Máquina). O uso deste tipo de teclado visa economizar a quantidade de pinos utilizados no MCU, onde normalmente seria usada uma entrada digital para conectar cada chave. Sendo assim, seriam necessárias 16 entradas para conectar as 16 teclas, o que é uma quantidade grande de pinos.
Figura 1. teclado matricial 4x4.
Fonte: Dx.com
O diagrama deste teclado, visto na Fig. 2, mostra que há uma matriz de contatos formada por 4 linhas e 4 colunas. Quando uma tecla é pressionada a conexão mecânica é fechada, conectando uma linha à uma coluna, como mostra a Fig. 3, quando a tecla n° 1 é pressionada.
Figura 2. Esquema de ligação das teclas.
Fonte: Elaborado pelo autor.
Figura 3. Tecla pressionada.
Fonte: Elaborado pelo autor.
Para que um circuito digital perceba o pressionamento de uma tecla, é necessário transmitir um nível lógico para uma entrada digital. Portanto, precisamos, por exemplo, transmitir um nível lógico "alto", do pino 1 para o pino 5. Para que as demais teclas sejam percebidas por um circuito, cada linha precisa ser energizada para que as colunas transmitam o nível "alto" para cada tecla pressionada.
Figura 4. Ligações do teclado 4x4.
Fonte: Elaborado pelo autor.
Tabela 1. Lista de conexões
Lista de Conexões
LIN1 = PTA12
LIN2 = PTD4
LIN3 = PTA2
LIN4 = PTA1
COL1 = PTC9
COL2 = PTC8
COL3 = PTA5
COL4 = PTA4
Fonte: Elaborado pelo autor.
O circuito mostrado na Fig. 4, mostra o esquema de ligações usado para este exemplo, com algumas precauções adicionais. A Tabela 1, lista as conexões feitas entre a FRDM-KL25Z e o teclado usado.
Em cada linha temos um resistor no valor de 220 Ohms para evitar um curto-circuito caso as teclas 1 e 4 sejam pressionadas ao mesmo tempo. Os resistores de 10 KOhms garantem que os pinos "COL" sejam mantidos em um nível lógico estável, evitando que os mesmos fiquem flutuando.
2. Implementação do código por varredura
Com o circuito montado e conectado, resta a implementação do código responsável pelo monitoramento das teclas. Com o projeto já criado, é necessário adicionar um componente do tipo BitIO para cada pino listado na Tabela 1.
Para efetuar o monitoramento das teclas é necessário habilitar cada uma das linhas e, em seguida, testar as quatro colunas existentes. Caso alguma tecla esteja pressionada, uma variável indica o valor da tecla pressionada como mostra o trecho de código da Tabela 2.
CÓDIGO
int main(void)
/*lint -restore Enable MISRA rule (6.3) checking. */
{
/* Write your local variable definition here */
uint8_t ui_tecla;
/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
PE_low_level_init();
/*** End of Processor Expert internal initialization. ***/
/* Write your code here */
/* For example: for(;;) { } */
for (;;) {
ui_tecla=0;
LIN1_SetVal();
if(COL1_GetVal()==1)
ui_tecla=1;
if(COL2_GetVal()==1)
ui_tecla=2;
if(COL3_GetVal()==1)
ui_tecla=3;
if(COL4_GetVal()==1)
ui_tecla=10;
}
/*** Don't write any code pass this line, or it will be deleted during code generation. ***/
/*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/
#ifdef PEX_RTOS_START
PEX_RTOS_START(); /* Startup of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of RTOS startup code. ***/
/*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
for(;;){}
/*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/
O código realiza apenas a varredura das teclas da linha 1 do teclado. Para varrer as demais linhas pode-se usar um componente Wait para temporizar o processo e uma variável para controlar a linha testada como mostra o código da tabela 3.
CÓDIGO
uint8_t ui_tecla=0;
/* Write your local variable definition here */
/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
PE_low_level_init();
/*** End of Processor Expert internal initialization. ***/
LIN1_ClrVal();
LIN2_ClrVal();
LIN3_ClrVal();
LIN4_ClrVal();
/* Write your code here */
/* For example: for(;;) { } */
for (;;) {
LIN1_SetVal();
WAIT1_Waitms(50);
if (COL1_GetVal() == 1)
ui_tecla = 1;
if (COL2_GetVal() == 1)
ui_tecla = 2;
if (COL3_GetVal() == 1)
ui_tecla = 3;
if (COL4_GetVal() == 1)
ui_tecla = 10;
LIN1_ClrVal();
LIN2_SetVal();
WAIT1_Waitms(50);
if (COL1_GetVal() == 1)
ui_tecla = 4;
if (COL2_GetVal() == 1)
ui_tecla = 5;
if (COL3_GetVal() == 1)
ui_tecla = 6;
if (COL4_GetVal() == 1)
ui_tecla = 11;
LIN2_ClrVal();
LIN3_SetVal();
WAIT1_Waitms(50);
if (COL1_GetVal() == 1)
ui_tecla = 7;
if (COL2_GetVal() == 1)
ui_tecla = 8;
if (COL3_GetVal() == 1)
ui_tecla = 9;
if (COL4_GetVal() == 1)
ui_tecla = 12;
LIN3_ClrVal();
LIN4_SetVal();
WAIT1_Waitms(50);
if (COL1_GetVal() == 1)
ui_tecla = 17;
if (COL2_GetVal() == 1)
ui_tecla = 0;
if (COL3_GetVal() == 1)
ui_tecla = 18;
if (COL4_GetVal() == 1)
ui_tecla = 13;
LIN4_ClrVal();
}
/*** Don't write any code pass this line, or it will be deleted during code generation. ***/
/*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/
#ifdef PEX_RTOS_START
PEX_RTOS_START(); /* Startup of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of RTOS startup code. ***/
/*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
for(;;){}
/*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
Como isto no trecho de código da Tabela 3, a programação começa a ficar trabalhosa devido ao grande número de instruções, o que aumenta a chance de erros. Para evitarmos o excesso de trabalho, o Kinetis fornece um Bean para monitoramento de teclado que será explicado na seção seguinte.
3. Uso de interrupções
As interrupções são uma das funcionalidades mais poderosas que encontramos nos microcontroladores, pois permitem escrever partes do programas em outras rotinas e liberar do programa principal a preocupação de executá-las.
Este exemplo mostra o uso de interrupções para a varredura das teclas da matriz 4x4, o circuito anteriormente usado passou por algumas modificações, vistas na tabela 2.
Tabela 2. Lista de conexões
Lista de Conexões
LIN1 = PTA12
LIN2 = PTD4
LIN3 = PTC8
LIN4 = PTC9
COL1 = PTA1
COL2 = PTA2
COL3 = PTA4
COL4 = PTA5
Fonte: Elaborado pelo autor.
São utilizados dois componentes para implementar a varredura por interrupções: GPIO_LDD e o TimerInt. O componente GPIO_LDD permite capturar os estados dos pinos configurados de uma determinada porta. Nos exemplos anteriores foram usados pinos aleatoriamente, mas neste exemplo são usados os pinos da porta A, listados na tabela 2, para que o componente consiga capturar os eventos gerados por estes pinos.
Figura 5. Componentes usados no exemplo.
Fonte: Elaborado pelo autor.
3.1 Configurando os pinos
Além dos componentes mencionados também é necessário um objeto BitIO para cada pino LIN listados na tabela 2. No componente GPIO_LDD são configurados os pinos de uma porta que devem gerar a interrupção. Na figura 6 são mostradas as configurações gerais do componente.
Figura 6. Configurando o GPIO_LDD.
Fonte: Elaborado pelo autor.
O campo component pode ser alterado para um nome mais amigável para o programador. No campo Port deve ser escolhida uma das portas disponíveis, neste exemplo é usada a Porta A e habilitar a caixa do campo Interrupt service/event. No combo Interrupt Priority é possível escolher o nível de prioridade desta interrupção. Nesta aplicação é necessário declarar 4 COLs com um pino em cada.
Figura 7. Configuração dos pinos.
Fonte: Elaborado pelo autor.
Na aba Bit fields, deve ser adicionado pelo menos um bit field e os pinos a serem utilizados vem ser nomeados conforme a aplicação. Para cada pino devem ser configurados o nome (Field name), atribuir o pino, a direção e o tipo de interrupção (nível, borda, etc). Além destas configurações, na aba Initialization devem ser habilitadas as caixas auto initialization e OnPortEvent. Para esta aplicação o tipo de interrupção deve ser por borda de subida (rising edge) pois ao pressionar uma tecla uma entrada vai perceber uma borda de subida.
3.2 Configurando o timer
As configurações feitas até então habilitam a geração de interrupções nos pinos listados. Para completar a varredura do teclado é necessário habilitar cada um dos sinais de linha. Esta etapa é feita com o uso do TimerInt no modo temporizador. Na figura 8 é mostrado as opções deste objeto.
Figura 8. Configuração do TimerInt.
Fonte: Elaborado pelo autor.
A configuração deste objeto consiste na escolha do tempo de estouro no campo Interrupt period.
3.3 Código do programa
Com os objetos configurados falta adicionar as porções de código para as duas interrupções. Quando pressionado, o botão Generate Processor Expert code, visto na figura 9, gera os códigos dentro do arquivo Events.c com as funções para cada interrupção.O trecho abaixo mostra parte do código que deve ser adicionado na interrupção do TimerInt que habilita uma linha cada 20 ms.
CÓDIGO
/*
** ===================================================================
** Event : TI1_OnInterrupt (module Events)
**
** Component : TI1 [TimerInt]
** Description :
** When a timer interrupt occurs this event is called (only
** when the component is enabled - <Enable> and the events are
** enabled - <EnableEvent>). This event is enabled only if a
** <interrupt service/event> is enabled.
** Parameters : None
** Returns : Nothing
** ===================================================================
*/
void TI1_OnInterrupt(void)
{
/* Write your code here ... */
if(LIN4_GetVal()==1)
{
LIN4_ClrVal();
LIN1_SetVal();
}
else if(LIN3_GetVal()==1)
{
LIN3_ClrVal();
LIN4_SetVal();
}
O trecho acima mostra que a cada 20 ms uma linha deve ser desabilitada e a linha seguinte habilitada. Se uma tecla for pressionada a interrupção do objeto GPIO_LDD é chamada.
Na rotina de interrupção, vista no trecho abaixo, o estado da porta deve ser lido usando-se a função _GetFieldValue(), onde o primeiro parâmetro é um ponteiro para a porta que deve ser inicializada no arquivo main.c. O segundo, é nome do Bit field declarado na etapa de configuração do componente. No código abaixo o ponteiro "teclas", recebe a referência da porta, porém este ponteiro está declarado no arquivo main.c e para que este seja acessado de outro arquivo, o ponteiro deve ser declarado novamente com a palavra reservada extern para que o compilador entenda que a variável do arquivo main.c será acessada deste arquivo.
CÓDIGO
*
** ===================================================================
** Event : GPIOA_OnPortEvent (module Events)
**
** Component : GPIOA [GPIO_LDD]
*/
/*!
** @brief
** Called if defined event on any pin of the port occured.
** OnPortEvent event and GPIO interrupt must be enabled. See
** SetEventMask() and GetEventMask() methods. This event is
** enabled if [Interrupt service/event] is Enabled and disabled
** if [Interrupt service/event] is Disabled.
** @param
** UserDataPtr - Pointer to RTOS device
** data structure pointer.
*/
/* ===================================================================*/
extern LDD_TDeviceData *ptr_port;
void GPIOA_OnPortEvent(LDD_TUserData *UserDataPtr)
{
/* Write your code here ... */
uint8_t ui_auxi;
ui_auxi=GPIOA_GetPortValue(ptr_port);
}
Para aplicações envolvendo interrupções a porta precisa devidamente inicializada, isto significa que um ponteiro precisa receber a referência do objeto da porta. Fazendo: ptr_port =GPIOA_Init((LDD_TUserData *)NULL); a porta é inicializada e a referência é passada para o ponteiro "teclas". Sendo o tipo de ponteiro igual a LDD_TDeviceData *ptr_port;
Executando código passo-a-passo é possível verificar se a interrupção está sendo executada. Basta adicionar um ponto de parada (breakpoint) em um das linhas do código das interrupções em questão e executar o programa.
Bons projetos!!!
Autor: Júlio César M. Ruzicki Eng.