概要

因客户外部负载较大,步进电机出现丢步现象,所以需要进行闭环控制,保证最后走到相应的位置即可,所以我采用的是电机停止后与编码器值(转化实际脉冲数 相比较 误差在 256(256细分对应1.8°) 以外 再次运动差值。

整体流程

这个实现较为简单,没啥复杂的思路

  • TIM5采用编码器模式 累计编码器的值
  • TIM8输出PWM控制电机转动
  • TIM13 间隔1ms读取编码器的值,判定是否需要进行补偿

代码实现

TIM8 PWM控制

/***********************************************
TIM8_CH2(PC7) 单脉冲输出+重复计数功能初始化
TIM8 时钟频率 84*2=168MHz
arr:自动重装值
psc:时钟预分频数
************************************************/
void TIM8_OPM_RCR_Init(u16 arr,u16 psc)
{		 					 
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8,ENABLE);  	//TIM8时钟使能    
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); 	//使能PORTC时钟	                                                                     	
	GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM8); //GPIOC7复用为定时器8
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;           //GPIOC7
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;      //下拉
	GPIO_Init(GPIOC,&GPIO_InitStructure);               //初始化PF9

	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值   
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
	TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	TIM_ClearITPendingBit(TIM8,TIM_IT_Update);

	TIM_UpdateRequestConfig(TIM8,TIM_UpdateSource_Regular); /********* 设置只有计数溢出作为更新中断 ********/
	TIM_SelectOnePulseMode(TIM8,TIM_OPMode_Single);/******* 单脉冲模式 **********/
 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出2使能
	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; /****** 比较输出2N失能 *******/
	TIM_OCInitStructure.TIM_Pulse = arr>>1; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM8, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx

	TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable);  //CH2预装载使能	 
	TIM_ARRPreloadConfig(TIM8, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_ClearITPendingBit(TIM8,TIM_IT_Update);
	TIM_ITConfig(TIM8, TIM_IT_Update ,ENABLE);  //TIM8   使能或者失能指定的TIM中断
 
	NVIC_InitStructure.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn;  //TIM8中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级1级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级1级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
	
	TIM_ClearITPendingBit(TIM8, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源
	TIM_Cmd(TIM8, ENABLE);  //使能TIM8											    
}

/******* TIM8更新中断服务程序 *********/
void TIM8_UP_TIM13_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM8,TIM_FLAG_Update)!=RESET)//更新中断
	{
		TIM_ClearITPendingBit(TIM8,TIM_FLAG_Update);//清除更新中断标志位		
		if(MT8.is_rcr_finish==0)//重复计数器未设置完成
		{
			if(MT8.rcr_integer!=0) //整数部分脉冲还未发送完成
			{
				TIM8->RCR=RCR_VAL8;//设置重复计数值
				MT8.rcr_integer--;//减少RCR_VAL8+1个脉冲				
			}else if(MT8.rcr_remainder!=0)//余数部分脉冲 不位0
			{
				TIM8->RCR=MT8.rcr_remainder-1;//设置余数部分
				MT8.rcr_remainder=0;//清零
				MT8.is_rcr_finish=1;//重复计数器设置完成				
			}else goto out;   //MT8.rcr_remainder=0,直接退出			 
			TIM_GenerateEvent(TIM8,TIM_EventSource_Update);//产生一个更新事件 重新初始化计数器
			TIM_CtrlPWMOutputs(TIM8,ENABLE);	//MOE 主输出使能	
			TIM_Cmd(TIM8, ENABLE);  //使能TIM8	
			if(MT8.motor_dir==CW8) //如果方向为顺时针   
				MT8.current_pos+=(TIM8->RCR+1);//加上重复计数值
			else          //否则方向为逆时针
				MT8.current_pos-=(TIM8->RCR+1);//减去重复计数值							
		}else
		{
out:		MT8.is_rcr_finish=1;//重复计数器设置完成
			TIM_CtrlPWMOutputs(TIM8,DISABLE);	//MOE 主输出关闭
			TIM_Cmd(TIM8, DISABLE);  //关闭TIM8				
		}	
	}
	
//定时器13中断
	if(TIM_GetITStatus(TIM13,TIM_IT_Update)==SET) //溢出中断
	{
		EncPos2 = circlecnt2*(divider2) + TIM5->CNT+ 10000 ;                // + 10000;  
		MT8.FeedbackLocation = ((EncPos2  - divider2/2 ) * 12.8) ;   //(+10000是防止MT8.FeedbackLocation溢出)
		//Axis_2_loop();
		Axis_2_lnc();//闭环补偿
		TIM_ClearITPendingBit(TIM13,TIM_IT_Update);  //清除中断标志位
	}		
	
}

TIM5 编码器计数

