2025-07-24 4:16 AM
Trouble Report: STM32G474 FD CAN RX Interrupt Causes HardFault Due to VTOR Left at 0x00000000
Platform Details
----------------
- Microcontroller: STM32G474VETx
- IDE: STM32CubeIDE
- Version: 1.16.0
- Build: 21983_20240628_1741
- Startup project: Standard CubeMX-generated application with FDCAN peripheral using interrupt-based RX
- RTOS: None (bare metal)
- Debugger: ST-LINK / OpenOCD
Problem Summary
---------------
When enabling FDCAN RX FIFO0 interrupts via HAL_FDCAN_ActivateNotification() on STM32G474VETx,
the system crashes immediately with a hard fault (0xFFFFFFF9) on the first received CAN message.
After the fault, resetting via the debugger fails to return to main(). On inspection, SCB->VTOR is
found to be 0x00000000, which causes all vector table lookups to point to incorrect (and un-programmed)
memory, resulting in undefined interrupt dispatch.
Technical Findings
------------------
- FDCAN1_IT0_IRQHandler() is correctly defined and linked (nm confirms symbol is present and in vector table).
- HAL_FDCAN_ActivateNotification(...) returns HAL_OK.
- HAL_FDCAN_ConfigFilter(...) is used correctly with valid filter indexes (≤ ExtFiltersNbr - 1).
- Only extended ID messages are used.
- StdFiltersNbr = 0, ExtFiltersNbr = 4, as required.
- Interrupt is not handled; system faults without entering handler.
- Debugger shows return address 0xFFFFFFF9 — invalid exception return due to unhandled interrupt.
- After crash, SCB->VTOR is 0x00000000. This is incorrect — the vector table lives at 0x08000000.
Workaround
----------
Manually setting SCB->VTOR = 0x08000000; at the top of main() resolves the issue and restores correct interrupt behavior.
After applying this fix:
- The CAN interrupt is handled correctly
- Callback is invoked as expected
- System remains stable
- Debugger reset behaves correctly
Suggested Root Cause
---------------------
CubeMX-generated startup or system init code for STM32G474 may not explicitly set SCB->VTOR to the
correct Flash base (0x08000000). If the CPU starts with VTOR = 0x00000000 (e.g., after soft-reset or
via debugger), the vector table lookup fails, leading to:
- IRQs jumping to invalid addresses
- Crashes on first interrupt
- Unrecoverable core state even after reset
Recommended Actions by ST
-------------------------
1. Ensure that CubeMX and HAL startup code always initializes SCB->VTOR explicitly, even in single-image,
no-bootloader projects.
2. Consider adding a warning or assertion if SCB->VTOR is zero at main().
3. Update documentation for FDCAN usage to mention VTOR importance if not using a bootloader.
4. Provide CubeMX configuration option for VTOR setup under system settings.
Supporting Files Available on Request
-------------------------------------
- main.c, stm32g4xx_it.c, FDCAN_Config() source
- ELF map and nm dump confirming ISR linkage
- Screenshot or logs from debugger showing VTOR = 0x00000000
This resulted in a lot of re-coding the 'CANFD' and trying to fix this. With no interrupts enabled the code ran as expected and I could POLL and RX can . But as soon as the interrupts were enabled, on the first CAN message rxd it crashed. Nasty as well as the trashed vectors meant reset in debug did not work either
2025-07-24 5:16 AM
Hello.
Thank you for this detailed report.
In older versions of CMSIS that ST used to provide, the function SystemInit() (called by the startup code prior to main() entry) would always initialize the VTOR to point to FLASH_BASE or SRAM_BASE (depending on project configuration). This was causing some other issues issues and was later removed, so the VTOR currently indeed is not initialized during startup and left at reset value of 0 - this was intentional change.
This, in most cases, doesn't constitute a problem, since, depending on the Boot mode, either Flash, system memory (bootloader), or SRAM is cloned/remapped to the base of the address space (0x0), so that the NVIC can see a copy of the program, including the vector table, at 0x0.
I have a strong suspicion that your underlying problem is that for whichever reason, different address gets remapped to 0x0 than your application. It is possible that e.g. SysTick handler is being handled correctly, just not by the handler in your application, hence the application doesn't crash at first SysTick ISR. Most typical suspect is the "Boot from bank 2" BFB2 option bit. Please verify that your option bytes differ from factory values only in ways you intend.
If the change to the boot address is intentional, you can either do this by uncommenting the definition of #USER_VECT_TAB_ADDRESS in system_stm32g4xx.c (and potentially change its address too), or just rewrite the VTOR at the beginning of main().
If you still suspect that this is caused by the way the project is generated, please let us know more.
2025-07-24 6:24 AM - edited 2025-07-24 6:25 AM
I still think this is wrong. Bare metal designs used to work correctly
with code automatically generated. I think that many people will spend time
scratching their heads because they will (naturally assume) that the
interrupt code they have just implemented has some kind of bug in it, not
that ANY new interrupt will be unhandled. So sorry I disagree. This has the potential to cause a lot of user frustration and wasted time. I think this
is a BUG.
2025-07-24 6:46 AM
This is not booting from BANK 2. But the VTOR is zero to start with
2025-07-24 6:51 AM - edited 2025-07-24 6:52 AM
I understand your frustration. It is not a problem that users should find themselves having unless they started doing nonstandard modifications to the hardware and project settings. It most certainly isn't anything that should happen with a generated project with hardware in factory state.
First of all, I would like to verify, whether this is the former or the latter. Can you please check the hardware configuration (mainly option bytes and boot pins) as I've written previously? I would like to identify the root cause here, only then we can tell whether it's plain bug in the toolchains, or a misconfiguration (potentially due to some problem in documentation).
As I've explained previously as well, having the VTOR left at 0x0 is absolutely intended and should not by itself cause any of the behavior you have experienced.
Please do let me know whether anything I've written helps you narrow down the root cause.
(Personally I also think that startup code should initialize the VTOR to point to an address taken from the linker script - this is possible, but requires three different #IFDEF'd codes for the three supported toolchains. For various reasons, this is not what's done. That's not the underlying cause of this issue, though.)
2025-07-24 7:12 AM
2025-07-24 7:17 AM
Can you please check the MEM_MODE and FB_MODE values in the SYSCFG_MEMRMP to see which part memory is being mapped to 0x0?
2025-07-24 7:40 AM - edited 2025-07-24 7:41 AM
Hope this is what you wanted. The function was called as first function in main (i.e. no HAL config calls yet)
2025-07-24 7:58 AM
That's pretty strange, MEM_MODE == 0 and FB_MODE == 0 should mean that main Flash memory at 0x08000000 should be mapped onto 0x00000000 and changing VTOR from 0x0 to 0x08000000 should make no difference.
Did you enable the clock for SYSCFG before reading it? That would be RCC_APB2ENR_SYSCFGEN. Without the clock, it would always read as zero.
If it actually contains zeroes (you can make sure by checking whether you're able to toggle the bits using the debugger), can you re-check that the problem exists when VTOR=0 and gets fixed when VTOR=0x08000000, and if yes, check whether you see the same or different memory content at memory 0x0 and 0x08000000?
2025-07-24 8:21 AM
Ok this does not appear to be configured. Where would I find this in cubeMX part of stm32ide?