admin管理员组

文章数量:1569941

进一步理解PID算法

引言

在【满满干货,机器人电机测速和PID控制看这篇就够了!】一文中,我们介绍了使用增量式PID来控制车轮转速的方法,但限于篇幅,在该文章中未对控制方案的选择和编程实现的方法进行展开介绍,本文主要对这两部分内容进行介绍,希望能让大家对PID算法的应用有更深入的理解。

一、再谈PID控制

对于PID结构体,我们新增了三个成员变量P_part 、I_part 和D_part ,分别存储比例、积分和微分三部分的运算结果,方便调试时进行观察和分析。

typedef struct
{
	double Kp; 
	double Ki;
	double Kd;
	
	double error;		//偏差
	double last_error;  //上一次偏差
	double prev_error;  //上上次偏差
	
	double P_part;
	double I_part;
	double D_part;
	
	double pwm_added;	//计算出的PWM,最后给到电机时需强制转换为int型
	int limit_pwm;   	//限制的PWM幅值
}PID;
/**
*  @brief  由期望值与当前值,计算应输出的PWM值  
*  @param  targetValue:期望值, currentValue:当前值, p:PID的结构体指针
*/
void PID_Cal(double targetValue, double currentValue, PID *p)
{
	p->error = targetValue - currentValue;
	
	//根据增量PID公式计算应输出的PWM值
	p->P_part = p->Kp*(p->error - p->last_error);
	p->I_part = p->Ki*p->error;
	p->D_part = p->Kd*(p->error - 2*p->last_error + p->prev_error);
	p->pwm_added += p->P_part + p->I_part + p->D_part;   

	p->prev_error = p->last_error;  //更新上次误差
	p->last_error = p->error;    	//更新本次误差

	//对输出进行限幅
	if(p->pwm_added > p->limit_pwm) 	
        p->pwm_added = p->limit_pwm;		
	if(p->pwm_added < -(p->limit_pwm)) 
        p->pwm_added = -(p->limit_pwm);
}

对于PID计算函数PID_Cal() 来说,输入的是数字,计算结果(pwm_added)也是数字。而我们想让它的计算结果的物理意义是控制电机的PWM信号。

首先,我们需要认识到:期望值是什么,取决于反馈值是什么:根本原因是因为我们使用了这个物理量作为反馈值,而期望值需要与反馈值做差才有物理意义。

回顾在【满满干货,机器人电机测速和PID控制看这篇就够了!】一文中我们使用PID_Cal() 函数时的情形,我们当时使用速度作为反馈,函数的前两个参数——期望值和反馈值的物理意义都是速度。因此,PID_Cal() 其实是使用速度的误差值进行计算,其计算结果本来的物理意义自然也就是“应输出的速度值”,而非我们想要的“应输出的PWM值”。不过,由于它归根结底就是个数字,在调整出合适的PID系数Kp、Ki和Kd后,我们是可以用它来直接作为PWM值输出,达到期望的控制效果(快稳准地到达期望车轮转速)的。

但是,当速度的取值范围和PWM值的取值范围相差较大时(比如在本例中,速度的取值范围约为050cm/s,而PWM值的取值范围为01000,相差了20倍),在PID调参的时候会发现参数的值(尤其是比例系数Kp)会偏大,调参过程的盲目性也更高,花费的时间会更长。

空口无凭,我们用数据来说话。

设定目标速度为30cm/s,分别比较PWM波频率为18kHz、PWM值的取值范围为1000PWM波频率为10kHz、PWM值的取值范围为100 这两种情况下调出的PID参数。

①PWM波频率为18kHz、PWM值的取值范围为1000

​ PID参数:Kp=21,Ki=15,Kd=12.5

​ 调节时间为(1.312 - 0.912 =) 0.4s

②PWM波频率为10kHz、PWM值的取值范围为100

​ PID参数:Kp=2.625,Ki=2.25,Kd=0.5

​ 调节时间为0.484s(1.521-1.037=0.484)

两组参数(Kp=21,Ki=15,Kd=12.5 和Kp=2.625,Ki=2.25,Kd=0.5)差别很大,难以看出有什么联系。

二、先建立映射关系的PID控制实施方案

在上面我们使用的PID控制方案中,我们用直接用速度作为反馈,让PID计算函数根据速度的误差进行计算,并直接用计算结果作为控制电机的PWM输出、在其基础上进行调参,暂时忽略了计算结果本来的物理意义是“应输出的速度值”而非“应输出的PWM值”这一个不影响实现PID控制的客观事实。

