引出
这篇主要讨论一下步进电机在速度控制时发现的一个现象,不知道有没有人遇见,就是电机在慢速的调速时,会发现速度变得慢,变速的时间周期长,例如从0rpm变成200rpm,在这个过程中可能需要的时间比较长;步进电机在快速的调速中机会发现速度变化的特别快,例如当前是800rpm加速到1000rpm,就会瞬间加速完成。
上面所说的其实就是,明明设置的加速度是一样的,但为什么加速度在实际过程中不一样,我理解的是步进电机在速度变化时,与伺服电机不太相同,步进电机有一定的惯性,速度过快时,再进行变速就会出现加速度很高,较短时间就能完成变速,但是想到减速时也是这个特性,所以上面这个说法就不太确定了。所以有了解的大神可以帮忙解答。
解决
但是我也想了个简单的办法,解决一下这个问题,就是通过在速度变化过程中当前速度变化,进行简单的调整加速度。完全通过逻辑代码加上调试来实现。(后续附上全部代码)
1、首先我需要有一个简单的梯形变速函数
梯形变速就是利用相同的加减系数进行自加或自减。
在这里利用了步进电机的转/分钟当做变速的转速参数,所以需要一个转速转换为ARR值的函数来改变PWM的频率。(因为计算存在无限循环小数,所以误差比较大)
//--------------------------------------------------------------------------------------
/**
* 函 数:转速(rev/min)转换成ARR的值,在进行速度变换操作
* 参 数:Current_RPM 当前转速(rev/min)
* 返 回 值:转换后的ARR的值
*/
uint32_t RPM_SetARR(uint32_t Current_RPM)
{
//直接返回ARR重装载寄存器的值
uint32_t ARR_Value = (TIM_Hz / (Init_SetPSC + 1)) / ((Current_RPM * Motor_SetSpeed) / 60);
return ARR_Value;
}
接下来是是加减速的函数
#define GPIO_Pin_Motor_A GPIO_Pin_3 //输出搅拌电机的PWM引脚
#define TIM_Hz 72000000 //时钟频率为72MHz
#define Init_SetPSC 72 - 1 //初始化的PSC预分频的值
#define Motor_SetSpeed 1600 //步进电机的脉冲设置(实际上步进电机设置的是1600Pulse/rev)
#define Init_SetARR 5000 - 1 //初始化ARR的值
//-----------------------------------加速减速函数-----------------------------------
/**
* 函 数:加速减速函数(保证启动和停机的减震)(通过ARR改变PWM的频率进行调速)
* 参 数:Initial_Velocity 起始速度(ARR的值)
* 参 数:Target_Velocity 目标速度(ARR的值)
* 参 数:Speed_Ratio 转速倍数(1为++的变换,2为+=2的变换)
* 返 回 值:无
*/
void Speed_Transformation(uint16_t Initial_Velocity, uint16_t Target_Velocity, uint8_t Speed_Ratio)
{
//计算出速度对应的ARR值
Initial_Velocity += 30; //给一个20转每分钟的初始速度,来提高加减速效率
int Temp_Target_Velocity = Target_Velocity; //保存输入的目标速度
uint16_t Temp_InitSpeed_ARR = RPM_SetARR(Initial_Velocity);
uint16_t Temp_TargetSpeed_ARR = RPM_SetARR(Target_Velocity);
uint16_t Temp_Velocity_ARR = 0; //中间变量来进行递加并赋值给ARR
Motor_SetENA(0); //电机使能
//如果目标速度为0(就是想要设置为停机)
if(Target_Velocity == 0)
{
Temp_Velocity_ARR = Temp_InitSpeed_ARR;
int Motor_RPM = 0;
while(Temp_Velocity_ARR < RPM_SetARR(30))
{
PWM_SetSetARR_PUL(Temp_Velocity_ARR += Speed_Ratio); //中间变量递加减速
// Delay_ms(2);
OLED_ShowNum(4,11,Temp_Velocity_ARR,4); //测试打印
Motor_RPM = ARR_SetRPM(Temp_Velocity_ARR);
unsigned char buf1[64];
OLED_ShowNum(4,11,Motor_RPM,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Motor_RPM);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
PWM_SetCompare_TIM2_CH4(0); //占空比设置为0,电机停止转动
}
else
{
//如果是加速的话
int Motor_RPM = 0;
if(Initial_Velocity < Target_Velocity)
{
Temp_Velocity_ARR = Temp_InitSpeed_ARR;
if(Initial_Velocity == 0)
{
Temp_Velocity_ARR = Init_SetARR;
PWM_SetCompare_TIM2_CH4((Init_SetARR + 1) / 2); //占空比设置为50%,电机停止转动
}
while(Temp_Velocity_ARR > Temp_TargetSpeed_ARR)
{
PWM_SetSetARR_PUL(Temp_Velocity_ARR -= Speed_Ratio); //ARR的值减到目标速度的ARR值时
// Delay_ms(2);
OLED_ShowNum(4,11,Temp_Velocity_ARR,4); //测试打印
Motor_RPM = ARR_SetRPM(Temp_Velocity_ARR);
unsigned char buf1[64];
OLED_ShowNum(4,11,Motor_RPM,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Motor_RPM);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
//如果是减速的话
else if(Initial_Velocity > Target_Velocity)
{
Temp_Velocity_ARR = Temp_InitSpeed_ARR;
while(Temp_Velocity_ARR < Temp_TargetSpeed_ARR)
{
PWM_SetSetARR_PUL(Temp_Velocity_ARR += Speed_Ratio); //ARR的值加到目标速度的ARR值时
// Delay_ms(2);
OLED_ShowNum(4,11,Temp_Velocity_ARR,4); //测试打印
Motor_RPM = ARR_SetRPM(Temp_Velocity_ARR);
unsigned char buf1[64];
OLED_ShowNum(4,11,Motor_RPM,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Motor_RPM);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
}
}
速度小于30转/分钟时,变速太慢了,所以就让步进电机的最低速度变为30转/分钟。
Initial_Velocity += 30; //给一个20转每分钟的初始速度,来提高加减速效率
以下代码是因为使用了串口屏,进行数据上报到串口屏上(不重要)
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
2、有了上面函数就可以进行逻辑调用了
//-------------------------------------电机控制函数---------------------------------------
/**
* 函 数:电机控制函数
* 参 数:无
* 返 回 值:无
* 主要完成收到设置电机速度的数据后,调用梯形加减速的函数处理。
* 发现在小于200转/分钟时,电机的速度变化过慢,所以需要进行处理,提高加速度
* 在大于200转/分钟时,电机的加速度过高,所以也需要处理,降低加速度。
*/
void Motor_Control(void)
{
if(Motor_Threshold > Motor_RPM) //加速
{
if(Motor_RPM < 200) //当前速度小于200rpm时
{
Speed_Transformation(Motor_RPM,200,6); //快速加到200rpm
Motor_RPM = 200; //更新当前转速
Speed_Transformation(Motor_RPM,Motor_Threshold,1); //高于200rpm的部分要降低加速度进行加速
Motor_RPM = Motor_Threshold; //加速完成后更新当前速度
}
else //当前速度大于等于200rpm时
{
Speed_Transformation(Motor_RPM,Motor_Threshold,1); //较小加速度的进行加速
Motor_RPM = Motor_Threshold; //更新当前速度
}
}
else //减速
{
if(Motor_Threshold > 200) //目标速度大于200rpm时
{
Speed_Transformation(Motor_RPM,Motor_Threshold,1); //较慢加速度进行减速
Motor_RPM = Motor_Threshold; //更新当前速度
}
else //目标速度小于等于200rpm时
{
Speed_Transformation(Motor_RPM,200,1); //先将速度进行小加速度的减速
Motor_RPM = 200; //将200的速度更新给当前速度
Speed_Transformation(Motor_RPM,Motor_Threshold,6); //剩下的速度减速过慢,所以就用x6的加速度进行减速
Motor_RPM = Motor_Threshold; //最后再将当前速度更新
}
}
}
注释比较详细,当我需要进行电机变速时,可以直接调用上面这个函数,因为里面的两个电机速度Motor_Threshold(目标速度)和Motor_RPM(当前速度)是全局变量,测试问题不大,可以使用。(后面附上我的步进电机全部的配置文件,主函数循环可以直接循环调用电机控制函数,就不写了)
3、完整文件
#include "stm32f10x.h" // Device header
#include "Motor.h"
#include "OLED.h"
#include "OLED_Font.h"
#include "Delay.h"
#include "Serial.h"
#include <stdio.h>
#define GPIO_Pin_Motor_A GPIO_Pin_3 //输出搅拌电机的PWM引脚
#define TIM_Hz 72000000 //时钟频率为72MHz
#define Init_SetPSC 72 - 1 //初始化的PSC预分频的值
#define Motor_SetSpeed 1600 //步进电机的脉冲设置(实际上步进电机设置的是1600Pulse/rev)
#define Init_SetARR 5000 - 1 //初始化ARR的值
//--------------------------------------------------------------------------------------
/**
* 函 数:搅拌电机PWM初始化
* 参 数:无
* 返 回 值:无
*/
void Motor_PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA和GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_Motor_A;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
/*1500个脉冲每分钟,步进电机设置为1600Pulse/rev,大约每分钟一圈(误差角度为22.5度)*/
TIM_TimeBaseInitStructure.TIM_Period = Init_SetARR; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = Init_SetPSC; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*输出比较初始化*/
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值(占空比)
//TIM2的4通道
TIM_OC4Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC4Init,配置TIM2的输出比较通道4
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
//-------------------------------------------------------------------------------------
/**
* 函 数:单独设置占空比(停机开机时使用的函数)
* 参 数:Compare 设置的占空比
* 返 回 值:无
*/
void PWM_SetCompare_TIM2_CH4(uint16_t Compare)
{
TIM_SetCompare4(TIM2, Compare);
}
//-------------------------------------------------------------------------------
/**
* 函 数:搅拌电机PWM设置ARR重装在寄存器,改变频率从而改变电机的转速(占空比是ARR的50%)
* 参 数:ARR_Value 写入重装载寄存器的数值
* 返 回 值:无
*/
void PWM_SetSetARR_PUL(uint16_t ARR_Value)
{
TIM_SetAutoreload(TIM2,ARR_Value);
TIM_SetCompare4(TIM2, ARR_Value / 2);
}
//---------------------------------------------------------------------------------
/**
* 函 数:转速(rev/min)转换成ARR的值,在进行速度变换操作
* 参 数:Current_RPM 当前转速(rev/min)
* 返 回 值:转换后的ARR的值
*/
uint32_t RPM_SetARR(uint32_t Current_RPM)
{
//直接返回ARR重装载寄存器的值
uint32_t ARR_Value = (TIM_Hz / (Init_SetPSC + 1)) / ((Current_RPM * Motor_SetSpeed) / 60);
return ARR_Value;
}
//-------------------------------------------------------------------------------------
/**
* 函 数:ARR转成转速
* 参 数:ARR的值
* 返 回 值:转换后的转速
*/
uint32_t ARR_SetRPM(uint32_t Current_ARR)
{
//直接返回ARR重装载寄存器的值
uint32_t RPM_Value = ((TIM_Hz / (Init_SetPSC + 1) / Current_ARR) * 60) / Motor_SetSpeed;
return RPM_Value;
}
//-------------------------------------------------------------------------------------
/**
* 函 数:加速减速函数(保证启动和停机的减震)(通过ARR改变PWM的频率进行调速)
* 参 数:Initial_Velocity 起始速度(ARR的值)
* 参 数:Target_Velocity 目标速度(ARR的值)
* 参 数:Speed_Ratio 转速倍数(1为++的变换,2为+=2的变换)
* 返 回 值:无
*/
void Speed_Transformation(uint16_t Initial_Velocity, uint16_t Target_Velocity, uint8_t Speed_Ratio)
{
//计算出速度对应的ARR值
Initial_Velocity += 30; //给一个20转每分钟的初始速度,来提高加减速效率
int Temp_Target_Velocity = Target_Velocity; //保存输入的目标速度
uint16_t Temp_InitSpeed_ARR = RPM_SetARR(Initial_Velocity);
uint16_t Temp_TargetSpeed_ARR = RPM_SetARR(Target_Velocity);
uint16_t Temp_Velocity_ARR = 0; //中间变量来进行递加并赋值给ARR
Motor_SetENA(0); //电机使能
//如果目标速度为0(就是想要设置为停机)
if(Target_Velocity == 0)
{
Temp_Velocity_ARR = Temp_InitSpeed_ARR;
int Motor_RPM = 0;
while(Temp_Velocity_ARR < RPM_SetARR(30))
{
PWM_SetSetARR_PUL(Temp_Velocity_ARR += Speed_Ratio); //中间变量递加减速
// Delay_ms(2);
OLED_ShowNum(4,11,Temp_Velocity_ARR,4); //测试打印
Motor_RPM = ARR_SetRPM(Temp_Velocity_ARR);
unsigned char buf1[64];
OLED_ShowNum(4,11,Motor_RPM,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Motor_RPM);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
PWM_SetCompare_TIM2_CH4(0); //占空比设置为0,电机停止转动
}
else
{
//如果是加速的话
int Motor_RPM = 0;
if(Initial_Velocity < Target_Velocity)
{
Temp_Velocity_ARR = Temp_InitSpeed_ARR;
if(Initial_Velocity == 0)
{
Temp_Velocity_ARR = Init_SetARR;
PWM_SetCompare_TIM2_CH4((Init_SetARR + 1) / 2); //占空比设置为50%,电机停止转动
}
while(Temp_Velocity_ARR > Temp_TargetSpeed_ARR)
{
PWM_SetSetARR_PUL(Temp_Velocity_ARR -= Speed_Ratio); //ARR的值减到目标速度的ARR值时
// Delay_ms(2);
OLED_ShowNum(4,11,Temp_Velocity_ARR,4); //测试打印
Motor_RPM = ARR_SetRPM(Temp_Velocity_ARR);
unsigned char buf1[64];
OLED_ShowNum(4,11,Motor_RPM,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Motor_RPM);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
//如果是减速的话
else if(Initial_Velocity > Target_Velocity)
{
Temp_Velocity_ARR = Temp_InitSpeed_ARR;
while(Temp_Velocity_ARR < Temp_TargetSpeed_ARR)
{
PWM_SetSetARR_PUL(Temp_Velocity_ARR += Speed_Ratio); //ARR的值加到目标速度的ARR值时
// Delay_ms(2);
OLED_ShowNum(4,11,Temp_Velocity_ARR,4); //测试打印
Motor_RPM = ARR_SetRPM(Temp_Velocity_ARR);
unsigned char buf1[64];
OLED_ShowNum(4,11,Motor_RPM,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Motor_RPM);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
unsigned char buf1[64];
OLED_ShowNum(4,11,Target_Velocity,5); //测试打印
sprintf((char *)buf1,"page0.n4.val=%d",Temp_Target_Velocity);
HMI_Sends((char *)buf1);
HMISendb(0xff);//结束符
}
}
}