0
\$\begingroup\$

I'm outputting a square wave from a GPIO pin but the signal shows that the time it is low and the time it is high are changing even though they should be the same. How do I fix this?

Note that the period you see on the oscilloscope does not match the period set in the code. That additional delay is presumably from other things the MCU is doing, but I find it weird how as I was adjusting the period the additional delay was changing too. Now you see an additional delay of about 11 us, but when the period was 5 us the additional delay was also just 5 us. How does this make sense as the only thing that was changing in the code was the frequency of GPIO switching? oscilloscope

while(1)
  {
      GPIOA->BSRR = GPIO_BSRR_BS2;

      delay_us(7.5f);

      GPIOA->BSRR = GPIO_BSRR_BR2;

      delay_us(7.5f);

  }
void delay_us(float time)
{

    __HAL_TIM_SET_COUNTER(&htim1, 0);

    while(( (float)__HAL_TIM_GET_COUNTER(&htim1) / 2.0f) < time) {}


}

EDIT:

I tried using the timer with an interrupt via HAL_TIM_PeriodElapsedCallback(). Its main clock is running at 64MHz, the prescaler at 32 (=2MHz timer freq) and ARR at 25-1 so it could create a delay of 12.5 uS which I need for the 40kHz. When I run it it's producing a square wave of roughly 25.4kHz, which does not change when I change the prescaler to 16, even though it should double the frequency. When I decrease ARR to 10-1, the frequency increases slightly to a bit under 30kHz.

int state = 0;
int count = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim == &htim1)
    {
        if(state)
        {
            GPIOA->BSRR = GPIO_BSRR_BR2;
            state = 0;
        } else {
            GPIOA->BSRR = GPIO_BSRR_BS2;
            state = 1;
        }

        count += 1;
    }
}

Is the interrupt taking too much time so I couldn't toggle the GPIO in 12.5uS? If so, what is an alternate approach or how can I make it take less time?

\$\endgroup\$
4
  • 4
    \$\begingroup\$ If you want microsecond accurate timing use the on-chip timer hardware and not those blocking HAL commands that are actually doing floating point math.. \$\endgroup\$ Commented Mar 13 at 16:59
  • 2
    \$\begingroup\$ You fix it by completely re-designing and then re-writing the code. If you want precision you will use peripheral hardware and a timer. Even then there will be tiny variations as the rising and falling times of pins with respect to the CPU clock aren't perfect (close, subcycle errors only, but not perfect.) You may also write crafted assembly code where you count cycles across branch edges and ensure that the exact same number of cycles occur for a 0 as for a 1 output. But that takes sit-down time on paper. What you did makes the least possible sense (almost.) \$\endgroup\$ Commented Mar 13 at 16:59
  • \$\begingroup\$ @periblepsis It might be impossible to write cycle accurate code on STM32 MCUs. They can run the CPU at higher rate than peripheral buses, have caching and code prefetching. Any DMA cycle on the same bus will cause a delay when the CPU write goes through to the peripheral. Expect at least some amount of jitter. Plus, the HAL uses system timer tick interrupts for timekeeping. \$\endgroup\$
    – Justme
    Commented Mar 13 at 17:38
  • \$\begingroup\$ @Justme I agree that I am not experienced with the STM32 family. So I'll grant the comment you made. I don't know any better. On the PIC family and on the ADSP-21xx DSP families I can actually do cycle counting, easily. So my comment there is definitely family-dependent and I should have added that note. Thanks for including your caveat to my comment. It may help. One of these days, I'll grab up an STM32 demo board and see. I'm curious. \$\endgroup\$ Commented Mar 13 at 17:41

2 Answers 2

4
\$\begingroup\$

For one, you are converting different integer values into different float values.

Depending on how it's done, different integers may take different amount to convert into a float.

It may make different time to divide different float values by two.

It may take different time to compare two float values.

It simply does not make any sense to use floats in that code for many reasons.

Yet another reason is that with large enough floats you start to lose precision, for example after values 24-bit value of 16777215, you can't increment by 1, as floats have only 24 mantissa bits (well 23 but with implicit 1).

Also everything else the code does in the background like interrupts will take time away from running your main loop.

\$\endgroup\$
10
  • 3
    \$\begingroup\$ yes; this really should just have been done by configuring one of the timer/PWM units to output a square wave. \$\endgroup\$ Commented Mar 13 at 17:27
  • \$\begingroup\$ I stopped using floats but the inconsistency still happens. Also it is not an option to use a hardware timer directly for making the square-wave. I just need to make a 40 kHz square wave. You'd think it could be done with software reading CNT and using it to make the square wave on the GPIO port. \$\endgroup\$
    – epicMan123
    Commented Mar 13 at 18:35
  • 1
    \$\begingroup\$ @epicMan123 It could be done in theory, there is just something else going on you either don't know or forgot to mention. Like how is the MCU clocks set up. What's happening before the main while loop? Are you using an RTOS? There' also different STM32 MCUs, some go up to few ten MHz, some go up to few hundred MHz. Which exact MCU model it is? \$\endgroup\$
    – Justme
    Commented Mar 13 at 19:31
  • 1
    \$\begingroup\$ "it is not an option to use a hardware timer directly for making the square-wave" ... can you expand on why? \$\endgroup\$
    – Attie
    Commented Mar 14 at 0:04
  • \$\begingroup\$ @Justme before the while loop it starts a PWM channel and the ADC, which uses DMA. All the APB/AHB timer clocks and HCLK1 are at 64MHz. HCLK2 is at 32MHz. Not using an RTOS. I am using an STM32WB55CGU6. \$\endgroup\$
    – epicMan123
    Commented Mar 15 at 14:12
0
\$\begingroup\$

The problem was caused by the ADC DMA that was running in the background. I now call HAL_ADC_Stop_DMA() before I want to generate the square wave and it works great now!

\$\endgroup\$
1
  • \$\begingroup\$ No! He weeps. Hardware timers to the rescue. \$\endgroup\$ Commented Mar 16 at 11:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.