那么,我们是否可以先建立一个速度到PWM的映射关系,把速度从自己的取值范围映射到PWM的取值范围上,然后用由“当前速度”和“期望速度”映射得到的“当前PWM值”和“期望PWM值”来作为PID计算函数的输入呢?这样不仅计算结果的物理意义就直接是“应输出的PWM值”,而且函数的输入和计算结果的取值范围还是相同的,岂不是两全其美?

从理论上进行分析,这个思路当然是可行的,那么,下面我们就通过实践来检验一下这种PID控制实施方案的实际效果如何吧!

1. 建立速度到PWM的映射关系

要实现这个映射并不复杂,我们只需要让速度先除以满占空比时的车轮转速,再乘上满占空比时的PWM值就可以了,整个过程相当于乘一个系数。

满占空比时的PWM值是我们自己设置的已知值,而满占空比时的车轮转速需要我们测算。

使用Function Editor 功能批量收集速度值

在Keil的调试模式中,我们可以通过Watch Windows功能来观察全局变量数值的实时变化,但却无法把这个值的变化过程给批量复制出来作更细致的分析(另外,这些变量的数值其实并非精确的实时更新,而反映的是在窗口右下方的t变化时对变量的数值进行采样的结果)。

这里我们介绍调试模式的另一个强大功能,Function Editor,我们可以通过它来生成可以打印变量的命令。具体操作步骤如下:

① 在代码未处于运行状态下点击Function Editor 选项

② 导入自己预先写好的ini功能文件

ini功能文件创建方法:

先新建一个文本文档,把后缀名由ini改为txt后,将以下内容粘贴进去即可:

FUNC void displayvalues(void) {

int i;

exec("log > MyValues.log");

for(i=0;i<2000;i++) {

printf ("{leftSpeed}%lf\n", g_c_leftSpeed);
printf ("{rightSpeed}%lf\n", g_c_rightSpeed);

}

exec("log off");

}

从代码中可以看出是打印变量g_c_leftSpeed和g_c_rightSpeed的值。

③ 点击Compile编译并导入命令

④ 确认是否编译成功

同时,在下方的Command选项卡中可以看到导入的信息

⑤ 在Command选项卡的命令输入口输入函数名displayvalues()

然后按下回车键,就可以看到我们想要的数据被打印出来啦

选中数据,快捷键Ctrl+A全选、Ctrl+C复制

再粘贴到Excel表中,准备求平均值

使用Excel 提取速度数据,求平均值

具体步骤如下:

①. 删除非数据部分

在上方插入1行(不然在下一步使用筛选功能的时候会出问题,因为筛选功能是从下一行开始,不包括本行)

② 使用数据选项中的筛选功能,先筛选出左轮的数据

先点击A列的标题,选中整列的数据。然后选择导航栏中数据选项的筛选功能。

点击A1格新出现的小三角图标,在弹出的筛选窗口中内容筛选的输入栏里输入筛选关键词leftSpeed ,然后勾选“全选”

将筛选出来的数据复制到另一列

③ 使用数据栏中的分列功能,将数据提取出来

用 } 作为分隔符号

左轮速度数据提取完成!

④ 求出左轮最大速度的平均值

按顺序依次选择


最后,选中第I列,再按下回车键即可求出左轮速度的平均值

精度我们取到小数点后3位,即46.823cm/s

⑤ 求出右轮最大速度的平均值

先点击A列的绿色筛选图标,在弹出的筛选窗口中点击反选→确定


之后与前面第②~④步操作类似,可求出右轮最大速度的平均值约为45.578cm/s

使用宏定义来确定速度到PWM值的映射系数
#define	MAX_PWM			100		 //满占空比时的PWM值

#define	LEFT_MAX_SPEED	46.823   //满占空比时的速度,单位:cm/s
#define	RIGHT_MAX_SPEED	45.578

#define LEFT_SPEED_TO_PWM	MAX_PWM/LEFT_MAX_SPEED	//速度到PWM值的映射系数
#define	RIGHT_SPEED_TO_PWM	MAX_PWM/RIGHT_MAX_SPEED

这样一来,我们让左右轮的速度值乘上对应的系数LEFT_SPEED_TO_PWM 或RIGHT_SPEED_TO_PWM 就可以完成速度到PWM值的映射啦!

2. 先建立映射后调出的PID参数

