Assembly header files are not found in default include path

I’m supposed to build a simple assembly code to blink an LED for a university lab. It includes a header file. But compiler cannot find the header file m328pdef.inc though it is inside both default include paths include and src. I have tried changing main.asm to main.s and main.S. But error is still there.

Note that, I have build C code with platformio for the same platform. But there were no similar kind of errors during the build process when including headers or libraries. So I’m really confused. I looked through the platformio docs, but could not get my code to work. Please help.

Thanks

This is my main.asm file

.include "m328pdef.inc"
.org	0x00

           CLR R17	      ; clear R17 register
           ldi R16,0x10     ; load 0001 0000 into R16
           out DDRB,R16	; set PORTB pin5 to output 

start    :eor R17,R16	
           out PORTB,R17	; write to PORTB (Turn on/off LED)

Delay :LDI R18,0x04
	
Again :DEC R18
          BRNE Again
          rjmp start

This is my terminal output of a clean build

Processing ATmega328P (platform: atmelavr; board: ATmega328P)
----------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/ATmega328P.html
PLATFORM: Atmel AVR (4.1.0) > ATmega328P/PA
HARDWARE: ATMEGA328P 16MHz, 2KB RAM, 32KB Flash
DEBUG: Current (avr-stub) External (avr-stub, simavr)
PACKAGES: 
 - toolchain-atmelavr @ 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Compiling .pio/build/ATmega328P/src/main.o
src/main.asm: Assembler messages:
src/main.asm:1: Error: can't open m328pdef.inc for reading: No such file or directory
*** [.pio/build/ATmega328P/src/main.o] Error 1

This is my platformio.ini

type or paste code here
[env:ATmega328P]
platform = atmelavr
board = ATmega328P
framework = arduino

This is my project structure

Moving the m328pdef.inc in the same folder as src/ doesn’t help?

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. :wink:

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.

1 Like

I have tried it, as it is already mentioned. But it didn’t made any difference. Still getting the error.

It is expected to be fully written in assembly.

I removed the .include "m328pdef.inc" with the following block, as you have mentioned

#define __SFR_OFFSET 0
#include <avr/io.h>

.section .text
.global main

main:

Now it is working as a charm. Thank you very much. You saved my university module.

Ok, I’m glad we got that sorted. The problem was down to the differences between what gcc/gas expects and what normal assemblers expect. It seems that your m328pdef.inc is written for gcc/gas because if it’s written for a normal assembler, then compiling it with gcc/gas throws up numerous errors! I know, because I tried! :wink:

Are you aware that the pin you set as output is not PB5, it’s PB4?

ldi R16,0x10     ; load 0001 0000 into R16

That’s PB4. PB5 is 0x20 or 0b0010 0000, however, it’s easier to do this:

ldi R16,(1 << PORTB5)

It’s also almost self documenting.

Also, what is the clock speed on your board? With the delay you are using I’d expect to see something like 1.45 MHz at 16 MHz clock speeds. When I corrected the output pin as above, the LED didn’t light up (or didn’t appear to) – your delay is only 11 clocks (between “delay” and “rjmp start”. Even at 1 MHz clock speed, that’s still 90 Hz.

Cheers,
Norm.

In which case, PlatformIO is not going to be helpful as it uses gcc/gas for the compilation/assembly process and anything written fully in Assembly Language needs a proper assembler – not one which is mainly a compiler back end utility.

Cheers,
Norm.

Actually I copied this code from the homework as it was our first assembly program. I just wanted to find out if I can upload it without errors. Yeah, I know that having this much small amount of delay does not make any sense. But it only for understanding the basic theories, just a very basic code. I asked the same question during the today lab, and my instructor told that we will be taught to use more practical delays in the upcoming labs.

At my lab, all the other students are using Atmel studio on their Windows machines. But I had to go with Platformio as I’m using a M1 MacBook Pro. That is the reason why I got into this issue, in the first place. Thanks one again.

Hmm, you might have other problems later in the course with PlatformIO and gcc/gas, due to the nature of gas. I thouroughly recommend gavrasm from Gerd's AVR Assembler however that’s not for Macs.

The replacement for Atmel Studio is MPE Lab X, and that does have downloads for Macs at MPLAB® Ecosystem Downloads Archive | Microchip Technology which might be useful?

I believe it’s a free download, but you need an account at Microchip, which is no great hardship.

HTH

Cheers,
Norm.