2022-11-13 01:54 AM
I need to generate PWM signals with adjustable number and frequency to drive a step motor. I expect the frequency ranges from 50Hz to 200KHz and the pulses count ranges from 1 to 2k.
I have tried 3 different ways including: using timer interrupt to count the pulse number; setting the PWM timer as master and using a slave timer in gated mode to count the pulse; using One Pulse Mode and the 16bit Repetition Counter (RCR). I used to think the last is the simplest and the most reliable cause all of the work is done by hardware. But the problem is the same. When the frequency reaches 200kHz the pulses count is inaccurate. More precisely, the count is always less than expected.
Details of my MCU settings and code are in below.
#define CLK_FREQ 144000 /* CLK_INT / 1000 */
#define DT 1 /* dt in millisecond */
#include "tim.h"
#include "gpio.h"
#include "math.h"
void motorMove(uint8_t xdir,uint16_t xsteps, uint8_t ydir,uint16_t ysteps)
{
uint16_t psc = 0;
uint16_t arr = 0;
uint16_t ccr = 0;
xdirOut(xdir);
getFreq(xsteps, DT, &psc, &arr, &ccr);
xpwmOut(psc, arr, ccr, xsteps);
ydirOut(ydir);
getFreq(ysteps, DT, &psc, &arr, &ccr);
ypwmOut(psc, arr, ccr, ysteps);
}
void xpwmOut(uint16_t psc,uint16_t arr,uint16_t ccr,uint16_t stp)
{
htim1.Init.Prescaler = psc;
htim1.Init.Period = arr;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ccr);
htim1.Init.RepetitionCounter = stp - 1;
HAL_TIM_Base_Init(&htim1);
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
}
void ypwmOut(uint16_t psc,uint16_t arr,uint16_t ccr,uint16_t stp)
{
htim8.Init.Prescaler = psc;
htim8.Init.Period = arr;
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, ccr);
htim8.Init.RepetitionCounter = stp - 1;
HAL_TIM_Base_Init(&htim8);
HAL_TIM_PWM_Start_IT(&htim8, TIM_CHANNEL_1);
}
void xdirOut(uint8_t dir)
{
if(dir)
{
HAL_GPIO_WritePin(X_DIR_GPIO_Port, X_DIR_Pin, GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(X_DIR_GPIO_Port, X_DIR_Pin, GPIO_PIN_SET);
}
}
void ydirOut(uint8_t dir)
{
if(dir)
{
HAL_GPIO_WritePin(Y_DIR_GPIO_Port, Y_DIR_Pin, GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(Y_DIR_GPIO_Port, Y_DIR_Pin, GPIO_PIN_SET);
}
}
void getFreq(uint16_t steps, const uint16_t dt, uint16_t* psc, uint16_t* arr, uint16_t* ccr)
{
double temp;
temp = CLK_FREQ * dt / steps;
temp = sqrt(temp);
*(psc) = ((uint16_t) temp) - 1;
*(arr) = *(psc);
*(ccr) = *(arr) / 2;
}
2022-11-13 03:09 AM
no clock, really?
should be:
2022-11-13 05:41 AM
emm...I don't know why. But yes, the timer goes well without selecting a clock source in CubeMX. Maybe the default is Internal Clock. And I have just tested to set the Internal Clock. But the problem remains even when the frequency is just 20kHz.
Here is my clock configuration.
2022-11-13 06:39 AM
so, clk is on now. (not only by ...lucky preset or error)
to get it right: frequency is right? did you check?
but only puls count is wrong ??
first: leave away "repetition", pwm mode out, set arr , just to control with DSO: frequ. 100% ok ?
then set repetition and stop counter on rep. event.
2022-11-13 12:29 PM
"16bit Repetition Counter (RCR)"
Repetition counter is 8bit ... maximum number of pulses using repetition counter is 255.
2022-11-13 01:02 PM
>> "16bit Repetition Counter (RCR)"
> Repetition counter is 8bit ... maximum number of pulses using repetition counter is 255
Good observation, except that in 'F303 it's actually 16-bit (RCR width varies across STM32 families).
JW
2022-11-13 01:11 PM
> But the problem is the same. When the frequency reaches 200kHz the pulses count is inaccurate.
> More precisely, the count is always less than expected.
How are you checking that? What is the difference to expected number of pulses?
Do you stop or restart the timer somehow?
Read out and check TIM registers content just before the counter gets enabled. That probably involves stepping through Cube code (which is open source so it's perfectly okay). Note, that RCR is effectively preloaded, i.e. it does not get effective until the next Update event.
If you do anything which is not supported directly by clicking in CubeMX, Cube may get into way more than help. Don't use Cube. Timers are not that hard to manage. You can play with them without writing code, in the debugger, to get the grips.
JW
2022-11-13 05:17 PM
There are many things wrong. As already noted, several timer registers are preloaded. The timer clock is not 144 MHz - even the CubeMX shows it clearly and correctly.
The getFreq() code is also most likely inappropriate.
double temp;
temp = CLK_FREQ * dt / steps;
temp = sqrt(temp);
As a minimum, replace it with this:
float temp = sqrtf(((uint32_t)CLK_FREQ * dt) / (float)steps);
Note the letter "f" at the end of sqrtf(). Now the division and square root calculation is done as a float type, which on Cortex-M4 can run on hardware FPU, and it still provides a 24-bit resolution. But...
72 MHz / 2^16 / 50 Hz = 21,97 = 22
Set a prescale factor to 22 and pulse width to a constant value. Now you can get your range from 50 Hz by just adjusting the period and it still has a 16-bit resolution. At the highest speeds the period of 16 counter ticks gives 204,5 kHz and a 17 ticks gives 192,5 kHz. Should be good enough resolution for many purposes.
2022-11-13 08:07 PM
Thank you! I just checked the content of every register of TIM1 in debug mode. I found the cotent was accurate in debug mode. And the pulse count was also accurate up to 200kHz. The difference between debug mode and normal startup, I think, is the delay between breakpoints. So I just add a 1ms delay and the problem disappears.
Maybe it needs some time to reload the register...
2022-11-13 08:10 PM
Thank you for your great advice!