(考虑到篇幅关系,我们仅对第①种条件下的调参过程进行详细介绍,其中图片部分仅展示左轮的速度曲线,右轮的速度曲线大致相同)

① PWM波频率为18kHz、PWM值的取值范围为1000
(1)P调节(纯比例调节):
  • Kp=1

可以看到离目标速度30cm/s差距较大,因此需要增加比例系数Kp的值。

  • Kp=1.5
    可以看到当前的速度有所提高,离目标速度更接近了。

  • Kp=2

发现离目标速度30cm/s差距又变大了,所以Kp还是取1.5比较合适。

我们将1.5乘上0.7得到新的Kp=1.05,并加入Ki参与调节(将纯比例调节的比例系数乘0.7再加入积分或微分调节是一种行之有效的经验做法)。

(2)PI调节(比例积分调节):
  • Kp=1.05,Ki=0.25
    可以看到加入积分调节后,速度值就能够达到目标值30cm/s了,但是调节时间(速度从0cm/s增加到30cm/s的时间)较长(2.699 - 1.138 =1.561s,>1s)。我们通过增大Ki来缩短调节时间。

  • Kp=1.05,Ki=0.75

在这组PID参数下,调节时间降到了0.361s (1.232 - 0.871 =0.361)< 0.5s,比较理想

通过放大观察,可以看到系统达到稳态后的波动幅度(称为稳态误差)约为目标值的1%(0.3cm/s),相当理想(工程上一般认为当波动幅度<5%时系统视作到达稳态,对于要求较高的系统,则要求波动幅度<2%)

同时,我们注意到速度变化曲线出现了一个略大于目标值的7%(2.1cm/s)但<10%的小超调,下面我们通过加入微分调节来消除它,让速度变化更加平滑

(3)PID调节:
  • Kp=1.05,Ki=0.75,Kd=0.25

可以看到加入D调节后,原本超调的部分被很好地抑制了

调节时间为0.485s(1.165 - 0.68 =0.485)

  • PID参数:Kp=1.05,Ki=0.75,Kd=0.5

增大Kd,调节时间变为0.442s(1.474 - 1.032 =0.442)

可以看到Kd增大后,调节时间虽然有略微的改善,但也使系统出现了更多的振荡;而Kd如果取得过小又不足以抑制系统的超调。所以从系统的整体性能角度考虑,Kd还是取0.25最合适。

因此,最终得到的左轮PID参数为:Kp=1.05,Ki=0.75,Kd=0.25。

右轮的调参方式与左轮相同,最终得到的PID参数为:Kp=1.05,Ki=0.75,Kd=0.5,与左轮的大致相同。

② PWM波频率为10kHz、PWM值的取值范围为100

PID参数:Kp=1.05,Ki=0.75,Kd=0.25

调节时间为0.48s(1.371 - 0.891 =0.48)

可以发现:对于左轮转速,即便PWM波频率和PWM值的取值范围发生了变化,PWM波频率为18kHz、PWM值的取值范围为1000PWM波频率为10kHz、PWM值的取值范围为100 这两种情况下调出的参数却都是Kp=1.05,Ki=0.75,Kd=0.5;且三个系数在数值上都不大(<2),调参时不会出现先从5换成15再换成30这种跨度很大的情况,试参数时盲目性降低了很多~~(对于程序员的精神状态十分友好)~~ 。

三、落地测试

为了方便调参,前文所述的PID控制针对的现实场景是把小车架空,让轮子在空中飞转的情形调出来的参数,而小车当然是要放到地上跑的,让我们来看看我们调出来的参数的落地表现如何吧!

将小车架空进行调参时,我们可以使用以CH340为代表的有线USB转串口模块来将我们电机控制板上的串口引脚连接到电脑(上位机),有线串口模块的优点在于即插即用,使用便捷;但是,如果我们在小车落地测试时还使用有线串口模块的话,那显然就不合适了——受数据传输线的长度所限,小车只能在很短的距离内跑动才能收集到数据,那么应该怎么解决这个问题呢?其实说难也不难,问题的根源在哪里,我们就从哪里出发进行思考:既然是数据传输线的存在限制了我们收集数据,那么我们就干脆不使用数据传输线来传数据,改为使用无线的蓝牙串口模块。

下面我们先介绍一款常用的蓝牙串口模块:HC-05

蓝牙串口模块HC-05

