ATmega4809 intterupt vector

Hello

I have some problem with the interrupt vector of ATmega4809, I am coding an external interrupt in assembler on an Arduino Nano Every.
Here my interrupt vector definition:

;**************************************
;	Interrupt vector
;**************************************

.org	0x00
        jmp	main

.org	0x0A
        jmp	isrA

.org	0x0C
        jmp	isrA

.org	0x100

When I try to assemble it I receive this error:

Compiling .pio/build/nano_every/src/main.o
src/main.S: Assembler messages:
src/main.S:53: Error: attempt to move .org backwards
*** [.pio/build/nano_every/src/main.o] Error 1

Changing the jmp with rjmp the error disappear (though the interrupt does not work…). It looks like the addresses in .org would be byte based while on the datasheet they are word based (jmp opcode is 32 bits while rjmp is 16bits).

Does anyone know why this happens and what should I do ?

Thank you very much

reto

The avr-as page contains an example that implements an interrupt, but I found that to not work somehow in regards to “just name the function / label in accordance to the C interrupt function and it will be put in the vector table”.

What I did find is however that if you name a label (and declare it as .global) with the name scheme __vector_<vector number> it does link it correctly.

So e.g., per ATMega4909 datasheet page 66

When I write src\main.S as

.global main
    main:
       RJMP main

// vector 5 = CCL - Configurable Custom Logic (CCL_CCL_vect)
.global __vector_5
__vector_5:
    RETI

// vector 6 = PORTA - External interrupt (PORTA_PORT_vect)
.global __vector_6
__vector_6:
    RETI

with the platformio.ini

[env:nano_every]
platform = atmelmegaavr
board = nano_every

I get a successful compilation and the complete disassembly shows

>C:\Users\Max\.platformio\packages\toolchain-atmelavr\bin\avr-objdump.exe -d .pio\build\nano_every\firmware.elf

.pio\build\nano_every\firmware.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:   0c 94 50 00     jmp     0xa0    ; 0xa0 <__ctors_end>
   4:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
   8:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
   c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  10:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  14:   0c 94 5d 00     jmp     0xba    ; 0xba <__vector_5>
  18:   0c 94 5e 00     jmp     0xbc    ; 0xbc <__vector_6>
  1c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  20:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  24:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  28:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  2c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  30:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  34:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  38:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  3c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  40:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  44:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  48:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  4c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  50:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  54:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  58:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  5c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  60:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  64:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  68:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  6c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  70:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  74:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  78:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  7c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  80:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  84:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  88:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  8c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  90:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  94:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  98:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  9c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>

000000a0 <__ctors_end>:
  a0:   11 24           eor     r1, r1
  a2:   1f be           out     0x3f, r1        ; 63
  a4:   cf ef           ldi     r28, 0xFF       ; 255
  a6:   cd bf           out     0x3d, r28       ; 61
  a8:   df e3           ldi     r29, 0x3F       ; 63
  aa:   de bf           out     0x3e, r29       ; 62
  ac:   0e 94 5c 00     call    0xb8    ; 0xb8 <main>
  b0:   0c 94 5f 00     jmp     0xbe    ; 0xbe <_exit>

000000b4 <__bad_interrupt>:
  b4:   0c 94 00 00     jmp     0       ; 0x0 <__vectors>

000000b8 <main>:
  b8:   ff cf           rjmp    .-2             ; 0xb8 <main>

000000ba <__vector_5>:
  ba:   18 95           reti

000000bc <__vector_6>:
  bc:   18 95           reti

000000be <_exit>:
  be:   f8 94           cli

000000c0 <__stop_program>:
  c0:   ff cf           rjmp    .-2             ; 0xc0 <__stop_program>

