Pure AVR Assembler programming


I’m a newbie here, downloaded (Mac OS) PlatformIO 2 days ago.

I want to write purely in assembly language (AVR) to some Attiny85 or Atmega328P µC. So far I made it immediately and smoothly working with an Raspberry PI using the avrdude via the Terminal. Now I wanted to use my Mac and a simple programmer as well as PlatformIO to do basically the same (maybe with less wires and raw electronic stuff around): just writing pure assembly code for some of these Attinys and Atmegas.

My question is, do I need to go via all this “create projects” options and do i need this workspace with “.pioenvs” and “.vscode” and all that? If yes, how do I do it?

For now I went via creating a new project and chose a generic attiny85 with the Arduino platform. But i dont want any Arduino integration on the chip. I wrote a simple assembly program and wanted to include in the header a special tn85def.inc file (long story short, some of the tn85def.inc files around have an error in their timer/interrupt definitions).

Where do I have to put this file so that the “Build” option does not reply with an error of not finding the tn85def.inc file? So far located it in “src”, but also in “include”.
Thanks for any kind of help, which is very much appreciated.

1 Like

You can remove the framework = arduino line and it will use the _bare builder. Meaning it will just use avr-gcc -x assembler-with-cpp .. to compile stuff.

Example platformio.ini:

platform = atmelavr
board = attiny85


;  turns on an LED which is connected to PB5 (digital out 13)

#define PORTB 0x18
#define DDRB 0x17

.global main

	ldi r16,0b00100000
	out DDRB,r16
	out PORTB,r16
	rjmp Start

no other files are needed.

platformio -f -c eclipse run 
avr-gcc -x assembler-with-cpp -Os -Wall -ffunction-sections -fdata-sections -flto -mmcu=attiny85 -DF_CPU=8000000L -DPLATFORMIO=40000 -DARDUINO_AVR_ATTINYX5 -Isrc -Iinclude -c -o .pioenvs\attiny85\src\main.o src\main.S
avr-gcc -o .pioenvs\attiny85\firmware.elf -Os -mmcu=attiny85 -Wl,--gc-sections -flto -fuse-linker-plugin .pioenvs\attiny85\src\main.o -L.pioenvs\attiny85 -Wl,--start-group -lm -Wl,--end-group
MethodWrapper(["checkprogsize"], [".pioenvs\attiny85\firmware.elf"])
avr-objcopy -O ihex -R .eeprom .pioenvs\attiny85\firmware.elf .pioenvs\attiny85\firmware.hex
Memory Usage -> http://bit.ly/pio-memory-usage
DATA:    [          ]   0.0% (used 0 bytes from 512 bytes)
PROGRAM: [          ]   0.7% (used 60 bytes from 8192 bytes)

Be careful with the assembler include files though, the tn85def.inc I found is apparently written in the assembler syntax for the Atmel Studio assembler and will cause syntax errors when compiled with avr-gcc/avr-as.

1 Like

Thank you for your quick reply and the hint for leaving out the framework Arduino line in the ini. I succeeded in setting up avra and avrdude together with the usbasp programmer and simply put some hex files in the flash of an attiny85. I would love PlatformIO to simply take care of all the commands so that i simply press a button and the program is inside the flash. …think I need to experiment further a bit.

1 Like

You can also tell PlatformIO to use usbasp as the programmer, with e.g.

upload_protocol = usbasp
upload_flags = -Pusb

see Atmel AVR — PlatformIO latest documentation.

Then use pio run -t program to execute the ‘Upload using Programmer’ target. or use the appropiate target in the IDE.

So that worked now just perfectly! Thanks a lot!! I can now use the PlatformIO upload button and its on the attiny85.
Now, in Finder it says that my folder for that program is 71MB, basically its the .vscode Folder inside my “project folder”. Can I erase that folder or how can I make assembly program projects in PlatformIO that do not use this .vscode folders?

Cleaning compilation files is done by executing the pio run -t clean target.

You can also just create projects from the CLI without having to generate project files for an IDE. For example, run pio run -b attiny85 in an empty folder. Then adapt the platformio.ini to your needs again and upload using the CLI with pio run -t upload etc.

in the Terminal it gives me: pio: command not found

Ah, because when you install the PlatformIO IDE (VScode), it has its own seperate PIO core and is not included in the system path. You can install a system-wide PlatformIO using pip install -U platformio and then, if needed, tell the VSCode PIO plugin to use the global core instead of the builtin core. See Redirecting.... Also refer to Redirecting....

That was helpful, thank you again! I read through the documentation, I am able to use pio commands from the bash and I’m now able to create workspace folders and write assembly code in it. What i didn’t make running was to integrate e.g. a file like tn85def.inc … lets say i create this file in the include folder of a workspace. The file was only recognized when I saved it (copy and paste in Finder) into my project folder, but gave me a lot of errors (e.g. tn85def.inc:664: Error: expected comma after “PCI0addr”).
Any idea how i can integrate certain *.inc files? (in your first comment you warned me of using files such as tn85def.inc because they might cause syntax issues. Assume its happening what you meant, isn’t it?)

Exactly. If it’s all just definitions, a well-written regex will transform the .EQU XY = Z; into C-style #define XY Z or whatever syntax is accepted by the avr-as assembler invoked by avr-gcc.

… but isn’t somewhere with the PlatformIO IDE Package coming a folder with all Atmel Chip definitions, which I could just integrate in a certain way?
If not, I should integrate register definitions in the header of my codes for keeping it minimal and simple (can be a good way of learning and memorizing by heart all hex addresses of any register, port and so on of these Atmel chips)

