Catto / Mapa de Temas · Electrónica Digital IV 7° año
Electrónica Digital IV · 120 h · Eje 3 de 6

Las interrupciones

Un programa que pregunta todo el tiempo «¿pasó algo?» desperdicia el procesador y llega tarde. Una interrupción invierte la relación: el hardware avisa, el programa deja lo que estaba haciendo, atiende y vuelve exactamente donde estaba.

Vector Latencia Prioridades volatile

01Qué ocurre exactamente

programaprincipalrutina deatencioncodigo normalsigue donde estabarutina de atencioneventolatencia1. El periferico levanta su bandera de pedidoEl evento ocurrio. La bandera queda marcada aunque la interrupcion todavia no se atienda.2. El nucleo termina la instruccion en cursoNunca se interrumpe una instruccion por la mitad: primero se completa.3. Guarda el contexto en la pilaDireccion de retorno y estado del procesador, para poder volver exactamente al mismo punto.4. Salta al vector de esa interrupcionBusca en la tabla de vectores la direccion de la rutina y salta ahi.5. Se ejecuta la rutina, cortaLevanta una bandera o guarda un dato. En muchas familias hay que borrar el pedido a mano.6. Restaura el contexto y continuaLa instruccion de retorno devuelve todo a como estaba: el programa principal ni se entera.La latencia se alarga muchisimo si hay otra rutina corriendo o una seccion critica abierta: por eso deben ser cortas.
Figura 1. La secuencia completa, animada. Entre el evento y la primera instrucción de la rutina de atención pasa un tiempo —la latencia— que hay que conocer cuando el temporizado importa.
PasoQué pasa
1El periférico detecta el evento y levanta su bandera de pedido.
2Si esa interrupción está habilitada y las interrupciones globales también, el núcleo termina la instrucción en curso.
3Guarda en la pila la dirección de retorno y el estado del procesador: es el contexto.
4Busca en la tabla de vectores la dirección de la rutina de atención y salta ahí.
5Se ejecuta la rutina. En muchas familias hay que borrar la bandera a mano.
6La instrucción de retorno restaura el contexto y el programa sigue como si nada.
Latencia

Es el tiempo entre el evento y la primera instrucción de la rutina. Se compone de lo que falta para terminar la instrucción en curso, el guardado del contexto y el salto: típicamente entre 4 y 20 ciclos. A 16 MHz eso es del orden de un microsegundo.

Pero la latencia real puede ser muchísimo mayor si en ese momento se está ejecutando otra rutina de atención o una sección crítica con interrupciones deshabilitadas. Ese es el motivo por el cual las rutinas deben ser cortas.

02De dónde vienen

FuentePara qué se usa
Externa por pinUn pulsador, un final de carrera, un sensor. Configurable por flanco de subida, de bajada, ambos o por nivel.
Cambio de estado en un puertoUn solo vector para todo un grupo de pines: la rutina tiene que averiguar cuál cambió.
TemporizadorDesbordamiento o comparación: es la base de toda tarea periódica y de las bases de tiempo del sistema.
Comunicación serieByte recibido o registro de transmisión vacío. Evita perder datos sin tener que estar mirando.
Conversor A/DConversión terminada: el programa no espera, lo avisan.
Captura de entradaGuarda el valor exacto del temporizador cuando ocurre un flanco. Es la forma precisa de medir períodos y anchos de pulso.
Perro guardiánSi el programa se cuelga y no lo refresca, reinicia el equipo.

03Prioridades y anidamiento

tiempoprincipalbaja prioridadalta prioridadllega Allega Bprincipalrutina Arutina BprincipalB esperaSin anidamientoB llega mientras corre A y tiene que esperar a que A termine. Si A es larga, B llega tarde o se pierde.principalArutina BA sigueprincipalCon anidamientoB interrumpe a A porque tiene mas prioridad, y A retoma despues. Mejor respuesta, mas pila y mas dificilde razonar.En los micros de 8 bits sencillos la prioridad la da la posicion en la tabla de vectores; en los ARM es configurable.
Figura 2. Dos interrupciones que llegan casi juntas, animado. Con anidamiento, la de mayor prioridad interrumpe a la de menor; sin anidamiento, espera a que la otra termine.
Cómo se resuelve el empate
  • En los microcontroladores de 8 bits sencillos, la prioridad la da la posición en la tabla de vectores: no es configurable, y no hay anidamiento salvo que se habilite a mano.
  • En los ARM Cortex-M, el controlador de interrupciones permite asignar una prioridad numérica a cada fuente, y el anidamiento es automático: menor número, mayor prioridad.
  • Anidar da mejor respuesta a lo urgente, pero consume más pila y hace mucho más difícil razonar sobre el programa.
