3 votos

Uso de campos de bits en aplicaciones basadas en interrupciones

Cuando implemento aplicaciones basadas en interrupciones, suelo crear un campo de bits para realizar un seguimiento de las diferentes interrupciones. Por ejemplo:

volatile struct {
    unsigned char ISR0: 1;
    unsigned char ISR1: 1;
    ...
    unsigned char ISR7: 1;
} ISRstatus;

El resto de la aplicación podría tener la siguiente estructura:

ISR(ISR0) {
    // set status flag
    ISRstatus.ISR0 = 1;
}

void main() {
    while(1) {
        if (ISRstatus.ISR0){
            // serve interrupt
            /* ... */
            // clear status flag    
            ISRstatus.ISR0 = 0;
        } /* ... */
    }
}

Hace poco me encontré con algunos artículos que sugerían evitar los campos de bits debido a su comportamiento inesperado en diferentes compiladores y arquitecturas.

Suponiendo que mi compilador sea GCC, ¿es mala idea utilizar este enfoque?

1voto

Al pacino Puntos 415

CORRECCIÓN: Como Ben (y otros comentaristas) han señalado, borrar la bandera de estado en el código principal es un problema. Las escrituras en campos de bits se implementan normalmente como lectura-modificación-escritura, donde (en tu caso) se lee el byte completo, luego se activa o desactiva un bit, y luego se vuelve a escribir el byte modificado. En pseudo-código, ISRstatus.ISR0 = 0 se convertiría en:

char temp = ISRstatus;
temp &= ~0x01;
ISRstatus = temp;

El problema aquí es que una interrupción puede llegar en medio de esta secuencia. Por ejemplo, digamos que la bandera ISR0 está activada y la interrupción 5 llega. Lo que sucede es:

<interrupt 0>
    ISRstatus |= 0x01;  //Not really atomic, but it doesn't matter here
<exit interrupt 0>

if (ISRstatus.ISR0)
{
    char temp = ISRstatus;
    temp &= ~0x01;
    <interrupt 5>
        ISRstatus |= 0x20;  //Not really atomic, but it doesn't matter here
    <exit interrupt 5>
    ISRstatus = temp;
}

En este ejemplo, ISRstatus debería ser igual a 0x20 después de la sentencia if, pero en su lugar es igual a 0x00. La bandera ISR5 se perdió.

La forma de solucionarlo es desactivar las interrupciones al escribir en la variable global en el código principal. (Las lecturas son seguras siempre que se cargue toda la estructura a la vez, como debería ser para una estructura de 8 bits).

El estándar C no garantiza ningún orden o empaquetamiento particular de los campos de bits. Esto significa que usar campos de bits para acceder a datos almacenados en un formato específico (como campos de registro o de cabecera de paquete) no es portable. Si sólo hay un compilador para tu CPU, la portabilidad no será un problema, así que puedes salirte con la tuya.

Mi interpretación de la norma es que los campos de bits están pensados para ser utilizados exactamente de la forma en que tú los estás utilizando. Sólo hay que tener en cuenta las limitaciones.

EDIT v2: Es probable que el compilador no permita que un campo de un solo bit cruce el límite de una unidad de almacenamiento. El manual de tu compilador debería tener más información, pero puede que necesites un poco de ensayo y error para averiguar los casos extremos. Dado que lo único que te importa son los datos en los campos individuales y no su disposición dentro de la unidad de almacenamiento, esto no debería importar.

Dicho esto, la portabilidad no suele ser una gran preocupación para el código de interrupción, y es poco probable que un compilador cambie la forma en que maneja los campos de bits en una versión más reciente.

1voto

Neil Foley Puntos 1313

De hecho, los campos de bits están muy mal especificados y el único propósito para el que se pueden utilizar con seguridad es para "trozos de banderas booleanas" ( ver esto para ver ejemplos de por qué no debe utilizar campos de bits).

Aun así, tampoco tiene sentido utilizar campos de bits para ese fin, porque hay formas mejores. En tu caso:

typedef uint8_t ISRstatus;

volatile ISRstatus status;

if(status & (1 << isr_n))
{
  // flag is set
}
else
{
  // flag is not set
}

Con las optimizaciones activadas, esto debería reducirse al mismo código máquina (una comprobación de bits/conjunto de bits). Las ventajas de lo anterior son:

  • 100% portable entre compiladores, microcontroladores y sistemas. El orden de los bits está garantizado, no hay relleno, no hay tonterías, el código se vuelve endianess-independiente.
  • 1 << n es el estándar de facto de la industria de la incrustación para acceder a un bit en C.
  • Permite cosas más complejas como if(status & ISR_MASK_1_4) donde ISR_MASK_1_4 sería 0x0F .

1voto

Alex Puntos 1140

Tanto si activas y desactivas los bits manualmente como con un campo de bits, esto podría fallar si se utiliza en un microcontrolador que no tiene instrucciones atómicas para activar y desactivar bits individuales.

En estas circunstancias, el establecimiento y borrado de bits requeriría una lectura-modificación-escritura, lo que en el código principal daría la oportunidad de que se produjera una interrupción entre la lectura y la escritura, borrando un bit que acaba de ser establecido antes de que pueda ser comprobado.

Así pues, tomando como base el código anterior, podría producirse la siguiente secuencia:

  1. La interrupción 0 se dispara y activa ISR0 .
  2. Comprobaciones del código principal ISR0 y comienza a ejecutar el código correspondiente.
  3. El código principal llega a la línea que borra ISR0 y dice ISRstatus en el registro.
  4. La interrupción 1 se dispara y activa ISR1 .
  5. El código principal continúa, borrando ISR0 en el registro. ISR1 ya es cero desde la lectura anterior.
  6. El valor del registro se escribe en ISRstatus causando ISR1 (y ISR0 ).

Si las interrupciones pueden interrumpirse entre sí, esto también podría ocurrir cuando un ISR está activado.

Por lo tanto, a menos que existan instrucciones para fijar y borrar bits atómicamente (y el compilador las utilice), sería más seguro utilizar un almacenamiento separado para estos valores.


Como se menciona en un comentario a la pregunta original, sería más seguro borrar el bit al principio del código, no al final.

Aún más seguro es no tener la misma variable escrita tanto por una interrupción como por el código principal. Por ejemplo, una forma común de tratar la transmisión de datos es tener un búfer circular, con un puntero de escritura incrementado en el código principal y un puntero de lectura incrementado en la interrupción (y viceversa para la recepción de datos).

0voto

John Vrbanac Puntos 1407

Una mejor implementación sería un contador activo para cada fuente de interrupción.

El gestor de interrupciones incrementaría el contador activo correspondiente.

El main() guardaría un valor de contador inicial. entonces durante cada bucle, si el contador guardado no coincide con el contador activo, entonces realiza la operación, entonces incrementa el contador guardado..

Así no se perdería ningún evento de interrupción

Nota:

Si la interrupción se produce más rápido de lo que el gestor de interrupciones puede ejecutar y devolver, entonces nada podría evitar que se pierda la interrupción.

0voto

rksprst Puntos 195

Con avr-gcc (es decir, GCC para microcontroladores AVR de 8 bits) esperaría que el campo de bits se almacenara en un archivo int . Un int es de 16 bits por defecto en avr-gcc mientras que el tamaño de palabra que el procesador puede leer y almacenar atómicamente es de 8 bits.

Teniendo en cuenta este caso, el uso de un uint8_t valor con #define d constantes y las manipulaciones habituales de bits parecen ser más portables.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X