Antirrebote de software para detectar si el interruptor se ha presionado durante T segundos

Necesito detectar si se ha presionado un interruptor durante más tiempo que el establecido, sin usar ningún registro de temporizador en mi código incrustado.

Estoy usando el software antirrebote de la siguiente manera para detectar una pulsación normal del interruptor.

int buttonPress (void)
{
  static int downCount= 0; // static = value isn't lost between calls.

  int currentButton = ((IO0PIN & 0x00000200) == 0x00000200); 

  if (currentButton ) { // button is down

    if (downCount == 5) { // button has been down for 5 counts
      downCount++;      // increase the count so we don't trigger next time
      return 1;
    } else {              // some count other than 5
      if (downCount < 5)  // increase if it's less than 5
        downCount++;
    }
  } else   {    // button is up
    downCount = 0; 
  }
  return 0;
}   

Sin embargo, me gustaría detectar si este botón se ha presionado durante 5 segundos o más. El problema al que me enfrento es que parece que no puedo contar hasta 300,000 y, si no se alcanza, recurro al original downCountde 5 para marcar como una pulsación de botón normal.

(Dado que la velocidad de reloj de mi microcontrolador es de 60 MHz, tardará 5 segundos en llegar a 300 000).

He jugado con algo de lógica de la siguiente manera, sin embargo, en el caso que se muestra a continuación, debido a la introducción de la whiledeclaración, estoy contando hasta 300,000 independientemente de cuánto tiempo se haya presionado el botón.

if (currentButton ) {   // button is down

  if (downCount == 5) { // button has been down for 5 counts
    downCount++;      // increase the count so we don't trigger next time
    while (downCount > 5) { 
      if (debounce == 300000) {
        flag_5SEC = 1;
      } else if (debounce < 300000) {
         debounce++;
      }
    }
    return 1;
  } else {              // some count other than 5
    if (downCount < 5)  // increase if it's less than 5
      downCount++;
  }
}

¿Algún consejo o sugerencia sobre cómo realizar esta tarea?

Nota : quiero detectar una pulsación de interruptor más prolongada explícitamente en el código antirrebote anterior, ya que hacer cualquier otra cosa puede tener efectos secundarios adversos en el resto del código, que otra persona había escrito previamente.


Me las arreglé para completar la tarea antes mencionada usando temporizadores, sin embargo, desde entonces, he usado todos los registros de temporizadores (y las resistencias de coincidencia que están disponibles) para tareas más importantes dentro del mismo proyecto.

No necesito detectar un retraso de tiempo preciso, simplemente si el botón ha estado presionado por más de, digamos, 2-3 segundos.

El comentario de @DiBosco aclara aún más:

Entonces, lo que está diciendo es que si presiona un interruptor durante, digamos, veinte conteos y se suelta, actúa como una presión normal que realiza una función, y si se presiona y se mantiene presionado durante dos segundos más o menos, lleva a cabo otra funcion...

Me parece que una vez que downCount llega a cinco, simplemente estás atrapado en ese bucle while. ¿Supongo que estás llamando a esto desde el bucle infinito principal? Necesita una mejor manera de ingresar a la función desde el ciclo principal, incrementando el contador y saliendo si no ha alcanzado su conteo objetivo. Sin embargo, a menos que haga esto desde un temporizador (podría establecer una bandera en una interrupción incluso si es una interrupción compartida) y entrar aquí desde el bucle principal cuando se establece esa bandera, será muy impreciso.
PD: Cuando respondas a alguien, asegúrate de usar su nombre para que sepa que has respondido. :)
@DiBosco, gracias por su comentario perspicaz, sí, estoy llamando a esto desde el bucle infinito principal. Estoy de acuerdo con todas sus declaraciones mencionadas, y estoy tratando de encontrar una mejor manera de hacer todas las cosas mencionadas. ¿Sería bueno el método que estoy tratando de usar (sin temporizadores) si simplemente quiero detectar un botón presionado por más de, digamos, 2 segundos, en lugar de un valor exacto?
Lo siento, leí mal el código... aunque no estoy seguro de cómo lo estás llamando...
Tengo una if(buttonPress())declaración, que solo es cierta enreturn 1
@ Rrz0 ya ese tipo de trabajos suponiendo que las llamadas son a intervalos regulares y lo suficientemente largos, pero no demasiado largos.
@DiBosco, actualmente estoy buscando una solución en la dirección de las respuestas actuales (que parecen ser la mejor opción), sin embargo, he visto un fragmento de la respuesta que publicaste y me preguntaba si podrías modificarla o recuperarla.
Hecho. Como había otras dos buenas respuestas y accidentalmente presioné enviar antes de que estuviera lista, la mía parecía innecesaria, pero espero que ayude.
Se agregó un poco más para mayor claridad (¡con suerte!)
Dices que no quieres usar temporizadores, pero ¿hay alguna función en la plataforma que estás usando que devuelva el tiempo transcurrido? (Similar al millis()Arduino).
@NickGammon, no, no hay...
¿Cómo vas a detectar "5 segundos o más" si no tienes idea de cuánto tiempo ha transcurrido? Está muy bien decir que contarás las instrucciones, pero si agregas algo al ciclo de conteo, se perderá.
"Dado que la velocidad de reloj de mi microcontrolador es de 60 MHz, tardará 5 segundos en llegar a 300 000". ¿Me estoy perdiendo algo, o (1) confundió mega por kilo y (2) asumió que una iteración del ciclo se realiza en un ciclo de reloj?
@ThomasPadron-McCarthy, gracias por señalarlo. Sí, eso es un error de mi parte.

