cancel
Showing results for 
Search instead for 
Did you mean: 

Tricky GPIO Input bug in Nucleo STM32F4 bare metal project

OSell.1
Associate II

Hi,

 

I at a high level I have designed a program to toggle the user LED on PA5 if a gpio input sees a high signal (shorter than 150ms) on PA0.

 

The program works well most of the time except in some instances when it will miss an input pulse entirely, seemingly for no reason. I have checked my timer delay function and even switched to a for loop style delay. This allowed me to validate that my timer was not the issue.

 

The bug only occurs when the output pin is low. (Never when it is high)

 

Scope Captures of Normal Operation (Input - yellow; Output - blue)

0693W00000aHAlFQAW.png0693W00000aHAlUQAW.png 

Scope Capture of the Insidious Bug

0693W00000aHAkvQAG.png 

Here is the main function code and I've attached a copy of the project below also.

#include <Input_Capture.h>
#include <tim.h>
#include "stm32f4xx.h"
 
int main(void)
{
	/* Set GPIO Pin A0 to Input Capture Mode */
	gpio_a_init();
	IOPA0_Input_cfg();
	IOPA5_Output_cfg();
	tim3_150ms_init();
 
	while(1)
		{
			////////// The LED CLAPPER LOGIC ///////////
			/* If a high signal is detected on PA0,
			 * and we aren't already turning the LED on */
			if ((GPIOA->IDR & GPIO_IDR_ID0) && !(GPIOA->ODR & GPIO_ODR_OD5))
			{
				/* Turn on the LED */
				GPIOA->BSRR = GPIO_BSRR_BS5;
				/* Wait 150ms */
				tim3_150ms_delay();
				//for(int i = 0; i<1000000; i++){/*Do Nothing */__NOP();}
			}
			/* If a high signal is detected on PA0,
			 * for this case, we know the LED will be on */
			else if (GPIOA->IDR & GPIO_IDR_ID0)
			{
				/* Turn off the LED */
				GPIOA->BSRR = GPIO_BSRR_BR5;
				/* Wait 150ms */
				tim3_150ms_delay();
				//for(int i = 0; i<1000000; i++){/*Do Nothing */__NOP();}
			}
			else
			{
				/* do nothing */
			}
		}
}

 

 

Any help is much appreciated! 🙂

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions

This happens if the input pulse's rising edge happens during execution of code between the first and second if():

0693W00000aHD0gQAG.png 

A: (GPIOA->IDR & GPIO_IDR_ID0) is false, thus the whole first if() is false, proceeding to else-leg i.e. second if()

B: if ((GPIOA->IDR & GPIO_IDR_ID0) is true, so output is cleared (it's already zero so you see no change) and the 150ms delay follows

C: the input pulse is <150ms delay, so at the point where 150ms delay ends, the input pulse is already 0 again

You should detect the edge of input signal (i.e. "if previous level == 0 and current level == 1, toggle output level and do the debouncing (delay); remember previous level; loop). Loopdelays are good only for testing programs, you should learn how to get rid of them.

Remarks on the timer code:

JW

View solution in original post

5 REPLIES 5
S.Ma
Principal

This is a pure programming exercise without using any HW resource.

If the pulse width is 1 usec, your code won't work well. The minimum pulse width is missing crutial parameter to determine how to do the function.

The HW assisted implementation would for example use Timer Input Capture resource.

Take a free running and overflow 16 bit timer, say running at 10 kHz (overflow period 65536 slower)

connect GPIO pin to, say, channel 1. Some STM32 DMA can repetively capture cyclically in RAM all the timer ceptured values so reduce the interrupt frequency need.

This way, with nearly no SW running, you can collect your edge data in RAM.

Variations can be having rising edge on channel 1, and falling edge on channel 2, detect no pulse within the timer period, checking the pin level to prevent overrun conditions, use more jittery EXTI interrupt scheme with timer snapshots (works on more GPIOs), use a timer as clocked by the incoming signal. Use timer modes to make monostable function, etc...

OSell.1
Associate II

Sorry I didn't quite understand your suggested solution @S.Ma​, so I will give a bit more info to hopefully help get us on the same page.

The input pulses I have tested with that are being missed have a duration of 50ms. Plenty of time for several iterations of checking.

I do plan to add complexity in the future by using interrupts or perhaps this suggested DMA. But first I want to be sure that the basic functionality works as expected.

The purpose of the delay is actually to be used as a filter for an input from a cheap microphone sensor that essentially produces a randomly varying frequency PWM output when sound above a certain decibel level in sensed. The multiple pulse output is less than 150ms in duration which allows me to easily trigger as soon as the gpio input goes high and filter the rest.

If there something fundamentally wrong with any of my logic, please let me know.

Thanks,

Orlando

This happens if the input pulse's rising edge happens during execution of code between the first and second if():

0693W00000aHD0gQAG.png 

A: (GPIOA->IDR & GPIO_IDR_ID0) is false, thus the whole first if() is false, proceeding to else-leg i.e. second if()

B: if ((GPIOA->IDR & GPIO_IDR_ID0) is true, so output is cleared (it's already zero so you see no change) and the 150ms delay follows

C: the input pulse is <150ms delay, so at the point where 150ms delay ends, the input pulse is already 0 again

You should detect the edge of input signal (i.e. "if previous level == 0 and current level == 1, toggle output level and do the debouncing (delay); remember previous level; loop). Loopdelays are good only for testing programs, you should learn how to get rid of them.

Remarks on the timer code:

JW

S.Ma
Principal

I would suggest to use ChatGPT to generate a code for the chosen MCU and use Timer Input Capture with interrupt scheme. The code skeleton will be as precise as the question.

OSell.1
Associate II

That was the issue thanks for the help @Community member​ and for the tips with the timer! 🙂