Tips and trick for efficient programming

As well as the usual things a good programmer will do, there are some issues specific to mspgcc which you should be aware of.

  1. If you are sure your main routine will never exit, you can use the "-mendup-at=main" flag when compiling. This will save 6 bytes of ROM.

  2. Avoid passing long argument lists to functions. Avoid returning long values from functions. The most efficient function types to use are void, int, or pointer.

  3. Avoid initializing global variables within a small function. Instead, assign a value during variable definition.

  4. Avoid converting chars to another type. char variables can be located anywhere in RAM, while word variables can only be at even addresses. Because of this, the following code:

    const char *a = "1234";
    int k;
    k = *((int *)((char *a) + 3));
    
    will result in unpredictable CPU behaviour.

  5. Use int instead of char or unsigned char if you want a small integer within a function. The code produced will be more efficient, and in most cases storage isn't actually wasted.

  6. Inspect assembler code (-S compiler flag). The compiler cannot eliminate dead code in some cases. Do not write dead code :)

  7. Do not declare your own SFRs. They are all declared in include files in the right way to achieve maximum code performance.

  8. Try to minimise the use of addition and subtraction with floating point numbers. These are slow operations.

  9. Use shift instead of multiplication by constants which are 2^N (actually, the compiler may do this for you when optimization is switched on).

  10. Use unsigned int for indices - the compiler will snip _lots_ of code.

  11. Use 'switch/case' constructs rather than a chain of 'if/else' constructs.

  12. Use logical or ('|') rather than '+' for bitmasks.

  13. When defining bit fields, try to use signed integers. This produces more compact code that bit fields of unsigned integers.

  14. Use 'alloca' instead of 'malloc' for locals. In embedded applications trying to avoid any dynamic memory allocation is usually even better ;).

  15. C++ recomendations aside :-), it can be more efficient to use:

    #define SMTS 1234567890l
    
    rather than declaring
    const long smts = 1234567890l;
    

  16. Delay loops are very sophisticated routines.:-) Developers often do something like:

    int i = 1234;
    
    while (i--);
    
    or
    int i;
    
    for (i = 0;  i < 1234;  i++);
    
    NEITHER WILL WORK AS YOU EXPECT when optimisation is switched on!!! The optimizer will detect dead code in both examples and will eliminate it. It might even eliminate the loop completely. Adding the volatile attribute to the definition of 'i' might help, but don't count on it if 'i' is a local variable. The compiler can still detect the calculations are wasteful, and eliminate them.

    Regardless of these optimisation issues, this type of delay loop is poor programming style - you have no idea how long or short a delay it might produce (although there is an obvious minimum bound!). It would be better, and more reliable to define something like:

    static void __inline__ brief_pause(register unsigned int n)
    {
        __asm__ __volatile__ (
    		"1: \n"
    		" dec	%[n] \n"
    		" jne	1b \n"
            : [n] "+r"(n));
    }
    
    and call this routine where needed. This is simple, compact, and predictable.

  17. Use the volatile attribute for variables which may be changed or inspected by more than one processing thread (e.g. the main code and an interrupt routine). If you are used to less efficient compilers, you may be used to writing code like:

    int i;
    
    interrupt (TIMERA0_VECTOR) irq_routine(void)
    {
        i = 42;
    }
    
    void func(void)
    {
    
        i = 0;
        while (i == 0);
    }
    
    If we assume interrupts are working, and "func" is called, you might expect "func" to return after a timer A interrupt occurs. The optimiser will produce code that actually holds "i" in a register within "func", and the loop in "func" will never respond to the change to the memory location for "i", caused within the interrupt routine. The compiler may even eliminate the test of "i" completely, leaving a true infinite loop.

    To tell the compiler that it must look at the actual memory location of "i" each time it checks or manipulates "i", the "volatile" attribute should be added the definition of "i".

  18. Avoid using the "volatile" attribute, unless it is actually necessary. Because it forces the compiler to generate additional code to load, store and test a variable's memory location under all circumstances, it significantly reduces the effectiveness of the compiler's optimiser. The temptation to make lots of global definitions "volatile", just to err on the safe side, comes at a price.

Try to avoid doing tricky things in your code, unless you are really confident you understand their consequences. :-)