Saturday, August 9, 2014

Global variables are good

It's a rather absolute statement, to the point of being ridiculous.  However many embedded systems "experts" say global variables are evil, and they're not saying it tongue-in-cheek.  In all seriousness though, I will explain how global variables are not only necessary in embedded systems, but also how they can be better than the alternatives.

Every embedded MCU I'm aware of, ARM, PIC, AVR, etc., uses globals for I/O.  Flashing a LED on PB5?  You're going to use PORTB, which is a global variable defining a specific I/O port address.  Even if you're using the Wiring API in Arduino, the code for digitalWrite ultimately refers to PORTB, and other global IO port variables as well.  Instead of avoiding global variables, I think a good programmer should localize their use when it can be done efficiently.

When using interrupt service routines, global variables are the only way to pass data.  An example of this is in my post Writing AVR interrupt service routines in assembler with avr-gcc.  The system seconds counter is stored in a global variable __system_time.  Access to the global can be encapsulated in a function:
uint32_t getSeconds()
    uint32_t long system_time;
    system_time = __system_time;
    return system_time;

On a platform such as the ARM where 32-bit memory reads are atomic, the function can simply return __system_time.

Global constants

Pin mappings in embedded systems are sometimes defined by global constants.  When working with nrf24l01 modules, I saw code that would define pin mappings with globals like:
uint8_t CE_PIN = 3;
uint8_t CSN_PIN = 4;

While gcc link-time optimization can eliminate the overhead of such code, LTO is not a commonly-used compiler option, and many people are still using old versions of gcc which don't support LTO.  While writing a bit-bang uart in assembler, I also wrote a version that could be used in an Arduino sketch.  The functions to transmit and receive a byte took a parameter which indicated the bit timing.  I wanted to avoid the overhead of parameter passing and use a compile-time global constant.

Compile-time global constants are something assemblers and linkers have supported for years.  In gnu assembler, the following directives will define a global constant:
.global answer
.equ answer 42

When compiled, the symbol table for the object file will contain an (A)bsolute symbol:
$ nm constants.o | grep answer
0000002a A answer

Another assembler file can refer to the external constant as follows:
.extern answer
 ldi, r0, answer

There's no construct in C to define absolute symbols, so for a while I didn't have a good solution.  Gcc supports inline assembler.  I find the syntax rather convoluted, but after reading the documentation over, and looking at some other inline assembler code, I found something that works:
// dummy function defines no code
// hack to define absolute linker symbols using C macro calculations
static void dummy() __attribute__ ((naked));
static void dummy() __attribute__ ((used));
static void dummy(){
asm (
    ".equ TXDELAY, %[txdcount]\n"
    ::[txdcount] "M" (TXDELAYCOUNT)
asm (
    ".equ RXSTART, %[rxscount]\n"
    ::[rxscount] "M" (RXSTARTCOUNT)
asm (
    ".equ RXDELAY, %[rxdcount]\n"
    ::[rxdcount] "M" (RXDELAYCOUNT)

The inline assembler I used does not work outside function definitions, so I had to put it inside a dummy function.  The naked attribute keeps the compiler from adding a return instruction at the end of the dummy function, and therefore no code is generated for the function.  The used attribute tells the compiler not to optimize away the function even though it is never called.

Build constants

The last type of constants I'll refer to are what I think are best defined as build constants.  One example would be conditionally compiled debug code, enabled by a compile flag such as -DDEBUG=1.  Serial baud rate is another thing I think is best defined as a build constant, such as how it is done in optiboot's Makefile.

No comments:

Post a Comment