Gestión de pulsadores

18 mayo, 2014

La lectura de un pulsador es una de las tareas más sencillas y prácticas a la que todos nos hemos enfrentado en nuestros comienzos.
Y una vez que te enfrentas a ello, lo que en principio parece muy obvio, te encuentras con distintos problemas y oportunidades de mejora que van complicando la tarea.

pulsador-switch-12mm

 

 

 

 

Por ejemplo, ¿no habéis tenido que programar un delay antirrebotes?, ¿no tenéis pulsadores en lógica directa y otros en lógica inversa?, ¿no os gustaría en determinadas acciones que vuestro pulsador tuviera autorrepetición?

Intentando dar servicio a estos problemas de una forma estándar y abierta he preparado la función que veis a continuación, y que está comentada para que sea más fácil de digerir.

Esta función está pensada para ser metida en un bucle, ya sea en el main o en una interrupción del timer, y ofrece:

  • la función devuelve un 1 tan pronto como recibe una pulsación
  • pero no admite otra pulsación válida hasta que se supere el periodo marcado como ANTIRREBOTES
  • si la tecla se mantiene pulsada, devolverá un 1 cuando toque hacer una autorrepetición, y un 0 durante las pausas. Estas pausas pueden ser configuradas en tres bloques en función del tiempo que el usuario mantenga la tecla apretada
  • también admite un parámetro que definirá si la tecla está en lógica directa o inversa

 

La librería está comentada para su fácil comprensión:

unsigned int1 Pulsacion(unsigned int8 Indice, unsigned int8 Pin, unsigned int1 TipoPin, unsigned int1 Logica) {
/*
Esta función gestiona las entradas de pulsador, mediante el control de un retardo
antirrebotes y con la opción de autorrepetición de hasta 3 velocidades

Para que funcione, hay que dimensionar la variable Entradas con el nº de pulsadores
que queramos controlar.

ENTRADAS
Indice: hace referencia al nº de pulsador a chequear
Pin: indica el estado del pin del pulsador (lógica inversa)
TipoPIN:
TipoPin=0 -> No tiene autorrepetición
TipoPin=1 -> Pin con autorrepetición

SALIDA
La función devuelve un 1 si el pulsador está en condiciones de ser procesado
y un 0 en caso contrario.

*/


#define ANTIRREBOTES 20 // Nº de ciclos mínimo antes de detectar otra pulsación
#define CAMBIO1 100 // Nº de ciclos para el primer intervalo
#define CAMBIO2 500 // Nº de ciclos para el segundo intervalo
#define CAMBIO3 2000 // Nº de ciclos para el tercer intervalo
#define VELOCIDAD1 100 // Velocidad de autorrepetición más lenta
#define VELOCIDAD2 50 // Velocidad de autorrepetición mediana
#define VELOCIDAD3 10 // Velocidad de autorrepetición más rápida
#define SIN_REPETICION 0 // La tecla no repite si se mantiene pulsada
#define CON_REPETICION 1 // La tecla sí repite si se mantiene pulsada
#define LOGICA_DIRECTA 0 // Al pulsar se recibe un 0
#define LOGICA_INVERSA 1 // Al pulsar se recibe un 1

static unsigned int16 Entradas[5]={0,0,0,0,0}; // Dimensionar en función del nº de pulsadores
unsigned int16 j;
unsigned int1 resultado=0;

j=Entradas[Indice]; // Como leemos muchas veces el array, lo pasamos a variable local para
// incrementar la velocidad de ejecución

// Si se detecta pulsación y antes no había, devolvemos un 1 en resultado
if ((((!Pin && Logica==LOGICA_INVERSA) || (Pin && Logica==LOGICA_DIRECTA)) && (j==0))) {
resultado=1;
j=1;
};

// Cada vez que se entra a la función se incrementa una unidad el array
if (j>0)
Entradas[Indice]++;

// Si se detecta que ya no se está pulsando, inicializamos a 0 el array
if (((Pin && Logica==LOGICA_INVERSA) || (!Pin && Logica==LOGICA_DIRECTA)) && j>ANTIRREBOTES)
Entradas[Indice]=0;

if (TipoPin) { // Si el pulsador tiene autorrepetición
// Cuando se supera el nº de ciclos 3, devolveremos un 1 con la frecuencia VELOCIDAD3
if ((j>=CAMBIO3) && !(j%VELOCIDAD3)){
resultado=1;
} else
// Cuando se supera el nº de ciclos 2, devolveremos un 1 con la frecuencia VELOCIDAD2
if ((j>=CAMBIO2) && !(j%VELOCIDAD2)){
resultado=1;
} else
// Cuando se supera el nº de ciclos 1, devolveremos un 1 con la frecuencia VELOCIDAD1
if ((j>CAMBIO1) && !(j%VELOCIDAD1)){
resultado=1;
};
}

return (resultado);
}

 

