Multiple definitions of TIM2_IRQHandler?

Post here first, or if you can't find a relevant section!
Post Reply
mconsidine
Posts: 2
Joined: Mon Nov 10, 2025 3:52 pm

Multiple definitions of TIM2_IRQHandler?

Post by mconsidine »

Hi,

TL;DR A compilation fails with "multiple definitions" noted and a call like this

Code: Select all

extern "C" void TIM2_IRQHandler(void) __attribute__((weak));
extern "C" void TIM2_IRQHandler(void) {
doesn't seem to work. What is the way around this or is there just a more straightforward way to accomplish the same idea using higher level calls?

The details:
I am working with a "Blue Pill" STM32F103C8T6 (per the stamp on the chip) with 3.3V to "VBAT" for the RTC and am trying to have it initialized with two clocks. One clock would be set to universal time (UTC) and the other to sidereal ("star") time.

In working with some code on the 'net and with an AI assist, I can get a clock initialized and it will survive a reset. Where I am having compilation trouble is in implementing an interrupt handler for the second clock. Specifically, I get this compilation error:

Code: Select all

Linking everything together...
"C:\\Users\\User\\AppData\\Local\\Arduino15\\packages\\STMicroelectronics\\tools\\xpack-arm-none-eabi-gcc\\14.2.1-1.1/bin/arm-none-eabi-gcc" -mcpu=cortex-m3 -mthumb -Os -DNDEBUG --specs=nano.specs -Wl,--defsym=LD_FLASH_OFFSET=0x0 -Wl,--defsym=LD_MAX_SIZE=65536 -Wl,--defsym=LD_MAX_DATA_SIZE=20480 -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common "-Wl,--default-script=C:\\Users\\User\\AppData\\Local\\Arduino15\\packages\\STMicroelectronics\\hardware\\stm32\\2.11.0\\variants\\STM32F1xx\\F103C8T_F103CB(T-U)/ldscript.ld" "-Wl,--script=C:\\Users\\User\\AppData\\Local\\Arduino15\\packages\\STMicroelectronics\\hardware\\stm32\\2.11.0\\system/ldscript.ld" "-Wl,-Map,C:\\Users\\User\\AppData\\Local\\arduino\\sketches\
<snip verbose output>
C:/Users/User/AppData/Local/Arduino15/packages/STMicroelectronics/tools/xpack-arm-none-eabi-gcc/14.2.1-1.1/bin/../lib/gcc/arm-none-eabi/14.2.1/../../../../arm-none-eabi/bin/ld.exe: C:\Users\User\AppData\Local\arduino\sketches\1D1AECCACB69967E535E120A0E791A7C\libraries\SrcWrapper\HardwareTimer.cpp.o: in function `TIM2_IRQHandler':
HardwareTimer.cpp:(.text.TIM2_IRQHandler+0x0): multiple definition of `TIM2_IRQHandler'; C:\Users\User\AppData\Local\arduino\sketches\1D1AECCACB69967E535E120A0E791A7C\sketch\STM32_SiderealClockpt12.ino.cpp.o:STM32_SiderealClockpt12.ino.cpp:(.text.TIM2_IRQHandler+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
Using library STM32duino RTC at version 1.8.0 in folder: C:\Users\User\Documents\Arduino\libraries\STM32duino_RTC 
Using library SrcWrapper at version 1.0.1 in folder: C:\Users\User\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.11.0\libraries\SrcWrapper 
Using library USBDevice at version 1.0.0 in folder: C:\Users\User\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.11.0\libraries\USBDevice 
exit status 1

Compilation error: exit status 1
A stripped down version of the code that generates this same error is:

Code: Select all

#include <STM32RTC.h>
#include <rtc.h>
#include <math.h>

/*
 * STM32 Internal RTC Sidereal Time Clock
 * --------------------------------------
 * - Uses STM32 internal RTC (via STM32RTC library)
 * - Stores UTC internally
 * - Uses TIM2 (1 MHz base) for sidereal-rate timekeeping
 * - Provides getCurrentSiderealTime() in hours
 */

// ============================================================
//  Hardware and constants
// ============================================================

STM32RTC& rtc = STM32RTC::getInstance();

#define SIDEREAL_TIMER        TIM2
#define SIDEREAL_TIMER_IRQn   TIM2_IRQn
#define SIDEREAL_TIMER_HANDLER TIM2_IRQHandler

// ============================================================
//  Globals
// ============================================================

volatile uint32_t realtime_seconds = 0;  // updated by ISR every second
bool sidereal_initialized = false;

// ============================================================
//  TIM2 initialization
// ============================================================

extern "C" void SIDEREAL_TIMER_HANDLER(void);

void initializeSiderealTimer()
{
  SIDEREAL_TIMER->CR1 &= ~TIM_CR1_CEN;
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

  // 72 MHz / (PSC+1) = 1 MHz
  SIDEREAL_TIMER->PSC = 71;
  SIDEREAL_TIMER->ARR = 1000000 - 1;
  SIDEREAL_TIMER->CNT = 0;
  SIDEREAL_TIMER->SR  = 0;
  SIDEREAL_TIMER->DIER |= TIM_DIER_UIE;

  NVIC_SetPriority(SIDEREAL_TIMER_IRQn, 1);
  NVIC_EnableIRQ(SIDEREAL_TIMER_IRQn);

  realtime_seconds = 0;
  SIDEREAL_TIMER->CR1 |= TIM_CR1_CEN;
  sidereal_initialized = true;

  Serial.println("TIM2 initialized for 1Hz ISR (1µs base).");
}

extern "C" void SIDEREAL_TIMER_HANDLER(void)
{
  if (SIDEREAL_TIMER->SR & TIM_SR_UIF) {
    SIDEREAL_TIMER->SR &= ~TIM_SR_UIF;
    realtime_seconds++;
  }
}

uint64_t getElapsedRealMicros()  //uint64_t ????
{
  uint32_t s1, s2, cnt;
  do {
    noInterrupts();
    s1 = realtime_seconds;
    cnt = SIDEREAL_TIMER->CNT;
    s2 = realtime_seconds;
    interrupts();
  } while (s1 != s2);
  return (uint64_t)s1 * 1000000ULL + (uint64_t)cnt;
}

void setup() {
}

void loop() {
}

Admittedly, dealing with the timers, interrupts, etc at this level is new to me, but I feel like I'm missing a solution that is straightforward. I've searched the forums here, checked the wiki pages (specifically re HAL) but am still not sure of the path forward. I see that there are higher level API calls for the timers and perhaps they accomplish what I need? If have tried renaming the IRQHandler call to override it, but that doesn't seem to work. E.g. along the lines of doing this:

Code: Select all

extern "C" void TIM2_IRQHandler(void) __attribute__((weak));
extern "C" void TIM2_IRQHandler(void) {
  if (TIM2->SR & TIM_SR_UIF) {
    TIM2->SR &= ~TIM_SR_UIF;
    
    // Increment sidereal microseconds (adjusted for sidereal rate)
    sidereal_micros += (uint32_t)(1000000.0 * SIDEREAL_RATIO);
  }
}
If this is the right approach, how can I avoid the "multiple definition" error?

The goal is to have the clocks initialized and running (with battery backup) such that future sketches uploaded have access to UTC and sidereal time without having to have the overhead of calculating the values.

If this should be posted to a different board, please advise. And thank you in advance for any help.
Regards,
mconsidine
PS I'm compiling this with Arduino IDE 2.3.6 on Windows 10. And this is my first post here so apologies in advance if I've stepped on a protocol
mconsidine
Posts: 2
Joined: Mon Nov 10, 2025 3:52 pm

Re: Multiple definitions of TIM2_IRQHandler?

Post by mconsidine »

It would appear that using HardwareTimer.h is a far better approach. When I get a solution, I'll post it.
TIA,
mconsidine
Jaiaasey
Posts: 2
Joined: Wed Feb 26, 2025 3:31 pm

Re: Multiple definitions of TIM2_IRQHandler?

Post by Jaiaasey »

mconsidine wrote: Thu Nov 13, 2025 3:46 pm Hi,

TL;DR A compilation fails with "multiple definitions" noted and a call like this

Code: Select all

extern "C" void TIM2_IRQHandler(void) __attribute__((weak));
extern "C" void TIM2_IRQHandler(void) {
doesn't seem to work. What is the way around this or is there just a more straightforward way to accomplish the same idea using higher level calls?

The details:
I am working with a "Blue Pill" STM32F103C8T6 (per the stamp on the chip) with 3.3V to "VBAT" for the RTC and am trying to have it initialized with two clocks. One clock would be set to universal time (UTC) and the other to sidereal ("star") time.

In working with some code on the 'net and with an AI assist, I can get a clock initialized and it will survive a reset. Where I am having compilation trouble is in implementing an interrupt handler for the second clock. Specifically, I get this compilation error:

Code: Select all

Linking everything together...
"C:\\Users\\User\\AppData\\Local\\Arduino15\\packages\\STMicroelectronics\\tools\\xpack-arm-none-eabi-gcc\\14.2.1-1.1/bin/arm-none-eabi-gcc" -mcpu=cortex-m3 -mthumb -Os -DNDEBUG --specs=nano.specs -Wl,--defsym=LD_FLASH_OFFSET=0x0 -Wl,--defsym=LD_MAX_SIZE=65536 -Wl,--defsym=LD_MAX_DATA_SIZE=20480 -Wl,--cref -Wl,--check-sections -Wl,--gc-sections -Wl,--entry=Reset_Handler -Wl,--unresolved-symbols=report-all -Wl,--warn-common "-Wl,--default-script=C:\\Users\\User\\AppData\\Local\\Arduino15\\packages\\STMicroelectronics\\hardware\\stm32\\2.11.0\\variants\\STM32F1xx\\F103C8T_F103CB(T-U)/ldscript.ld" "-Wl,--script=C:\\Users\\User\\AppData\\Local\\Arduino15\\packages\\STMicroelectronics\\hardware\\stm32\\2.11.0\\system/ldscript.ld" "-Wl,-Map,C:\\Users\\User\\AppData\\Local\\arduino\\sketches\
<snip verbose output>
C:/Users/User/AppData/Local/Arduino15/packages/STMicroelectronics/tools/xpack-arm-none-eabi-gcc/14.2.1-1.1/bin/../lib/gcc/arm-none-eabi/14.2.1/../../../../arm-none-eabi/bin/ld.exe: C:\Users\User\AppData\Local\arduino\sketches\1D1AECCACB69967E535E120A0E791A7C\libraries\SrcWrapper\HardwareTimer.cpp.o: in function `TIM2_IRQHandler':
HardwareTimer.cpp:(.text.TIM2_IRQHandler+0x0): multiple definition of `TIM2_IRQHandler'; C:\Users\User\AppData\Local\arduino\sketches\1D1AECCACB69967E535E120A0E791A7C\sketch\STM32_SiderealClockpt12.ino.cpp.o:STM32_SiderealClockpt12.ino.cpp:(.text.TIM2_IRQHandler+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
Using library STM32duino RTC at version 1.8.0 in folder: C:\Users\User\Documents\Arduino\libraries\STM32duino_RTC 
Using library SrcWrapper at version 1.0.1 in folder: C:\Users\User\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.11.0\libraries\SrcWrapper 
Using library USBDevice at version 1.0.0 in folder: C:\Users\User\AppData\Local\Arduino15\packages\STMicroelectronics\hardware\stm32\2.11.0\libraries\USBDevice 
exit status 1

Compilation error: exit status 1
A stripped down version of the code that generates this same error is:

Code: Select all

#include <STM32RTC.h>
#include <rtc.h>
#include <math.h>

/*
 * STM32 Internal RTC Sidereal Time Clock
 * --------------------------------------
 * - Uses STM32 internal RTC (via STM32RTC library)
 * - Stores UTC internally
 * - Uses TIM2 (1 MHz base) for sidereal-rate timekeeping
 * - Provides getCurrentSiderealTime() in hours
 */

// ============================================================
//  Hardware and constants
// ============================================================

STM32RTC& rtc = STM32RTC::getInstance();

#define SIDEREAL_TIMER        TIM2
#define SIDEREAL_TIMER_IRQn   TIM2_IRQn
#define SIDEREAL_TIMER_HANDLER TIM2_IRQHandler

// ============================================================
//  Globals
// ============================================================

volatile uint32_t realtime_seconds = 0;  // updated by ISR every second
bool sidereal_initialized = false;

// ============================================================
//  TIM2 initialization
// ============================================================

extern "C" void SIDEREAL_TIMER_HANDLER(void);

void initializeSiderealTimer()
{
  SIDEREAL_TIMER->CR1 &= ~TIM_CR1_CEN;
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

  // 72 MHz / (PSC+1) = 1 MHz
  SIDEREAL_TIMER->PSC = 71;
  SIDEREAL_TIMER->ARR = 1000000 - 1;
  SIDEREAL_TIMER->CNT = 0;
  SIDEREAL_TIMER->SR  = 0;
  SIDEREAL_TIMER->DIER |= TIM_DIER_UIE;

  NVIC_SetPriority(SIDEREAL_TIMER_IRQn, 1);
  NVIC_EnableIRQ(SIDEREAL_TIMER_IRQn);

  realtime_seconds = 0;
  SIDEREAL_TIMER->CR1 |= TIM_CR1_CEN;
  sidereal_initialized = true;

  Serial.println("TIM2 initialized for 1Hz ISR (1µs base).");
}

extern "C" void SIDEREAL_TIMER_HANDLER(void)
{
  if (SIDEREAL_TIMER->SR & TIM_SR_UIF) {
    SIDEREAL_TIMER->SR &= ~TIM_SR_UIF;
    realtime_seconds++;
  }
}

uint64_t getElapsedRealMicros()  //uint64_t ????
{
  uint32_t s1, s2, cnt;
  do {
    noInterrupts();
    s1 = realtime_seconds;
    cnt = SIDEREAL_TIMER->CNT;
    s2 = realtime_seconds;
    interrupts();
  } while (s1 != s2);
  return (uint64_t)s1 * 1000000ULL + (uint64_t)cnt;
}

void setup() {
}

void loop() {
}

Admittedly, dealing with the timers, interrupts, etc at this level is new to me, but I feel like I'm missing a solution that is straightforward. I've searched the forums here, checked the wiki pages (specifically re HAL) but am still not sure of the path forward. I see that there are higher level API calls for the timers and perhaps they accomplish what I need? If have tried renaming the IRQHandler call to override it, but that doesn't seem to work. E.g. along the lines of doing this:

Code: Select all

extern "C" void TIM2_IRQHandler(void) __attribute__((weak));
extern "C" void TIM2_IRQHandler(void) {
  if (TIM2->SR & TIM_SR_UIF) {
    TIM2->SR &= ~TIM_SR_UIF;
    
    // Increment sidereal microseconds (adjusted for sidereal rate)
    sidereal_micros += (uint32_t)(1000000.0 * SIDEREAL_RATIO);
  }
}
If this is the right approach, how can I avoid the "multiple definition" error?

The goal is to have the clocks initialized and running slope game 2 (with battery backup) such that future sketches uploaded have access to UTC and sidereal time without having to have the overhead of calculating the values.

If this should be posted to a different board, please advise. And thank you in advance for any help.
Regards,
mconsidine
PS I'm compiling this with Arduino IDE 2.3.6 on Windows 10. And this is my first post here so apologies in advance if I've stepped on a protocol

The error happens because TIM2_IRQHandler is already defined in the core via HardwareTimer, so redefining it causes the conflict. Using HardwareTimer with attachInterrupt() is the cleaner fix unless you remove or override the existing handler at the linker level.
Post Reply

Return to “General discussion”