Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Programming Arduino Uno (ATmega386P) in assembly

There are a few different sources online that describe the ways that you can program Arduino Uno/ATmega328P in assembly, and in the last couple of days I've had to switch through most of them just to get the basic setup and initial programs working. This is an attempt to aggregate all those sources in a centralized document, with two simple example programs.

Requirements

There are two major options for the task (of which I'm aware of), and the setup I've settled on is accidental in retrospect [1]. But you might research on your own the alternative setup based around the AVR Libc project.

For the setup I use you need: avrdude and avra. Do note that I'm using a Linux box, and in case you're on another operating system you'll be on your own with the setup/configuration of these tools.

Optional (but must read)

When connected via USB my Uno would have it's serial interface mapped to /dev/ttyACM0. Because I wanted a more intuitive device name (and the fact that it took me some time to find out what device name it had in the first name), I wrote an udev rule to map my Uno board to /dev/arduino-uno.

$ cat /etc/udev/rules.d/10-arduino.rules
KERNEL=="ttyACM0", SYMLINK+="arduino-uno", OWNER="mhitza"

If you do the symlink, be sure to also include the OWNER directive; otherwise every time you upload the new program to your Uno you will have to call avrdude with sudo.

NOTE If you skipped this step, be sure to replace any occurence of /dev/arduino-uno with /dev/ttyACM0, and prefix all avrdude calls with sudo in the followup Makefile.

Also you'll see a reference to picocom (program used for serial communication with the board) in the Makefile I use, and it should work but I haven't tested it yet.

A simple Makefile

NOTE There is already a popular Makefile project for Arduino, however that one relies on the AVR Libc project. And as with other "prepackaged" solutions, like oh-my-zsh, I prefer to start from a small base. I find it easier to understand and maintain.

The makefile is available towards the end of the page.

Given a program named blink.asm

  • make (or make blink.hex) - compile it to a hex file
  • make program=blink upload - upload the program to your Arduino board. If you didn't run the previous step manually it will also compile the program for you
  • make monitor - monitor serial data

Where do the names come from?

When reading other example assembly code online you will find references to named constants that are not built into the assembler. Those usually come from m328Pdef.inc. From where you download that file doesn't really matter, since basically everyone is carrying a copy around with them; or so it seems[2].

I personally use it as a learning reference, and I would recommend you copy only the definitions you need in your program until you get familiar with the names. That's the way I approach it, as you'll see in my example programs.

The project for the sample code

Arduino Uno LED project on a breadboard Project rendered on 123d.circuits.io

Three LEDs and a simple switch. With each press on the switch the LEDs turn on (from left to right), one by one, and once the red LED is reached further presses will turn off the LEDs in reverse order until we reach the initial state. Rinse and repeat.

Two implementations will be shown, the first one is the way most people - I think - would write the implementation (in terms on reacting to button presses, not necessary how they'd toggle the LEDs) (assuming they don't know about interrupts yet), and the second version will use an interrupt instead of "polling" the pin for voltage (state) changes.

References

NOTE the assembler manual is for AVRASM32.exe, not avra. However avra is a compatible assembler, with just a few extra features

NOTE if you see online references to avrasm2, you have to know that piece of software is different from AVRASM32.exe and avra. And avra is highly unlikely to be able to compile code written for that assembler.

You need to keep these references close by when programming AVR in assembly:


  1. Initially I started with avr-as, but the helpfull stackoverflow answers I've found pointed to avra and as soon as I was able to write a simple program I didn't look back.
  2. Maybe it could matter where you download it from. Some dastardly individual might provide that file in an altered format where the mappings wouldn't be correct and you'd write to the wrong memory bits and spend hours/days debugging the assembly code.

While mentioned in the previous document that you should download the assembly instruction set manual, for the following examples I'd like to detail the instructions I will use (so that you can follow along the code examples).

  • jmp label or rjmp label - jump to a label
  • sbi memory_location, bit_position - set bit immediate[1]
  • cbi memory_location, bit_position - clear bit immediate
  • clr register - clear register
  • sbis memory_location, bit_position - skip branch immediate (bit) set[2]
  • sbic memory_location, bit_position - skip branch immediate (bit) clear
  • tst register - test register for zero or negative number, if true, the Z flag is set[3]
  • brne label - branch not equal
  • ldi register, byte_value - load data immediate
  • reti - return from interrupt
  • out memory_location, register - write a register to a memory location
  • lds register, data_space - load from data space (register, I/O memory, SRAM) into register
  • ori register, byte_value - bitwise OR immediate
  • sts register, data_space - store to data space from register
  • sei - enable interrupts
  • in register, memory_location - read memory location into register

  1. Immediate here means that a value (bit_position/byte_value) can be specified directly for the operation.
  2. With skip branch instructions, when the comparison turns out to be true the followup instruction is skipped. Most of the time that followup instruction would be a jmp
  3. There are a couple of instructions that work on implicit values inside the SREG registry. For example the Z flag is bit 1 in that register. tst is one of the many instructions that sets that value, while the brne/breq instructions are two example instructions that make use of the Z bit value.
%.hex: %.asm
avra -fI $<
rm *.eep.hex *.obj *.cof
all: $(patsubst %.asm,%.hex,$(wildcard *.asm))
upload: ${program}.hex
avrdude -c arduino -p m328p -P /dev/arduino-uno -b 115200 -U flash:w:$<
monitor:
picocom --send-cmd "ascii_xfr -s -v -l10" --nolock /dev/arduino-uno
.PHONY: all upload monitor

You are encouraged to implement the simple Arduino project presented, download the files (there's a download button at the top) and build the project. If you're unfamiliar with the resistor values from the attached image, here's the handy cheat sheet I'm using.

Resistor cheat sheet Taken from the "Arduino Projects Book", licensed under CreativeCommons BY-NC-SA 3.0


Except where otherwise noted, this work is licensed under a Creative Commons Attribution 4.0 International License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.