Is the code expected to be fully written in assembly? Or are you writing a blink
function that is to be called from C/C++? I suspect and assume the former.
TL;DR
- Use
#include "m328pdef.inc"
as opposed to .include
because pio will be using the gcc compiler with the gas assembler.
- In order to get headers included, your source file(s) must have
.S
extensions otherwise gcc will not run the pre-processor, so the #includes
don’t get included.
- Remove the
framework = arduino
from platformio.ini
because if you don’t, the linker will complain about a missing main
entry point.
- Change your entry point to
main
and make sure it is .global
if you continue to use gcc/gas with or without the Arduino framework. gcc wraps your assembly code in the normal C/C++ startup, interrupt handling and such like. It needs an entry point to your code named main
.
Gory Details
If you are writing everything in main.S
then compiling with the gcc compiler, the gas assembler will get called to do the work. As you are using the Arduino framework, you will need to make sure that your entry point in the assembly code is called main
and is defined with .global
attributes otherwise the linker will complain.
If your main source is main.S
, with a capital ‘S’ then the gcc pre-processor will get called prior to gas, and header files should be properly included. In addition, you can use C/C++ style comments as well as assembly ones.
If you have main.s
as your file, then the pre-processor will not get called first, and headers will not be included.
If you have main.asm
as your file, I suspect that you need to be using an actual stand-alone assembler such as gavrasm as opposed to gas from gcc – PlatformIO will barf:
...
Compiling .pio/build/uno/src/main.o
src/main.asm: Assembler messages:
src/main.asm:10: Error: invalid operands (*ABS* and *UND* sections) for `<<'
*** [.pio/build/uno/src/main.o] Error 1
Which, incidentally, is the same error as when you have the main file as main.s
.
When I’m writing assembly code I do this, when compiling with gcc/gas, the file is main.S
:
#define __SFR_OFFSET 0
#include <avr/io.h>
.section .text
.global main
main:
pinMode:
ldi r16,(1 << DDB5)
out DDRB,r16
digitalWrite:
out PORTB,r16
loop:
rjmp loop
The __SFR_OFFSET
thing is required because otherwise all references to DDRB
/PORTB
would need wrapping in the _SFR_IO_REG_ADDR
macro. The preprocessor pulls in avr/io.h
and that pulls in the correct header for the microcontroller in use.
Unfortunately, the gcc headers define I/O registers like DDRB
using their data memory address, which is 32 bytes higher than their actual I/O register address, and instructions like in
and out
need the latter. By default __SFR_OFFSET
is defined as 32. You have to redefine it prior to the #include avr/io.h
.
If you didn’t, the above code would look like this:
#define __SFR_OFFSET 0
#include <avr/io.h>
.section .text
.global main
main:
pinMode:
ldi r16,(1 << DDB5)
out _SFR_IO_ADDR(DDRB),r16
digitalWrite:
out _SFR_IO_ADDR(PORTB),r16
loop:
rjmp loop
If you are using a stand alone assembler such as gavrasm, then none of this is necessary.
The above example simply turns on the UNO’s built in LED (D13/PB5) and leaves it on. I call it “halfBlink” and my platformio.ini
file looks like:
[env:uno]
platform = atmelavr
board = uno
I can compile/assemble and upload with no problems.
So, I tried with an test.inc
file in include
which simply contained:
#define __SFR_OFFSET 0
#include <avr/io.h>
I removed those two lines from main.S
and replaced them with #include "test.inc"
and it compiled happily.
You caught me at a good time as I’m writing my third Arduino book, and I am concentrating on Assembly Language. This is something I was playing with recently.
There are a few subtle differences between the gas assembler and “normal” assemblers such as gavrasm. The problem here is that when using PlatformIo to assemble code, it used the gas assembler. That works fine, but is not intended to be a standalone assembler, it’s a back end for the compiler, so it has a few foibles that you need to be aware of when writing Assembly code intended to be comiled in PlatformIO (or the Arduino IDE) with gcc/gas as opposed to normal assemblers.
I suspect your documentation is for a normal assembler, hence the use of .include
, but gcc/gas needs #include
and obvioulsy, the uppercase ‘S’ extes=nsion.
Beware also, when you get to it, the Z register in normal assembly gets loaded with the byte address of some address in code by multiplying the address by two, or shifting it left one place. With gcc/gas that multiply/shift is not needed as the assembler assumes you want the byte address and not the word address, so does it automatically for you.
On those occasions where you need the word address, you have to wrap it in yet another macro to prevent gcc/gas from doing the multiply silently.
And also, when loading values > 255 into register pairs, you use hi8
and lo8
macros in gcc/gas but high
and low
in normal assembly. So, this mess results:
;-------------------------------------------------------------
; Use this method when working with the Arduino IDE, or with
; avr-gas directly -- which is not advised! Program labels are
; byte addresses by default for the gcc assemblers.
;-------------------------------------------------------------
usingGCC:
ldi ZH,hi8(helloWorld) ; Gcc uses bytes by default.
ldi ZL,lo8(helloWorld)
;-------------------------------------------------------------
; Use this method when working with non-gcc assemblers such as
; avrasm, gavrasm and so on. Program labels are word addresses
; by default for these assemblers.
;-------------------------------------------------------------
usingAssembly:
ldi ZH,high(helloWorld<<1) ; Other assemblers use words.
ldi ZL,low(helloWorld<<1)
;-------------------------------------------------------------
; And this is the label that the Z register should be
; addresssing as a byte address, for the LPM instruction.
;-------------------------------------------------------------
helloWorld:
.asciz "Hello World!\r\n"
Obvioulsy, just an example of the bonkersness of mixing and matching assemblers! :winnk:
Cheers,
Norm.