I've bought an STM32F411 nucleo board and now I'm trying to understand various bits and pieces of the HAL. Starting with external interrupts seemed to be a good idea, because the board has a push button which is connected to PC13. So I've set up a simple toggle-the-frequency blinky. The code below is a bit simplified:
#define LED_PIN GPIO_PIN_5
#define BTN_PIN GPIO_PIN_13
static uint32_t blink_period = 250;
int main(void)
{
HAL_Init();
SystemClock_Config();
__GPIOA_CLK_ENABLE();
GPIO_InitTypeDef pinConfig;
pinConfig.Pin = (LED_PIN);
pinConfig.Pull = GPIO_NOPULL;
pinConfig.Mode = GPIO_MODE_OUTPUT_PP;
pinConfig.Speed = GPIO_SPEED_FAST;
HAL_GPIO_Init(GPIOA, &pinConfig);
__GPIOC_CLK_ENABLE();
pinConfig.Pin = (BTN_PIN);
pinConfig.Pull = GPIO_NOPULL;
pinConfig.Mode = GPIO_MODE_IT_FALLING;
pinConfig.Speed = GPIO_SPEED_LOW;
HAL_GPIO_Init(GPIOC, &pinConfig);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x0F, 0x00);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
while (1)
{
HAL_GPIO_TogglePin(GPIOA, LED_PIN);
HAL_Delay(blink_period);
}
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(BTN_PIN);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == BTN_PIN)
{
if (blink_period == 500)
{
blink_period = 250;
}
else
{
blink_period = 500;
}
}
}
When I push the button, an interrupt is generated and the blinky frequency changes from 1 to 2 Hz (or vice-versa). This works as intended, but why? I forgot to clear the pending interrupt flag, so the ISR should be called over and over. The datasheet clearly states that
When the selected edge occurs on the external interrupt line, an interrupt request is generated. The pending bit corresponding to the interrupt line is also set. This request is reset by writing a ‘1’ in the pending register.
Reading a bit further reveals that this is a bit different for events:
When the selected edge occurs on the event line, an event pulse is generated. The pending bit corresponding to the event line is not set.
However, I'm not setting the button pin mode to any of the GPIO_MODE_EVT_...
modes so I'm not using the event mechanism (to be honest I don't yet know what that even is - I just think that I'm not using it. Any hints are welcome).
So somewhere I should have to call void HAL_NVIC_ClearPendingIRQ (IRQn_Type IRQn)
, shouldn't I? It seems that clearing the flag by software is not necessary, because the ISR is not called more than once per falling edge. I've added a breakpoint in HAL_GPIO_EXTI_Callback
to verify this.
As mentioned in the comments, the flag clearing code is in ST's implementation of the GPIO interrupt handler:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
This handler needs to be called by the actual ISR (which is done in my code) and it clears the pending flag corresponding to the GPIO_Pin argument. So I have to write an ISR which sorts out which flags are set, and call HAL_GPIO_EXTI_IRQHandler
for each, which in turn calls my HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
, again with the pin as an argument. For each external interrupt, the pin number would get checked some ~3 times (in the ISR, in the handler and in the callback)!
If that is the solution, I want my problem back.
If you don't clear the flag, and the hardware doesn't clear the flag (this occurs for some flags but not all) then as soon as the interrupt ends the interrupt controller will see the interrupt flag and fire the ISR again.
Generally, you clear it when you enter the routine, indicating that you are going to handle whatever is causing the interrupt from that moment in time forward. If another interrupt event occurs during your servicing of the initial interrupt you don't want to lose that interrupt notification.
The main reason for clearing the interrupt condition before servicing the interrupt is to not lose a interrupt.
Clearing an interrupt means to tell the system that you've handled the reason for the interrupt and are ready for the system to return to normal operation (like interrupting you the next time around).
You don't have to call HAL_NVIC_ClearPendingIRQ (IRQn_Type IRQn)
because the pending bit in the NVIC will be cleared automatically upon entering HAL_GPIO_EXTI_IRQHandler
.
The HAL_GPIO_EXTI_IRQHandler()
implementation clears the pending bit in the peripheral, not in the NVIC. If it didn't clear the pending bit by calling __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin)
, then the handler would be called again and again. The point is that you must distinguish between the interrupt pending bit in the peripheral and the pending bit in the NVIC.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With