stm32gpiointerrupt-handlingdebouncing

Stm32 Interrupts. Push button to increment variable by one


I am very new to Stm32 programming and to the concept of interrupts, and I really need some guidance. For my program, I want a (global) variable to be incremented by 1 every time I press a button (in PB4).

I have tried to program this, but it isn't working. The interrupt is triggered, yet the debugger tells me that when I press the button (once), the variable just keeps getting continuously and infinitely incremented without stop.

What do I need to consider in order to achieve my goal (1 time pressed, variable ++) ?

Here is my code for interrupt initialization and the ISR :

void pb4_exti_init(void){ //GPIO_init();

//Enable clock access for GPIOB
//Enable clock access for SYSCFG
RCC->APB2ENR |= SYSCFGEN;

//Select PORTB for EXTI4
SYSCFG->EXTICR[1]  |= (1U<<0);

//Unmask EXTI4
EXTI->IMR1 |= (1U<<4);

//Select falling edge trigger
 EXTI->FTSR1 |= (1U<<4);

//Enable EXTI line in NVIC
NVIC_EnableIRQ(EXTI4_IRQn);
 }

void EXTI4_IRQHandler(void){
        z++;
}

Solution

  • Two things:

    1. Minor, but necessary improvement is a debounce. Your human finger actually doesn't perfectly "click" the button once, it produces some jumpy jitter on the line, basically multiple presses. There are various ways to debounce it, you can put an RC filter on the button and/or deactivate an interrupt for a little while as soon as it happens. Since you're just learning interrupts, it may not matter for you at all, it's no big deal if your button will occasionally increment your variable by 2 or 3 or 4 (because jitter will cause multiple events very quickly). If you're just about mastering an interrupt concept, you simply may not care about it for now.
    1. Software problem. You have set up the things correctly. GPIO Port X is attached to EXTI line Y to have an interrupt on Port X Pin Y. EXTI sends an interrupt event to the NVIC. NVIC checks if that interrupt of EXTI is enabled, and if it is, it triggers the ISR. Everything is correct here. The problem is, EXTI sends an interrupt event WHENEVER its corresponding event bit is set to 1. So when you go into ISR - IRQHandler, that function automatically clears interrupt pending flag in NVIC, but your EXTI still has an interrupt event bit set to 1, so it keeps sending a signal to NVIC. The first rule of handling an interrupt is to make sure that all the necessary flags are in the correct state. You need to clear the pending flag of the EXTI in the ISR, so that EXTI stops sending NVIC the trigger.

    Let's look together at this register of EXTI (I'm using STM32F746, yours should be similar/identical):

    Pending register

    Notice the bits are rc_w1. It's very important. From the same reference manual, it says:

    enter image description here

    So if we want to remove pending interrupt from EXTI, we need to write 1 into the corresponding bit. In your case, into PR4.

    EXTI->PR = EXTI_PR_PR4; //or 1U << 4U; check the name of the register, it can be PR1, then adjust the right hand side too
    

    Why am I not using |=? Because writing 0 to other bits doesn't change them in any way. Even if there were other EXTI interrupts and there were 1s, writing 0 doesn't change those bits, so they stay 1s. You want to keep flag clearing as small as possible, preferably atomic. It doesn't really matter if you use |= here, but if you have registers, where you can write 0, this can become a problem if some other interrupt happens in the middle of flag clearing, you may accidentally clear other flags (if interrupt happens after you read from the register, but before you write clear flag, you may write 0 to freshly happened another interrupt). But! It's not applicable here and it's far too early for you to worry about it now. It's enough to say that these things do exist, and you should just be aware of them. You'll remember this when you need it. For now, clear the EXTI pending flag, and your code should function well.


    As a hint, you don't have to shift stuff 4 bits to the left. There are CMSIS definitions for that. Otherwise you'll go crazy if you need to setup 3 registers with 10 different fields for each peripheral.

    EXTI->IMR1 |= (1U<<4);
    

    Can be written as

    EXTI->IMR1 |= (1U<<EXTI_IMR1_IM4_Pos);
    

    It will be super helpful when you try other peripherals.