How Atmel Studio converts C language code into Assembly

This article has the purpose to explain how Atmel Studio converts C/C++ language software into Assembly code, and then into a HEX file. In a previous article, I already explained how to compile a simple C program using Atmel Studio, and flash it on an Arduino Nano equipped with Atmel ATmega328p microcontroller. The "Hello World" program simply blinks LED 13 ON (about 500ms) and OFF (about 2500ms), so the total cycle time is about 3000ms. The project file, including the C file source code, is available here: nano_helloworld.

The source code is very simple, as visible above. Inside the main() function, the microcontroller port B is at first initialized as output, by the instruction DDRB=0xFF. Then, inside the while cycle, which is repeated indefinitely, the output pin B5 is continuously turned ON and OFF. After turning ON the pin, the microcontroller waits for 500ms, using the function aspetta_100ms(), which repeats a 100ms delay period for 5 times. After turning OFF the pin, the same function is called and repeats 25 times a 100ms delay period, for a total of 2500ms.

The Assembly source file (created by Atmel Studio) is inside the project ZIP file (nano_helloworld), and its name is "Nano_HelloWorld.lss". The complete Assembly code created is visible below.

In the program file, it is written how much ROM memory space is used by each part of the program:

  • "data", which corresponds to initialized global variables, is using 0x02 bytes. This corresponds to the initial values of cicli_ON (initialized to 5) and cicli_OFF (initialized to 25). Each of the 2 global variables uses 1 byte of ROM memory. These initial values are stored in the ROM memory, and then copied into RAM memory at microcontroller startup.
  • "text", which corresponds to program instructions, is using 0xd8 bytes, which means a total size of 216 bytes. According to the ATmega328p datasheet, each instruction uses 16 bit (2 bytes, for most instruction), or 32 bit (4 bytes, for few instructions, such as "jump"). For this reason, the number of instruction is about half of the number of bytes inside "text" section.

atmega328p_interruptsAs shown in the table above, which is inside ATmega328p microcontroller datasheet, for each interrupt, 2 program addresses (4 bytes in total, since 1 program address corresponds to 2 bytes = 16 bit size) are used. In fact, inside Assembly file, it can be noticed that flash memory bytes 0x00 - 0x67 (a total of 104 bytes) contain "jmp" (jump) instructions which are executed when an interrupt happens. The most important interrupt is the "reset" interrupt, which is located at byte 0x00. When the microcontroller program starts (reset event), the program starts to be executed from byte 0x00, and suddenly jumps to the instruction located at byte 0x68.

The following instructions have the purpose of initializing the SREG register (status register), which is located at RAM relative address 0x3F (inside IO memory, absolute address is 0x5F) and the stack pointer: RAM IO memory relative addresses 0x3D (SPL) and 0x3E (SPH) are initialized at the value of 0x08FF, which corresponds to the address of the last byte of RAM memory (internal RAM memory has a size of 2048 bytes, starting from absolute address 0x0100 and ending at address 0x08FF).

After initializing the SREG register and stack pointer registers SPL and SPH, the program does the following. First of all, the RAM memory addresses 0x0100 and 0x0101 are initialized with the values of cicli_ON=5 and cicli_OFF=25. These initialization values are saved into flash memory, at addresses 0xD8 and 0xD9, which means the last 2 bytes of the HEX file (which has a total of 218 bytes). After performing this initialization, main() function is called, by jumping to program instruction located at memory address 0xB8.

The main() function is composed by the following instructions. First of all, DDRB register (port B direction register) is set as output. After that, the program instruction loop between bytes 0xBE and 0xD3 continues to be executed, until microcontroller power is removed or reset button is pressed. Inside each loop cycle, the waiting function "aspetta_100ms()" is called 2 times, with different parameters: the first time (ON), the delay cycles number loaded is 5 (value stored in RAM address 0x0101); the second time (OFF), the delay cycles numbers loaded is 25 (values stored in RAM address 0x0100). This byte parameter passed to the function "aspetta_100ms()" is located on fast register "r24". The function is called with Assembly instruction "call", and the software execution jumps to ROM memory address 0x96.

The function Assembly code is shown below. The function instructions start at ROM address 0x96. The 100ms waiting cycles are executed until "r25" address has the same value as "r24", which is the function input parameter. When this condition is respected, the function sets register "r24" to 0 (return 0) and the program flow comes back to main().

Author: Davide Cavaliere

I am an Italian Electrical Engineer graduated at Politecnico di Milano. My interests are motorcycles and cars, electronics, programming, Internet of Things, and Japanese culture.

Leave a Reply

Your email address will not be published. Required fields are marked *