//用于编码器
void TIM5_Int_Init()
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_ICInitTypeDef        TIM_ICInitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0| GPIO_Pin_1; 
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP ;
	GPIO_Init(GPIOA,&GPIO_InitStructure); 
	TIM_DeInit(TIM5);	
	
	TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
	
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM5);

	TIM_TimeBaseStructure.TIM_Period = divider2; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler = 0; //设置用来作为TIMx时钟频率除数的预分频值  不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); 
	
	//设置定时器.为编码器模式  
	TIM_EncoderInterfaceConfig(TIM5, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
	TIM_ICStructInit(&TIM_ICInitStructure);
	TIM_ICInitStructure.TIM_ICFilter = 0x00;
	TIM_ICInit(TIM5, &TIM_ICInitStructure);
	
	TIM_ClearFlag(TIM5, TIM_FLAG_Update);  //清除所有标志位
	TIM_ITConfig(TIM5, TIM_IT_Update, ENABLE); //允许中断更新

	TIM5->CNT = divider2/2; //初始值设置为50%Max 避免负数
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn; //定时器3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM5, ENABLE);  //使能TIM1
	
}
u32 divider2= 60000000 ;//作为中间值,不出现负数
extern int circlecnt2;//圈数累计

void TIM5_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
	{
		if(TIM5->CNT<divider2/2) 
		{ 
			circlecnt2++;
		}
		else
		{ 
			circlecnt2--;
		}
		TIM_ClearITPendingBit(TIM5,TIM_IT_Update);  //清除中断标志位
	}
}

TIM13 闭环控制

void TIM13_Int_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE);  ///使能TIM13时钟  APB1 84M时钟
	
	TIM_TimeBaseInitStructure.TIM_Period = arr; 	//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=psc;  //定时器分频
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
	
	TIM_TimeBaseInit(TIM13,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM13,TIM_IT_Update,ENABLE); //允许定时器13更新中断
	TIM_Cmd(TIM13,ENABLE); //使能定时器13
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM8_UP_TIM13_IRQn; //定时器13中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级1
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
}

中断在TIM8那边

u8   Motor8_3f_flag=0;//调用单独轴控制 再去判断位置跟随
//闭环判断
void Axis_2_lnc(void)
{	
	Axis_2_Pos=MT8.target_pos + 128000;//脉冲值
	if(((TIM8->CR1 & (uint16_t)TIM_CR1_CEN)) || !Motor8_3f_flag) //2轴电机未停止 返回
	{
		return;
	}
	if(MT8.FeedbackLocation<Axis_2_Pos)
	{
		Num2=(Axis_2_Pos - MT8.FeedbackLocation);//误差脉冲(未到位)
		if(Num2>=256)
		{
			Locate8_INC(Num2,50000,1);
		}
		else{
			Motor8_3f_flag=0;
		}
	}

	if(MT8.FeedbackLocation>Axis_2_Pos)
	{
		Num2=(MT8.FeedbackLocation - Axis_2_Pos);//过冲
		if(Num2>=256)
		{
			Locate8_INC(Num2,50000,0);
		}
		else{
			Motor8_3f_flag=0;
		}
	}
	else{
			Motor8_3f_flag=0;
		}	
}

接口函数

void Axis_2(u32 num,u32 fre,u32 dir)
{
	DIR8=dir;
	Locate8_Rle(num,fre,dir);
}

/********************************************
//相对定位函数 
//num 0~2147483647
//frequency: 20Hz~100KHz
//dir: CW(顺时针方向)  CCW(逆时针方向)
*********************************************/
void Locate8_Rle(long num,u32 frequency,DIR8_Type dir) //相对定位函数
{
	if(num<=0) //数值小等于0 则直接返回
	{		
		return;
	}
	if(TIM8->CR1&0x01)//上一次脉冲还未发送完成  直接返回
	{
		return;
	}
	if((frequency<20)||(frequency>100000))//脉冲频率不在范围内 直接返回
	{
		return;
	}
	MT8.motor_dir=DIR8;//得到方向	
#if 0
	if(MT8.motor_dir==1)//顺时针
		MT8.target_pos=MT8.current_pos+num;//目标位置
	else if(MT8.motor_dir==0)//逆时针
		MT8.target_pos=MT8.current_pos-num;//目标位置
#else
	if(MT8.motor_dir==1)//顺时针
		MT8.target_pos=MT8.target_pos+num;//目标位置
	else if(MT8.motor_dir==0)//逆时针
		MT8.target_pos=MT8.target_pos-num;//目标位置
#endif

	MT8.rcr_integer=num/(RCR_VAL8+1);//重复计数整数部分
	MT8.rcr_remainder=num%(RCR_VAL8+1);//重复计数余数部分
	MT8.is_rcr_finish=0;//重复计数器未设置完成
	TIM8_Startup(frequency);//开启TIM8
	TIM_Cmd(TIM5, ENABLE);//开启计数
}

效果展示

人为堵转,编码器的值通过了校准。
单次50000个脉冲
在这里插入图片描述
在这里插入图片描述

小结

实现方式较为简单逻辑上没啥难点,定时器配置好,直接控制即可,至于编码器相关计算请跳到这一篇观看。
传送门

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