Sonsivri
 
*
Welcome, Guest. Please login or register.
Did you miss your activation email?
October 18, 2024, 09:36:28 09:36


Login with username, password and session length


Pages: [1]
Print
Author Topic: Unrealiable STM32 timings problem  (Read 281 times)
0 Members and 2 Guests are viewing this topic.
LithiumOverdosE
Senior Member
****
Offline Offline

Posts: 357

Thank You
-Given: 382
-Receive: 581


« on: October 15, 2024, 10:56:11 22:56 »

I'm having really frustrating problems with generating precise timings/delays in the project I am working on.
The target device is STM32L431RCT6 on a fully functional custom board.
Clock is derived from 20MHz external crystal with 22pF capacitors so HSE is used as a PLL source and SYSCLK is 80MHz, APB2 timer clock is 10MHz and APB2 peripheral clock is 5MHz.
I used STMCubeIDE 1.16.1 and majority of configuration was made in MX.

I'm trying to achieve pulses of very precise duration from 1us to 15us on pins in order to drive three separate switching bridges and I have to regulate pulse width in 0.5us steps or less (not necessarily a round number).

Normally I would use some dsPIC or even 18F but due to their pretty much determinable clock/cycle duration behaviour but in this case I'm stuck with STM32 which I don't normally use and don't have much experience with it.


Obviously my first attempts were to use timer interrupts but unlike dsPIC/PIC their behaviour seems to be somewhat unpredictable (BTW I was unplesantly surprised when I found out that jumps to simple functions take 300 ns which is terrible compared to dsPIC/PIC).


According to experiments in HW so far the only reasonable functional approach is by using DWT as in this code example in Main():

