Manejo de interrupciones en microcontroladores y ejemplo de FSM

9

Pregunta inicial

Tengo una pregunta general sobre el manejo de interrupciones en microcontroladores. Estoy usando el MSP430, pero creo que la pregunta puede extenderse a otras uC. Me gustaría saber si es o no una buena práctica habilitar / deshabilitar las interrupciones con frecuencia a lo largo del código. Quiero decir, si tengo una porción de código que no va a ser sensible a las interrupciones (o peor aún, no debe escuchar las interrupciones, por alguna razón), es mejor:

  • Deshabilite las interrupciones antes y luego vuelva a habilitarlas después de la sección crítica.
  • Ponga una bandera dentro del ISR respectivo y (en lugar de deshabilitar la interrupción), establezca la bandera en falso antes de la sección crítica y restableciéndola a verdadero, justo después. Para evitar que se ejecute el código del ISR.
  • Ninguno de los dos, así que las sugerencias son bienvenidas.

Actualización: interrupciones y gráficos de estado

Proporcionaré una situación específica. Supongamos que queremos implementar un gráfico de estado, que está compuesto por 4 bloques:

  1. Transiciones / Efecto.
  2. Condiciones de salida.
  3. Actividad de entrada.
  4. Hacer actividad

Esto es lo que nos enseñó un profesor en la universidad. Probablemente, la mejor manera de hacerlo no es siguiendo este esquema:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Supongamos también que, por ejemplo STATE_A, quiero ser sensible a una interrupción proveniente de un conjunto de botones (con sistema de debouce, etc., etc.). Cuando alguien presiona uno de estos botones, se genera una interrupción y la bandera relacionada con el puerto de entrada se copia en una variable buttonPressed. Si el rebote se establece en 200 ms de alguna manera (temporizador de vigilancia, temporizador, contador, ...), estamos seguros de que buttonPressedno se puede actualizar con un nuevo valor antes de 200 ms. Esto es lo que te pregunto (y a mí :) por supuesto)

¿Necesito habilitar la interrupción en la actividad de OD STATE_Ay deshabilitar antes de irme?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

De manera que estoy seguro de que la próxima vez que ejecute el bloque 1 (transición / efectos) en la próxima iteración, estoy seguro de que las condiciones verificadas a lo largo de las transiciones no provienen de una interrupción posterior que ha sobrescrito el valor anterior de buttonPressedese I necesidad (aunque es imposible que esto suceda porque deben transcurrir 250 ms).

Umberto D.
fuente
3
Es difícil hacer una recomendación sin saber más sobre su situación. Pero a veces es necesario deshabilitar las interrupciones en los sistemas integrados. Es preferible mantener las interrupciones desactivadas solo por cortos períodos de tiempo para que no se pierdan las interrupciones. Es posible deshabilitar solo interrupciones particulares en lugar de todas las interrupciones. No recuerdo haber usado nunca la técnica de flag-inside-ISR como la que describiste, así que soy escéptico sobre si esa es tu mejor solución.
kkrambo

Respuestas:

17

La primera táctica es diseñar el firmware general para que esté bien que ocurran interrupciones en cualquier momento. Tener que desactivar las interrupciones para que el código de primer plano pueda ejecutar una secuencia atómica debe hacerse con moderación. A menudo hay una forma arquitectónica a su alrededor.

Sin embargo, la máquina está ahí para servirle, no al revés. Las reglas generales generales son solo para evitar que los malos programadores escriban códigos realmente malos. Es mucho mejor comprender exactamente cómo funciona la máquina y luego diseñar una buena forma de aprovechar esas capacidades para realizar la tarea deseada.

Tenga en cuenta que, a menos que sea realmente estricto con los ciclos o las ubicaciones de memoria (ciertamente puede suceder), de lo contrario, desea optimizar la claridad y facilidad de mantenimiento del código. Por ejemplo, si tiene una máquina de 16 bits que actualiza un contador de 32 bits en una interrupción de marca de reloj, debe asegurarse de que cuando el código de primer plano lea el contador, sus dos mitades sean consistentes. Una forma es apagar las interrupciones, leer las dos palabras y luego volver a activar las interrupciones. Si la latencia de interrupción no es crítica, entonces eso es perfectamente aceptable.

