How to avoid global variables when using interrupt handlers?

I am mostly self taught in C. I program embedded microcontrollers. (eg dsPIC33fj128gp804) I usually use globals and everything I've ever read denounces using globals like the plague. I have been working on using less, but there is a scenario that I don't know how not to use globals.

The microcontroller is equipped with interrupts. An interrupt is an event triggered by external equipment. When interrupts are triggered, execution of the main code stops, the current runtime variables are saved, the previously assigned function is executed, and then the main code returns to where it left off. Because an interrupt is a separate function that can run at any time and nothing can be passed to or from the function.

For example, when a UART hardware receives a byte of data, that data must be removed from the hardware buffer before it is overwritten.

void __attribute__((interrupt, no_auto_psv)) _U2RXInterrupt(void)
{
    GlobalVariable = U2RXREG; // Move data to global variable
    IFS4bits.U2RXIF = 0; // Clear the UART2 Receive Interrupt Flag
}

      

Is there a way to do this without globals or is this an exception?

+4


source to share


2 answers


A distinction is made between a globally linked variable and a static file scope variable. You can solve your problem with the latter.

static volatile int shared_variable ;

int getShared(){ return shared_variable ; }

static void isr_handler()
{
    shared_variable++ ;
}

      

Thus, in the above example, the only access to a shared variable external to the translation unit is through the accessor function getShared()

. This method is, of course, based on using a separate compilation, but for many reasons this is not a bad thing.

For other methods to avoid globalization, and for an explanation of why you should do it, see Jack Gansl: Smallpox on Globals.



Another thing to consider, and the reason why globals are especially problematic in this case, is that the shared variable must be either atomic or accessible in a critical section. For example, in a 16-bit dsPIC, 32-bit accesses are not atomic, in which case the necessary protection can be placed in an accessor function, whereas if it were global, each access would have to be individually protected:

For example:

static volatile uint32_t shared_variable ;

int getShared()
{ 
    uint32_t ret ;

    _disable_interrupts() ;
    ret = shared_variable ;
    _enable_interrupts() ;

    return ret ;
}

      

+3


source


Use static global values โ€‹โ€‹as much as possible so that the variable is only in scope in that particular source file. Use static variables declared in functions that use them for even more isolation.

Use volatile for all variables used in both interrupt routines and the main code loop.

Note that volatile does NOT mean that you are "safe" using this variable between the ISR and the main code. This is NOT guaranteed to be Atomic access accessed by a variable with a single CPU instruction. For example, a 16-bit variable on an 8-bit chip will take multiple read instructions to read the value. If the interrupt is triggered between you, you will have corrupted 16-bit data because only half of the variable was read. First 8-bit before ISR and another 8-bit after ISR return. This is bad data and can cause huge problems if pointers are involved, rather than just passing the ADC data value, for example. This can lead to stackoverflow.



Simple access should quickly disable interrupts, read the value, and change them to provide sequential access.

With small embedded systems using static globals, it's a great way to go, in my opinion, as long as you keep it to a minimum and straight to the point! Use structures to further subdivide globals into smaller ones.

Globals are only "evil" when you have too many and they refer to them back and forth across many files. It just gets very messy and you can easily create another variable that has the same name as another existing global. Not good.

+1


source







All Articles