步进电机是将电脉冲信号转变为角位移或线位移的开环控制电机,是现代数字程序控制系统中的主要执行元件,应用极为广泛。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度,称为“步距角”,它的旋转是以固定的角度一步一步运行的。可以通过控制脉冲个数来控制角位移量,从而达到准确定位的目的;同时可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。
步距角:对应一个脉冲信号,电机转子转过的角位移用θ表示。θ=360度/(转子齿数运行拍数),以常规二、四相,转子齿为50齿电机为例。四拍运行时步距角为θ=360度/(504)=1.8度(俗称整步),八拍运行时步距角为θ=360度/(50*8)=0.9度(俗称半步)。
转速(Rotational Speed或Rev):是做圆周运动的物体单位时间内沿圆周绕圆心转过的圈数(与频率不同)。常见的转速有额定转速和最大转速等。
硬盘转速以每分钟多少转来表示,单位表示为RPM,RPM是Revolutions Per minute的缩写,是转/每分钟。RPM值越大,内部传输率就越快,访问时间就越短,硬盘的整体性能也就越好。
S型曲线的的方程:
y=1/(1+exp(-x));
x在[-5,5]的图形如下图所示:
在Matlab软件中代码如下:
x=-5:0.01:5;
y=1./(1+exp(-x));
plot(x,y);
如要将此曲线应用在步进电机的加、减速过程中,需要将方程在XY坐标系进行平移,同时对曲线进行拉升变化:
Y=A+B/(1+exp(-ax+b));
其中的A分量在y方向进行平移,B分量在y方向进行拉伸,ax+b分量在x方向进行平移和拉伸。
其中的A分量在y方向进行平移,B分量在y方向进行拉伸,ax+b分量在x方向进行平移和拉伸。
项目中加速过程:从600Hz加速到20000Hz,采用4细分。输出比较模块所用的定时器驱动频率为10M,采用1000个点进行加速处理。最终根据项目的需要,在加速过程中采用的曲线方程为:
Fcurrent = Fmin + (Fmax-Fmin)/(1+exp( -flexible(i - num )/num) );*
步进电机S型曲线加减速算法与实现。
其中的Fcurrent为length(1000)个点中的单个频率值。Fmin起始频率为600; Fmax为最大频率20000; -flexible*(i - num)/num是对S型曲线进行拉伸变化,其中flexible代表S曲线区间(越大代表压缩的最厉害,中间(x坐标0点周围)加速度越大;越小越接近匀加速。理想的S曲线的取值为4-6),i是在循环计算过程中的索引,从0开始,num为 length/2 大小(这样可以使得S曲线对称)。在项目中i的区间[0,1000), num=1000/2=500。这些参数均可以修改。
下面代码是基于STM32F103定期器2中断产生方波:
/*********************这部分是MOTOR.H文件***********************/
#define ADDSPEEDNUM 500 //加速度脉冲数
#pragma pack(1)
typedef struct _FreSpeed
{
uint16_t MaxPeriod; //最大速度比较器寄存器值
uint32_t MaxNum; //最大速度脉冲数
uint16_t IncPeriod[ADDSPEEDNUM]; //加减速脉冲比较器寄存器值
uint32_t IncNum; //加减速脉冲数
uint8_t AvrFlag; //匀速标志
uint8_t CurState; //当前状态
}FreSpeed;
#pragma pack()
enum SpeedFlagType
{
IncSpeedState, //加速状态
DecSpeedState, //加速状态
AvrSpeedState //加速状态
};
#define TIM2_Prescaler 0
#define TIM2_ClockDiv 0
/********************************************/
void STimer2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// timer2_ch1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;//PA0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = TIM2_Prescaler; //72/(0+1) = 72M
TIM_TimeBaseStructure.TIM_ClockDivision = TIM2_ClockDiv;//0
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 60000; //(72M/60000/2)=600Hz
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//must
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);
UNMOTORY_TEAR;//使能电机
}
void TIM2_IRQHandler(void)
{
uint16_t capture;
static uint16_t iCount=0;
static uint8_t SpeedMode = IncSpeedState;
/* enter interrupt */
if ( TIM_GetITStatus(TIM2 , TIM_IT_CC1) != RESET )
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
capture = TIM_GetCapture1(TIM2);
switch(SpeedMode)
{
case IncSpeedState:
TIM_SetCompare1(TIM2, capture + MotorSpeed.IncPeriod[iCount++]);
if(iCount == MotorSpeed.IncNum-1)
{
//rt_kprintf("Tim2 IRQ AddSpeed\n");
SpeedMode = ( MotorSpeed.AvrFlag == RT_TRUE )?AvrSpeedState:DecSpeedState;
}
break;
case AvrSpeedState:
TIM_SetCompare1(TIM2, capture + MotorSpeed.MaxPeriod);
MotorSpeed.MaxNum--;
if(MotorSpeed.MaxNum ==0)
{
//rt_kprintf("Tim2 IRQ AvrSpeed\n");
SpeedMode = DecSpeedState;
}
break;
case DecSpeedState:
TIM_SetCompare1(TIM2, capture + MotorSpeed.IncPeriod[iCount--]);
if(iCount==0)
{
//rt_kprintf("Tim2 IRQ DecSpeed\n");
SpeedMode = IncSpeedState;
Set_Timer2_Isr_Disable();
}
break;
default:
SpeedMode = IncSpeedState;
//rt_kprintf("Tim2 IRQ Error\n");
break;
}
}
}
static void Set_Timer2_Isr_Enable(void)
{
ENMOTORY_TEAR;
TIM_ClearFlag(TIM2, TIM_IT_CC1);
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); //´ò¿ªÖжÏ
TIM_Cmd(TIM2, ENABLE);
}
static void Set_Timer2_Isr_Disable(void)
{
//UNMOTORY_TEAR;
TIM_Cmd(TIM2, DISABLE);
TIM_ITConfig(TIM2, TIM_IT_CC1, DISABLE);
}
/************************************************************************
* ------------------------------------------------------------------------------------
* 2018/12/18 V1.0 CCPY 实现S型加减速算法
************************************************************************/
uint8_t CalculateSModelLine(FreSpeed *iSpeed, float len,uint32_t PulseNum, float fre_min,float fre_max, float flexible)
{
int i;
float TimClock= 72000000.0/(TIM2_Prescaler+1)/(TIM2_ClockDiv+1);
float deno;
float melo;
float delt = fre_max-fre_min;
if(delt<=0)
{
rt_kprintf("fre_max error\n");
return RT_FALSE;
}
if((PulseNum -2*len)>0) //存在匀速运动
{
iSpeed->AvrFlag = RT_TRUE;
iSpeed->MaxPeriod = (uint16_t)( TimClock/ (fre_max) / 2);
iSpeed->MaxNum = PulseNum -2*len;
iSpeed->IncNum = len;
}
else //不存在匀速运动
{
len = PulseNum/2.0;
iSpeed->AvrFlag = RT_FALSE;
iSpeed->MaxPeriod = RT_FALSE;
iSpeed->MaxNum = RT_FALSE;
iSpeed->IncNum = len;
}
for(i=0; i<len; i++ )
{
melo = flexible * (i-len/2) / (len/2);
deno = 1.0 / (1 + exp(-melo));
iSpeed->IncPeriod[i] = (uint16_t)(TimClock / (delt * deno + fre_min) / 2);
}
return RT_TRUE;
}
static void SMoveTrans(float iMaxVel,int32_t PulseNum,float flexible)
{
float fre_min = 600; //最小速度600hz
// float flexible = 8;
float Addlen =ADDSPEEDNUM;
if(CalculateSModelLine(&MotorSpeed,Addlen,PulseNum,fre_min,iMaxVel,flexible) ==RT_TRUE)
{
rt_kprintf("Calculate OK\n");
Set_Timer2_Isr_Enable();
}
else
{
rt_kprintf("Calculate Error\n");
}
}
void ST2Contrel(u32 cnt)
{
SMoveTrans(5000,cnt,8);
Set_Timer2_Isr_Enable();
}
代码写完之后,keil5使用软件仿真,PA1引脚可以产生方波,即可出现如上图:频率从600hz-20000hz,再减速到600hz。以此可以粗略验证算法程序的正确性。
参考网址:http://www.elecfans.com/kongzhijishu/sifuyukongzhi/583149.html