Search code examples
cgccassemblyc-preprocessoravr

C Macro - how to get an integer value into a string literal


Is it possible to get the value of a #defined integer symbol to be inserted verbatim into a string literal that is part of an assembly section in GCC (AVR Studio)?

I want the "LEDS" to be replaced by 48 within the string literal inside the asm() block below.

#define LEDS 48 //I only want ONE mention of this number in the source
int x = LEDS;   //I'm using the value directly too

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, LEDS       \n\t" //<-- substitution needed here
...
}

But I want the compiler/assembler (after the preprocessor has done it's job) to see this...

#define LEDS 48 //I only want ONE mention of this number in the source
int x = LEDS;   //I'm using the value directly too

void DrawFrame()
{
    asm(
    "ldi        R27, 0x00       \n\t"
    "ldi        R26, 0x00       \n\t"
    "ldi        R18, 48         \n\t" //<-- substitution needed here
...
}

So far I have tried all the macro tricks I can think of (#stringification, arg substitution and even #including files with various combinations of values and double quotes and whatnot).

I'm not at all familiar with the magic of inlining AVR assembly code into AVR Studio's GCC compiler.

I'm trying to avoid having multiple occurrences of the "48" literal in my source, if the preprocessor can perform this substitution for me that would be great.

Edit: This is for a microcontroller firmware project - and just to make life interesting, there is almost no spare room for new code to be added.


Solution

  • I think it's good to have a stringifying macro in your utils header:

    #define STR_IMPL_(x) #x      //stringify argument
    #define STR(x) STR_IMPL_(x)  //indirection to expand argument macros
    

    Then you can keep the macro numerical and stringify it on the spot:

    #define LEDS 48 
    int x = LEDS;   
    
    void DrawFrame()
    {
        asm(
        "ldi        R27, 0x00       \n\t"
        "ldi        R26, 0x00       \n\t"
        "ldi        R18, "STR(LEDS)"       \n\t"
    ...
    }
    

    The above preprocesses to:

    int x = 48;
    
    void DrawFrame()
    {
        asm(
        "ldi        R27, 0x00       \n\t"
        "ldi        R26, 0x00       \n\t"
        "ldi        R18, ""48""       \n\t"
    ...
    }
    

    which relies on the fact that adjacent string literals get concatenated.


    Alternatively, you could create a STR macro that's tolerant of commas:

    #define STR_IMPL_(...) #__VA_ARGS__      //stringify argument
    #define STR(...) STR_IMPL_(__VA_ARGS__)  //indirection to expand argument macros
    

    and then stringify the whole asm body with it as follows:

    asm( STR(
        ldi        R27, 0x00;
        ldi        R26, 0x00;
        ldi        R18, LEDS; //LEDS gets expanded
    )); //renders: "ldi R27, 0x00; ldi R26, 0x00; ldi R18, 48;"
    

    (Personally, I write asm on clang/gcc/tinycc like this because I find it more convenient than explicit string literals). This approach has the potential downside in that the assembly will be just one line and will assemble as long as the assembler also accepts ; as an instruction separator in addition to newlines (works at least for the x86-64 assembler on clang/gcc/tinycc).