Cómo se asignan
  • Máxima: seguridad. Parada de emergencia, sobrecorriente, falla de alimentación.
  • Alta: lo que se pierde si llega tarde. Captura de flancos, recepción serie a alta velocidad.
  • Media: bases de tiempo y control periódico.
  • Baja: interfaz de usuario, registro, comunicación no crítica.

04El problema de las variables compartidas

Una interrupción puede ocurrir entre dos instrucciones cualesquiera del programa principal. Todo lo que ambos toquen es una fuente potencial de errores que aparecen una vez cada mil, y sólo con el equipo en producción.

/* MAL: el compilador puede guardar 'listo' en un registro y no releerla nunca */
uint8_t listo = 0;
ISR(INT0_vect) { listo = 1; }
while (!listo) { }          /* puede quedarse colgado para siempre */

/* BIEN: volatile obliga a leerla de memoria en cada vuelta */
volatile uint8_t listo = 0;

/* MAL: 'pulsos' es de 32 bits; en un micro de 8 bits se lee en cuatro pasos */
volatile uint32_t pulsos;
total = pulsos;            /* si la interrupcion cae en el medio, sale un valor imposible */

/* BIEN: seccion critica, lo mas corta posible */
cli();                     /* deshabilita interrupciones */
total = pulsos;
sei();                     /* las vuelve a habilitar */
Las dos reglas que evitan el 90 % de los problemas
  1. Toda variable compartida entre una rutina de interrupción y el programa principal se declara volatile.
  2. Todo acceso a una variable de más bits que el ancho del procesador se hace dentro de una sección crítica, lo más corta posible.

Y una tercera, más importante que las dos: la rutina de atención hace lo mínimo. Levanta una bandera, guarda un dato, y el trabajo pesado lo hace el programa principal.

Lo que nunca va dentro de una rutina de atención
  • Esperas y demoras. Bloquean todo el sistema.
  • Escribir por puerto serie o en una pantalla. Son operaciones lentas.
  • Cálculos con coma flotante en un microcontrolador sin unidad de punto flotante.
  • Reservar memoria dinámica.
  • Esperar otra interrupción: si están deshabilitadas, no va a llegar nunca.

05El patrón que conviene usar siempre

/* Antirrebote de un pulsador por interrupcion + temporizador */
volatile uint8_t  evento = 0;
volatile uint16_t t_ultimo = 0;

ISR(INT0_vect)              /* flanco de bajada en el pulsador */
{
    uint16_t ahora = ms_actual();
    if ((uint16_t)(ahora - t_ultimo) > 30) {   /* 30 ms de rebote */
        evento   = 1;
        t_ultimo = ahora;
    }
}                            /* la rutina termina en microsegundos */

int main(void)
{
    inicializar();
    while (1) {
        if (evento) {
            cli(); evento = 0; sei();   /* se consume el evento sin perder otro */
            atender_pulsador();          /* aca si se puede tardar */
        }
        otras_tareas();
    }
}
Por qué este patrón funciona

La rutina es corta y determinista, el antirrebote no consume tiempo de espera, y el trabajo pesado ocurre en el bucle principal, donde puede tardar sin afectar a nadie. El detalle de la resta con conversión explícita hace que el conteo siga siendo correcto cuando el contador de milisegundos da la vuelta, que es el error que aparece a los 65 segundos de funcionamiento y desconcierta a todo el mundo.

06En el laboratorio

Práctica 1 · Sondeo contra interrupción

Detectar un pulsador de dos maneras: preguntando en el bucle principal y por interrupción, con el programa haciendo además una tarea larga. Medir con el osciloscopio el tiempo entre el flanco y la respuesta en cada caso, y comparar el peor caso de los dos.

Práctica 2 · Medir la latencia

