Need help passing defined C constants to asm instruction

I have the following code in a C program for a ATmega microcontroller:

define sbi(port,bit) asm("SBI " #port "," #bit)
[...]
sbi(PORTB,1);

The problem is that the compiler does not recognize PORTB when it's processing the assembly code, despite being defined in an included header file. Is there a way to tell the precompiler to put the defined value of PORTB (0x1b) in the ASM instruction rather than the literal text PORTB? The assembly instruction works fine when I manually do this like so:

sbi(0x1b,1);

But for obvious reasons I'd like to be able to use PORTB and other defined names in my C code.

Please note, I'm only using this ASM macro for defined constants, not variables, so I don't need anything too fancy to bridge between my ASM macro and my C code. I just need the preprocessor to replace PORTB with 0x1b in the ASM string.


Solution 1:

You generally should not use stringification (the macro # operator) to construct strings for the asm construct. Instead, use the extended asm syntax.

To put operands into assembly instructions, use that syntax with constraints that tell the compiler how it may give the operands to the instructions. For immediate operands, use i:

asm("SBI %[port], %[bit]"
    : // Output operands would be here.
    : // Input operands are here.
        [port] "i" (PORT),
        [bit]  "i" (1)
);

The %[port] in the instruction string is replaced by the operand named port. The [port] in the operand list later on says the name of that operand is port. Of course this matches PORT except for the case, but you can use other names.

The "i" says to use an immediate operand, which must have a compile-time constant value.

The PORT in (PORT) is an expression that gives the value to use for the operand. For example, you could also use (PORT+1).

The 1 operand could be hard-coded, but the above shows how to keep it flexible.

For a default immediate operand with i, GCC and Clang include the punctuation that marks an immediate operand in assebmly code, such as $ in some assemblers. If this is a problem for some reason, you can use %c[port] instead of %[port] to get the bare operand, and insert the # manually, as with:

asm("SBI #%c[port], #%c[bit]"
    : // Output operands would be here.
    : // Input operands are here.
        [port] "i" (PORT),
        [bit]  "i" (1)
);