How to Manually Program an Interrupt Service Routine (ISR) for STM32F4 MCU Without Using Libraries?

Good day everyone, I have gone through the Datasheet and User Manual for my STM32F4 Microcontroller Unit (MCU) in great detail, including PM0214 for STM32F4xx MCUs. Additionally, I have searched various online resources that provide information on programming interrupts without using a library but didn't find much guidance. My question is if NVIC's integration with hardware essentials makes it difficult to program an interrupt manually by specifying ISR address and function without relying on any libraries? Everywhere I look there are functions like: Enable IRQn using NVIC. Setting the priority for a specified IRQn type can be achieved by using NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority). What if someone desires to manually code an ISR for the purpose of learning? Which steps do I need to take? Is there documentation available for reference? Would it be advisable or worthwhile to follow this approach?
8 Replies
ke7c2mi
ke7c2mi•4mo ago
First things first - lets keep in mind there are multiple layers of abstraction in the ecosystem You have the ST high level and low level HALs - it is very common to not use these, or just use them as reference / guidance when implementing your own drivers Then there is CMSIS - this is a standard made by arm so we don't reinvent the wheel a million times in how some basic functions are handled, taking care of some of the core Cortex-XX things. In general, use CMSIS and don't throw it away - it is much less common to not use CMSIS than it is to not use whichever HAL NVIC_SetPriority comes from CMSIS - so typically we would always use it (it's worth noting here, part of why Cortex-M3 was novel was ARM made not just the core, but a bunch of supporting stuff too, they mandated that there would be an NVIC and a Systick in all the systems and so these parts can be common even across different manufacturers, never mind parts - this is a huge step up from the older days. We can see some of the pain of the many interrupt handler implementations in the RISCV ecosystem today, though I suspect that will normalise over time) If you look at it though, all it does is poke a couple of registers, there is absolutely nothing stopping you poking those registers directly - we can definitely replace NVIC_SetPriority if we want to - I would go as far as to say this is easy. One other note on things to consider throwing away and things to keep - keep the low level device headers i.e. the ones which give names to the memory map: https://github.com/STMicroelectronics/cmsis_device_f4/blob/master/Include/stm32f407xx.h - we generate them now from svd To do an interrupt from scratch we need to go further though. At the end of the day, an interrupt is some function, with some specific calling convention, which exists at some explicit location in memory. We will see some systems let us point the NVIC to an interrupt table at various locations and know this can be somewhat configurable.
GitHub
cmsis_device_f4/Include/stm32f407xx.h at master · STMicroelectronic...
Provides the STM32Cube MCU Component "cmsis_device_f4" of the STM32F4 series. - STMicroelectronics/cmsis_device_f4
ke7c2mi
ke7c2mi•4mo ago
But how do we define that table? Well we can see how ST do it here: https://github.com/STMicroelectronics/cmsis_device_f4/blob/master/Source/Templates/arm/startup_stm32f401xc.s
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
....
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size

__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
....
This is startup code, we see the table of vectors being declared at line 60, but that's only half the story, we should also look at the linker file: https://github.com/STMicroelectronics/STM32CubeF4/blob/master/Projects/STM32446E-Nucleo/Templates/STM32CubeIDE/STM32F446RETX_FLASH.ld This is where the table actually gets placed at a specific location:
/* The startup code into "ROM" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >ROM
/* The startup code into "ROM" Rom type memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >ROM
In English - put the symbol ".isr_vector" at the start of the ROM section - which is flash - which is where our table should be on startup ... we're still not done yet...
ke7c2mi
ke7c2mi•4mo ago
Lets go back to the startup code and look inside the table... what is this?
; External Interrupts
DCD WWDG_IRQHandler ; Window WatchDog interrupt ( wwdg1_it)
DCD PVD_AVD_IRQHandler ; PVD/AVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
; External Interrupts
DCD WWDG_IRQHandler ; Window WatchDog interrupt ( wwdg1_it)
DCD PVD_AVD_IRQHandler ; PVD/AVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
That's all the symbols for all the interrupt handlers being declared... we are about to have some fun though because what is this....
Default_Handler PROC

EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_AVD_IRQHandler [WEAK]
EXPORT TAMP_STAMP_IRQHandler [WEAK]
EXPORT RTC_WKUP_IRQHandler [WEAK]
Default_Handler PROC

EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_AVD_IRQHandler [WEAK]
EXPORT TAMP_STAMP_IRQHandler [WEAK]
EXPORT RTC_WKUP_IRQHandler [WEAK]
ke7c2mi
ke7c2mi•4mo ago
This is declaring a "weak linkage" handler for each interrupt. It's important to understand this weak linkage as it can be a source of confusion - it means "if no other thing provides this symbol, provide this value for it" - this lets the startup code install default handlers for every interrupt. When you declare the symbol in your code i.e. make a function void RTC_WKUP_IRQHandler(void)... you override that linkage, and now your function instead of the default handler will be there. I want to call this part out in particular. It is a common mistake in my experience for people to misname an interrupt vector - there are not really warnings when this happens because no symbol is missing, just the interrupt handler isn't called anywhere - this will make it "just not work" and it wont be clear why - if you have a debugger you will see when the interrupt fires you get stuck in the default handler - that is the clue you messed up there! All of this is just a very long way to say - if you are using the ST startup code, you just need to define a function with a specific name and it will handle that interrupt - making your own interrupt handler for RTC wakeup events is as simple as creating the function RTC_WKUP_IRQHandler and doing any setup to get the interrupts
ZacckOsiemo
ZacckOsiemo•4mo ago
I think you scared him 🙂 for sure all the info though.
ZacckOsiemo
ZacckOsiemo•4mo ago
@Sterling if you look at the code around this commit https://github.com/zacck/stm32f4xx_device_drivers/commit/094f0bd64252e6b5323cbf0b62e137d8039e0453 you will see interrupts from scratch
GitHub
Interrupt work · zacck/stm32f4xx_device_drivers@094f0bd
- Added SYSCFG struct and its peripheral - Added a macro to convert a base address to a port code - Defined IRQ numbers for the EXTI Peripheral - Added code to configure pin interrupts
ke7c2mi
ke7c2mi•4mo ago
This is really neat 🤓
ZacckOsiemo
ZacckOsiemo•4mo ago
Ah it's all just reading the reference manual and trying to code it up, mostly I fail.
Want results from more Discord servers?
Add your server