Native Assembler Programming on Arduino

arduinoThis article is about programming an Arduino board natively in pure Assembler without carrying the rucksack of standard libraries and functions. Using a simple example and an Arduino Uno board, I will show how to assemble, link, and upload your code to the board without using the Arduino development environment. This article does not teach you how to write Assembler code.

About Arduino

Arduino is an open source embedded hardware platform which is easy to use. There are several variants and different hardware add-ons available, such as e.g. Wifi or Ethernet. The Arduino project offers a really easy to use development environment which runs on Linux, MacOS, as well as on Windows, and it is designed to be used by people not really educated in microcontrollers  and their programming. This is one of its success factors.

The standard programming language which is used in the development environment is C++ — although they don’t officially say that 😉 — and it comes with several libraries which provide a lot of useful functions to interact with the Arduino hardware. Typically, this is the best choice for most people.


Arduino boards are equipped with an AVR-type microcontroller. In case of the Arduino Uno it is an ATmega328P. Thus, you need the corresponding binutils which are found in the gcc-avr package (on most Linux distros). Furthermore you need the flash utility which is called avrdude. Both are installed as a dependency if you install the Arduino development package arduino. That’s probably the most easiest way to accomplish the tool installation.

Optionally for debugging purpose, you might also install the debugger gdb-avr and the simulator simulavr.

Since almost all features are actually those of the ATmega controller, you will need the ATmega328P datasheet and probably the instruction set manual which are both found at the Atmel Internet page.

After successfull installation you should have a set of tools available called avr-*, e.g. avr-gcc.

Simple LED Example

Let’s start with a trivial example called simple_led_blink.s. This is the typical microcontroller-Hello-World. It lets an LED blink 😉

First the system is initialized, clearing the system status register (SREG) and setting the stack pointer to the end of the RAM. Then the port pin to which the LED is connected (bit 5 on PORTB) is configured as an output port. Finally in the main loop the LED is switched on and off by xoring it to itself and in between the CPU cycles in a stupid wait loop (otherwise we wouldn’t see it flashing).

.equ RAMEND, 0x8ff
.equ SREG, 0x3f
.equ SPL, 0x3d
.equ SPH, 0x3e
.equ PORTB, 0x05
.equ DDRB, 0x04
.equ PINB, 0x03

.org 0
   rjmp main

   ldi r16,0 ; reset system status
   out SREG,r16 ; init stack pointer
   ldi r16,lo8(RAMEND)
   out SPL,r16
   ldi r16,hi8(RAMEND)
   out SPH,r16

   ldi r16,0x20 ; set port bits to output mode
   out DDRB,r16

   clr r17
   eor r17,r16 ; invert output bit
   out PORTB,r17 ; write to port
   call wait ; wait some time
   rjmp mainloop ; loop forever

   push r16
   push r17
   push r18

   ldi r16,0x40 ; loop 0x400000 times
   ldi r17,0x00 ; ~12 million cycles
   ldi r18,0x00 ; ~0.7s at 16Mhz
   dec r18
   brne _w0
   dec r17
   brne _w0
   dec r16
   brne _w0

   pop r18
   pop r17
   pop r16

Build Chain

Now we can start assembling it with the GNU avr-as with the following command:

avr-as -g -mmcu=atmega328p -o simple_led_blink.o simple_led_blink.s

Option -g enables the generation of debug symbols. Obviously, this is only necessary if you intend to debug the program with gdb. Option -mmcu selects the type of microcontroller. You should change this according to your board type. After successful invocation of this command there is an object file called simple_led_blink.o. It contains the binary code but is not located to any specific memory location. This is done in the next step by the linker which binds all source objects together and locates the code to specific physical address:

avr-ld -o simple_led_blink.elf simple_led_blink.o

The result is an executable ELF binary. Before being able to upload this to the controller board it has to be converted into Intel HEX format. This is an ASCII-based file format for binary data. It is generally widely used for microcontroller programming. The format conversion is done with the objcopy tool:

avr-objcopy -O ihex -R .eeprom simple_led_blink.elf simple_led_blink.hex

Now it is ready to get uploaded. Most modern microcontrollers are shipped with a pre-programmed bootloader which is mainly used to flash programs onto it. Arduino Uno comes with an USB port which is used as a serial connection for uploading your software.1

Connect your Arduino board to your computer. On Linux it will be registered by the kernel (see output of the dmesg command) and will in turn create a tty device node in the /dev directory. On a typical Linux system (tested on kernel versions 3.14, 3.10, and 3.2) it is called /dev/ttyACM0. Look up the correct device name on your system. Now upload it with avrdude as follows:

avrdude -C /etc/avrdude.conf -p atmega328p -c arduino -P /dev/ttyACM0 \
-b 115200 -D -U flash:w:simple_led_blink.hex:i

Option -C specifies the configfile. A standard config file should be shipped with the avrdude package. Option -p specifies the type of controller, -P the USB device node, -b the serial baud rate, -D disables a full chip erase und -U specifies the file to upload.

Side Notes

Although it looks pretty simple and I’m in general pretty familiar with microcontroller programming (on different architectures) it was not as trivial as you think. It took many hours to find that out, beginning with a simple C example from the Arduino IDE. Native Assembler programming on Arduino seams to be a rare discipline 😉 I’d like to point out the following two URLs which helped a lot.

Bonus Track

Finally, there’s one extra sample which is a little bit more sophisticated 😉 It makes use of a timer and the sleep instruction to avoid busy waiting and reduce power consumption.

Have fun playing with Arduino and assembler 😀

.equ OVLCNT, 40
.equ RAMEND, 0x8ff
.equ SREG, 0x3f
.equ SPL, 0x3d
.equ SPH, 0x3e

.equ PORTB, 0x05
.equ DDRB, 0x04
.equ PINB, 0x03
.equ TCCR0A, 0x24
.equ TCCR0B, 0x25
.equ TCNT0, 0x26
.equ TIMSK0, 0x6e

.org 0
   rjmp  main

.org 0x40
   rjmp t0_handler

t0_handler:       ; timer 0 interrupt handler
   in    r19,SREG
   dec   r18
   out   SREG,r19
   ldi   r16,0    ; reset system status
   out   SREG,r16 ; init stack pointer
   ldi   r16,lo8(RAMEND)
   out   SPL,r16
   ldi   r16,hi8(RAMEND)
   out   SPH,r16

   ldi   r16,0x20    ; set port bits to output mode
   out   DDRB,r16

   clr   r16         ; init timer 0
   out   TCCR0A,r16
   ldi   r16,0x05
   out   TCCR0B,r16
   clr   r16
   out   TCNT0,r16
   ldi   r16,1       ; enable timer interrupt
   ldi   r28,lo8(TIMSK0)
   ldi   r29,hi8(TIMSK0)
   st    Y,r16

   ldi   r18,OVLCNT
   ldi   r16,0x20
   clr   r17
   sei               ; globally enable interrupts

   tst   r18         ; test if r18 is 0
   brne  mainloop
   ldi   r18,OVLCNT  ; reset r18 to init val
   eor   r17,r16     ; invert output bit
   out   PORTB,r17   ; write to port
   rjmp  mainloop    ; loop forever

  1. Of course you can use the USB port for generic serial communication as well.