ATTiny85 Assembly First Steps
The ATTiny85 is a readily available, arduino-compatible chip that is tiny (like its name suggests) and fairly straightforward to understand. Let’s program it in assembly!
The ATTiny85 is readily available in Arduino format, as Digistump kickstarted a board called the “Digispark”. At time of writing, these can be ordered from Amazon from several vendors and arrive the next day for about $4 per part in quantities of 4-5 pieces. Search “digistump” or “attiny85”. Digistump themselves may sell the product directly but they were currently out of stock. I just bought four, as desoldering one to program manually would still leave me some Arduino compatible toys.
To get started with, I tinkered with the Arduino studio. I added the digistump board support to the IDE (tools -> manage boards -> search digistump and install the AVR board support) and wrote a hello world that will blink an LED you can breadboard up to the pin labeled p0 (or pb0):
void setup() {
// put your setup code here, to run once:
pinMode(0, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
digitalWrite(0, HIGH);
delay(1000);
digitalWrite(0, LOW);
delay(1000);
}
I was not fortunate enough to be able to flash to the chip directly from Arduino in my Linux environment. If you are on a Linux machine and having similar issues (I saw segfaults for micronucleus
in dmesg
) the issue may be that you’re not running as root. I wasn’t able to install board support when running Arduino as root, so I located micronucleus from the board support files and ran the following command to manually flash as root after building:
sudo ~/.arduino15/packages/digistump/tools/micronucleus/2.0a4/micronucleus -cdigispark /tmp/arduino_build_103319/sketch_jul19a.ino.hex
After that, with an LED and 220 ohm resistor wired from PB0 to ground I had blinking!
Onward to assembly…
I took a quick moment to inspect the hex file, again using tools installed by Arduino to disassemble:
~/.arduino15/packages/arduino/tools/avr-gcc/4.8.1-arduino5/bin/avr-objdump -D -m avr25 /tmp/arduino_build_103319/sketch_jul19a.ino.hex
This is a handy way to see what the program is actually doing, but has limited value right now except for to see approximately how big our file is:
/tmp/arduino_build_103319/sketch_jul19a.ino.hex: file format ihex
Disassembly of section .sec1:
00000000 <.sec1>:
0: 16 c0 rjmp .+44 ; 0x2e
2: 25 c0 rjmp .+74 ; 0x4e
4: 24 c0 rjmp .+72 ; 0x4e
6: 23 c0 rjmp .+70 ; 0x4e
8: 45 c0 rjmp .+138 ; 0x94
...
2b0: 80 df rcall .-256 ; 0x1b2
2b2: ce de rcall .-612 ; 0x50
2b4: d0 de rcall .-608 ; 0x56
2b6: fe cf rjmp .-4 ; 0x2b4
2b8: f8 94 cli
2ba: ff cf rjmp .-2 ; 0x2ba
I looked up the -m avr25
architecture value in this avr-mcus.def file.
Next up is to make sure that we can use a programmer to read the flash from the ATTiny. I needed a refresher, and this guide was quite helpful. I then tried to use my TL866 reader using minipro
with ISP headers to wire up to the RST/MOSI/MISO/CLK wires that appeared to be hooked up correctly on the board according to the diagram and datasheet, but was unable to read:
$ sudo minipro -p "ATTINY85" -r ~/Downloads/attiny85_ino.bin -i
Found TL866II+ 04.2.126 (0x27e)
An error occurred while parsing XML database.
Invalid Chip ID: expected 0x1E930B, got 0x0000 (unknown)
(use '-y' to continue anyway at your own risk)
Further reading seemed to indicate that the reset pin isn’t properly exposed while it’s mounted to the PCB. So I then desoldered an ATTiny from its board using a hot air rework station and mounted it in the socket that came with the TL866 and read it:
$ sudo minipro -p "ATTINY85" -r ~/Downloads/attiny85_ino.bin -i
[sudo] password for shawn:
Found TL866II+ 04.2.126 (0x27e)
Chip ID OK: 0x1E930B
Reading Code... 0.44Sec OK
Reading Data... 0.03Sec OK
Reading fuses... 0.00Sec OK
$ cat ~/Downloads/attiny85_ino.fuses.conf
fuses_lo = 0xe1
fuses_hi = 0xdd
fuses_ext = 0xfe
lock_byte = 0xff
The above is a human-readable (ish) file containing the fuse configuration that sets how functions of the chip such as whether the clock signal is read externally from pins or set with an internal oscillator, so I was able to successfully read the chip!
Next up is coming up with a hello world! Rjhcoding has excellent avr assembly tutorials so I followed the blink example. The first line is to import a set of .def
statements that map all of the registers, memory mapped io, and various constant values to standard names. The file above is targeting an Atmega328, while we’re targeting an ATTiny85. The official AVR studio appears to come with these include files, but the avra
assembler I’m using doesn’t. Fortunately, the author of the site also uploaded the .def files, and the includes can be found inside his avra GitHub repo.
I downloaded tn85def.inc into a directory, and authored blink.asm
from the example, changing the include appropriately:
.include "tn85def.inc"
.def mask = r16 ; mask register
.def ledR = r17 ; led register
.def oLoopR = r18 ; outer loop register
.def iLoopRl = r24 ; inner loop register low
.def iLoopRh = r25 ; inner loop register high
.equ oVal = 71 ; outer loop value
.equ iVal = 28168 ; inner loop value
.cseg
.org 0x00
clr ledR ; clear led register
ldi mask,(1<<PINB0) ; load 00000001 into mask register
out DDRB,mask ; set PINB0 to output
start: eor ledR,mask ; toggle PINB0 in led register
out PORTB,ledR ; write led register to PORTB
ldi oLoopR,oVal ; initialize outer loop count
oLoop: ldi iLoopRl,LOW(iVal) ; intialize inner loop count in inner
ldi iLoopRh,HIGH(iVal) ; loop high and low registers
iLoop: sbiw iLoopRl,1 ; decrement inner loop registers
brne iLoop ; branch to iLoop if iLoop registers != 0
dec oLoopR ; decrement outer loop register
brne oLoop ; branch to oLoop if outer loop register != 0
rjmp start ; jump back to start
Then compiled:
$ avra blink.asm
AVRA: advanced AVR macro assembler Version 1.3.0 Build 1 (8 May 2010)
Copyright (C) 1998-2010. Check out README file for more info
AVRA is an open source assembler for Atmel AVR microcontroller family
It can be used as a replacement of 'AVRASM32.EXE' the original assembler
shipped with AVR Studio. We do not guarantee full compatibility for avra.
AVRA comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of avra under the terms
of the GNU General Public License.
For more information about these matters, see the files named COPYING.
Pass 1...
tn85def.inc(44) : PRAGMA directives currently ignored
tn85def.inc(48) : PRAGMA directives currently ignored
tn85def.inc(53) : PRAGMA directives currently ignored
tn85def.inc(54) : PRAGMA directives currently ignored
tn85def.inc(647) : PRAGMA directives currently ignored
tn85def.inc(648) : PRAGMA directives currently ignored
tn85def.inc(649) : PRAGMA directives currently ignored
tn85def.inc(650) : PRAGMA directives currently ignored
Pass 2...
tn85def.inc(44) : PRAGMA directives currently ignored
tn85def.inc(48) : PRAGMA directives currently ignored
tn85def.inc(53) : PRAGMA directives currently ignored
tn85def.inc(54) : PRAGMA directives currently ignored
tn85def.inc(647) : PRAGMA directives currently ignored
tn85def.inc(648) : PRAGMA directives currently ignored
tn85def.inc(649) : PRAGMA directives currently ignored
tn85def.inc(650) : PRAGMA directives currently ignored
done
Used memory blocks:
Code : Start = 0x0000, End = 0x000C, Length = 0x000D
Assembly complete with no errors.
Segment usage:
Code : 13 words (26 bytes)
Data : 0 bytes
EEPROM : 0 bytes
And that’s much smaller than the arduino example!
$ avr-objdump -D -m avr25 blink.hex
blink.hex: file format ihex
Disassembly of section .sec1:
00000000 <.sec1>:
0: 11 27 eor r17, r17
2: 01 e0 ldi r16, 0x01 ; 1
4: 07 bb out 0x17, r16 ; 23
6: 10 27 eor r17, r16
8: 18 bb out 0x18, r17 ; 24
a: 27 e4 ldi r18, 0x47 ; 71
c: 88 e0 ldi r24, 0x08 ; 8
e: 9e e6 ldi r25, 0x6E ; 110
10: 01 97 sbiw r24, 0x01 ; 1
12: f1 f7 brne .-4 ; 0x10
14: 2a 95 dec r18
16: d1 f7 brne .-12 ; 0xc
18: f6 cf rjmp .-20 ; 0x6
Flash it to the microcontroller:
$ sudo minipro -p ATTINY85 -w blink.hex
[sudo] password for shawn:
Found TL866II+ 04.2.126 (0x27e)
Chip ID OK: 0x1E930B
Found Intel hex file.
Erasing... 0.01Sec OK
Writing Code... 1.07Sec OK
Reading Code... 0.43Sec OK
Verification OK
And it blinks, in 26 bytes of code!