Manejo de interrupciones en microcontroladores y FSM ejemplo

10

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 unidades de control de usuario. Me gustaría saber si es o no una buena práctica habilitar / deshabilitar las interrupciones a lo largo del código. Quiero decir, si tengo una parte del código que no será sensible a las interrupciones (o, lo que es peor, no debe escuchar las interrupciones, por cualquier motivo), es mejor:

  • Deshabilite las interrupciones antes y luego vuelva a habilitarlas después de la sección crítica.
  • Coloque un indicador dentro del ISR respectivo y (en lugar de deshabilitar la interrupción), establezca el indicador en falso antes de la sección crítica y restablézcalo en verdadero, justo después. Para evitar que el código del ISR se ejecute.
  • Ninguno de los dos, ¡las sugerencias son bienvenidas!

Actualización: interrupciones y gráficos de estado

Voy a 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 un profesor nos enseñó en la universidad. Probablemente, no es la mejor manera de hacerlo 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 desde, por ejemplo, STATE_A , quiero ser sensible a una interrupción proveniente de un conjunto de botones (con el sistema 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 buttonPressed no se puede actualizar con un nuevo valor antes de 200 ms. Esto es lo que te estoy preguntando a ti (ya mí :) por supuesto)

¿Debo habilitar la interrupción en la actividad de OD de STATE_A y deshabilitarla 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 haya sobrescrito el valor anterior de buttonPressed que necesito (aunque es imposible que esto suceda porque deben transcurrir 250 ms)

    
pregunta Umberto D.

5 respuestas

18

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á allí para servirle, no al revés. Las reglas generales generales son solo para evitar que los programadores malos escriban código realmente malo. Es mucho mejor entender exactamente cómo funciona la máquina y luego diseñar una buena manera de aprovechar esas capacidades para realizar la tarea deseada.

Tenga en cuenta que, a menos que esté realmente cerca de los ciclos o las ubicaciones de la memoria (sin duda puede suceder), de lo contrario, debe optimizar la claridad y la capacidad 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 que las dos mitades sean coherentes. Una forma es apagar las interrupciones, leer las dos palabras y luego volver a activarlas. Si la latencia de interrupción no es crítica, eso es perfectamente aceptable.

En el caso de que tenga una 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 ninguna latencia de interrupción. Hay varios pequeños trucos. Otra opción 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 tasa de interrupción del contador es lo suficientemente lenta como para que el bucle de eventos haga el incremento antes de que se establezca nuevamente el indicador.

O, en lugar de una bandera, use un contador de una sola 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 firmar del contador en vivo menos el valor guardado para determinar la cantidad de tics a la vez que debe 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 sola respuesta correcta. Nuevamente, entienda cómo funciona la máquina, entonces no necesitará reglas prácticas.

    
respondido por el Olin Lathrop
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 pueda ser interrumpida.

Por lo tanto, deshabilitar las interrupciones, que generalmente se manejan con una sola instrucción del 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 que puede perderse. Algunos microcontroladores establecen los indicadores independientemente del estado de la habilitación de interrupción global, y después de abandonar la sección crítica, las interrupciones se ejecutan y se retrasan. Pero si tiene una interrupción que se produce a una velocidad alta, puede perderse una segunda vez que la interrupción ocurrió si bloquea las interrupciones durante un período de tiempo demasiado largo.

Si su sección crítica requiere solo una interrupción para no ejecutarse, pero otra debe ejecutarse, el otro enfoque parece viable.

Me encuentro programando las rutinas de servicio de interrupción lo más cortas posible. Así que simplemente establecen una marca que luego se comprueba durante las rutinas normales del programa. Pero si lo hace, tenga cuidado con las condiciones de la carrera mientras espera que se establezca esa bandera.

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

    
respondido por el Arsenal
5

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

  

Coloque un indicador dentro del ISR respectivo y (en lugar de deshabilitar la interrupción), establezca el indicador en falso antes de la sección crítica y restablézcalo en verdadero, justo después. Para evitar que se ejecute el código del ISR.

Esto todavía permitiría que se produjera 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 diseñar el ISR para establecer un indicador en lugar de realizar la comprobación (sería más corto) y manejar el indicador en su rutina de código normal. Esto suena como si alguien hubiera puesto demasiado código en la interrupción, y están usando la interrupción para realizar acciones más largas que deberían tener lugar en el código normal.

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

El problema principal con hacerlo de forma de bandera es que no ejecutas la interrupción en absoluto, lo que puede tener repercusiones más adelante. La mayoría de los microcontroladores seguirán los indicadores 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 ocurre una interrupción durante la sección crítica, una se ejecuta después.
  • Si se producen múltiples 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, las interrupciones rara vez afectarán negativamente a su otro código. Tenga la captura de interrupción o libere los datos si es necesario, o configure / reinicie salidas, etc. según sea necesario, luego haga que la ruta del código principal preste atención a las banderas, búferes y variables a las que afecta la interrupción para que el procesamiento prolongado pueda realizarse en el bucle principal, en lugar de la interrupción.

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

    
respondido por el Adam Davis
1

Poner una bandera dentro del ISR como usted 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ías hacer esto muy a menudo. Tenga en cuenta que cualquier lectura o escritura que se realice a través de una sola instrucción no debería estar protegida, ya que la instrucción se ejecuta o no.

Mucho depende de qué tipo de recursos estás tratando de compartir. Si está suministrando datos desde el 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 pasas con las interrupciones deshabilitadas.

    
respondido por el Adam Haun
0

Hay una diferencia sutil que debes tener en cuenta. Puede elegir "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 del hardware, es que una vez que habilitamos 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 demorar el manejo de estas interrupciones. Y luego asegúrese de que todos los casos en los que las interrupciones están deshabilitadas se terminarán en menos tiempo.

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

    
respondido por el ghellquist

Lea otras preguntas en las etiquetas