基于STM32F4实现步进电机闭环控制实现(无PID)
文章目录
- 概要
- 整体流程
- 代码实现
- TIM8 PWM控制
- TIM5 编码器计数
- TIM13 闭环控制
- 效果展示
- 小结
概要
因客户外部负载较大,步进电机出现丢步现象,所以需要进行闭环控制,保证最后走到相应的位置即可,所以我采用的是电机停止后与编码器值(转化实际脉冲数 相比较 误差在 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复用为定时器8GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //GPIOC7GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //下拉GPIO_Init(GPIOC,&GPIO_InitStructure); //初始化PF9TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_timTIM_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脉冲宽度调制模式2TIM_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中指定的参数初始化外设TIMxTIM_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_timTIM_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; //抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //子优先级3NVIC_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); //使能定时器13NVIC_InitStructure.NVIC_IRQChannel=TIM8_UP_TIM13_IRQn; //定时器13中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; //子优先级1NVIC_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 0if(MT8.motor_dir==1)//顺时针MT8.target_pos=MT8.current_pos+num;//目标位置else if(MT8.motor_dir==0)//逆时针MT8.target_pos=MT8.current_pos-num;//目标位置
#elseif(MT8.motor_dir==1)//顺时针MT8.target_pos=MT8.target_pos+num;//目标位置else if(MT8.motor_dir==0)//逆时针MT8.target_pos=MT8.target_pos-num;//目标位置
#endifMT8.rcr_integer=num/(RCR_VAL8+1);//重复计数整数部分MT8.rcr_remainder=num%(RCR_VAL8+1);//重复计数余数部分MT8.is_rcr_finish=0;//重复计数器未设置完成TIM8_Startup(frequency);//开启TIM8TIM_Cmd(TIM5, ENABLE);//开启计数
}
效果展示
人为堵转,编码器的值通过了校准。
单次50000个脉冲
小结
实现方式较为简单逻辑上没啥难点,定时器配置好,直接控制即可,至于编码器相关计算请跳到这一篇观看。
传送门