domingo, 24 de marzo de 2013

Lectura de Inputs en un pic

En los post anteriores estuvimos usando el bit 0 del puerto B del PIC16F88 para encender y apagar un led cada 500 ms. Esto quiere decir que usamos ese bit como salida. Ahora les mostraré como utilizar un bit en modo entrada de datos digital utilizando un botón de tipo "push".

Cuando un pin de nuestro PIC (ej.: RB4) se utiliza como entrada, lo que debemos hacer es conectarlo a +5 Volts mediante 2 resistencias de 1k (R1) y 10k (R2) en ese orden. Entre estas 2 resistencias colocamos el botón (S1) conectado al negativo de nuestra fuente.

Imagen 1 - Diagrama de conexión de una entrada

Imagen 2 - Switch (S1) contectado entre las resistencias y los 0Volts


El efecto que provoca es que el Input siempre esté en estado alto (HIGH o 1). Cuando se presiona el botón (S1) los 5V se dirigen a GND lo que provoca que el input esté en estado bajo (LOW o 0).

Ejemplo 1:

Para nuestro primer ejemplo vamos a hacer lo siguiente:

Si el botón está pulsado (RB4 = 0), prender el led,
Sino, mantenerlo apagado.

Utilizaremos el bit 4 del puerto B tal como aparece en la figura anterior.

#define LED     PORTBbits.RB0 // El Led se encuentra conectado en RB0
#define posLED  0             // Posición en la que se encuentra el Led

#define BUTTON  PORTBbits.RB4 // El botón se encuentra conectado en RB4
#define posBUTTON   4         // Posición en la que se encuentra el botón

void main(void)
{
    // Configura el oscilador
    ConfigureOscillator();

    // Configura un 0 en la posición que le corresponde al LED
    // y un 1 a todas las demás.
    // 0 = Salida, 1 = entrada
    TRISB = ~(1 << posLED) | (1 << posBUTTON);
    // La línea anterior es equivalente a
    // TRISB = 0b11111110;

    // Inicializo el led apagado
    LED = 0;

    // Bucle principal
    while(true) {
        // Enciende el led sólo si está presionado el botón
        LED = BUTTON ? 0 : 1;
    }
}

En el bucle principal hacemos que el valor del LED sea igual al 0 si BUTTON (RB4) es HIGH, sino 0.

Código completo del ejemplo 1

Ejemplo un poco más complicado:


En este ejemplo vamos a probar lo siguiente: Cuando se presione el botón, si el LED estaba apagado, encenderlo, si el LED estaba encendido, apagarlo.

Para esto podríamos reemplazar el bucle principal del ejemplo anterior por lo siguiente:

// Bucle principal
while(true) {
     while(BUTTON == 1) {
     }
     LED = 1;

     while(BUTTON == 0) {
     }
     LED = 0;
}

Si probamos compilarlo y pasarlo a nuestro PIC, este código funciona correctamente, sin embargo lo hará de manera errática. A veces nos hará caso y a veces parecerá que el LED se apaga y se prende muy rápidamente.

Este comportamiento se debe al "rebote" eléctrico que hace el botón al presionarse. Para explicarlo de forma simple, el código dentro del bucle principal se ejecuta muy rápidamente, a razón de millones de instrucciones por segundo mientras que el movimiento del dedo empujando el botón es muy lento lo que provoca que este toque la superficie y rebote hasta estabilizarse pasando de alto a bajo en cuestión de microsegundos.

Para evitar esto debemos manejar este rebote mediante un contador. En este ejemplo haremos que un botón esté presionado sólo si se mantiene apretado durante 10ms.

uint8_t contadorDeRebotes;      // Cuenta los rebotes de un input

// Bucle principal
for(;;) {
     // Espera hasta que el botón esté presionado unos 10 ms como mínimo
     for(contadorDeRebotes = 0 ; contadorDeRebotes < 10 ; contadorDeRebotes++) {
          __delay_ms(1);
          if(BUTTON == 1)
               contadorDeRebotes = 0;
     }

     LED ^= 1;   // Hace un XOR entre el valor actual y 1
                 // Con esto obtiene el valor contrario.

     // Espera hasta que el botón esté levantado unos 10 ms como mínimo
     for(contadorDeRebotes = 0 ; contadorDeRebotes < 10 ; contadorDeRebotes++) {
          __delay_ms(1);
          if(BUTTON == 0)
               contadorDeRebotes = 0;
     }
}

Ahora sí, nuestro código funciona como esperamos.

Una mejora que le podemos hacer al código es utilizar lo que se llama "shadow register". Esto es una variable auxiliar que mantiene en todo momento el estado de un registro. Lo que permite es que en lugar de leer el estado que tiene un bit de un puerto (que en ciertos casos puede leerse de forma incorrecta si está pasando de un estado a otro), leamos un valor previamente guardado cuyo valor no cambiará a menos que se lo especifiquemos.

Con un shadow register el ejemplo quedaría así:

uint8_t contadorDeRebotes;      // Cuenta los rebotes de un input
uint8_t shadowPortB = 0;        // Shadow copy del puerto B

// Bucle principal
for(;;) {
     // Espera hasta que el botón esté presionado unos 10 ms como mínimo
     for(contadorDeRebotes = 0 ; contadorDeRebotes < 10 ; contadorDeRebotes++) {
          __delay_ms(1);
          if(BUTTON == 1)
               contadorDeRebotes = 0;
     }

     shadowPortB ^= 1 << posLED; // Hace un XOR entre el valor actual y 1
                                 // sobre el shadow register
     PORTB = shadowPortB;        // Copia el valor del registro al puerto B

     // Espera hasta que el botón esté levantado unos 10 ms como mínimo
     for(contadorDeRebotes = 0 ; contadorDeRebotes < 10 ; contadorDeRebotes++) {
          __delay_ms(1);
          if(BUTTON == 0)
               contadorDeRebotes = 0;
     }
}

Código completo del ejemplo 2

Bueno, espero que se hayan entendido estos ejemplos básicos. El código con los ejemplos está en mi repositorio de GitHub :

Fuentes:
http://www.gooligum.com.au/tutorials/midrange/PIC_Mid_C_1.pdf

No hay comentarios:

Publicar un comentario

Related Posts Plugin for WordPress, Blogger...