Interrupts are asynchronous hardware signals that can be handled in software. They are handled by the microcontroller immediately, therefore interrupting whatever was being executed before the interrupt.
Interrupts can be generated by different events, such as exceptions, timer counter overflows, incoming messages on a bus, GPIO level change etc...
All these events generate an Interrupt REQuest (IRQ), which is managed in software by the corresponding Interrupt Request Handler (IRQHandler): a software function which is called every time the interrupt takes place.
This page gives some quick information on how to use Interrupts with MIOSIX in STM32 boards.
Step 1: Enable IRQ Generation
Some Interrupts, called unmaskable interrupts, are enabled by default and are handled by the OS, e.g. exceptions.
Other interrupts have to be explicitly enabled. In STM32 boards, this typically means that you have to set a register of the peripheral to tell it to generate an interrupt when a certain event occurs.
1.1 Timer peripherals
For TIMER peripherals in STM32 you have to set the DIER
register. You can find this kind of information in the microcontroller's datasheet.
// enable interrupt on Update Event (overflow) of TIM2
TIM2->DIER |= TIM_DIER_UIE;
// start the timer
TIM2->SR &= ~TIM_SR_UIF;
TIM2->CR1 |= TIM_CR1_CEN;
1.2 GPIO interrupts (EXTI peripheral)
To generate an interrupt on a GPIO rising/falling edge you will have configure the EXTI
and SYSCFG->EXTICR
registers.
GPIOs with the same pin number are part of the same interrupt line (e.g. PA1
, PB1
, PC1
are assigned to line 1
). There exist 16 lines (from 0 to 15).
Each line can be associated to only one pin at a time: for line 1 you can generate an interrupt for PA1
or PB1
, but not for both of them. In fact pins on the same line are multiplexed. But for example you can enable the interrupt both for PA1
and PC3
since they are on different lines (1 and 3).
Finally consider that only lines from 0 to 4 have their dedicated interrupt handler function (one handler for each line 0-4). Lines 5-9 and 10-15 are associated to the same interrupt handler (one for lines 5-9 and one for lines 10-15).
Anyway, if you have to enable multiple interrupts on the same line or on those lines that have a shared handler, when the interrupt is received you can check the EXTI->PR
register to know which line triggered the interrupt. The same is done in skyward-boardcore when an interrupt is triggered on lines 5-9 or 10-15, in src/shared/drivers/interrupt/external_interrupts.cpp
: after checking on which line the interrupt was triggered, a specific function is called for each interrupt line from 5 to 9 or from 10 to 15.
This is an example of how to enable interrupt generation on the falling edge of PC4
. More details can be found in the SYSCFG
section of the STM32 reference manual (see references below). There you can find the way in which the SYSCFG->EXTICR
register has to be configured to assign the interrupt to a specific GPIO. SYSCFG->EXTICR
is an array of four elements (SYSCFG->EXTICR1
to SYSCFG->EXTICR4
).
// Clear the mask on line 4
EXTI->IMR |= EXTI_IMR_MR4;
// Trigger the interrupt on a falling edge of line 4
EXTI->FTSR |= EXTI_FTSR_TR4;
// Trigger the interrupt on a rising edge of line 4
// EXTI->RTSR |= EXTI_RTSR_TR4;
EXTI->PR = EXTI_PR_PR4; // Reset pending register of line 4
// Assign the interrupt of line 4 to GPIOC (=> PC4)
// this sets the SYSCFG->EXTICR2 register to 0010 (bits 0:3 refer to EXTI4)
SYSCFG->EXTICR[1] = 0x2;
Moreover, src/shared/drivers/interrupt/external_interrupts.h
defines a helper function called enableExternalInterrupt() which manages the external interrupt configuration by only specifying the GPIO port, the pin number and the interrupt trigger (rising edge, falling edge or both).
Step 2: Enable IRQ Handling
After enabling the interrupt generation, you also have to enable the interrupt handling in the NVIC (Nested Vector Interrupt Controller), giving it a priority (lower value, higher priority). More information on NVIC can be found on the datasheet.
// Enable the interrupt called EXTI4
NVIC_EnableIRQ(EXTI4_IRQn);
// Set its priority to 15
NVIC_SetPriority(EXTI4_IRQn, 15);
Step 3: Writing an IRQHandler
In the core/stage_1_boot.cpp
file of your board (located in miosix-kernel/miosix/arch/<ARCH>/<BOARD>/
) all the possible IRQHandlers are defined. They are all defined with __attribute__((weak))
: this means that if you provide your own implementation of the same function in a .cpp
file and compile it, the default one will be overridden by yours.
To do this you will have to write the following code, which contains some magic related to GCC name mangling, in a .cpp
file.
RESET THE INTERRUPT PENDING BIT REGISTER in your IRQHandler, otherwise you will enter an infinite loop.
DON'T USE BLOCKING OR "HEAVY" FUNCTIONS (e.g. printf()) inside your interrupt. Remember that the CPU will stall for the whole time it will execute your IRQHandler. Most of the times, the only thing you will need in your IRQHandler is to set a global variable or to operate with registers.
// You need to define the IRQHandler you want to override as "naked".
// It must have the same name of the IRQHandler in the stage_1_boot file.
void __attribute__((naked)) EXTI4_IRQHandler()
{
saveContext();
// write the mangled name of the function that you want to call
// in the form: _Z<LENGTH_OF_FUNCTION_NAME><FUNCTION_NAME><ARGUMENTS('v'=void)>
asm volatile("bl _Z20EXTI4_IRQHandlerImplv");
restoreContext();
}
// This is the function executed. It has to be declared as "used".
void __attribute__((used)) EXTI4_IRQHandlerImpl()
{
EXTI->PR = EXTI_PR_PR4; // Always reset pending register!!!
// Write me!
}