2.3.1 解决的问题
解决带编码器直流电机的速度闭环问题。
2.3.2 PID理论
将偏差的比例、积分、微分,通过线性组合构成控制量,用控制量对被控对象进行控制,这样的控制器称为PID控制器。在连续空间中,我们通常探讨模拟PID的控制原理,如图所示:

我们这里用电机速度控制为例,讲解PID控制系统。r(t)为设定电机速度、y(t)为实际电机速度、e(t)=y(t)-r(t)为速度差值作为PID控制器的输入、u(t)为PID控制器的输出,作用到被控对象电机上。根据模拟PID控制器,科学家们也得出了模拟PID控制的公式,如图所示: 
其中Kp、Ti、Td,分别为控制器的比例系数、积分系数、微分系数。该理论用在控制的例子比比皆是。但是模拟PID控制系统是在连续空间的上描述的,无法在计算机上用代码实现。于是就有数字PID控制理论,将连续空间的PID控制系统在离散空间上描述。积分变成了求和、微分变成了求斜率,于是就出现数字PID控制系统的理论公式,如图所示:

其中Kp、Ti、Td和上面描述的一样,T为采用周期,ek是本次差值,ek-1上一次的差值,直接通过模拟PID转化的数字PID又叫做位置式PID,该方式的PID的输出直接是控制量,非常不适合经常出现异常的系统,另外一种方式是增量式PID,每次只输出一个正向或者反向的调节量,就算出现异常,也不会产生巨大的影响。具体数学公式如下所示:该方法较多的应用于生产生活中,本论文中电机的速度PID控制当然也不例外。

有了上面的理论基础,开始代码实现的介绍。首先就是明确增量式PID系统的输入、输出、控制对象。将速度的设定值和速度的测得值作为PID控制器的输入参数,PID的输出参数为对PWM的调节偏差,控制对象PWM进而驱动电机达到设定速度。以上内容确定之后,就是PID控制器的代码部分了。其实仔细看看增量式PID就只有一个公式,所以使用代码实现并不困难。如下所示核心代码就这一句。

