Search code examples
cmacrosc-preprocessorcompiler-optimizationavr-gcc

How does C compiler or Preprocessor handle MACRO with arguments differently?


I am working on a code for the Atmel Microcontroller and using ATMEL Studio.

You can check the toolchain and studio version from here.

*\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\lib\gcc\avr\5.4.0*

I have a code in my two programs.

CASE_1:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)BAUD_RATE)) + 0.5)
USART1.BAUD = (uint16_t)USART_BAUD_RATE(300);

CASE_2:

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)BAUD_RATE)) + 0.5)
/* uint32_t my_BaudRate = 300; //I set this value in the program, normally it's 115200, 2400, but sometimes it can be 300*/
USART1.BAUD = (uint16_t)USART_BAUD_RATE(my_BaudRate );

Sample Example:

#include <stdio.h>
#include <stdint.h>

#define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)(BAUD_RATE))) + 0.5)

uint16_t getValue_1() {
    return (uint16_t)USART_BAUD_RATE(300);
}

uint16_t getValue_2(uint32_t inVal) {
    return (uint16_t)USART_BAUD_RATE(inVal);
}

int main()
{
    printf("Macro with Constant argument: %d\r\n", getValue_1());
    printf("Macro with variable argument: %d\r\n", getValue_2(300));
    return 0;
}

// Output
/* 
Macro with Constant argument: 65535
Macro with variable argument: 1131
*/

I found out that the USART.BAUD has different values in both programs although it should be the same if I set the my_BaudRate to 300.

In Program 1: USART1.BAUD has the value 0xFFFF, although it should have been overflown and had 0x046B. I tried changing the BAUD_RATE value to 280 or less, it always sets it to 0xFFFF. Only after I set the value to 306, it sets the actual value less than 0xFFFF.

In Program 2: it works as expected, USART1.BAUD has the value 0x046B.

I think it has something to do with preprocessor or compiler optimization, but I am not sure if this is a known behavior or if I need to be careful about these kinds of macros.

I Would be grateful for any insights.

Best Regards.


Solution

  • I'm pretty sure the difference between the two cases is that the invocation

    USART_BAUD_RATE(300)

    Will be compiled by the optimiser into load a constant value which is computed at compile time (potentially more accurately and better controlled than at runtime). This contrasts with the second example passing a variable when the runtime system is obliged to do all the computation. It would be worth taking a look at the assembler code listing to see if this is the case.

    It is worth commenting on the lost art of efficient integer arithmetic for embedded systems. Casting to float is unnecessary (and can bring in a lot of extra baggage on embedded systems that lack hardware FP support).

    #define USART_BAUD_RATE(BAUD_RATE) ((float)(5000000 * 64 / (16 * (float)(BAUD_RATE))) + 0.5)
    

    The first float probably wants to be inside the numerator too (on some systems it may overflow).

    This can be rewritten in a pure integer form without much difficulty.

    #define USART_BAUD_RATE(B) (( 5000000 * 64 + 8*(B)) / (16 * (B))
    

    Or if you can be sure the denominator multiplier will always be at least a multiple of 8 it can be simplified to

    #define USART_BAUD_RATE(B) (( 5000000 * 8 + (B)) / (2 * (B))
    

    If you want the answer modulo 0xffff then do a bitwise & to get the result into the right range and if you want saturation then min(x,0xffff). Assigning an out of range value to a smaller type is undefined behaviour (although in many cases it may appear to do what you expect - until one day it doesn't). I'm assuming some of these constants are clock frequency and hardware divider ratios so that the multiplies are all needed,