Light up LEDs on an Arduino UNO using Assembly Language

Light up LEDs on an Arduino UNO using Assembly Language

I've been exploring assembly language the past few weeks. As a .NET developer, I've never really been all that careful with memory (why worry? Just put more RAM sticks in it). Assembly language is an entirely different approach to development and is really close to the metal. I'm really enjoying learning about the science behind CPUs and what makes them work.

I started by learning some 6502 assembler (Atari 2600, Commodore 64, etc.), but I didn't really have any way to get immediate feedback.

So, I thought it would be cool to light up an LED on an Arduino using assembler.

To do this, I used Atmel Studio (a free IDE from Microchip Technologies based on Visual Studio - a tool I'm already familiar with).

I was inspired by this cool video from MalwareTech:

I followed the great directions from MalwareTech's website and was up in running after about an hour (I could never get avrdude to program my Pro Micro clone, so I had to switch to my Uno).

Here is the custom tool that I used in the project properties window in Atmel Studio to get avrdude to work with my board:

C:\Arduino\avrdude.exe -vvvv -V -patmega328p -carduino -PCOM3 -b115200 -D -V -U flash:w:"$(MSBuildProjectDirectory)\$(Configuration)\$(OutputFileName).hex":i

Here's the code that I used. I couldn't figure out what pins were what at first, but the sample code from the Atmel datasheet pointed me to some constants that you can use to program the Arduino ports.

;
; Blinky1.asm
;
; Created: 9/26/2019 7:27:06 PM
; Author : Michael Earls
;

; Pin Constant Values
; PD0 - 0
; PD1 - 1
; PD2 - 2
; PD3 - 3
; PD4 - 4
; PD5 - 5
; PD6 - 6
; PD7 - 7

; PB0 - 8
; PB1 - 9
; PB2 - 10
; PB3 - 11
; PB4 - 12
; PB5 - 13 - System LED

start:

	; Set pins 0-7 to high
	ldi		r17, (1<<PD7)|(1<<PD6)|(1<<PD5)|(1<<PD4)|(1<<PD3)|(1<<PD2)|(1<<PD1)|(1<<PD0) 
	out		PORTD, r17

	; Set pins 8-13 to high
    ldi		r16, (1<<PB5)|(1<<PB4)|(1<<PB3)|(1<<PB2)|(1<<PB1)|(1<<PB0) 
	out		PORTB, r16

	; Set pins 0-7 to output mode
	ldi		r18, (1<<DDD7)|(1<<DDD6)|(1<<DDD5)|(1<<DDD4)|(1<<DDD3)|(1<<DDD2)|(1<<DDD1)|(1<<DDD0) 
	out		DDRD, r18

	; Set pins 8-13 to output mode
	ldi		r19, (1<<DDB5)|(1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0) 
	out		DDRB, r19

loop:
	rjmp loop ; loop forever

This code sets some pins to high. I setup a simple circuit with an LED and a 220Ω resistor and plugged it into pins 7, then pin 0 and played with the code to turn it on and off.

PORTB is how you program pins 8-13


PORTD is how you program pins 0-7

Basically, you just send a byte to these ports and the 1 bits will set them high and the 0 bits will set them low. I used the constants to OR the values into the appropriate memory location:

(1<<PD7)|(1<<PD6)|(1<<PD5)|(1<<PD4)|(1<<PD3)|(1<<PD2)|(1<<PD1)|(1<<PD0) 

This sets pins 0-7 to high.

You have to set the pin mode of the pins, too. That's where the DDRB and DDRD constants come in. You set them to 1 for output mode and 0 for input mode.

I haven't yet done any reads, so I'll probably do that next. I also have a lot to learn about representing numbers and assembly programming in general. It's not C#, that's for sure.

I'm probably going to order either an Adafruit Metro M4 or the Adafruit Grand Central and start playing with a really strong Arm M4 processor. If I am correct, I believe I can still use Atmel studio to code for them, as well.

Obviously, with the advent of CircuitPython and the already existing C++ Arduino editor out there, there is no good reason to program an Arduino with assembler, but this has been a lot of fun anyway.

Update: I have been working on my code and finally got a blinky working. Here is the source code:

;
; Blinky1.asm
;
; Created: 9/26/2019 7:27:06 PM
; Author : Michael Earls
;

; Pin Constant Values
; PD0 - 0
; PD1 - 1
; PD2 - 2
; PD3 - 3
; PD4 - 4
; PD5 - 5
; PD6 - 6
; PD7 - 7

; PB0 - 8
; PB1 - 9
; PB2 - 10
; PB3 - 11
; PB4 - 12
; PB5 - 13 - System LED

.DEF PTD = r16
.DEF PTB = r17

.MACRO Delay1
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
	rcall delay
.ENDMACRO

start:
	rcall	init ; Initialize pins	
loop:
	Delay1
	rcall	setpinhigh
	Delay1
	rcall	setpinlow
	rjmp	loop

init:
	; Set pins 0-7 to low
	ldi		r16,(0<<PD7)|(0<<PD6)|(0<<PD5)|(0<<PD4)|(0<<PD3)|(0<<PD2)|(0<<PD1)|(0<<PD0) 
	out		PORTD,r16

	; Set pins 8-13 to low
    ldi		r17,(0<<PB5)|(0<<PB4)|(0<<PB3)|(0<<PB2)|(0<<PB1)|(0<<PB0) 
	out		PORTB,r17

	; Set pins 0-7 to output mode
	ldi		r18,(1<<DDD7)|(1<<DDD6)|(1<<DDD5)|(1<<DDD4)|(1<<DDD3)|(1<<DDD2)|(1<<DDD1)|(1<<DDD0) 
	out		DDRD,r18

	; Set pins 8-13 to output mode
	ldi		r19,(1<<DDB5)|(1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0) 
	out		DDRB,r19

	nop ; nop for settling down after pins set

	ret ; return from subroutine

setpinhigh:
	ldi		PTD,1<<PD0
	out		PORTD, PTD
	ret
setpinlow:
	ldi		PTD,0<<PD0
	out		PORTD, PTD
	ret
delay:
	ldi ZH,HIGH(65535)
    ldi ZL,LOW(65535)
count:
    sbiw ZL,1
    brne count
	ret