Search code examples
variablesstaticcoding-styleembeddedconventions

Are global variables as frowned upon in embeded systems programming (C)?


I learned years ago, that in the application world, global variables are a "bad" or "frowned upon", so it became a habit to try to avoid them and use them very scarcely.

Seems like that in the embedded world they are almost unavoidable when it comes to working with hardware interrupts. They also have to be made volatile so that the compiler does not optimize them out if it sees them never being touched in the running program.

Are both of these statements true ? is there a way to avoid those variables in the case I described without bending too far backward ?


Solution

  • Seems like that in the embedded world they are almost unavoidable when it comes to working with hardware interrupts. They also have to be made volatile so that the compiler does not optimize them out if it sees them never being touched in the running program.

    Are both of these statements true ? is there a way to avoid those variables in the case I described without bending too far backward ?

    Neither of those statements are true.

    First, let's clarify that by global variables we mean file scope variables that have external linkage. These are variables that could be called upon with the extern keyword or by mistake.

    With regard to the first statement:

    Seems like that in the embedded world they are almost unavoidable when it comes to working with hardware interrupts.

    Global variables are avoidable when working with hardware interrupts. As others have pointed out in the comments, global variables in an embedded environment are not uncommon, but they aren't encouraged either especially if you can afford to implement proper encapsulation. This article, which someone provided in the comments to your question, actually contains a reader response that provides a good example of where a proper implementation of encapsulation was not possible (you don't have to go far, it's the first one).

    With regard to the second statement:

    They also have to be made volatile so that the compiler does not optimize them out if it sees them never being touched in the running program.

    This statement is, let's say 'almost true'. The compiler knows when the memory location for a variable needs to be accessed (written to/read from memory) so when optimization is turned on it will avoid unnecessary memory access. The volatile keyword tells the compiler not to do that, which means access to that memory location will happen every time the variable is used.

    Cases where using the volatile keyword is necessary

    • Global variable(s) updated an interrupt
    • Global variable(s) accessed by multiple threads in a multi-thread application
    • Memory-mapped peripheral registers

    In the case of a global variable that is updated by an interrupt, the volatile keyword is imperative because the interrupt can happen at any time and we do not want to miss that update. For global variables that are not updated by an interrupt and the application is single threaded, the volatile keyword is completely unnecessary and can actually slow your code down since you'll be accessing the memory location for that variable every time!

    is there a way to avoid those variables in the case I described without bending too far backward ?

    The answer to that really depends. Probably most important is what do you have to gain from making this design change? Also, is this a professional project, one for school, or one for fun?

    From my experience as an engineer, time to market is often times the most important thing a company is worried about when developing a new product. There is usually going to be some legacy code that you get stuck with that was developed during the research and development phase, and it 'works' so why spend time to fix something that isn't broken? Seriously, it better be a very convincing argument otherwise don't waste your time.

    From my educational experience, taking the time to go back and implement a proper design philosophy and document it is definitely worth it, but only if you actually have the time to do so! If you are close to a deadline, don't risk it. If you are ahead of the game, do it. It'll be worth it in more ways than one.

    Lastly, to properly encapsulate the Interrupt Service Routine (ISR) for a hardware interrupt, you need to place it in an actual device driver (CAN, UART, SPI, etc). All communication with the ISR should be facilitated by the device driver and device driver only. Variables shared between the ISR and the device driver should be declared static and volatile. If you need access to any of those variables externally, you create setters and getters as part of your public API for the driver. Check this answer out for a general guideline to follow.