Electrical and Computer Engineering 4760 AVR mega644/1284
Mixing assembler with GCC
It is possible to mix assembler and GCC in different ways:
- Separate file. Write a pure assembler
*.Sfile and link it with the main C file.
This approach has the advantage of simpler syntax in a separate file. Another advantage is that C saves/restores registers for you (see below).
It has a runtime speed disadvantage for short assembler routines due to the function linkage generated by C.
- Inline assembler. Write inline assembler directly into the C code.
You have to save/restore any registers you use. You get exactly what you write unless you use register constraints.
If you use constraints the compiler can play with the register assignments, but specifiying the constraints is bewildering.
The only runtime speed penality is the save/restore overhead, but you may be able to avoid the overhead with register constraints.
Inline assembler seems to be the only way to write a
NAKEDinterrupt service routine.
- Assembler macro. Write an assembler macro and instantiate it in C (reusable inline assembler).
This is useful if you need to use a short chunk of inline assembler many times in a program.
The runtime speed is very good and register constraints allow the GCC optimizer to play with the code.
Before you actually write any assembly code you will need to read the instruction set architecture and description of AVR opcodes, and look at a bunch of assembler examples. Some examples are below. There are some tutorials, for instance scienceprog and Mixing C and asm. I find that the best way to learn assembler is to look at the assembler output of the compiler. In AVRstudio projects, the
*.lss assembler listing file is in the
default folder (in the project folder). Code up a few lines of C, open the
file, search for a line of C (included as comment by the compiler), and
see what the compiler did. There is an example below of compiler output
from a video generator I wrote in C (assembler comments added for this
page). The code line is in a function with x the first (char) parameter
and y the second.
;C comment: int i = (x >> 3) + (int)y * bytes_per_line ; ldi r24, 0x14 ; bytes_per_line=20=0x14 mul r22, r24 ; since y was the second (char) parameter of a function call, it is in r22 movw r26, r0 ; 2-byte move to get product at r27:r26 eor r1, r1 ; MUST clear r1 after a mult mov r24, r30 ; The compiler had moved the first parameter from r24 to r30 lsr r24 ; 3x lsr for the >>3 lsr r24 lsr r24 add r26, r24 ; do the add adc r27, r1 ; and add the carry to the high byte using r1=0 register
lss file can tell you other stuff also. If you search for
you will get the interrupt service routine entry points. You will see
by following the undefined interrupt vectors, that GCC defaults to
resetting the MCU for any undefined interrupt. The zero entry point is
RESET vector where program execution starts. Searching
for that address will lead you to the MCU and C initialization code. The
first few lines of the reset code are shown below with my comments
eor r1, r1 ; clear r1 (C assumes r1 equals zero) out 0x3f, r1 ; zero the SREG which is i/o register 0x3f ldi r28, 0xFF ; load the low byte of the top-of-memory address ldi r29, 0x40 ; load the top byte of the top-of-memory address out 0x3e, r29 ; store the top byte in top byte of stack pointer (i/o register 0x3e) out 0x3d, r28 ; store the low byte in low byte of stack pointer (i/o register 0x3d)
The next few lines shown in the
lss file clear memory and set up the C environment, then jump to
map file in the same folder will show you where the variables are stored in RAM.
Syntax and registers
Global variables defined in C are available to the assembler. For a global variable defined in C as
volatile char vname;
Using the declared C variable name in a load/store command like those below loads/stores the value...