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