Code:
while (1)

  {

  //if(start) {

  if(treatment_time<duration) {

  if(TIM1->CNT>=repetition_rate) {

   treatment_time+=(float)repetition_rate/1000;

   if(mode==1) {     //1 channel, without pause

    __disable_irq();

    TIM1->CNT=0;

    start_time=0;

    ch1_A_on();

    start = DWT->CYCCNT;

    ticks = pulse_width * (HAL_RCC_GetSysClockFreq() / 1000000); 

    while ((DWT->CYCCNT - start) < ticks);

    ch1_off(); 

   

 ch1_B_on();

    start = DWT->CYCCNT;

    ticks = pulse_width * (HAL_RCC_GetSysClockFreq() / 1000000); 

    while ((DWT->CYCCNT - start) < ticks);

    ch1_off();

    __enable_irq();

    } else if (mode==2) {       

     TIM1->CNT=0;

     start_time=0;

     ch1_A_on();

     start = DWT->CYCCNT;

     ticks = pulse_width * (HAL_RCC_GetSysClockFreq() / 1000000); 

     while ((DWT->CYCCNT - start) < ticks);

     //ch1_off();

     //delay_us(pulse_width);

     ch1_off();

     start = DWT->CYCCNT;

     ticks = pulse_width * (HAL_RCC_GetSysClockFreq() / 1000000); 

     while ((DWT->CYCCNT - start) < ticks);

     ch1_B_on();

     start = DWT->CYCCNT;

     ticks = pulse_width * (HAL_RCC_GetSysClockFreq() / 1000000); 

     while ((DWT->CYCCNT - start) < ticks);

     ch1_off();

    } 

   }

  }

 //}



In the code variable mode is always 1 and ch1_A_on(), ch1_B_on() and ch1_off are just bit manipulation functions like this:

Code:
void ch1_A_on(void){

 GPIOB->ODR |= GPIO_PIN_15; //A1=HIGH

 GPIOA->ODR |= GPIO_PIN_8;  //A2=HIGH

 GPIOA->ODR &= ~GPIO_PIN_9; //B1=LOW

 GPIOB->ODR &= ~GPIO_PIN_13; //B2=LOW

}

void ch1_B_on(void){

 GPIOA->ODR |= GPIO_PIN_9;  //B1=HIGH

 GPIOB->ODR |= GPIO_PIN_13;  //B2=HIGH

 GPIOB->ODR &= ~GPIO_PIN_15;  //A1=LOW

 GPIOA->ODR &= ~GPIO_PIN_8;  //A2=LOW

}

void ch1_off(void){

 GPIOB->ODR &= ~GPIO_PIN_15;  //A1=LOW

 GPIOA->ODR &= ~GPIO_PIN_8;  //A2=LOW

 GPIOA->ODR &= ~GPIO_PIN_9;  //B1=LOW

 GPIOB->ODR &= ~GPIO_PIN_13;  //B2=LOW

}


The logic analyzer measurements (also verified with oscilloscope) gives me the following:

For 5us pulse width I get 5.792us for A1 and 6.0us for B1 pulse
For 8us pulsewidth  I get 8.792us for A1 and 9.0us for B1 pulse
For 15us pulsewidth  I get 15.708us for A1 and 15.917us for B1 pulse

I know that function calls ch1_A_on(), ch1_B_on() and ch1_off() also take some time (around 300 ns!).

What is also perplexing to me is that my A and B outputs are generated in the same way but they don't produce exactely same pulse width, even when I generate only a single iteration of it.


So my question is if there is some other approach to the problem of precise timings in STM32 and regulation of pulse duration in smaller steps (for example <= 500 ns)?
Logged
digitalmg
Junior Member
**
Offline Offline

Posts: 97

Thank You
-Given: 139
-Receive: 110


« Reply #1 on: October 16, 2024, 08:25:27 08:25 »

Hi,
In the GPIO configuration of the CubeMX output pins, you must set Maximum output speed: Very High,
when you want to get small switching times of the pins.

Logged
dennis78
Active Member
***
Offline Offline

Posts: 122

Thank You
-Given: 272
-Receive: 154


« Reply #2 on: October 16, 2024, 08:44:17 08:44 »

Have you thought about one-pulse mode of timer? Of course, if it can fit into rest of concept your app.
« Last Edit: October 16, 2024, 08:47:00 08:47 by dennis78 » Logged
UncleBog
Active Member
***
Offline Offline

Posts: 133

Thank You
-Given: 165
-Receive: 176


« Reply #3 on: October 16, 2024, 09:03:02 09:03 »

Your software controlled approach will be subject to the usual program timings such as clock rate, interrupt and branch overhead and optimisation level. You should be able to achieve timing to your clock resolution by setting up a timer and some comparators that are configured to switch GPIO directly.
Logged
sam_des
Senior Member
****
Offline Offline

Posts: 256

Thank You
-Given: 128
-Receive: 151


« Reply #4 on: October 16, 2024, 01:22:37 13:22 »

Hi,

Why not use the hardware PWM modes for pulse generation ? You can set the frequency once & adjust duty cycles as you require.
Minimal software intervention & precise pulse widths.
You can route the PLLed SYSCLK to timer as its clock. Check the clocks config page in CubeMX .
AFAIK, you can also combine 2 16-BIT timers to form a 32-BIT timer, giving you more resolution.
CubeMX will also most probably enable High Speed Output drivers on PWM pins to reduce Rise/Fall times.

sam_des
Logged

Never be afraid to do something new. Remember Amateurs built the Ark, Professionals built the Titanic !
LithiumOverdosE
Senior Member
****
Offline Offline

Posts: 357

Thank You
-Given: 382
-Receive: 581


« Reply #5 on: October 16, 2024, 08:35:27 20:35 »

Hi,
In the GPIO configuration of the CubeMX output pins, you must set Maximum output speed: Very High,
when you want to get small switching times of the pins.

That's the first thing I did.




Have you thought about one-pulse mode of timer? Of course, if it can fit into rest of concept your app.

I did. The problem is that this particular processor doesn't seem to support it.



Your software controlled approach will be subject to the usual program timings such as clock rate, interrupt and branch overhead and optimisation level. You should be able to achieve timing to your clock resolution by setting up a timer and some comparators that are configured to switch GPIO directly.

Yes that's the first thing I tried but it seems that there is a lag somewhere else towards output pin registers.





Hi,

Why not use the hardware PWM modes for pulse generation ? You can set the frequency once & adjust duty cycles as you require.
Minimal software intervention & precise pulse widths.
You can route the PLLed SYSCLK to timer as its clock. Check the clocks config page in CubeMX .
AFAIK, you can also combine 2 16-BIT timers to form a 32-BIT timer, giving you more resolution.
CubeMX will also most probably enable High Speed Output drivers on PWM pins to reduce Rise/Fall times.

sam_des

HW PWM is not an option because I have to drive 3 bridges and there is only a single PWM which I use in different part of circuit for bucking converter.
Combining 2 timers might be a good idea I have to check that out.

Posted on: October 16, 2024, 08:22:45 20:22 - Automerged

In the meantime, this code seems to work somewhat better when I address directly the BSSR registers.
There is less variation between pulses but still in the range of a few hundred ns (the adjustment resolution is better though).

Code:
while(1) {
        __asm volatile (
            "LDR R0, =0x48000418 \n"     // Load address of GPIOB->BSRR
            "MOVS R1, #0x8000 \n"         // Set bit for PB15 (0x8000)
            "STR R1, [R0] \n"                  // Set PB15
            "MOVS R1, #0x80000000 \n" // Reset bit for PB15 (0x80000000)
            "STR R1, [R0] \n"                  // Reset PB15
        );
}


I also tried running it from RAM with pretty much the same results.

Code:
attribute((section(".data"))) void Toggle_Pins_RAM(void) {
    while(1) {
        GPIOB->BSRR = GPIO_BSRR_BS15;  // Set PB15
        GPIOB->BSRR = GPIO_BSRR_BR15;  // Reset PB15
    }



What I failed to mention is that because I'm driving 3 full bridges I have to address 12 pins.
That said, the problem appears also when running just 2 pins for tests.


I got additional advice to turn on cache and prefetch but I'm yet to try this (I'm a bit sceptical but will see what happens).
Logged
Pages: [1]
Print
Jump to:  


DISCLAIMER
WE DONT HOST ANY ILLEGAL FILES ON THE SERVER
USE CONTACT US TO REPORT ILLEGAL FILES
ADMINISTRATORS CANNOT BE HELD RESPONSIBLE FOR USERS POSTS AND LINKS

... Copyright © 2003-2999 Sonsivri.to ...
Powered by SMF 1.1.18 | SMF © 2006-2009, Simple Machines LLC | HarzeM Dilber MC