2022-01-06 08:27 PM
If a digital output pin value is changed multiple times within the same main loop, within the same interrupt, or within the same task and not separated by a delay then the output pin does not work as expected.
This applies to both HAL_GPIO_TogglePin() and HAL_GPIO_WritePin()
Using the NUCLEO-G031K8 PCB with the STM32CubeIDE
Working Example:
Code in main() while loop with no interrupts, and no RTOS
HAL_GPIO_TogglePin() is called 2 times and is separated with a delay
Output pins work as expected
PB0 & PB1 toggle 1 time per ms
Channe1 = PB0, Channel2 = PB1
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
HAL_Delay(0); //Delay 1 ms
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
HAL_Delay(0); //Delay 1 ms
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
Example with digital output not working as expected:
Code in main() while loop with no interrupts, and no RTOS
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0) is called 2 times with no delay in between
GPIO_PIN_0 (PB0) does not toggle when it should
PB0 toggles a small random number of times (example: 4 times) then there is a large random length delay (can be hundreds of ms long)
GPIO_PIN_0 (PB1) works as expected because it is separated with delays
Channe1 = PB0, Channel2 = PB1
The main loop is called every 2 ms so the yellow trace (GPIO_PIN_0) and the blue trace (GPIO_PIN_1) should toggle each time through the main loop, but the yellow trace is not toggling when it should.
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
HAL_Delay(0); //Delay 1 ms
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
HAL_Delay(0); //Delay 1 ms
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
2022-01-07 12:24 AM
Welcome, @EMill.1, to the community!
first of all, you benefit from a protection function built into the HAL: HAL_Delay(0) would actually be interpreted as a zero millisecond delay, however the function contains:
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait) { }
}
In this respect, you also have a millisecond waiting time when specifying 0 - as long as the __weak function was not overwritten and the preset SysTick with 1ms was used.
Now to your question: if you have two statements HAL_GPIO_TogglePin() immediately following each other, of course you only have the delay that HAL_GPIO_TogglePin() needs to switch the pin. With the frequency of the CPU, this is typically in the range of nanoseconds, since only a few instructions are used for toggling:
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
uint32_t odr;
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
/* get current Output Data Register value */
odr = GPIOx->ODR;
/* Set selected pins that were at low level, and reset ones that were high */
GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
}
Please review your programme and requirements in this context.
Regards
/Peter
2022-01-07 07:07 AM
Hi Peter
Thank you for the quick reply.
If there are two statements of HAL_GPIO_TogglePin() immediately following each other or 2 statements of HAL_GPIO_WritePin() following each other then the output pin will toggle a few times as expected and then stops responding for a long random period of time (hundreds of milliseconds).
It seems that I need at least a 1 ms delay between the statements that change the output pin state or the output pin stops working correctly. I have tried small and large delays using HAL_Delay() in the main loop, I tried using osDelay() in a task, and I have tried creating my own microsecond delay function, but the output pin does not respond as expected unless the delay is at least 1 millisecond long.
My application requires that I create a periodic narrow pulse (nanoseconds or microseconds in width) on an output pin. During the rising edge of the pulse I take an analog reading.
Do you know a way I can reliably generate 1 narrow pulse (nanoseconds or microseconds in width) every 10 milliseconds without using PWM ?
2022-01-07 09:30 PM
1. Set the highest GPIO_OSPEEDR setting for given pin
2. Switch oscilloscope to its highest resolution - it won't sample ns at 100ms/div.
JW
2022-01-07 11:08 PM
Pick some timer. Set period to 10ms. Enable interrupts from timer update event ans start timer.
In timer IRQ routine disable interrupts, perform Pin_set and Pin_reset (generate pulse) and enable interrupts back.
But... you should have a good reason to not use PWM.
If i remember correctly HAL_Delay() is in principle unprecise (-0ms to +1ms ?). Random delay in range to hundred miliseconds smells like Systick overflow value (2^24 / 64MHz = 262ms). Ask to STM32 gurus, can HAL_Delay(0) suffer from some kind of "race" condition between two HAL_GetTick() calls ?
2022-01-07 11:42 PM
Use ADC with a conversion triggered by a pin edge, if you want a sw delay, or a filter of the incoming trigger, use a timer capture which will output a trigger compare.
In the original code I feel the issue is that the HAL code function doesn't tell if it is atomic (read modify write safe in multiple async call of the function, such as interrupt, multi thread, multi cores)
2022-01-08 12:14 AM
Write patterns to GPIO->BSRR via a pointer with the correct volatile attribute, or use assembler for tight/specific control.
Use a free-running maximal TIM for a timebase to resolve the time line sub-microsecond. ie read and delta TIM->CNT
Consider if you can drive a pattern buffer to GPIO-<BSRR via TIM+DMA