Aquí, un vídeo a modo de ejemplo de uso de unos pulsadores. En el vídeo sólo usamos tres, uno de ellos no tiene autorrepetición, y sirve para encender/apagar los puntos del display. Los otros dos sí que tienen autorrepetición y sirven para aumentar o disminuir el contador del display respectivamente.

Este es el programa completo del ejemplo del vídeo:

#include <16F876A.h>
#device ADC=16

#FUSES NOWDT //No Watch Dog Timer
#FUSES NOBROWNOUT //No brownout reset
#FUSES NOLVP //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O

// PREPROCESADOR
#use delay(crystal=20000000,restart_wdt)
#use fast_io(A)
#use fast_io(C)
#define DELAY 250

/****** HARDWARE ********

Display de 7-segmentos triple: LM3643-11
A- RB0
B- RB1
C- RB2
D- RB3
E- RB4
F- RB5
G- RB6
Punto- RB7
Dígito1 RA0
Dígito2 RA1
Dígito3 RA2

PulsosIN RC0/T1CKI
Subir RA3
Bajar RA4
Reset RA5
Start RC1

**************************/


#define PulsosIN PIN_C0
#define Subir PIN_A3
#define Bajar PIN_A4
#define Reset PIN_A5
#define Start PIN_C1

// PROTOTIPOS DE FUNCIONES

unsigned int1 Pulsacion(unsigned int8 Indice, unsigned int8 Pin, unsigned int1 TipoPin);
void RefrescaDisplay();
void Digitos(unsigned int16 Valor);
unsigned int1 Pulsacion(unsigned int8 Indice, unsigned int8 Pin, unsigned int1 TipoPin);

// VARIABLES GLOBALES
unsigned int8 Caracter[11]= {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
0b00000000 // apagado
};

unsigned int8 Dig[3]={0,0,0};
unsigned int8 Puntos=0;
unsigned int16 Contador=0;