Respuestas (4)

Me las arreglé para completar la tarea antes mencionada usando temporizadores, sin embargo, desde entonces, he usado todos los registros de temporizadores (y las resistencias de coincidencia que están disponibles) para tareas más importantes dentro del mismo proyecto.

En ese caso, lo estás haciendo todo mal. A menos que necesite absolutamente la alta resolución, el bajo jitter u otras características de hardware de los temporizadores de hardware, debe realizar su temporización en software, utilizando uno de los temporizadores de hardware para generar interrupciones periódicas continuas que puede contar en el ISR.

Una "interrupción de latido" con un período de entre 100 µs y 100 ms (o más) es una característica común de los sistemas integrados. Esto le permite crear un número arbitrario de temporizadores de software.

Incluso si ya está usando todos los temporizadores de hardware, siempre que al menos uno de ellos esté haciendo algo periódico, como generar una señal PWM o crear un reloj para una interfaz serial, puede habilitar las interrupciones y usarlos. para la base de tiempo de su software. Es posible que el período no sea un valor "conveniente", pero ese es un problema de escala simple que el software puede resolver.

Me gustan las mentes :) 341 para ir... ¡redoble de tambores!
Por lo tanto, si tengo una interrupción del temporizador con un período de 1 ms, simplemente puedo configurar una bandera cuando se presiona el botón y luego, mientras la bandera está alta, contar dentro de la interrupción. ¿Es correcta esta afirmación?
¡Ni siquiera soy del tipo EE, y esta fue una respuesta extremadamente bien escrita y fácil de entender! Lo único con lo que no estoy familiarizado es "ISR", pero para eso está Google;)
@Rrz0: Sí, pero en realidad necesita dos contadores: uno para contar el tiempo de "rebote" de aproximadamente 10 ms para determinar que el interruptor ha cambiado de estado, y otro para contar el tiempo de "retención" de 5000 ms para el interruptor.
Dave, en realidad solo necesitas un contador y dos niveles de comparación de conteo. Si el reloj de tictac es de 1 mS, la cuenta de 10 = sin rebote, la cuenta de 5000 = retenida, luego deje de contar.
@Trevor_G: Hay muchas formas de implementar múltiples temporizadores cuando tiene una interrupción periódica. Tener un contador separado para cada uno es probablemente lo más simple de entender. Un solo contador con múltiples comparadores es generalmente más eficiente, pero más complicado de explicar. La elección depende en gran medida de otros aspectos del sistema.

Su código parece estar un poco fuera de lugar, y es difícil de entender realmente, ya que no está claro dónde se encuentra el ciclo while con respecto a la interrupción o lo que sea que establezca la función DownCount.

También parece estar incrementando el contador de rebote en dos lugares en el ciclo while, lo que parece incorrecto.

Su solución en línea es problemática por muchas razones, una de las cuales es que su tiempo variará dependiendo de qué más esté sucediendo en el micro a menos que se detenga y ajuste el ciclo mientras cuenta. Pero detenerse a contar cinco segundos también es una mala idea.

Este tipo de función normalmente se maneja en micros mediante el uso de un contador de pulsos, también conocido como latido del corazón. Básicamente, es una práctica común reservar un temporizador para que actúe como un reloj para su código. El temporizador generaría una interrupción regular, quizás 10 veces por segundo.

Ese controlador de interrupciones puede encargarse de numerosas funciones de mantenimiento, como interruptores antirrebote, LED parpadeantes, verificación de tiempos de espera de comunicación, etc. También puede incluir contar cuánto tiempo se ha presionado un botón. Cuando se ha presionado durante un período de tiempo adecuado, dicho controlador llama al controlador adecuado para ese evento.

Este método le permite usar un solo temporizador para numerosas funciones.

Gracias por señalar el error en mi código. Eso es realmente incorrecto.
Empecé a comprender que el método mencionado anteriormente es, de hecho, el camino a seguir. ¿Sugiere que me ocupe de que el software se elimine por completo dentro de la interrupción?
@ Rrz0 sí, también puede manejar el rebote en la interrupción del reloj de tic en función de las banderas de la interrupción del botón, si hay una, o sondeando la línea IO, y las banderas y variables establecidas desde el estado anterior del reloj de garrapata.
@ Rrz0 También debo mencionar que, dado que no está claro en su código, debe eliminar el rebote tanto al presionar el botón como al soltar el botón. Sorprendentemente, los interruptores también rebotan cuando los sueltas.

Dado que los botones son operados por humanos muy lentos, en relación con la velocidad del procesador, puede salirse con la suya con un sondeo lento. Cada 10 milisegundos por ejemplo.
Puede crear fácilmente una función para manejar muchos botones, con muchos patrones de presión y acciones de retención.

