Uso correcto de una interrupción de cambio de pin

10

Estoy tratando de usar interrupciones de cambio de pin para detectar botones presionados. Hasta ahora nunca he trabajado con este tipo de interrupciones y hay algunos problemas, así que quiero asegurarme de que este sea el uso correcto.

Si obtengo la hoja de datos correctamente, se deben hacer las siguientes cosas para usar una interrupción de cambio de pin:

  1. Establezca los PIN que desea controlar en el registro PCMSK
  2. Habilitar el registro de PIN para el control de interrupción de cambio de pin (PCICR)
  3. Habilitar interrupciones
  4. Utilice el vector de interrupción correspondiente

Proyecto: Moodlamp simple, colores controlados mediante 4 botones.

Configuración:

  • Atmega168A-PU
  • 4 mini interruptores pulsadores
  • MOSFETS para controlar mi LED RGB de 3 vatios

Aquí está el código que estoy usando y no funciona como se esperaba:

#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define BUTTON1 (1<<PC5) 
#define BUTTON2 (1<<PC4) 
#define BUTTON3 (1<<PC3) 
#define BUTTON4 (1<<PC2) 

#define GREEN   (1<<PB1) 
#define BLUE    (1<<PB2) 
#define RED     (1<<PB3) 

void init() {

        // enable LED
        DDRB |= GREEN;
        DDRB |= BLUE;
        DDRB |= RED;

        // button pullups
        PORTC |= BUTTON1;
        PORTC |= BUTTON2;
        PORTC |= BUTTON3;
        PORTC |= BUTTON4;

        // pin change interrupts for buttons
        PCMSK1 |= PCINT13;
        PCMSK1 |= PCINT12;
        PCMSK1 |= PCINT11;
        PCMSK1 |= PCINT10;

        // enable pin change for buttons
        PCICR |= PCIE2;

        sei();

}

ISR(PCINT2_vect) {

                PORTB = BLUE;
}


void ledTest() {

                PORTB ^= RED;
                _delay_ms(250);
                PORTB ^= RED;
                _delay_ms(250);
                PORTB ^= RED;
                _delay_ms(250);
                PORTB ^= RED;


                PORTB ^= BLUE;
                _delay_ms(250);
                PORTB ^= BLUE;
                _delay_ms(250);
                PORTB ^= BLUE;
                _delay_ms(250);
                PORTB ^= BLUE;

                PORTB ^= GREEN;
                _delay_ms(250);
                PORTB ^= GREEN;
                _delay_ms(250);
                PORTB ^= GREEN;
                _delay_ms(250);
                PORTB ^= GREEN;
}

int main() {

        init();
        ledTest();

        _delay_ms(500);
        PORTB |= GREEN;

        while(1) {
                _delay_ms(100);
        }
}

Nota: Los botones deben ser rebotados. Ya que estoy intentando este paso a paso y no debería importar el encendido del LED, lo ignoré aquí.

Pregunta: ¿La forma en que estoy intentando usar las interrupciones es correcta?

Problemas con mi configuración:

  • Los botones 1-3 están totalmente ignorados.
  • Button4 está activando un reinicio de la atmega

Cosas que verifiqué:

  • Los botones no están conectados de ninguna manera al PIN de restablecimiento
  • Los botones están correctamente conectados a GND si se presionan
  • Los botones no están conectados a GND si no se presionan
  • Los botones

    funcionan bien si los uso sin interrupciones, por ejemplo:

    si (! (PINC & BUTTON4)) {                 PORTB ^ = AZUL;         }

  • Cristal externo de 16MHZ / cristal interno
  • Cualquier error en el enrutamiento
  • Estoy usando un capacitador de 100 nF entre PWR y GND en la atmósfera
  • VCC (7), GND (8), GND (22), AVCC (20) están conectados (ya que no necesito AREF, no está conectado)
pregunta echox

3 respuestas

14

Las interrupciones de cambio de pin generalmente no son una buena manera de detectar las acciones de los botones. Esto se debe a que los botones mecánicos rebotan, y obtendrás muchas interrupciones sin sentido, y de todos modos todavía tienes que hacer el rebote.

