This 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.
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.
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 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 mainloop: eor r17,r16 ; invert output bit out PORTB,r17 ; write to port call wait ; wait some time rjmp mainloop ; loop forever wait: 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 _w0: dec r18 brne _w0 dec r17 brne _w0 dec r16 brne _w0 pop r18 pop r17 pop r16 ret
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.
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.
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 reti 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 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 mainloop: sleep 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
- Of course you can use the USB port for generic serial communication as well. ↩