cancel
Showing results for 
Search instead for 
Did you mean: 

Is there a better way to generate a variable frequency using DMA and TIM2 than what is described in AN2820 Paragraph 2.2.1 Page 13/14?

KiptonM
Lead

It is doing exactly what I want, It is ramping up the frequency at the start, staying steady for some period of time, and then ramping down.

In my case, I need up to 38400 steps. And I want to count steps, not time.

My first thought was to have a big array (my STM32L443 has 64k RAM) and just load each step time in it so it ramps up at the start, keeps loading the same data through the middle and then ramps down at the end with one array.

I was having problems trying to figure out how they kept the clock symmetrical. (I am not really familiar with TIM2.) After wading through the code, I found the output was toggled, and they had 2 identical data points for each cycle. One for the low part and one for the high part. So now my data set just doubled in size. That will not work. I do not have enough memory with 64k.

I am driving the STCK pin on the L6474 and it can go to 2 MHz max, The datasheet does not say anything about whether it has to be symmetric (whether the high time must be the same as the low time). If it does not, then I think I can do it with a PWM and adjust the PWM frequency as opposed to the duty cycle. My maximum frequency is probably 32 kHz or less. So maybe I could make it a fixed high for 16 us and vary the low from 15.25 us to however slow I need it to go. Do you think that will work?

Is there a better way to do this?

Thanks in advance.

11 REPLIES 11

I'm running a CPU and TIM1 on L43x at 72 MHz and generating 256 microsteps at speeds up to 1000 (full) steps per second. Taking into account interrupt latency and minimum step pulse width, it leaves less than 260 cycles for interrupt processing until the next step pulse. Not that much for a code that manages acceleration, deceleration, direction changing and timer starting and stopping. Actually, if the code runs longer, at least in my case a few unnecessary microstep pulses would be harmless, but still better to avoid it and do everything quicker, which is more important in my case.

Generally, of course, I also keep interrupts short and defer processing for later, but this case is kind of an exception. Actually without an RTOS, the NVIC in Cortex-M cores can and should be used for creating an additional preemptive "tasks". By the way, I also don't understand why there are so many preemptive RTOS-es in the world, but almost no decent prioritized cooperative schedulers with software timers, events etc. Few examples can be found in this topic.

You can develop your timer code on that board fully, because porting it from F4 to L4 is trivial. I even never looked on my step pulses with a scope or something - I just plan the code analytically and see that signal in my mind... 😁

I am confused. The math does not work out unless you are interrupting every microstep.

Somewhere else you said to update every step and use the repetition counter for the microsteps. (In the second link you shared.)

You are interrupting every microstep.

This is where I would probably use a table and DMA. If you made your array 256 for the microsteps, then you have to update the table 1000 times a second which gets rid of all of the interrupt overhead. And you only have to update it if it is changing like acceleration or decelerating. And for 256 words, that could be a memcpy() or DMA transfer with no calculations.

And I am pretty sure you are not using the HAL, because it has so much bloat in the interrupts it would probably use most all of your 260 cycles in the HAL's part of the interrupt.

That would lower the number of interrupts by a factor of 256 and you would have time to do communications and read an encoder as it is stepping.

I am about to pitch the HAL on the Timer Interrupt. I could not figure out what the different call backs did from the documentation, so I had to dig into the interrupt service routine today. They call two routines

HAL_TIM_OC_DelayElapsedCallback and HAL_TIM_PWM_PulseFinishedCallback for the same interrupt flag.

/* Capture compare 1 event */
  if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)
  {
    if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) != RESET)
    {
      {
        __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
        htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1;
 
        /* Input capture event */
        if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U)
        {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
          htim->IC_CaptureCallback(htim);
#else
          HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
        }
        /* Output compare event */
        else
        {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
          htim->OC_DelayElapsedCallback(htim);
          htim->PWM_PulseFinishedCallback(htim);
#else
          HAL_TIM_OC_DelayElapsedCallback(htim);
          HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
        }
        htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
      }
    }
  }

And they clear the interrupt flag before calling these routines, so you do not know the source of the interrupt. I guess they assume you only use one channel interrupt (CC1IF, CC2IF, CC3IF or CC4IF) because there is no way to know which one was the source inside the callback. That is not how I would do it.

I also noticed I have interrupts disabled for CC2, CC3, CC4, but CC3IF and CC4IF still turn to 1. It must be a known silicon bug, because the HAL interrupt checks the interrupt enable if the Interrupt flag is 1. And only calls the callback if both are 1.

It looks like you have a fun project. I am only doing 16 microsteps per step.