Electronic – Using bit fields in interrupt-driven applications

cinterruptsmicrocontroller

When implementing interrupt-driven applications I usually create a bitfield to keep track of different interrupts. For example:

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

The rest of the application might have the following structure:

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

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

Recently I came across a few articles that suggested avoiding bitfields due to their unexpected behavior across different compilers and architectures.

Assuming my compiler is GCC, is it a bad idea to use this approach?

Best Answer

CORRECTION: As Ben (and other commenters) have pointed out, clearing the status flag in the main code is a problem. Writes to bit fields are normally implemented as a read-modify-write, where (in your case) the full byte is read, then one bit is set or cleared, then the modified byte is written back. In pseudo-code, ISRstatus.ISR0 = 0 would become:

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

The problem here is that an interrupt can come in the middle of this sequence. For example, let's say that the ISR0 flag is set and interrupt 5 comes in. What happens is:

<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;
}

In this example, ISRstatus should be equal to 0x20 after the if statement, but instead it's equal to 0x00. The ISR5 flag got lost.

The way to fix this is to disable interrupts when writing to the global variable in your main code. (Reads are safe as long as the entire structure is loaded at once, which it should be for an 8-bit structure.)

The C standard does not guarantee any particular ordering or packing of bit fields. This means that using bit fields to access data stored in a specific format (like register or packet header fields) is not portable. If there's only one compiler for your CPU, portability won't be a problem, so you can get away with it.

My reading of the standard is that bit fields are intended to be used in exactly the way you're using them. Just keep the limitations in mind.

EDIT v2: The compiler probably won't let a single bit field cross a storage unit boundary. Your compiler manual should have more information, but it might take some trial and error to figure out the edge cases. Since all you care about is the data in the individual fields and not their arrangement within the storage unit, this shouldn't matter.

All that being said, portability is usually not a huge concern for interrupt code, and it's unlikely for a compiler to change the way it handles bit fields in a newer version.