1 Like

Yes. I take everything back. The toolchain-atmelavr\avr\include\avr folder has everything you need. There is avr/iotnx5.h, and avr/iotn85.h etc for the the I/O definitions, though you must use #include <avr/io.h> to include them. You basically have avr-libc/avr-libc/include at master · vancegroup-mirrors/avr-libc · GitHub. This works the same:

#include <avr/io.h>

.global main

	ldi r16,0b00100000
	out DDRB,r16
	out PORTB,r16
	rjmp Start

Unfortunately I dont find that folder in my PlatformIO installation. How can I install and integrate it in my system so that i can just add the header #include <avr/io.h>?
Thanks for your patience and help!

In your PIO home folder (/home/<user>/.platformio or C:\Users\<user>\.platformio) you must have packages/toolchain-atmelavr folder where the compiler tools are, otherwise you wouldn’t have been able to compile the previous project. Do you find your PIO home folder at the expected place? (maybe cd / && find . -name ".platformio"?)

Yes. Found it. But when I use the #include … header, its compiling without errors, but the LEDs (even after playing a bit around with the LEDS or setting to HIGH other bits at the port, the LED doesnt light up.

And when using the previous #defines does it work or not work? PB5 is reset on the ATTiny85, maybe you should try another pin, like PB3.

Yes, the #define commands work.
Even the include <var/io.h> command doesnt produce an error when I “Upload”, but when I “Build” it says that "no such file or directory. When i put the entire folder root, nothing. “Upload” doesnt produce an error, but of course the LED do not light up (only with the #define commands.

In this simple program you can maybe use the avr-objdump tool disassemble the binary again to see where the difference is in the instruction code.

That tool is awesome. Thanks a lot. will play arround a bit. … not sure what I am looking for actually…

It disassembles you the entire firmware. Look e.g. here

C:\Users\Maxi\Desktop\atmega_bare_assembly\.pioenvs\attiny85>"C:\Users\Maxi\.platformio\packages\toolchain-atmelavr\bin\avr-objdump.exe" -d firmware.elf

firmware.elf:     file format elf32-avr

Disassembly of section .text:

00000000 <__vectors>:
   0:   0e c0           rjmp    .+28            ; 0x1e <__ctors_end>
   2:   15 c0           rjmp    .+42            ; 0x2e <__bad_interrupt>
   4:   14 c0           rjmp    .+40            ; 0x2e <__bad_interrupt>
   6:   13 c0           rjmp    .+38            ; 0x2e <__bad_interrupt>
   8:   12 c0           rjmp    .+36            ; 0x2e <__bad_interrupt>
   a:   11 c0           rjmp    .+34            ; 0x2e <__bad_interrupt>
   c:   10 c0           rjmp    .+32            ; 0x2e <__bad_interrupt>
   e:   0f c0           rjmp    .+30            ; 0x2e <__bad_interrupt>
  10:   0e c0           rjmp    .+28            ; 0x2e <__bad_interrupt>
  12:   0d c0           rjmp    .+26            ; 0x2e <__bad_interrupt>
  14:   0c c0           rjmp    .+24            ; 0x2e <__bad_interrupt>
  16:   0b c0           rjmp    .+22            ; 0x2e <__bad_interrupt>
  18:   0a c0           rjmp    .+20            ; 0x2e <__bad_interrupt>
  1a:   09 c0           rjmp    .+18            ; 0x2e <__bad_interrupt>
  1c:   08 c0           rjmp    .+16            ; 0x2e <__bad_interrupt>

0000001e <__ctors_end>:
  1e:   11 24           eor     r1, r1
  20:   1f be           out     0x3f, r1        ; 63
  22:   cf e5           ldi     r28, 0x5F       ; 95
  24:   d2 e0           ldi     r29, 0x02       ; 2
  26:   de bf           out     0x3e, r29       ; 62
  28:   cd bf           out     0x3d, r28       ; 61
  2a:   02 d0           rcall   .+4             ; 0x30 <main>
  2c:   05 c0           rjmp    .+10            ; 0x38 <_exit>

0000002e <__bad_interrupt>:
  2e:   e8 cf           rjmp    .-48            ; 0x0 <__vectors>

00000030 <main>:
  30:   00 e2           ldi     r16, 0x20       ; 32
  32:   07 bf           out     0x37, r16       ; 55
  34:   08 bf           out     0x38, r16       ; 56

00000036 <Start>:
  36:   ff cf           rjmp    .-2             ; 0x36 <Start>

00000038 <_exit>:
  38:   f8 94           cli

0000003a <__stop_program>:
  3a:   ff cf           rjmp    .-2             ; 0x3a <__stop_program>

You can see the main function after the initializer-stub inserted by avr-gcc. You can also see all the interrupts in the interrupt vector table, most of them being unpopulated.

And here we can also solve the mystery of why stuff doesn’t light up anymore. In the previous program we had

#define PORTB 0x18
#define DDRB 0x17
	ldi r16,0b00100000
	out DDRB,r16
	out PORTB,r16

which worked, now here when using <avr/io.h> we suddenly have

00000030 <main>:
  30:   00 e2           ldi     r16, 0x20       ; 32
  32:   07 bf           out     0x37, r16       ; 55
  34:   08 bf           out     0x38, r16       ; 56

which is completley wrong. I.e. a wrong definition for DDRB and PORTB get included… probably because there isn’t the correct macro set to select the device? I’ll have to look into this later.