Become a leader in the IoT community!
New DevHeads get a 320-point leaderboard boost when joining the DevHeads IoT Integration Community. In addition to learning and advising, active community leaders are rewarded with community recognition and free tech stuff. Start your Legendary Collaboration now!
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