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 obtuve la hoja de datos correcta, se deben hacer las siguientes cosas para usar una interrupción de cambio de pin:

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

Proyecto: Moodlamp simple, colores controlados a través de 4 botones.

Preparar:

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

Aquí está el código que estoy usando que 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 eliminados. Como estoy tratando de hacerlo paso a paso y no debería importar encender el LED, lo ignoré aquí.

Pregunta: ¿Es correcta la forma en que intento usar las interrupciones?

Problemas con mi configuración:

  • Los botones 1-3 se ignoran por completo.
  • Button4 está activando un reinicio de atmega

Cosas que revisé:

  • Los botones no están conectados de ninguna manera al PIN de reinicio
  • Los botones están conectados correctamente a GND si se presionan
  • Los botones no están conectados a GND si no se presionan
  • Los botones funcionan bien si los utilizo sin interrupción, por ejemplo:

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

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

Necesita el indicador PCIE1 (no PCIE2) y PCINT1_vect (no PCINT2)
microtherion

¿Por qué PCIE1? Estoy usando el Registro C, entonces, si cuento, ¿sería A (PCIE0), B (PCIE1), C (PCIE2)? De todos modos, lo intenté con PCIE1 nad PCINT1_vect y no hay reacción si presiono los botones.
echox

1
Puede ser un poco arriesgado asumir la ortogonalidad en tales asignaciones. En este caso en particular, sería casi correcto, excepto que el ATmega168 no tiene un puerto A. En cualquier caso, seguí la hoja de datos y pines. Otro consejo fue que estaba usando PCIE2, pero configurando bits en PCMSK1; eso no puede ser correcto (Desafortunadamente, no sé por qué su boceto revisado todavía no funciona).
microtherion

Gracias, también entiendo que la combinación de software de depuración que depende del hardware de autoconstrucción no es tan fácil ;-)
echox

Respuestas:


14

Las interrupciones de cambio de pin generalmente no son una buena manera de detectar acciones de botón. Esto se debe a que los botones mecánicos rebotan, y obtendrá muchas interrupciones sin sentido, y de todos modos aún tendrá que eliminar 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 de tiempo invertida en la interrupción será pequeña. Simplemente muestre el estado del botón en cada interrupción. Declare un nuevo estado del botón si ha visto el nuevo estado 50 ms seguidos. 50 ms es más largo que el rebote de la mayoría de los botones, pero aún es lo suficientemente corto como para que los humanos no noten ni se preocupen por el retraso.

Tenga en cuenta que de esta manera también puede manejar varios botones en la misma interrupción periódica de 1 ms. Todo lo que necesitas 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 quizás en aplicaciones muy críticas como un cronómetro, pero hasta ahora no me he encontrado con una. Lo probé 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 de un botón pulsador es de alrededor de 10 ms, con casi todo estableciéndose 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 50 ms y 0 ms de retraso si lo está buscando específicamente , pero eso es bastante diferente de presionar un botón y ver que sucede algo y no pensar en el retraso.

Por lo tanto, 50 ms es un buen tiempo de rebote porque el retraso está por debajo del límite de percepción en aplicaciones normales, 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 ese tiempo, por lo que es mejor que llegues al límite de percepción ya que no hay nada que perder.

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


1
50 ms puede ser demasiado largo para algunos casos (por lo general, 10-20 ms es el límite de la percepción humana, y eso debería ser suficiente para eliminar el rebote), pero el método descrito aquí es el camino a seguir.
Laszlo Valko

1
@Laszlo: No, 50 ms no es demasiado largo para el caso ordinario. Ver además de mi respuesta.
Olin Lathrop

Intenté 50ms, que funciona bien para mí :-) Todavía tengo curiosidad por qué la interrupción de cambio de pin no funciona (además de las cosas que rebotan), pero esto funciona :-) Gracias.
echox

1

Las interrupciones de cambio de pin son una mejor manera de eliminar el rebote que el sondeo. La interrupción generalmente 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 verifica el estado del pin y si todavía está en el estado dado que activó la interrupción, se cambia el estado del botón y se borra la bandera de interrupción y se establece la habilitación de interrupción. Si no está en el estado que causó el inicio, se establece la habilitación de interrupción y el estado permanece igual. Esto libera el procesador para otras tareas. Las interrupciones periódicas pierden tiempo en el programa.


-1

"Las interrupciones de cambio de pin no suelen ser una buena forma de detectar acciones de botón".

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

"Esto se debe a que los botones mecánicos rebotan, y obtendrás muchas interrupciones sin sentido, y de todos modos aún tendrás que eliminar los rebotes".

Correcto sobre el rebote. Sin embargo, NUNCA debe eliminar el botón / interruptor dentro de una rutina de interrupción (mismo motivo: pérdida de tiempo de CPU). Los ISR están destinados a ser realmente cortos y eficientes, en cuanto a código. Solo use el rebote de hardware. ¡Mantenga limpio su software!

La eliminación de rebotes del hardware es más conveniente, consulte aquí / Eliminación de rebotes RC + disparador Schmitt para referencia. Lo he usado innumerables veces con PC INT, nunca falló.

Entonces sí, puede (y debe) usar PC INT para obtener un estado de botón. Pero también tiene que usar el rebote de hardware adecuado.


2
El rechazo de software es un enfoque válido, y la mayoría de las veces la pequeña sobrecarga de CPU adicional es irrelevante. Decir que normalmente debes renunciar al hardware es cuestionable en el mejor de los casos. Decir que tiene que usar el rebote de hardware en todos los casos es simplemente incorrecto.
Olin Lathrop

En la mayoría de las aplicaciones, el controlador está inactivo la mayor parte del tiempo de todos modos, ejecutando el bucle principal. Además, el tiempo de CPU requerido para realizar una verificación de estado de E / S y potencialmente incrementar una variable es mínimo. Implementar una eliminación de rebotes simple en el software es casi gratis, el hardware cuesta dinero. Y no se ría de unos pocos centavos, el montaje también cuesta dinero y si ejecuta volúmenes de medianos a altos de un producto no es despreciable. Es cierto que el tiempo de ISR debe mantenerse corto, pero eso no es un argumento en este caso. Probablemente sea más crítico si el PC INT ISR se dispara 50 veces seguidas debido a rebotes.
Rev1.0

@ Nelson, la 'pérdida de tiempo de CPU' es importante en algunas aplicaciones y no en muchas otras. Debe calificar su respuesta para una situación en la que el tiempo de CPU es crítico.
user1139880
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.