1: Now I understand that when small file size and efficiency are highly desirable then the assembly is the way to go. But I still don't really get why the code in C takes more space and is slow when compared to a code in the assembly to do the same job. We can say that the C takes circuitous route to reach the destination while the assembly takes the shortest route available. But what's the reason for the C to take a longer route? How can we analogize it better?
C compiler has to "play everything safe" and it has to do this in very general level. This is a typical AVR-GCC generated interrupt routine (and explanation what happens during it):
Every AVR register that gets modified during the ISR is restored to its original value when the ISR exits. This is required, as the compiler can not make any assumptions on the time of execution of the interrupt. Therefore, it cannot optimize which registers require saving and which don't.
1. Program Counter (PC) is pushed to the stack. (This step is similar to normal function call)
• This is done by hardware
• Needed when returning from the ISR to the original program location
2. Execution jumps to the start of the ISR, interrupts are disabled.
3. Some registers are saved to the stack
This is done by a function entry code generated by the compiler:
PUSH R1 Push register on stack
PUSH R0 Push register on stack
IN R0,0x3F In from I/O location (0x3F = Status Register)
PUSH R0 Push register on stack
CLR R1 Clear Register (R1 must always be zero)
PUSH R24 Push register on stack
PUSH R25 Push register on stack
PUSH R26 Push register on stack
PUSH R27 Push register on stack
PUSH R30 Push register on stack
PUSH R31 Push register on stack
PUSH R28 Push register on stack
PUSH R29 Push register on stack
4. User written code is executed.
5. Previously saved registers are restored from the stack.
- Function exit code generated by the compiler:
POP R29 Pop register from stack
POP R28 Pop register from stack
POP R31 Pop register from stack
POP R30 Pop register from stack
POP R27 Pop register from stack
POP R26 Pop register from stack
POP R25 Pop register from stack
POP R24 Pop register from stack
POP R0 Pop register from stack
OUT 0x3F,R0 Out to I/O location (0x3F = Status Register)
POP R0 Pop register from stack
POP R1 Pop register from stack
RETI Interrupt return (Also enables interrupts)
6. The saved Program Counter is popped from the stack and the next instruction to be executed, is the address where this program counter points.
You can easily mix ASM and C, but you also have to be very careful with it.
If you are going to interface assembly routines with your C code, you need to know how GCC uses the registers. This section describes how registers are allocated and used by the compiler.
Register Use
r0: This can be used as a temporary register. If you assigned a value to this register and are calling code generated by the compiler, you’ll need to save r0, since the compiler may use it. Interrupt routines generated with the compiler save and restore this register.
r1: The compiler assumes that this register contains zero. If you use this register in your assembly code, be sure to clear it before returning to compiler generated code (use "clr r1"). Interrupt routines generated with the compiler save and restore this register, too.
r2–r17, r28, r29: These registers are used by the compiler for storage. If your assembly code is called by compiler generated code, you need to save and restore any of these registers that you use. (r29:r28 is the Y index register and is used for pointing to the function’s stack frame, if necessary.)
r18–r27, r30, r31: These registers are up for grabs. If you use any of these registers, you need to save its contents if you call any compiler generated code.
Function call conventions
Fixed Argument Lists: Function arguments are allocated left to right. They are assigned from r25 to r8, respectively. All arguments take up an even number of registers (so that the compiler can take advantage of the movw instruction on enhanced cores.) If more parameters are passed than will fit in the registers, the rest are passed on the stack. This should be avoided since the code takes a performance hit when using variables residing on the stack.
Variable Argument Lists: Parameters passed to functions that have a variable argument list (printf, scanf, etc.) are all passed on the stack. char parameters are extended to ints.
Return Values: 8-bit values are returned in r24. 16-bit values are returned in r25:r24. 32-bit values are returned in r25:r24:r23:r22. 64-bit values are returned in r25:r24:r23:r22:r21:r20:r19:r18.
Sorry for long post. The quotes are from my school project documentation. The assignment was to write "real time scheduler" for AVR microcontrollers using AVR-GCC compiler. Practically useless, but very educational.
Last edited: