上篇文章讲解了电机的速度环控制,可以控制电机快速准确地到达指定速度。
本篇来介绍电机的位置环控制,实现电机快速准确地转动到指定位置。
1 位置控制与速度控制的区别
回顾上篇,电机速度PID控制的结构图如下,目标值是设定的速度,通过编码器获取电机的转速作为反馈,实现电机转速的控制。
再来看电机位置PID控制,其结构图如下,目标值是设定的位置,通过编码器获取电机累计转动的脉冲数作为反馈,实现电机位置的控制。
所以:对比两张图,速度控制与位置控制的主要区别,就是控制量的不同。
2 核心程序
了解了速度控制与位置控制的区别后,下面就可以修改程序。
2.1 编码器相关
2.1.1 电机与编码器参数
编码器部分,需要根据自己电机的实际参数进行设定,比如我用到的电机:
- 编码器一圈的物理脉冲数为11
- 定时器编码器模式通过设置倍频来实现4倍频
- 电机的减速齿轮的减速比为1:34
所以,电机转一圈总的脉冲数,即定时器能读到的脉冲数为11*4*34= 1496
。
1 2 3 4 5 6 7
| #define ENCODER_RESOLUTION 11 #define ENCODER_MULTIPLE 4 #define MOTOR_REDUCTION_RATIO 34
#define TOTAL_RESOLUTION ( ENCODER_RESOLUTION*ENCODER_MULTIPLE*MOTOR_REDUCTION_RATIO )
|
想要了解更多关于编码器的使用,可参照之前的文章:
2.1.2 定时器编码器模式配置
用于编码器捕获的定时器的一些宏定义。
1 2 3
| #define ENCODER_TIM_PSC 0 #define ENCODER_TIM_PERIOD 65535 #define CNT_INIT 0
|
配置主要关注重装载值,倍频,溢出中断设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| void TIMx_encoder_init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_ICInitTypeDef TIM_ICInitStruct; NVIC_InitTypeDef NVIC_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4); GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); TIM_DeInit(TIM4); TIM_TimeBaseStructInit(&TIM_TimeBaseStruct); TIM_TimeBaseStruct.TIM_Prescaler = ENCODER_TIM_PSC; TIM_TimeBaseStruct.TIM_Period = ENCODER_TIM_PERIOD; TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStruct);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_ICStructInit(&TIM_ICInitStruct); TIM_ICInitStruct.TIM_ICFilter = 0; TIM_ICInit(TIM4, &TIM_ICInitStruct); TIM_SetCounter(TIM4, CNT_INIT); TIM_ClearFlag(TIM4,TIM_IT_Update); TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); TIM_Cmd(TIM4,ENABLE); NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x01; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); }
|
想要了解更多关于定时器编码器模式配置的详细介绍,可参照之前的文章:
2.1.3 读取编码器的值
读取值,这里直接读取原始值即可,读取后也不需要再设置计数初值,因为使用的溢出中断。
1 2 3 4 5 6
| uint32_t read_encoder(void) { uint32_t encoderNum = 0; encoderNum = (TIM4->CNT); return encoderNum; }
|
2.1.4 编码器计数值溢出处理
溢出中断中,主要判断是向上溢出还是向下溢出,因为电机可以正反转,所以需要记录溢出的方向。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| __IO int16_t EncoderOverflowCnt = 0;
void TIM4_IRQHandler(void) { if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET) { if((TIM4->CR1 & TIM_CounterMode_Down) != TIM_CounterMode_Down) { EncoderOverflowCnt++; } else { EncoderOverflowCnt--; } } TIM_ClearITPendingBit(TIM4,TIM_IT_Update); }
|
2.2 PID计算相关
2.2.1 周期定时
定时器配置,通过设置自动重装载值和定时器分频实现指定周期的定时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void TIMx_calcPID_init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7,ENABLE); 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(TIM7,&TIM_TimeBaseInitStructure); TIM_ITConfig(TIM7,TIM_IT_Update,ENABLE); TIM_Cmd(TIM7,DISABLE); NVIC_InitStructure.NVIC_IRQChannel=TIM7_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01; NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); }
TIMx_calcPID_init(100-1,8400-1);
|
定时器中断中,每10ms进行1次PID计算
1 2 3 4 5 6 7 8
| void TIM7_IRQHandler(void) { if(TIM_GetITStatus(TIM7,TIM_IT_Update)==SET) { AutoReloadCallback(); } TIM_ClearITPendingBit(TIM7,TIM_IT_Update); }
|
想要了解更多关于基础定时器的配置与使用,可参照之前的文章:
2.2.2 PID电机控制逻辑
周期定时器的回调函数中进行PID的计算,程序中被注释掉的两句是速度控制的代码,用于与位置控制进行对比,通过对比可以明显的看出,位置控制与速度控制的区别在于传入PID的控制量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void AutoReloadCallback() { static __IO int encoderNow = 0; static __IO int encoderLast = 0; int encoderDelta = 0; int res_pwm = 0;
encoderNow = read_encoder() + EncoderOverflowCnt*ENCODER_TIM_PERIOD; encoderDelta = encoderNow - encoderLast; encoderLast = encoderNow;
res_pwm = pwm_val_protect((int)PID_realize(encoderNow));
set_motor_rotate(res_pwm);
set_computer_value(SEND_FACT_CMD, CURVES_CH1, &encoderNow, 1);
}
|
3 实验演示
视频:https://www.bilibili.com/video/BV1ZK4y1976i
实验中,指定目标值1496,可以实现电机正转1圈,再指定目标值-1496,因为是相对位置,电机会反转2圈。当指定14960转10圈时进行观察,若PID的参数不合适,会出现静态误差、或是持续抖动、或是误差消除慢等情况。通过不断的调整参数,可以实际感受到PID各项的调节作用。