Search code examples
ccmakeavravr-gccatmega16

AVR-GCC Overwrites Interrupt Vector Table with Main


I have a simple C program written for an ATmega168:

#include "avr/io.h"
#include "avr/interrupt.h"

volatile uint8_t intrs;

int main(void){
   TCCR1B |= _BV(CS12) | _BV(CS10); \\ set clock source
   TIMSDK1 |= _BV(TOIE1); \\ Enable overflow interrupts
   ... // initialize timer and intrs
   sei();  \\ enable global interrupts
   while(1){}
}

ISR(TIMER1_OVF_vect){
    //do stuff
    reti();
}

My CMakeLists:

cmake_minimum_required(VERSION 3.16)
set(CMAKE_SYSTEM_NAME Generic)
SET(CMAKE_SYSTEM_VERSION 1)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -nostdlib -Wall -Wextra")

set(TARGET_CPU ATmega168)

project(clion_avr_c_demo C)

include(CMakeVerifyCompiler.txt)

add_compile_definitions("__AVR_${TARGET_CPU}__")

add_link_options(-Wl,--print-memory-usage)

add_executable(${PROJECT_NAME} src/main.c)

When I compile this (both directly from command line with avr-gcc and in CLion through my CMakeLists file), the compiler (linker?) seems to be overwriting the interrupt vector table with my main function. When I use objdump on the .elf file to generate the disassembly of the code, I get:

clion_avr_c_demo:     file format elf32-avr

Disassembly of section .text:

00000000 <main>:
   0:   cf 93           push    r28
   2:   df 93           push    r29
   4:   cd b7           in  r28, 0x3d   ; 61
   6:   de b7           in  r29, 0x3e   ; 62
   ... ; clearly the disassembled main function
00000054 <__vector_13>:
  54:   1f 92           push    r1
  56:   0f 92           push    r0
  ... ; clearly the disassembled ISR

According to the datasheet for the ATmega168, this is perfectly ok in a scenario where there are no interrupts:

"If the program never enables an interrupt source, the Interrupt Vectors are not used, and regular program code can be placed at these locations."

but since I have interrupts, I'm expecting the disassembly to look something like:

...
0x0000      jmp    main        ; RESET
...
0x001A      jmp    __vector_13 ; TIM1_OVF_vect
...

Is there something missing in my code or compilation that is causing the compiler to incorrectly think I'm not using interrupts, or am I completely misinterpreting my disassembly output?

Edit: Building with CMAKE_VERBOSE_MAKEFILE ON, I get the following for build and link:

...\avr-gcc.exe -D__AVR_ATmega168__ -nostdlib -Wall -Wextra -g -o ...\main.c.obj -c ...\main.c
...\avr-gcc.exe -nostdlib -Wall -Wextra -g -Wl,--print-memory-usage .../main.c.obj -o clion_avr_c_demo

which is exactly what's defined in the CMakeLists.txt (ie, nothing extra being thrown in).


Solution

  • I did some experiments and came to this conclusion:

    You don't get a vector table because you don't provide one. Apparently the option -nostdlib excludes that part (of the library?) that implements the table.

    So the first error is the usage of said option.

    But this is not enough, as the linker does not know the MCU, and so which table to use. (Or which part of the library to include.)

    So the second error is to not provide the appropriate option -mmcu=atmega168.

    With this linker call Now the vector table is built:

    avr-gcc -mmcu=atmega168 -Wall -Wextra -g main.c.obj -o clion_avr_c_demo

    Notes:

    • I did not try how to provide an own vector table. This definitely works.
    • By the usage of said option you also exclude the startup code. This startup code is a promise the standard makes, for example clearing the BSS that contains static variables, which are explicitly or implicitly initialized to zero. Please be aware of the DATA section, too.
    • I did not try how to provide an own startup code. This definitely works.
    • If you want to avoid functions of the standard library, don't use them. If you want to be sure that none is linked, let the linker generate a cross-reference and check it.