文章目录
- 概要
- 整体流程
- 代码实现
- 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复用为定时器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个脉冲
小结
实现方式较为简单逻辑上没啥难点,定时器配置好,直接控制即可,至于编码器相关计算请跳到这一篇观看。
传送门