Una mejor manera es tener una interrupción periódica, como cada 1 ms (velocidad de 1 kHz). Eso es mucho tiempo en la mayoría de los procesadores, por lo que la fracción del tiempo empleado en la interrupción será pequeña. Simplemente muestre el estado del botón cada interrupción. Declare un nuevo estado de botón si ha visto el nuevo estado 50 ms seguidos. 50 ms es más largo que la mayoría de los botones, pero es lo suficientemente corto para que los humanos no se den cuenta o se preocupen por el retraso.

Tenga en cuenta que de esta manera también puede manejar múltiples botones en la misma interrupción periódica de 1 ms. Todo lo que necesita es un contador para cada botón.

Más sobre el tiempo de rebote:

Ocasionalmente, como en este caso, alguien dice que 50 ms es un tiempo de rebote demasiado largo. Esto no es cierto para los botones ordinarios presionados por humanos. Puede ser un problema, tal vez en aplicaciones muy críticas como un cronómetro, pero hasta ahora no he encontrado ninguno. Hice pruebas sobre esto a principios de la década de 1980, y muchas otras personas también lo han hecho.

Es cierto que el tiempo de rebote típico del botón pulsador es de alrededor de 10 ms, y casi todos se asientan en 25 ms. El factor limitante en el tiempo de rebote es la percepción humana. 50 ms es un poco más corto que cuando las personas comienzan a notar un retraso cuando no lo están buscando. Incluso entonces, toma mucho más tiempo para que sea molesto. En algunos casos, puede ser posible que un humano detecte una diferencia entre el retraso de 50 ms y el de 0 ms si lo están buscando específicamente , pero eso es muy diferente de presionar un botón y ver que algo sucede y sin pensar en el retraso.

50 ms es, por lo tanto, un buen tiempo de rebote porque el retardo está por debajo del límite de percepción en aplicaciones ordinarias, muy por debajo del límite de molestia y muy por encima del tiempo de rebote de la mayoría de los interruptores. He encontrado interruptores que rebotaron durante casi tanto tiempo, por lo que también deberías presionar el límite de percepción ya que no hay nada que perder.

He hecho muchos productos con botones de rebote de firmware usando un tiempo de rebote de 50 ms. Ni una sola vez un cliente mencionó siquiera notar un retraso. Todos aceptaron que los botones funcionan bien sin problema.

    
respondido por el Olin Lathrop
2

Las interrupciones de cambio de pin son una mejor manera de rebotar que de sondeo. La interrupción por lo general pasa por alguna lógica, como un D-Flip Flop o D-Latch. Aunque esto es cierto, es más difícil implementar esta rutina de rebote con compiladores de nivel superior. Una vez que se produce la interrupción, el indicador de interrupción no se borra y la habilitación de interrupción se borra hasta que se produce un retraso. Una vez que se ha producido el retraso, se comprueba el estado del pin y, si aún se encuentra en el estado dado que activó la interrupción, se cambia el estado del botón, se borra el indicador de interrupción y se establece la habilitación de interrupción. Si no está en el estado que causó el inicio, la habilitación de interrupción se establece y el estado permanece igual. Esto libera el procesador para otras tareas. Periódico interrumpe el tiempo perdido en el programa.

    
respondido por el Jim Vernay
-1
  

"Las interrupciones por cambio de pin generalmente no son una buena manera de detectar el botón   acciones. "

Mal. PC INT es la mejor opción. Si usa el sondeo para verificar el estado de un botón, no se realizará nada la mayor parte del tiempo. Usted pierde mucho tiempo de CPU precioso. PC INT permite que las acciones se realicen solo bajo pedido.

  

"Esto se debe a que los botones mecánicos rebotan, y obtendrás un montón de   Interrupciones sin sentido, y luego todavía tienes que hacer el rebote   De todos modos. "

Corregir sobre rebotar. Sin embargo, NUNCA debe rebotar un botón / interruptor dentro de una rutina de interrupción (la misma razón: pérdida de tiempo de CPU). Los ISR están destinados a ser realmente cortos y eficientes, a nivel de código. Solo usa el debouncing de hardware Mantenga su software limpio!

El anuncio de hardware es más conveniente, vea aquí / RC debouncing + Schmitt trigger para referencia. Lo he usado innumerables veces con PC INT, nunca falló.

Entonces, sí, puedes (y deberías) usar PC INT para obtener un estado de botón. Pero también tienes que usar el debouncing de hardware adecuado.

    
respondido por el Nelson

Lea otras preguntas en las etiquetas