Search code examples
cembeddedglobal-variablesencapsulation

Is this strategy, to avoid global variables in C, right?


in my (personal) embedded project global variables are piling up. I need those variables to be acessible from the ISR (interrupt service routine) and/or from a menu system (so that they can be modified by the user) but at the same time I'd like to avoid having too many globals.

Since they can be grouped for modules I thought that I can encapsulate them in their own .c file, declared as static or static volatile , and expose to the outside world some functions to handle them.

Something along the line of:

in module1.c

#include module1.h

static volatile int module1_variable;

int getModule1Var(void){
    return module1_variable;
}

void setModule1Var(int i){
    //some code to disable interrupts for atomic operations
    module1_variable = i;
    //some other code to re-enable interrupts
    return;
}

module1.h would contain the function prototypes, structs and all the other elements for making the module work except for the static variable definition of course

in main.c

#include module1.h

void main(){
    //setting the variable value, could be done from a menu 
    setModule1Var(15);
    //remaining application code
}

void generic_ISR(){
    //trivial usage example
    doSomething(getModule1Var());
    return;
}

This scheme would naturally be extended to the other modules.

Now my questions are:
is this a good approach? Is it the same to simply have a bunch of globals? Any major drawbacks?

I also thought I could use some sort of mix like still having the global variables to allow direct manipulation by the ISR (since function calls from the ISR are sometimes frown upon) and the functions in every other case. Would this be better?


Solution

  • There are several questions in there:

    is this a good approach?

    Yes. There is perhaps an argument for placing the ISR in the same module as the access functions and data it shares, treating it as an access function itself allowing it to read the data directly without the access wrapper overhead.


    Is it better/worse/equal to simply have a bunch of globals?

    Much worse. With a global where are you going to place your access mutex, data validation or breakpoints? It increases module coupling, and that should always be minimised.


    Any major drawbacks?

    Not really. There is a function call overhead which may be critical in an ISR; but if that were an issue you would not be calling printf() in an ISR! You can mitigate any possible performance penalty applying my earlier suggestion about placing the ISR in the data module, or by in-lining the code - but your compiler is free to ignore that, and may do so if debugging is enabled, and may also inline regardless if optimisation is enabled. Some compilers have a "forced" inline extension that the compiler is not allowed to ignore; but it is not portable.

    One significant benefit is that if the variable is non-atomic, you need some access protection mechanism to ensure consistent access - and that is most easily and safely performed by having access functions. In this case you might have get / set functions that disable and re-enable the interrupt around the access for example, or use a spin-lock (suitable for where the ISR writes and the normal context reads - not the other way around as you have here - don't lock the ISR indefinitely!) .


    I also thought I could use some sort of mix like still having the global variables to allow direct manipulation by the ISR (since function calls from the ISR are sometimes frown upon) and the functions in every other case. Would this be better?

    Not really - see my point above about function call overhead and placement of the ISR with the data. The problem with calling functions in an ISR is seldom a time-overhead issue, but rather that the implementer of the function may not have designed it to be called safely and efficiently from an ISR and it may use excessive stack, non-deterministic execution time, busy-wait or not be re-entrant or thread-safe. Calling third-party or standard library functions for example may be ill-advised; calling functions you have specifically written and designed for the purpose and conforming to your specific constraints is not necessarily a problem.


    All you need to know about why globals are a bad idea and appropriate ways to avoid them can be found in Jack Ganssle's article "A Pox on Globals" - it is also an entertaining read.