Become a leader in the IoT community!
Join our community of embedded and IoT practitioners to contribute experience, learn new skills and collaborate with other developers with complementary skillsets.
Join our community of embedded and IoT practitioners to contribute experience, learn new skills and collaborate with other developers with complementary skillsets.
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?
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.
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
….
“`
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
“`
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…
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
“`
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]
“`
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
I think you scared him 🙂 for sure all the info though.
@ifreakio if you look at the code around this commit https://github.com/zacck/stm32f4xx_device_drivers/commit/094f0bd64252e6b5323cbf0b62e137d8039e0453 you will see interrupts from scratch
This is really neat 🤓
Ah it’s all just reading the reference manual and trying to code it up, mostly I fail.
CONTRIBUTE TO THIS THREAD