Skip to main content
Visitor II
March 12, 2020
Question

Bizarre GPIO behaviour: HAL_GPIO_TogglePin() doesn't allow toggle multiple pins

  • March 12, 2020
  • 10 replies
  • 7809 views

I am using the STM32F4 family. I have configured a GPIO pin as output push pull with no pullups. Speed is also set to low frequency. On some occasions, the GPIO pin seems to switch polarity on its own eg. when I set to low, it will sometimes switch it back to high. There is nowhere else in my code that sets or resets this pin. I have also added traces to the code to ensure that the pins are set and reset accordingly and that there is no other possible code paths that could set or reset this pin. It is hard to reproduce this behaviour and it can sometimes take very long for it to occur.

I now believe there is something else that could be causing this issue. Can anyone offer any plausible explanation as to why this could happen? What can I do or try?

Thanks in advance.

    This topic has been closed for replies.

    10 replies

    Super User
    March 12, 2020

    Which pin, in particular?

    Does the respective GPIO_ODR bit change to unexpected value?

    In debugger, you can try to place a data breakpoint on both the ODR and BSRR of the port in question.

    It won't catch the change if it originates at other bus masters (DMA), though.

    JW

    JTang.1Author
    Visitor II
    March 12, 2020

    This is on Port G, Pin 6. I did not check the ODR for the bit change but read back on the BSRR bit for the pin confirms the change.

    I have even wired it to an external interrupt pin to catch it and I have been able to catch it changing.

    What else can I check? I am out of ideas.

    Super User
    March 12, 2020

    As I've said, put a data breakpoint on GPIOG_ODR and BSRR.

    As a second idea:

    If the interrupt catching the change is of sufficiently high priority, chances are, that the function which was interrupted, might contain the offending code. So single-step the interrupt until its exit, and then examine what's in the function to which it returns.

    JW

    JTang.1Author
    Visitor II
    March 15, 2020

    Here's the even more bizarre part, I added some code to test the ODR bit for GPIOG in 6 and the code never trips! I have run it over 8000 cycles and it hasn't tripped up. Because of this, I haven't yet run this in debugger mode to try to unroll the interrupt if I catch it using the input interrupt. One more thing I just realized, BSRR register is read only, so what actually happens when I try to read it say with something like GPIOG->BSRR & GPIO_Pin_6?

    Seriously, I cannot offer any explanation for this bizarre behavior.

    JT

    Visitor II
    March 15, 2020

    BSRR is write only, not read only. See its documentation in the reference manual.

    Reading BSRR could return any value, or theoretically even change the state of the MCU in unpredictable ways.

    Super User
    March 15, 2020

    You want to read ODR.

    BSRR isb just a hardware hack which changes ODR, the real output register.

    JW

    Visitor II
    March 15, 2020

    Which board, STM32, package, using HAL, LL?

    First, check your schematics to make sure the signal from PG6 can't be forced by the other end of the PCB trace.

    Second, if the level is changing, probe the signal on the scope and check if the levels are clean 0V or 3.3V (if 3.3V supply used).

    If the ground is shifted higher or the high level lower than it should be, there is a push pull fight between 2 elements on the trace.

    You should go debug mode as you can put breakpoints and find out at which step the issue comes. You can also disable interrupts to see if it's the source.

    DO GO TO DEBUG MODE WITH BREAKPOINT PLACEMENT TRIALS THROUGH THE CODE.

    Be delicate when handling BSRR-type registers. Some family have the set/reset write only registers as 32 bit (16 setlow, 16 sethigh bits).

    If you want to read the pin level, use IDR.

    And remember that if MODER registers could be configured as alternate function for a peripheral, which in case the pin level is not controlled by GPIO register anymore.

    JTang.1Author
    Visitor II
    March 17, 2020

    To cut a long story short, I found the problem. Basically, the GPIO is set/reset in the code during interrupt. Now, setting/resetting using the HAL_GPIO_WritePin is atomic thus should be interrupt safe. However, the corresponding HAL_GPIO_TogglePin is not atomic. It is by pure coincidence that we had another interrupt routine calling HAL_GPIO_TogglePin assigned to the same port but a different pin. By chance, when the toggle pin interrupt happens within the HAL_GPIO_WritePin routine, the toggle pin routine altered the pin in the write pin routine.

    The solution is now putting a guard around the toggle pin routine. This is a good lesson to not take for granted that setting/resetting/toggling a GPIO pin is just a simple operation on the surface but under the covers, there are situations and conditions that will call for careful scrutiny and application.

    Thanks to everyone who has chimed in with their thoughts and suggestions for tracking this problem down.

    JT

    Visitor II
    March 17, 2020

    Instead of "putting a guard around" a buggy function, why not fix it?

    void togglepin(GPIO_TypeDef* port, uint16_t pinmask) {
     port->BSRR = (pinmask << 16u) | ((port->ODR & pinmask) ^ pinmask);
    }

    This will leave other pins alone even if interrupted.

    > when the toggle pin interrupt happens within the HAL_GPIO_WritePin routine

    The other way round. HAL_GPIO_TogglePin was interrupted.

    > This is a good lesson to not take for granted

    Don't take anything granted when using interrupts. Treat them as signal handlers, and do only stuff in interrupt handlers that are explicitly documented as safe to do in a signal handler.

    Don't take anything granted when using libraries. If the library documentation does not make guarantees about reentrant/concurrent/interrup safe usage, don't use it that way. Don't change any internal data structure or hardware register that the library depends on.

    Super User
    March 19, 2020

    Using the HAL_GPIO_TogglePin function would have prevented this issue:

    /**
     * @brief Toggles the specified GPIO pins.
     * @param GPIOx Where x can be (A..K) to select the GPIO peripheral for STM32F429X device or
     * x can be (A..I) to select the GPIO peripheral for STM32F40XX and STM32F427X devices.
     * @param GPIO_Pin Specifies the pins to be toggled.
     * @retval None
     */
    void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
    {
     /* Check the parameters */
     assert_param(IS_GPIO_PIN(GPIO_Pin));
     
     if ((GPIOx->ODR & GPIO_Pin) == GPIO_Pin)
     {
     GPIOx->BSRR = (uint32_t)GPIO_Pin << GPIO_NUMBER;
     }
     else
     {
     GPIOx->BSRR = GPIO_Pin;
     }
    }

    Visitor II
    March 19, 2020

    @TDK​ There are minors visiting this forum, next time could you please mark this kind of code as don't try this at home

    Super User
    March 19, 2020

    The code seems perfect to me. Maybe I'm missing the joke here...

    Graduate II
    April 8, 2020

    I want to note that @berendi​ has managed to strip off one instruction from our previous offered versions. The optimization is based on this text in reference manuals: "Note: If both BSx and BRx are set, BSx has priority." Thanks to him, now my pin toggle implementation is as simple as this:

    hPort->BSRR = (fPins << 16u) | (~hPort->ODR & fPins);

    The flaws of pin toggle code have been reported and improvements provided many times (for example here and here) by many people. This version is the ultimate evolution of all those suggestions.

    How many years will it require for ST to fix such a "complex and enormous" feature as a pin toggling in HAL and LL drivers?

    @Amel NASRI​ ​, @Imen DAHMEN​, someone?​

    Technical Moderator
    April 10, 2020

    Hello All,

    Thank you for your contribution and reported issues .

    I escalated the related threads internally to the IP Owner to fix the GPIO HAL and LL drivers.

    Thank you for your understanding.

    Best Regards,

    Imen