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.

Etiquetas: ,