There are many things to be noted here:

  • the compiler adds automatic startup files (since no -nostartfiles option is given to the linker by default) – you should not be using .org to modify the vector table by hand in this configuration
  • that adds the __vectors vector table by default
    • reset vector (first entry) is mapped to __ctors_end
    • all non-implemented interrupts go to __bad_interrupt which in turn jumps to the reset vector
    • functions called __vector_<n> implement the n-th vector per the datasheet
    • interrupt table uses 4-byte JMP instructions, or, 2 flash words (1 word = 16-bits). So the “vector address” from the datasheet has to be multiplied by 2. (see here)
  • before calling in o main, it executes some assembly code which set up the e.g. stack pointer – no need to do that in main

It’s still weird that I cannot do in assembly

.global PORTA_PORT_vect
PORTA_PORT_vect:
   RETI

and have it put in the vector tables as __vector_6, but welp.

Thank you Max, your solution works well.

I still have a question, what if I want to define the interrupt vector with .org?
I know it can seem stupid to redefine something that already works, but I am a teacher and would like to have an example to show my students.
I tried setting

build_flags: -nostartfiles

in platformio.ini and using

.org	0x18
    jmp	isrA

to define the vector (0x0C*2=0x18), but it does not work.
Is there some other flag I must set?

Well the : is wrong, that should be a =. But there are additional caveats: The -nostartfiles is a linkerflag, in terms of PlatformIO / SCons build system internals that should be a entry in the env["LINKFLAGS"] entry. Further, when -nostartfiles is added and PlatformIO applies the standard -Wl,--gc-sections (garbage collect sections) and -flto (link-time optimization) options, the resulting firmware would be empty because the compiler, without its default startup files and vector table, does not see how any function would be used at all and removes it (bootloader - Compiling with -flto and -nostartfiles results in no program at all - Arduino Stack Exchange). So to prevent that, we have to take care of add -nostartfiles as well as removing the offending -Wl,--gc-sections option (removing this one suffices). PlatformIO’s built-in scripting capabilities can be used for that.

So a project with

platformio.ini:

[env:nano_every]
platform = atmelmegaavr
board = nano_every
extra_scripts = no_startfiles.py

no_startfiles.py (this file has to be in the same folder as the platformio.ini)

Import("env")
# add nostartfiles option
env.Append(LINKFLAGS=["-nostartfiles"])
# remove default garbage-collection of sections, it kills off the custom code
old_linkflags = env["LINKFLAGS"]
new_linkflags = [x for x in old_linkflags if x != "-Wl,--gc-sections" ]
env["LINKFLAGS"] = new_linkflags

and src\main.S

.global main
.global ccl_vect
.global porta_ext_int_vect

.section .text

isr_table:
.org 0x00
        jmp main
.org 0x14
        jmp ccl_vect
.org 0x18
        jmp porta_ext_int_vect

// first free place after 40-entry vector table with 2 flash WORDS per entry
.org 0xa0
main:
   RJMP main //hang at the same place

// vector 5 = CCL - Configurable Custom Logic (CCL_CCL_vect)
ccl_vect:
    RETI

// vector 6 = PORTA - External interrupt (PORTA_PORT_vect)
porta_ext_int_vect:
    RETI

results in

Compiling .pio\build\nano_every\src\main.o
Linking .pio\build\nano_every\firmware.elf
Checking size .pio\build\nano_every\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   0.0% (used 0 bytes from 6144 bytes)
Flash: [          ]   0.3% (used 166 bytes from 48640 bytes)
Building .pio\build\nano_every\firmware.hex

and when we disassemble the final firmware again:

>C:\Users\Max\.platformio\packages\toolchain-atmelavr\bin\avr-objdump.exe -d .pio\build\nano_every\firmware.elf

.pio\build\nano_every\firmware.elf:     file format elf32-avr


Disassembly of section .text:

00000000 <__ctors_end>:
   0:   0c 94 50 00     jmp     0xa0    ; 0xa0 <main>
        ...
  14:   0c 94 51 00     jmp     0xa2    ; 0xa2 <ccl_vect>
  18:   0c 94 52 00     jmp     0xa4    ; 0xa4 <porta_ext_int_vect>
        ...

