Cornell University
Electrical and Computer Engineering 4760 AVR mega644/1284
Mixing assembler with GCC
Introduction
It is possible to mix assembler and GCC in different ways:
- Separate file. Write a pure assembler
*.S
file 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 aNAKED
interrupt 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 lss
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
The lss
file can tell you other stuff also. If you search for __vectors
,
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
the 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
added:
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 main
. The 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 of the variable...
Thank you so much for this! Last week I was playing with the NTSC generator code from your class website. It was no trouble to get it working on an ATmega328. For me this is very topical, because I've been paying particular interest to how the ASM is used in that case!
Such an incredible value for those of us learning at home!
Soon I'm going to try to port it to an ARM chip to learn the differences.
Years ago I was in the second class of the free online "how to program a self driving car" class, and I did 85% of it but not the final project. I really learned a lot, and came away from the experience really happy and with a lot of useful knowledge. And then a couple years later, I saw an interview with the professor, talking about how it was such a failure because so few people "finished." I was really disappointed in his attitude, I felt like he wasn't even asking if they learned anything, just worrying if they completed the checklist. I feel like, what you're doing of just making the material available outside of all the checklists is all that is needed. You are the future of education! The mainstream educator, whose lessons are also available for free for people who either don't want or don't have access to the full classroom experience.