Poner un pin en alto como primera instrucción de la rutina de atención y observar en el osciloscopio la distancia hasta el flanco que la disparó. Repetir con otra interrupción activa y con una sección crítica larga en el programa principal: se ve crecer la latencia.

Práctica 3 · Reproducir el error de volatile

Escribir el bucle de espera sin volatile y compilar con optimización activada. Verificar que se cuelga. Mirar el listado en ensamblador para ver que la variable ni siquiera se relee. Agregar volatile y comprobar la diferencia. Es la mejor manera de que no se olvide más.

Práctica 4 · Medir frecuencia por captura

Usar la interrupción de captura de entrada para medir el período de una señal, y comparar la precisión con la de medirlo por sondeo. Con captura, el valor del temporizador se toma por hardware en el instante del flanco: la diferencia de exactitud es notable.

07Errores frecuentes

SíntomaCausa habitual
La interrupción se dispara sin pararNo se borra la bandera de pedido dentro de la rutina.
La interrupción no se dispara nuncaFalta habilitarla individualmente, o falta habilitar las globales, o el vector está mal escrito.
El programa se cuelga en un bucle de esperaLa variable compartida no está declarada volatile.
Un contador de varios bytes da valores absurdosSe lee sin sección crítica y la interrupción cae en el medio de la lectura.
Un pulsador genera varios eventosRebote mecánico. Se filtra por tiempo, no agregando esperas dentro de la rutina.
El sistema se vuelve errático al agregar interrupcionesDesbordamiento de pila por anidamiento, o rutinas demasiado largas.
Falla exactamente a los 65 segundosDesbordamiento de un contador de 16 bits en milisegundos, mal comparado.
Todo se detiene por momentosHay una espera, una escritura serie o un cálculo pesado dentro de una rutina de atención.

08Autoevaluación

¿Qué guarda el procesador al atender una interrupción?

La dirección de retorno y el estado del procesador —el contexto— en la pila, para poder continuar exactamente donde estaba al terminar la rutina.

¿Qué es la tabla de vectores?

Una tabla en memoria donde cada fuente de interrupción tiene la dirección de su rutina de atención. El núcleo la consulta para saber adónde saltar.

¿Qué es la latencia y de qué depende?

El tiempo entre el evento y la primera instrucción de la rutina. Depende de la instrucción en curso, del guardado de contexto y, sobre todo, de si hay otra rutina ejecutándose o una sección crítica abierta.

¿Por qué hace falta declarar volatile una variable compartida?

Porque sin esa palabra el compilador puede guardarla en un registro y no volver a leerla de memoria. Como la interrupción la modifica «por afuera», el programa principal nunca se entera del cambio.

¿Qué es una sección crítica y cuándo hace falta?

Un tramo con las interrupciones deshabilitadas. Hace falta al leer o escribir variables de más bits que el ancho del procesador, para que la interrupción no caiga en el medio de la operación.

Nombrá tres cosas que no deben ir dentro de una rutina de atención.

Esperas o demoras, escrituras lentas como el puerto serie o una pantalla, y cálculos pesados o reserva de memoria dinámica.

¿Cómo se resuelve el rebote de un pulsador con interrupciones?

Comparando el instante actual con el del último evento válido y descartando los que llegan antes de unos 20 a 50 ms. Nunca con una espera dentro de la rutina.

¿Qué ventaja tiene la interrupción de captura frente a medir por sondeo?

El valor del temporizador se toma por hardware en el instante exacto del flanco, así que la medición no depende de cuándo llegue el programa a leerlo.

¿Cómo se asignan las prioridades en un sistema real?

Máxima para la seguridad —paradas de emergencia, sobrecorriente—, alta para lo que se pierde si llega tarde, media para las bases de tiempo y baja para la interfaz de usuario y el registro.

¿Por qué las rutinas de atención deben ser cortas?

Porque mientras una se ejecuta, las demás esperan: cada rutina larga aumenta la latencia de todas las otras y puede hacer que se pierdan eventos.

Desarrollo del eje «Las interrupciones» de Electrónica Digital IV (7° año), según la «Propuesta Curricular – Segundo Ciclo de la Modalidad Técnico Profesional, Educación Secundaria – Electrónica», Ministerio de Educación de la Provincia de Córdoba, DGETyFP. Volver al Mapa de Temas · catto.ar