unsigned int1 Pulsacion(unsigned int8 Indice, unsigned int8 Pin, unsigned int1 TipoPin, unsigned int1 Logica) {
/*
Esta función gestiona las entradas de pulsador, mediante el control de un retardo
antirrebotes y con la opción de autorrepetición de hasta 3 velocidades

Para que funcione, hay que dimensionar la variable Entradas con el nº de pulsadores
que queramos controlar.

ENTRADAS
Indice: hace referencia al nº de pulsador a chequear
Pin: indica el estado del pin del pulsador (lógica inversa)
TipoPIN:
TipoPin=0 -> No tiene autorrepetición
TipoPin=1 -> Pin con autorrepetición

SALIDA
La función devuelve un 1 si el pulsador está en condiciones de ser procesado
y un 0 en caso contrario.

*/


#define ANTIRREBOTES 20 // Nº de ciclos mínimo antes de detectar otra pulsación
#define CAMBIO1 100 // Nº de ciclos para el primer intervalo
#define CAMBIO2 500 // Nº de ciclos para el segundo intervalo
#define CAMBIO3 2000 // Nº de ciclos para el tercer intervalo
#define VELOCIDAD1 100 // Velocidad de autorrepetición más lenta
#define VELOCIDAD2 50 // Velocidad de autorrepetición mediana
#define VELOCIDAD3 10 // Velocidad de autorrepetición más rápida
#define SIN_REPETICION 0 // La tecla no repite si se mantiene pulsada
#define CON_REPETICION 1 // La tecla sí repite si se mantiene pulsada
#define LOGICA_DIRECTA 0 // Al pulsar se recibe un 0
#define LOGICA_INVERSA 1 // Al pulsar se recibe un 1

static unsigned int16 Entradas[5]={0,0,0,0,0}; // Dimensionar en función del nº de pulsadores
unsigned int16 j;
unsigned int1 resultado=0;

j=Entradas[Indice]; // Como leemos muchas veces el array, lo pasamos a variable local para
// incrementar la velocidad de ejecución

// Si se detecta pulsación y antes no había, devolvemos un 1 en resultado
if ((((!Pin && Logica==LOGICA_INVERSA) || (Pin && Logica==LOGICA_DIRECTA)) && (j==0))) {
resultado=1;
j=1;
};

// Cada vez que se entra a la función se incrementa una unidad el array
if (j>0)
Entradas[Indice]++;

// Si se detecta que ya no se está pulsando, inicializamos a 0 el array
if (((Pin && Logica==LOGICA_INVERSA) || (!Pin && Logica==LOGICA_DIRECTA)) && j>ANTIRREBOTES)
Entradas[Indice]=0;

if (TipoPin) { // Si el pulsador tiene autorrepetición
// Cuando se supera el nº de ciclos 3, devolveremos un 1 con la frecuencia VELOCIDAD3
if ((j>=CAMBIO3) && !(j%VELOCIDAD3)){
resultado=1;
} else
// Cuando se supera el nº de ciclos 2, devolveremos un 1 con la frecuencia VELOCIDAD2
if ((j>=CAMBIO2) && !(j%VELOCIDAD2)){
resultado=1;
} else
// Cuando se supera el nº de ciclos 1, devolveremos un 1 con la frecuencia VELOCIDAD1
if ((j>CAMBIO1) && !(j%VELOCIDAD1)){
resultado=1;
};
}

return (resultado);
}

#INT_TIMER0
void RefrescaDisplay() {
static unsigned int8 Pos;

Pos++;
if (Pos==3) Pos=0;
output_b(0); // Borramos los segmentos
output_a(1<<Pos); // Seleccionamos un nuevo display
output_b(Caracter[Dig[Pos]]+128*bit_test(Puntos,Pos)); // Encendemos los segmentos

if (Pulsacion(0,input(PulsosIN),SIN_REPETICION,LOGICA_INVERSA))
if (Puntos)
Puntos=0; // apagamos los tres puntos
else
Puntos=0x07; // encendemos los tres puntos

if (Pulsacion(1,input(Subir),CON_REPETICION,LOGICA_INVERSA)) {
if (Contador<999)
Contador++;
}
if (Pulsacion(2,input(Bajar),CON_REPETICION,LOGICA_INVERSA)) {
if (Contador)
Contador--;
}
}

void Digitos(unsigned int16 Valor) {
disable_interrupts(INT_TIMER0);
Dig[2]=Valor/100; // Centenas
if (Dig[2]==0) Dig[2]=10; // Se apagan si es 0
Dig[1]=(Valor-100*(Valor/100))/10; // Decenas
if ((Dig[2]==10) && (Dig[1]==0)) Dig[1]=10; // Se apagan si es 0 y primer valor
Dig[0]=(Valor++)%10; // Unidades
enable_interrupts(INT_TIMER0);
}

void main()
{
setup_timer_0(RTCC_DIV_64);
set_tris_a(0b11111000);
set_tris_c(0b11111111);
enable_interrupts(INT_TIMER0);
enable_interrupts(GLOBAL);

set_timer1(0);

while(true)
{
Digitos(Contador);
}
}

Como puedes ver, la gestión de los pulsadores se hace en la interrupción del Timer0, junto con el refresco de los displays de 7 segmentos. De esta manera dejamos nuestro main() libre de esa carga para poder usarlo en otro tipo de gestiones.

Seguro que se puede mejorar y optimizar, así que serán bienvenidas cuantas mejoras y ampliaciones que queráis hacerle.

Espero que os sea muy útil.

Share

Etiquetas: ,