Una versión muy simple de lo que uso a menudo se ve así. Solo tiene una bandera de retención de 5 segundos, ninguna de las pulsaciones cuenta y reconoce la bandera (las banderas se establecerán inmediatamente después de borrarse nuevamente).
Pero con un poco de creatividad puedes agregar muchas cosas de esta manera. Pero se hará un poco largo.
Incluso puede agregar canales de entrada de la máquina y eliminarlos de esta manera.

#define BUTTON_INTERVAL 10
#define BUTTON_COUNT    1

/* Typedef for a button */
typedef struct button_s {
    void* port;         /* Use GPIO port type, whatever your platform uses, maybe even via bitbanding */
    uint32_t hi_time;   /* Time high */
    uint8_t pin;        /* Pin number */
    uint8_t flags;      /* Output flags */
} button_t;

/* The array of all buttons */
button_t buttons[BUTTON_COUNT];

/* Handle button polling */
void buttonHandler(void){
    uint8_t i;
    for(i=0;i<NR_OF_BUTTONS; i++){
        button_t *b = buttons[i];
        uint8_t state = *b->port & (1<<b->pin);
        if(state){
            // Saturating unsigned 32-bit addition 
            // your cpu might have an instruction for this eg: arm __uqadd(a,b))
            if(b->hi_time < 0xFFFFFFFF) b->hi_time += BUTTON_INTERVAL;
        }else{
            b->hi_time = 0;
        }
        // One of many possible flags comparisons:
        if( b->hi_time >= 5000 ){
            b->flags |= BUTTON_HI_5S;
        }
    }
}

/* Example of flag usage */
void waitForButton(){
    if( button[0]->flags & BUTTON_HI_5S ){
        button[0]->flags &= ~BUTTON_HI_5S;
        // ..stuff..
    }
}

Los puntos clave son, si va por su cuenta:
- Una estructura para todos los datos, incluso puede reasignar claves sobre la marcha si lo desea.
- Botones infinitos usando estructuras. ¡Nunca vuelvas a escribir este código!
- Temporizadores de saturación, no se desbordan.
- Banderas, fácilmente reemplazables por llamadas al sistema operativo.

Si estoy entendiendo tus necesidades correctamente, quieres algo como esto:

void SwitchCheck(void)
{   
    DebounceTimer = false; // If using timer flag
    if (currentButton) 
        {
        debounce++; 
        }
    else 
        {
        if (debounce > NORMAL_OPERATION)
              {
              // Do normal stuff here
              }
        debounce = 0;
        } 
    if (debounce == MAX_TIMEOUT) 
         {
         // Do your held down thing here
         }
}

Llame a esto desde su bucle principal. Idealmente, solo llámelo después de haber configurado un bit en una interrupción del temporizador correctamente, para que tenga una sincronización precisa.

He hecho muchas, muchas veces a lo largo de los años cambiar el rebote sin temporizadores (aunque en realidad no es así), simplemente llamando desde el bucle principal y funciona. Sin embargo, si está buscando precisión en su tiempo de cinco segundos, esto no será lo suficientemente bueno.

Edición adicional:

En su temporizador ISR:

TimerISR()
{
DebounceTimer = true;
// Other stuff    
}

en principal:

if (DebounceTimer)
    SwitchCheck();

O simplemente llámelo desde main sin la verificación de la bandera si no está usando un temporizador y experimente empíricamente con MAX_TIMEOUT.

Puedo confirmar que no estoy buscando precisión en este caso particular.
@JohnSmith No estoy seguro de lo que quieres decir. (Sin ser sarcástico, realmente no estoy seguro de a qué se refiere). No hará nada hasta que llegue a MAX_TIMEOUT y luego ejecutará lo que debe ejecutarse y njust se retirará. Si se mantiene presionado durante MUCHO tiempo, corre el riesgo de que DebounceTimer dé la vuelta y se vuelva a ejecutar. Es posible que necesite una verificación para asegurarse de que eso no suceda. Sin embargo, es solo una guía en lugar de una solución completa. Es posible que me haya perdido algo por completo en lo que buscaba el OP.
@DiBosco, leyendo lo anterior, no, no te has perdido por completo lo que busco. Sin embargo, quiero poder detectar una pulsación de interruptor normal donde TIMEOUT sería mucho menor que MAX_TIMEOUT. Entonces, dado que no sucede nada hasta que llega a MAX_TIMEOUT, hay que hacer algunos ajustes.
@Rrz0 Entonces, lo que está diciendo es que si presiona un interruptor durante, digamos, veinte conteos y se suelta, actúa como una presión normal que realiza una función, y si se presiona y se mantiene presionado durante dos segundos más o menos, lleva a cabo una función diferente?
Exacto, estoy en proceso de implementar dicha funcionalidad. Aunque las otras respuestas pueden ser una mejor práctica, para este caso particular voy con este método.
@ Rrz0 OK, agregado extra. No lo he probado, pero creo que hará lo que quieras. Debe probar diferentes valores de MAX_TIMEOUT y NORMAL_OPERATION para que sea lo que desea.