cancel
Showing results for 
Search instead for 
Did you mean: 

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

JTang.1
Associate II

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.

16 REPLIES 16

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.1
Associate II

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.

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.1
Associate II

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

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.

S.Ma
Principal

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.

You want to read ODR.

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

JW

JTang.1
Associate II

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

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.