Learning inline assembly language on the Arduino AVR 8-bit platform is a daunting task for many (at least it was for me). Besides the cryptic syntax and the high level of understanding the semi-official documentation assumes, there exists very little information about GCC inline assembler coding. The main focus of existing documentation is the “Cookbook”, which dates from 2002. Trust me, any neophyte needs to spend hours searching and studying piece-meal examples while possessing overwhelming patience in order to crack the code. Again, at least I did. Hopefully this series of tutorials will help alleviate many of the discouraging troubles I encountered while teaching myself inline assembly coding. An Arduino Inline Assembly Tutorial, like this was long overdue!
In the Beginning
An inline assembly statement is a string of characters which specifies assembler code. The string can contain any instructions recognized by the assembler, including directives, but we will not discuss assembler directives here. GCC does not parse the assembler instructions and does not know what they mean or even whether they are valid. Multiple assembler instructions can be placed together in a single asm string.
Simply Inline Assembly
Here is the most basic example of an inline assembler statement:
asm ( "nop \n");
NOP is the opcode, or instruction for “No Operation”. This statement simply inserts a NOP into your program. NOP does nothing except take up program memory and waist processor time. NOP might seem worthless, however it does come in handy in several ways.
The general form of an inline assembler statement is:
asm( “code \n”);
The assembly code is encased in parenthesis preceded by the compiler keyword asm or __asm__. Each assembler instruction is enclosed inside quotations and terminated with the escape sequence for the linefeed character, “\n“. Sometimes you will see the line terminated with “\n\t“. The tab sequence is added after the linefeed simply to format the output.
The compiler takes each assembly string, strips off the quotation characters and passes the code verbatim onto the assembler. The avr-as assembler requires a single instruction per line, hence the need for the linefeed.
The Fine Print
Do not expect a sequence of multiple asm statements to remain perfectly consecutive after compilation. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement. GCC’s optimizers can move asm statements relative to other code, including across jumps.
asm statements may not perform jumps into other asm statements. GCC does not know about these jumps, and therefore cannot take account of them when deciding how to optimize. Jumps from asm to C labels are only supported under extended asm.
Under certain circumstances, GCC may duplicate (or remove duplicates of) your assembly code when optimizing. This can lead to unexpected duplicate symbol errors during compilation if your assembly code defines symbols or labels. And since GCC does not parse the Assembler instructions, it has no visibility of any symbols it may reference.
The compiler pre-processor does not perform macro expansion within strings. This is the reason why you can’t use macro definitions, like so:
#define NoOperation NOP __asm__("NoOperation \n”);
Instructions Sans Operands
You may be surprised to learn that the Basic asm statement uses only assembly instructions without operands. An operand is the part of a computer instruction which specifies what data is to be manipulated or operated on. Basic assembly is limited to just pure instructions. That’s it. To take advantage of more advanced features and processing, or any of the ‘%’ operators, one must use the “extended asm” (more on the Extended Assembler later).
What’s An Inline To Do?
So what can we do with the basic inline asm statement besides insert NOPs? There are 24 assembly instructions without operands, 16 of which deal with clearing and setting bits inside the Status Register (SREG). We will cover the Status Register in greater detail later. Beside the 16 SREG specific instructions, the other instructions are NOP, BREAK, SLEEP, WDR, ICALL/IJMP, RET/RETI.
ICALL, IJMP, RET and RETI are advanced instruction dealing with program flow, and we will also cover them at a later time. WDR resets the Watchdog timer and is rarely used in the Arduino. SLEEP instructs the Arduino to go into a low power sleep mode.
However, two operand-less instructions are very useful. These two instructions enable or disable global interrupts. CLI, which is the mnemonic for “CLear global Interrupt flag”, clears the Global Interrupt flag (I) in the Status Register (SREG). No interrupts will be executed after the CLI instruction, or even if the interrupt occurs simultaneously with the CLI instruction.
asm ( "cli \n");
SEI is the reverse of CLI, and sets the interrupt flag.
asm ( "sei \n");
Both CLI and SEI are defined as C MACROS inside the AVR and Arduino libraries, similarly like so:
#define CLI() asm("cli \n") #define noInterrupts() cli() #define SEI() asm("sei \n") #define interrupts() sei()
So far we have discovered only minimal usefulness for the inline assembler. But we now know the fundamentals of the inline assembler syntax. In the next tutorial we will expand upon these basics by studying the extended inline assembler.
Also available as a book, with greatly expanded coverage!