000000a0 <main>:
  a0:   ff cf           rjmp    .-2             ; 0xa0 <main>

000000a2 <ccl_vect>:
  a2:   18 95           reti

000000a4 <porta_ext_int_vect>:
  a4:   18 95           reti

We now get exactly the instructions we wanted – no compiler added sugar, full control over the vector table and all functions. (The ... gaps in between are undefined and will be 0x00 in the .hex file).

Compare that to the with-startfiles version

Disassembly of section .text:

00000000 <__vectors>:
   0:   0c 94 50 00     jmp     0xa0    ; 0xa0 <__ctors_end>
   4:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
   8:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
   c:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  10:   0c 94 5a 00     jmp     0xb4    ; 0xb4 <__bad_interrupt>
  14:   0c 94 5d 00     jmp     0xba    ; 0xba <__vector_5>
  18:   0c 94 5e 00     jmp     0xbc    ; 0xbc <__vector_6>
[..]
000000a0 <__ctors_end>:
[..]

and everything seems to be in its correct place (vector 5 implemented at absolute address 0x14, first real instruction code after vector table at 0xa0).

1 Like

Thank you very much, this is exactly what I was looking for.
There is still a problem with the interrupt, this is the full program, it does not very much, it’s more didactic.

;******************************************
;
; Programmazione ATmega 4809 in assembler 
; sulla piattaforma Arduino Nano Every
;
;******************************************

.global main

.global __vector_6


;**************************************
;	Definizione dei registri
;**************************************

.equ	VPORTA, 0x00
.equ	VPORTE, 0x10
;offset
.equ	DIR,     0x00
.equ	POUT,    0x01
.equ	IN,      0x02
.equ	INTFLAGS,0x03

.equ	PORTA,	0x0400
.equ	PORTE,	0x0480
;offset
.equ	PINCTRL, 0x10	; + numero del pin

.equ	CPU,0x30
;offset
.equ	CCP,0x04

.equ    CLKCTRL, 0x60
;offset
.equ 	MCLKCTRLB, 0x01

.equ	CPUINT, 0x0110
;offset
.equ	CTRLA, 0x00


;*************************************
; main	
;*************************************

main:

; configurazione del clk

	ldi	R16,0xD8
	ldi	R17,0x00
	out CPU+CCP,R16				;sblocca la scrittura per 4 cicli macchina
	sts CLKCTRL+MCLKCTRLB,R17	;spegne il prescaler

; configurazione delle porte

	ldi	R16,0x0B
	sts	PORTA+PINCTRL+0,R16 ; PA0 pullup, int falling
	sbi	VPORTE+DIR,1		;PE1 uscita
	sbi	VPORTE+DIR,2		;PE2 uscita (LEDBUILTIN)

; abilita interrupt

	sei


;***************************************
;	Loop
;***************************************

loop:	
	sbi VPORTE+POUT,1	;1 ciclo
	cbi VPORTE+POUT,1	;1 ciclo
	rjmp	loop		;2 cicli


;***************************************
; Interrupt service routine porta A
;***************************************

__vector_6:
	sbi	VPORTE+POUT,2
    reti

The output on the PE2 pin is this:

After the interrupt I have this:

So it looks like the clk frequency has changed.
This happens with both versions, the program without the “compiler added sugar” does the same.
Is there something I overlooked?

Per assembly - Preserving sreg in AVR interrupts - Stack Overflow, Der Kern der AVR-CPU and datasheet chapter “16.3.3 Interrupts”,

When an interrupt condition occurs, the corresponding Interrupt Flag is set in the Interrupt Flags register of the peripheral (peripheral.INTFLAGS).

An interrupt request is generated when the corresponding interrupt source is enabled and the Interrupt Flag is set. The interrupt request remains active until the Interrupt Flag is cleared. See the peripheral’s INTFLAGS register for details on how to clear Interrupt Flags.

So I added the definition

.equ CPU_SREG, 0x003F ; CPU status register

at the top and rewrote the interrupt to

