Timo Denk's Blog

Program Arduino in Assembly or C/C++

· Timo Denk

This post is a tutorial on how to get started on programming your Arduino in Assembly or C/C++. In order to follow you need a Windows machine and a microcontroller programmer like the Atmel-ICE.

Install Atmel Studio

Download the Visual Studio based IDE Atmel Studio from atmel.com/tools/atmelstudio.aspx. Follow the installation instructions.

Atmel Studio Select ArchitectureInstall Atmel Studio

New Project

Start Atmel Studio and create a new project (Ctrl+Shift+N). You can choose either C/C++ or Assembler.

Creating a new Project in Atmel Studio.

Creating a new Project in Atmel Studio.

On the next screen you will be prompted to select your device. Make sure you select exactly the one you are using. For my Arduino Uno that is “ATmega328P” (“ATmega328” would not work).

ATmega 328P

ATmega328P: The Arduino UNO’s microcontroller.

Writing the Code

After selecting your device the code editor appears. The following two snippets implement the Arduino Blink Example in both C and Assembly. The output pin is the Arduino pin D13, which is the fifth pin in the PORTB register. The delay between on and off is 1000ms.

The well-known Arduino Blink sketch implementation in plain C. Note that functions like digitalWrite or delay do not exist. Port manipulation and external header files need to be used instead. The microcontroller’s clock frequency (here $f=16\text{MHz}$) needs to be defined as well.

#define F_CPU 16000000
#define BLINK_DELAY_MS 1000

#include <avr/io.h>
#include <util/delay.h>

int main (void)
{
  // Arduino digital pin 13 (pin 5 of PORTB) for output
  DDRB |= 0B100000; // PORTB5
  
  while(1) {
    // turn LED on
    PORTB |= 0B100000; // PORTB5
    _delay_ms(BLINK_DELAY_MS);
    
    // turn LED off
    PORTB &= ~ 0B100000; // PORTB5
    _delay_ms(BLINK_DELAY_MS);
  }
}

Even more complicated: The Blink-sketch in Assembly. While being hard to read without the comments, this very low-level and probably the coolest version.

main:
  sbi 0x04, 5       ; PORTB5 output
loop:               ; main loop begin
  sbi 0x05, 5       ; PORTB5 high
  call delay_1000ms ; delay 1s
  cbi 0x05, 5       ; 5 PORTB5 low
  call delay_1000ms ; delay 1s
  rjmp  loop        ; main loop

delay_1000ms:       ; subroutine for 1s delay
                    ; initialize counters
  ldi r18, 0xFF     ; 255
  ldi r24, 0xD3     ; 211
  ldi r25, 0x30     ; 48
inner_loop:
  subi  r18, 0x01   ; 1
  sbci  r24, 0x00   ; 0
  sbci  r25, 0x00   ; 0
  brne  inner_loop
  ret

While the main program is relatively easy to understand, it’s probably not obvious, how the delay_1000ms function works (thanks to Ido Gendel for investigating on that). On a $16\text{ MHz}$ clock the task of a one-second-delay-function is essentially to keep the CPU busy for 16 million clock cycles.

  • The first three ldi ("Loads an 8-bit constant directly to register 16 to 31.") instructions take three cycles in total.
  • subi takes one cycle as well and “subtracts a register and a constant, and places the result in the destination register Rd.” The constant is 0x01 in the snippet and the execution time is once again one clock cycle.
  • Each of the following two  sbci instructions “subtracts a constant from a register and subtracts with the C Flag, and places the result in the destination register Rd”. The constant is 0x00 in both cases so the only thing that is subtracted is the C Flag. The C Flag however is only set, if the previous subtraction has resulted in an underflow ($0-1\rightarrow255$).
  • Finally, the brne instruction “[…] tests the Zero Flag (Z) and branches relatively to PC if Z is cleared.” This takes two cycles if it branches, otherwise one.

When going through the statements step by step that leads to a total number of $n$ clock cycles for the subroutine body: $$\begin{align}n=&3+256\cdot212\cdot5+256\cdot256\cdot48\cdot5-1\\=&16,000,002.\end{align}$$

Upload and Execute

To actually see an LED blink, you have to connect your Arduino to a power supply and to your programmer. Connect the programmer to your PC.

Atmel-ICE Arduino

Atmel-ICE programmer, the Arduino, and an LED.

Make sure the connection plug is facing into the right direction. If not it won’t cause any damage but you will encounter an error during the upload.

Atmel-ICE Arduino SPI Connection

The SPI connection between programmer and Arduino needs to be plugged in with the proper orientation, as shown in the photo.

Run

Now hit the Run button in Atmel Studio (hotkey F5). The IDE will prompt you to select a programmer (“Please select a connected tool and interface and try again.”).

(1) Select your programmer (e.g. “Atmel-ICE”) and the (2) ISP interface. Also make sure you (3) uncheck “Preserve EEPROM” in the programming settings section. Now you should be able to upload your code (hit F5 again).

The Arduino bootloader needs to be flashed back onto the Arduino, before it can be used in combination with the Arduino IDE again. Starting Electronics has a post that explains how this can be done: Burning the Bootloader to an Arduino Uno using Atmel Studio

Troubleshooting

See Atmel-ICE User Guide and AVRISP MkII Troubleshooting if the following sections do not help.

Failed to set-up tool (no context id returned)

Open the Device Pack Manager (Tools > Device Pack Manager) and install all updates.

Atmel Studio Device Pack Manager

The Atmel Studio Device Package Manager.

Failed to launch program

Error: Failed to start programming session before chip erase with eeprom preserve:Failed to enter programming mode. ispEnterProgMode: Error status received: Got 0xc0, expected 0x00 (Command has failed to execute on the tool)

This could be a connection issue (e.g. the ISP plug is connected the wrong way around). It could also be that your microcontroller is not supplied with power.

Unable to start debug session

“Atmel Studio was unable to start your debug session.
Please verify device selection, interface settings, target power and connections to the target device. Look in the details section for more information.”

Resources

In order to write your own, more complex C/C++ or Assembly programs you are definitely going to need your microcontroller’s data sheet at hand:

Very important for Assembly projects is also the AVR Instruction Set Manual.

Also note that you can view the generated assembly code after compiling a C/C++ project. That can be a good resource for improving assembly skills or for deeply understanding what’s going on behind the scenes. In order to look into the assembly code open the “Solution Explorer”, expand “Output Files”, and open the “.lss” file.