完成上面的代码,只是完成速度PID的一部分,剩下的是尤为重要的PID参数整定。该整定方法丰富多样,最为准确的是模型计算,但是对于我们做机器人多使用试凑法。虽然需要调节一段时间,但是不需要对机器人进行建模。试凑法一般按照P、I、D的顺序进行调节。
初始时刻将Ki和Kd都设置成0,按照经验设置Kp的初始值,就这样将系统投入运行,由小到大调节Kp。求得满意的曲线之后,若要引入积分作用,将Kp设置成之前的5/6,然后Ki由小到大开始调节。达到满意效果之后,若要引入微分作用,将Kd按照经验调节即可。经过有规律的试凑,最终达到一个我们满意的就行。
2.3.3 代码分享
(1)pid.h - #include "pid.h"
- struct pid_uint pid_Task_Letf;
- struct pid_uint pid_Task_Right;
- /****************************************************************************
- *函数名称:PID_Init(void)
- *函数功能:初始化PID结构体参数
- ****************************************************************************/
- void PID_Init(void)
- {
- //乘以1024原因避免出现浮点数运算,全部是整数运算,这样PID控制器运算速度会更快
- /***********************左轮速度pid****************************/
- pid_Task_Letf.Kp = 1024 * 0.5;//0.4
- pid_Task_Letf.Ki = 1024 * 0;
- pid_Task_Letf.Kd = 1024 * 0.08;
- pid_Task_Letf.Ur = 1024 * 4000;
- pid_Task_Letf.Adjust = 0;
- pid_Task_Letf.En = 1;
- pid_Task_Letf.speedSet = 0;
- pid_Task_Letf.speedNow = 0;
- reset_Uk(&pid_Task_Letf);
- /***********************右轮速度pid****************************/
- pid_Task_Right.Kp = 1024 * 0.35;//0.2
- pid_Task_Right.Ki = 1024 * 0; //不使用积分
- pid_Task_Right.Kd = 1024 * 0.06;
- pid_Task_Right.Ur = 1024 * 4000;
- pid_Task_Right.Adjust = 0;
- pid_Task_Right.En = 1;
- pid_Task_Right.speedSet = 0;
- pid_Task_Right.speedNow = 0;
- reset_Uk(&pid_Task_Right);
- }
- /***********************************************************************************************
- 函 数 名:void reset_Uk(PID_Uint *p)
- 功 能:初始化U_kk,ekk,ekkk
- 说 明:在初始化时调用,改变PID参数时有可能需要调用
- 入口参数:PID单元的参数结构体 地址
- ************************************************************************************************/
- void reset_Uk(struct pid_uint *p)
- {
- p->U_kk=0;
- p->ekk=0;
- p->ekkk=0;
- }
- /***********************************************************************************************
- 函 数 名:s32 PID_commen(int set,int jiance,PID_Uint *p)
- 功 能:PID计算函数
- 说 明:求任意单个PID的控制量
- 入口参数:期望值,实测值,PID单元结构体
- 返 回 值:PID控制量
- ************************************************************************************************/
- s32 PID_common(int set,int jiance,struct pid_uint *p)
- {
- int ek=0,U_k=0;
- ek=jiance - set;
-
- U_k=p->U_kk + p->Kp*(ek - p->ekk) + p->Ki*ek + p->Kd*(ek - 2*p->ekk + p->ekkk);
-
- p->U_kk=U_k;
- p->ekkk=p->ekk;
- p->ekk=ek;
-
- if(U_k>(p->Ur))
- U_k=p->Ur;
- if(U_k<-(p->Ur))
- U_k=-(p->Ur);
-
- return U_k>>10;
- }
- /***********************************************************************************
- ** 函数名称 :void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)
- ** 函数功能 :pid选择函数
- ***********************************************************************************/
- void Pid_Which(struct pid_uint *pl, struct pid_uint *pr)
- {
- /**********************左轮速度pid*************************/
- if(pl->En == 1)
- {
- pl->Adjust = -PID_common(pl->speedSet, pl->speedNow, pl);
- }
- else
- {
- pl->Adjust = 0;
- reset_Uk(pl);
- pl->En = 2;
- }
- /***********************右轮速度pid*************************/
- if(pr->En == 1)
- {
- pr->Adjust = -PID_common(pr->speedSet, pr->speedNow, pr);
- }
- else
- {
- pr->Adjust = 0;
- reset_Uk(pr);
- pr->En = 2;
- }
- }
- /*******************************************************************************
- * 函数名:Pid_Ctrl(int *leftMotor,int *rightMotor)
- * 描述 :Pid控制
- *******************************************************************************/
- void Pid_Ctrl(int *leftMotor,int *rightMotor)
- {
- Pid_Which(&pid_Task_Letf, &pid_Task_Right);
- *leftMotor += pid_Task_Letf.Adjust;
- *rightMotor += pid_Task_Right.Adjust;
- }
复制代码
(2)main.c
- #include "sys.h"
- //====================自己加入的头文件===============================
- #include "delay.h"
- #include "led.h"
- #include "encoder.h"
- #include "usart3.h"
- #include "timer.h"
- #include "pwm.h"
- #include "pid.h"
- #include "motor.h"
- #include <stdio.h>
- /*===================================================================
- 程序功能:直流减速电机的速度闭环控制测试
- 程序编写:公众号:小白学移动机器人
- 其他 :如果对代码有任何疑问,可以私信小编,一定会回复的。
- =====================================================================
- ------------------关注公众号,获得更多有趣的分享---------------------
- ===================================================================*/
- int leftSpeedNow =0;
- int rightSpeedNow =0;
- int leftSpeeSet = -300;//mm/s
- int rightSpeedSet = -300;//mm/s
- int main(void)
- {
- GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable,ENABLE);
- GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//禁用JTAG 启用 SWD
-
- MY_NVIC_PriorityGroupConfig(2); //=====设置中断分组
-
- delay_init(); //=====延时函数初始化
- LED_Init(); //=====LED初始化 程序灯
-
- usart3_init(9600); //=====串口3初始化 蓝牙 发送调试信息
- Encoder_Init_TIM2(); //=====初始化编码器1接口
- Encoder_Init_TIM4(); //=====初始化编码器2接口
-
- Motor_Init(7199,0); //=====初始化PWM 10KHZ,用于驱动电机 如需初始化驱动器接口
-
- TIM3_Int_Init(50-1,7200-1); //=====定时器初始化 5ms一次中断
- PID_Init(); //=====PID参数初始化
-
- while(1)
- {
- //给速度设定值和实时值赋值
- pid_Task_Letf.speedSet = leftSpeeSet;
- pid_Task_Right.speedSet = rightSpeedSet;
- pid_Task_Letf.speedNow = leftSpeedNow;
- pid_Task_Right.speedNow = rightSpeedNow;
-
- //执行PID控制函数
- Pid_Ctrl(&motorLeft,&motorRight);
-
- //根据PID计算的PWM数据进行设置PWM
- Set_Pwm(motorLeft,motorRight);
-
- //打印速度
- printf("%d,%d\r\n",leftSpeedNow,rightSpeedNow);
- }
- }
- //5ms 定时器中断服务函数
- void TIM3_IRQHandler(void) //TIM3中断
- {
- if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否
- {
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位
-
- Get_Motor_Speed(&leftSpeedNow,&rightSpeedNow);//计算电机速度
-
- Led_Flash(100); //程序闪烁灯
- }
- }
复制代码
2.3.4 总结
以上三篇内容,是关于直流减速电机的PWM控制、速度测量以及最后电机速度的闭环控制。现在对于电机的简单控制基本告一段落,对于做一个ROS小车的电机控制,这里基本是足够的。下面我们会介绍使用IIC+DMP获取MPU6050数据。
|