En el caso de que deba tener latencia de interrupción baja, puede, por ejemplo, leer la palabra alta, leer la palabra baja, leer la palabra alta nuevamente y repetir si cambió. Esto ralentiza un poco el código de primer plano, pero no agrega latencia de interrupción en absoluto. Hay varios pequeños trucos. Otro podría ser establecer un indicador en la rutina de interrupción que indique que el contador debe incrementarse, luego hacerlo en el bucle de eventos principal en el código de primer plano. Eso funciona bien si la frecuencia de interrupción del contador es lo suficientemente lenta como para que el bucle de eventos haga el incremento antes de que la bandera se vuelva a establecer.

O, en lugar de una bandera, use un contador de una palabra. El código de primer plano mantiene una variable separada que contiene el último contador al que ha actualizado el sistema. Hace una resta sin signo del contador en vivo menos el valor guardado para determinar cuántos ticks a la vez tiene que manejar. Esto permite que el código de primer plano pierda hasta 2 eventos N -1 a la vez, donde N es el número de bits en una palabra nativa que la ALU puede manejar atómicamente.

Cada método tiene su propio conjunto de ventajas y desventajas. No hay una única respuesta correcta. Nuevamente, comprenda cómo funciona la máquina, entonces no necesitará reglas generales.

Olin Lathrop
fuente
7

Si necesita una sección crítica, debe asegurarse de que la operación que protege su sección crítica sea atómica y no se pueda interrumpir.

Por lo tanto, deshabilitar las interrupciones, que generalmente se maneja con una sola instrucción de procesador (y se llama mediante una función intrínseca del compilador), es una de las apuestas más seguras que puede realizar.

Dependiendo de su sistema, puede haber algunos problemas con eso, como una interrupción podría perderse. Algunos microcontroladores establecen los indicadores independientemente del estado de la habilitación de interrupción global, y después de salir de la sección crítica, las interrupciones se ejecutan y simplemente se retrasan. Pero si tiene una interrupción que ocurre a una velocidad alta, puede perderse una segunda vez que la interrupción ocurrió si bloquea las interrupciones durante demasiado tiempo.

Si su sección crítica requiere que no se ejecute una sola interrupción, pero se debe ejecutar otra, el otro enfoque parece viable.

Me encuentro programando las rutinas del servicio de interrupción lo más corto posible. Por lo tanto, solo establecen una bandera que luego se verifica durante las rutinas normales del programa. Pero si hace eso, tenga cuidado con las condiciones de la carrera mientras espera que se establezca esa bandera.

Hay muchas opciones y seguramente no hay una sola respuesta correcta a esto, este es un tema que requiere un diseño cuidadoso y merece un poco más de reflexión que otras cosas.

Arsenal
fuente
5

Si ha determinado que una sección de código debe ejecutarse ininterrumpidamente, excepto en circunstancias inusuales, debe deshabilitar las interrupciones durante el mínimo tiempo posible para completar la tarea y volver a habilitarlas después.

Ponga una bandera dentro del ISR respectivo y (en lugar de deshabilitar la interrupción), establezca la bandera en falso antes de la sección crítica y restableciéndola a verdadero, justo después. Para evitar que se ejecute el código del ISR.

Esto aún permitiría que ocurriera una interrupción, un salto de código, una verificación y luego una devolución. Si su código puede manejar tanta interrupción, entonces probablemente debería simplemente diseñar el ISR para establecer un indicador en lugar de realizar la verificación, sería más corto, y manejar el indicador en su rutina de código normal. Esto suena como si alguien pusiera demasiado código en la interrupción, y está usando la interrupción para realizar acciones más largas que deberían tener lugar en el código regular.

Si está tratando con código donde las interrupciones son largas, una marca como sugiere podría resolver el problema, pero aún así será mejor simplemente deshabilitar las interrupciones si no puede volver a factorizar el código para eliminar el código excesivo en la interrupción .