__vector_6:
	; save SREG
    in	r16, CPU_SREG
	; no more interrupts until we are done
	cli
	; turn on PE2
    sbi VPORTE+POUT,2
	; clear 0th bit (for PA0) in INTFLAGS register of PORTA.
	; otherwise we are still in interrupt mode...
	cbi VPORTA+INTFLAGS, 0
	; restore SREG
	out	CPU_SREG, r16
	; enable interrupts
	sei
	; return from interrupt
    reti

and then I don’t observe a change in frequency anymore. Seems more like the MCU was locked up or not working because SREG wasn’t saved.

In fact when I add a little delay loop (generated through this), the program seems to be well-observable and and works as expected.

PE1 keeps blinking through the regular loop (but may is interrupted for a few cycles / microseconds when the interrupt is being handled), PE2 starts LOW and goes high when the interrupt fires, falling edge on PA0 triggers the interrupt and thus that PE2 goes high. After the interrupt, PE1 keeps cycling with the same frequency.

with

;******************************************
;
; Programmazione ATmega 4809 in assembler 
; sulla piattaforma Arduino Nano Every
;
;******************************************

.global main

.global __vector_6


;**************************************
;	Definizione dei registri
;**************************************

.equ	VPORTA, 0x00
.equ	VPORTE, 0x10
;offset
.equ	DIR,     0x00
.equ	POUT,    0x01
.equ	IN,      0x02
.equ	INTFLAGS,0x03

.equ	PORTA,	0x0400
.equ	PORTE,	0x0480
;offset
.equ	PINCTRL, 0x10	; + numero del pin

.equ	CPU,0x30
;offset
.equ	CCP,0x04

.equ    CLKCTRL, 0x60
;offset
.equ 	MCLKCTRLB, 0x01

.equ	CPUINT, 0x0110
;offset
.equ	CTRLA, 0x00

.equ CPU_SREG, 0x003F ; CPU status register

;*************************************
; main	
;*************************************

main:

; configurazione del clk

	ldi	R16,0xD8
	ldi	R17,0x00
	out CPU+CCP,R16				;sblocca la scrittura per 4 cicli macchina
	sts CLKCTRL+MCLKCTRLB,R17	;spegne il prescaler

; configurazione delle porte

	ldi	R16,0x0B
	sts	PORTA+PINCTRL+0,R16 ; PA0 pullup, int falling
	sbi	VPORTE+DIR,1		;PE1 uscita
	sbi	VPORTE+DIR,2		;PE2 uscita (LEDBUILTIN)

; abilita interrupt

	sei


;***************************************
;	Loop
;***************************************

loop:
	sbi VPORTE+POUT,1	;1 ciclo
	rcall delay_1sec
	cbi VPORTE+POUT,1	;1 ciclo
	rcall delay_1sec
	rjmp	loop		;2 cicli
	
; delay func for 1 second at 16MHz 
; ATMega4809 may also run at 20MHz, but fuses on Nano Every are burned for 16MHz
delay_1sec: 
    ldi  r18, 82
    ldi  r19, 43
    ldi  r20, 0
L1: dec  r20
    brne L1
    dec  r19
    brne L1
    dec  r18
    brne L1
	ret


;***************************************
; Interrupt service routine porta A
;***************************************

__vector_6:
	; save SREG
    in	r16, CPU_SREG
	; no more interrupts until we are done
	cli
	; turn on PE2
    sbi VPORTE+POUT,2
	; clear 0th bit (for PA0) in INTFLAGS register of PORTA.
	; otherwise we are still in interrupt mode...
	cbi VPORTA+INTFLAGS, 0
	; restore SREG
	out	CPU_SREG, r16
	; enable interrupts
	sei
	; return from interrupt
    reti

When I leave out

cbi VPORTA+INTFLAGS, 0

the MCU just dies

1 Like

Thank you very much, now it works.
I did not think the cause could be the interrupt flag, I still can not figure out why it is happening, in your case the MCU dies, in my case the MCLK changes frequency…