这是一款主从一体的蓝牙串口模块,由于它把有关蓝牙的部分给封装起来了,所以用户基本不需要具备蓝牙相关的专业知识,只要知道串口怎么编程使用,就可以实现透明传输,上手门槛低,使用方便。

进行嵌入式开发,我们需要养成一个良好的工作习惯:接触一个新的模块时,先查阅它的用户手册(User Manual)、数据手册(Datasheet)等相关技术资料,了解其基本信息和关键参数再上手操作,正所谓磨刀不误砍柴工,这是我们进行开发的硬件基础。相反,如果不重视这一步,想当然地套用自己以往使用其他外设的经验,如果你足够幸运、不出问题还好,一旦出问题,轻者出现各种玄学现象,逐个排查下来才发现是自己使用硬件的方法不对,白白浪费大量时间精力~~(笔者血的教训)~~;重者硬件报废,直接重头再来💀

1. 工作模式

通过阅读HC-05模块的规格书和AT指令集,我们了解到的主要信息如下:

它有两种工作模式:命令响应工作模式自动连接工作模式

  • 当模块处于命令响应工作模式(或称AT模式)时能才能执行AT命令,用户可向模块发送各种AT指令,为模块设定控制参数或发布控制命令。
    • AT指令简介:
      • AT指令是我们的PC与一些终端设备(例如蓝牙、WiFi、蜂窝等模块)之间进行通信的,配置这些终端设备参数的一套指令。
      • 其一般格式为以AT作首,字符(常为回车(\r,常表示为<CR>)或回车、换行(\r\n,常表示为<CR><LF>)结束的字符串。每个AT命令行中只包含一条AT指令。
      • 基于行业标准,物联网设备厂家会针对自己的产品制订专属的AT指令集,因此,在使用通过AT指令进行通信和配置的模块时,我们必须在官方提供的AT指令集的指导下进行开发。
    • 对于HC-05模块,其AT指令不区分大小写,且均以回车、换行字符结尾(\r\n),部分AT指令需要对34号脚(PIO11引脚,即EN引脚)一直置高才有效
  • 当模块处于自动连接工作模式时,将自动根据事先设定的方式连接的数据传输。在自动连接工作模式下,模块又可分为(Master)、(Slave)和回环(Loopback)三种工作角色。
    • 主角色:模块可以主动搜索并连接其它蓝牙模块并进行发送和接收数据。
    • 从角色:被动连接,只能被其它蓝牙模块搜索、连接,从而进行接收和发送数据。
    • 回环角色:被动连接,接收远程蓝牙主设备数据并将数据原样返回给远程蓝牙主设备。

那么,应该如何切换模块的工作模式呢?

模块的PIO11(即EN引脚,引脚号为34)为状态切换脚,置高时为AT命令响应工作状态,此时上电后模块上的LED灯表现为长间隔亮灭;低电平或悬空时为蓝牙常规工作状态,上电后模块上的LED灯表现为快速闪烁

向模块发送AT指令有两种方法:

  1. 模块上电,且未配对情况下,波特率为模块本身的波特率,默认为9600,发送一次AT指令时需要置高一次PIO11;
  2. 在PIO11 置高电平时,给模块上电,此时模块进入AT模式,波特率固定为38400,可以直接向其发送AT指令。

“置高一次PIO11”是什么意思呢?

在蓝牙模块的底板上有一个小按键,按一下就是置高一次PIO11。也就是说,第一种方法需要每发送一次AT指令按一次按键;而第二种方式是长按按键的过程中(或者给EN引脚接上高电平后)上电,之后就无需再管按键了,直接发送AT命令即可。推荐使用第二种方法。

2. 配置蓝牙模块的参数

首先,除了HC-05蓝牙串口模块,我们还需要准备1个USB转串口模块(比如常用的CH340模块),电脑上需要安装串口调试软件(比如COONEO机器人调试助手)

接线方面,蓝牙串口模块和USB串口模块的GND相接,TXD接另一方的RXD,RXD接另一方的TXD,而对于VCC,通过观察蓝牙串口模块底板背面上的丝印,我们知道它的VCC要求的电压范围为3.6V~6V,那么USB转串口模块上3V3引脚的输出电压显然就不够了,如果你使用的USB转串口模块上有5V输出的引脚自然是最好,蓝牙串口模块上的VCC直接和它相接就可以了;如果你和笔者一样,使用的USB转串口模块都没有5V引脚,那么我们就只能采用“曲线救国”的方案了——比如笔者就是把ST-Link也插到电脑上,让它和两个串口模块共地,然后用ST-Link的5V输出引脚来给蓝牙串口模块供电。办法总比困难多嘛!

在将USB转串口模块插上电脑后,我们需要知道它所在的USB口对应的是我们电脑的哪一个COM端口,我们可以通过查看设备管理器来达到这个目的:

首先用鼠标右键点击屏幕左下角的Windows开始图标,选择设备管理器。

然后在设备管理器界面中点击“端口(COM和LPT)”选项,在展开的详细列表中,我们可以看到USB转串口模块CH340所在的COM端口为COM4。

接着我们就可以通过向蓝牙模块发送AT指令来配置它啦!

先用前面介绍的第二种方法让模块进入命令响应模式,然后打开酷牛机器人串口调试助手,选择COM4(如果展开的COM口菜单里没有COM4,请检查USB转串口模块与电脑的连接是否接触良好,然后点击“刷新串口”,正常情况下再次点击展开COM口菜单里就能看到COM4了;如果还是看不到COM4,可以试试换一个USB口,再用设备管理器查看它对应的COM端口号),选择波特率为38400(命令响应模式的固定波特率),并在发送区和接受区设置里都勾选上自动换行,以方便我们发送AT指令和分辨返回的参数,最后点击打开串口:

常用的AT指令

查阅模块配套的AT指令集后,我们可以知道常用的AT指令如下:

测试指令:

设置/查询—模块角色:

设置/查询—串口参数:

设置/查询—配对码:

模块复位:

恢复默认状态:

知道了这些常用的AT指令后,我们就可以开始进行愉快的参数配置啦!

在发送区中输入测试指令AT,然后点击发送

看到接受区马上出现了OK

接着我们分别发送:

AT+ROLE?

AT+UART?

AT+UART=38400,0,0

AT+UART?

AT+PSWD?

AT+RESET

根据接受区的信息,我们可以知道模块处于自动连接工作模式时为从角色;初始波特率为9600,1位停止位,无校验位,为了方便,我们将其波特率配置为38400,停止位和校验位与默认参数一致;模块的初始配对码为1234,这个我们在后面的蓝牙配对时要用到;最后复位模块,参数配置就完成啦!

3. 手动添加蓝牙COM口

在一台电脑上初次与这款蓝牙串口模块配对之前,由于它会被当成“未知设备”,无法通过常规的添加蓝牙设备方式找到它,所以我们需要先手动为它添加蓝牙COM口,操作步骤如下(以Win10系统为例):

① 点击屏幕右下角通知栏,点击蓝牙选项开启蓝牙,再右键点击蓝牙选项转到蓝牙设置

② 在设置窗口中将右侧位置条拖到最下方,点击“更多蓝牙选项”

③ 在弹出的窗口中依次点击如下选项:



然后我们就可以看到HC-05模块作为串口出现在我们的COM口列表里面啦!最后别忘了点击确定退出蓝牙设置窗口哦

4. 蓝牙配对

请注意,即便我们为HC-05手动添加了蓝牙COM口,我们依然无法通过常规的添加蓝牙设备方式找到它(如下图)

那么,我们应该怎么找到它并进行配对呢?

打开我们的COONEO机器人调试助手,串口号选择我们刚刚手动添加的COM8,波特率选择我们前面设置的38400,然后点击“打开串口”,就能看到桌面右下角弹出来一个设置HC-05的提示

点击它之后就会进入到下面这个输入配对码的界面,填入我们刚才查询到的1234,点击“允许”,然后就能看到配对成功的提示啦!

落地测试结果

(先进行映射处理,PWM波频率18kHz、PWM值的取值范围为1000)

左轮(PID参数为:Kp=1.05,Ki=0.75,Kd=0.25):

可以看到,在进入稳态后,时长超过2min(从上图可知至少为[-195000ms - (-56000ms)]=139000ms=139s)的一段时间内,只有2个点的振荡超过了1cm/s(约为3.33%),说明我们的调出来的这组PID参数还是相对不错的。

今天的教程就到这里!如果你想进一步沟通,欢迎加入我们的交流群,该群面向热爱机器人研发的朋友们,方便大家一起学习、分享、交流智能机器人创造,结识更多志同道合的小伙伴。更有不定期的社区专属福利哦!关注公众号或并对话框发送“入群”,即可获取入群方式。如需完整代码,也可以进群讨论领取哦~


内容:Peckun

排版:WW

本文标签: 进阶机器人策略技巧pid