El principal problema al hacerlo de la manera indicada es que no ejecutas la interrupción en absoluto, lo que puede tener repercusiones más adelante. La mayoría de los microcontroladores rastrearán las banderas de interrupción incluso cuando las interrupciones estén deshabilitadas globalmente, y luego ejecutarán la interrupción cuando vuelva a habilitar las interrupciones:

  • Si no se produjeron interrupciones durante la sección crítica, ninguna se ejecuta después.
  • Si se produce una interrupción durante la sección crítica, se ejecuta una después.
  • Si se producen varias interrupciones durante la sección crítica, solo se ejecuta una después.

Si su sistema es complejo y tiene que rastrear las interrupciones más completamente, tendrá que diseñar un sistema más complicado para rastrear las interrupciones y operar en consecuencia.

Sin embargo, si siempre diseña sus interrupciones para realizar el trabajo mínimo necesario para lograr su función, y retrasa todo lo demás al procesamiento regular, entonces las interrupciones rara vez afectarán negativamente a su otro código. Haga que la interrupción capture o libere datos si es necesario, o configure / restablezca las salidas, etc., según sea necesario, luego haga que la ruta del código principal preste atención a los indicadores, las memorias intermedias y las variables que afecta la interrupción para que el procesamiento prolongado se pueda hacer en el bucle principal, en lugar de la interrupción.

Esto debería eliminar todas, excepto muy, muy pocas situaciones en las que pueda necesitar una sección de código ininterrumpida.

Adam Davis
fuente
He actualizado la publicación para explicar mejor la situación en la que estoy trabajando :)
Umberto D.
1
En su código de ejemplo, consideraría deshabilitar la interrupción del botón específico cuando no sea necesario y habilitarlo cuando sea necesario. Hacer esto con frecuencia no es una cuestión de diseño. Deje las interrupciones globales activadas para que pueda agregar otras interrupciones al código más adelante si es necesario. Alternativamente, simplemente restablezca el indicador cuando vaya al estado A e ignórelo. Si se presiona el botón y se establece la bandera, ¿a quién le importa? Ignóralo hasta que vuelvas al estado A.
Adam Davis
¡Si! Esa puede ser una solución porque en el diseño real con frecuencia voy a LPM3 (MSP430) con interrupciones globales habilitadas y salgo de LPM3, reanudando la ejecución, tan pronto como se detecta una interrupción. Entonces, una solución es la que presentó, que creo que se informa en la segunda parte del código: habilite las interrupciones tan pronto como comience a realizar la actividad de un estado que lo necesite y desactívela antes de ir al bloque de transiciones. ¿Otra posible solución sería deshabilitar la interrupción justo antes de abandonar el "Bloqueo de actividad" y volver a habilitarla en algún momento (¿cuándo?) Después?
Umberto D.
1

Poner una bandera dentro del ISR como lo describe probablemente no funcionará ya que básicamente está ignorando el evento que provocó la interrupción. Deshabilitar las interrupciones a nivel mundial suele ser una mejor opción. Como han dicho otros, no debería tener que hacer esto con mucha frecuencia. Tenga en cuenta que cualquier lectura o escritura que se realice a través de una sola instrucción no debería necesitar protección, ya que la instrucción se ejecuta o no.

Mucho depende de qué tipo de recursos intentes compartir. Si está alimentando datos del ISR al programa principal (o viceversa), podría implementar algo como un búfer FIFO. La única operación atómica sería actualizar los punteros de lectura y escritura, lo que minimiza la cantidad de tiempo que pasa con las interrupciones desactivadas.

Adam Haun
fuente
0

Hay una sutil diferencia que debes tener en cuenta. Puede optar por "retrasar" el manejo de una interrupción o "ignorar" e interrumpir.

A menudo decimos en el código que deshabilitamos la interrupción. Lo que probablemente sucederá, debido a las funciones de hardware, es que una vez que habilitemos las interrupciones, se activará. Esto de alguna manera está retrasando la interrupción. Para que el sistema funcione de manera confiable, necesitamos saber el tiempo máximo que podemos retrasar el manejo de estas interrupciones. Y luego asegúrese de que todos los casos en que las interrupciones estén desactivadas se terminen en un tiempo más corto.

A veces queremos ignorar las interrupciones. La mejor manera podría ser bloquear la interrupción a nivel de hardware. A menudo hay un controlador de interrupción o similar donde podríamos decir qué entradas deberían generar interrupciones.

ghellquist
fuente