基于STM32F103的步进电机S型曲线加减速算法与实现

步进电机是将电脉冲信号转变为角位移或线位移的开环控制电机,是现代数字程序控制系统中的主要执行元件,应用极为广泛。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度,称为“步距角”,它的旋转是以固定的角度一步一步运行的。可以通过控制脉冲个数来控制角位移量,从而达到准确定位的目的;同时可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。
步距角:对应一个脉冲信号,电机转子转过的角位移用θ表示。θ=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

猜你喜欢

转载自blog.csdn.net/weixin_42399752/article/details/85245942