一、背景
上一篇实现了小车运动控制,在程序框架下,基于FreeRTOS编写了驱动任务,包含电机控制、测速、PID调速、行走距离控制等和运动相关的功能。但 PID 调速只是完成了算法,并未真正实现调速,因为 PID 调速的核心是PID 参数的确定,即整定!而整定是 PID 最富挑战的内容,调速效果的好坏完全取决于参数。
而参数的整定一直困扰着实施者,早期全是靠经验,后来有人总结出一套方法,一直有人在尝试通过单片机程序自动实现参数的整定,Arduino 的分享库中就有一个,本篇就是想尝试应用此库实现小车的 PID 调速参数整定。
二、需求
这一步的需求是:
1)消化PID参数自整定库,搞清楚如何使用,应该注意些什么。
2)利用PID自整定库确定PID参数。
3)通过测试、完善小车的调速性能。
三、准备
我是学自动控制的,PID 参数整定一直困扰着我。
后来玩小车,在直流电机调速上再次遇到这个问题。在尝试使用 Arduino 的初期,我只是使用了 PID 库,参数整定还是按照传统方法:先比例后积分的模式手工完成;只是在程序上做了点辅助工具,可以将速度变化在PC 机上谱图,直观看到参数改变的效果。
后来发现自整定库时,正好在做别的事情,就没去尝试。
前两年遇到一个大学生朋友,交流时推荐他尝试一下,后来他告知我完成了,并且写了相应的文章分享。当时有事忙未去看,这次自己准备做时,先在网上查阅相关资料,正好在 CSDN 上看到了他的分享:
https://blog.csdn.net/yanggg1997/article/details/76674986
很有帮助。同时还找到了 PID 自整定库的相关说明:
http://brettbeauregard.com/blog/2012/01/arduino-pid-autotune-library/
以及爱好者将其翻译后分享的内容:
https://blog.csdn.net/foxclever/article/details/102645642
这些对理解、消化、正确使用 PID 自整定库有很大帮助。
但安装 PID 整定库后(这个库在 Arduino 的界面上好像没有,自己从Git上下载,拷贝到 Arduino 的库安装目录下即可),发现例子只有一个,而且没有注释,有点难以理解。
只好看源代码,好在有前面那几篇文章解释的原理,代码还能理解。这就是开源的好处!
关于自整定的原理,此处不再赘述,只贴一张作者的原图示意:
自整定工作原理就是通过输出控制人为形成输入参数震荡,测量输入值震荡的峰值、周期,结合控制值的变化,计算得到 PID 参数,如下表所示:
实际的输入信号没有上图中那么理想,会叠加一定噪声,有时噪声的幅度会导致输出控制误判,为此作者设计了类似于施密特触发器的回差逻辑,即参数中的 NoiseBand ,作用原理如下(作者原图):
通过改变判断门槛消除了输入噪声对控制的影响,但噪声同样会影响峰值的确定,作者通过回溯一段时间的所有峰值,以确定哪个是真正的峰值,原理如下(作者原图):
图中蓝色为可能的最大值,橙色为可能的最小值,紫色为经过回溯确定的真实峰值。具体算法可以阅读库程序。
通过阅读代码,总结了以下几点使用时的注意事项:
1、首先确定要调节对象的输入(input)、输出(output)数值范围,从而确定合适的输入控制方向转换判断值(setpoint)、回差值(NoiseBand),以及对应的输出中值(outputStart)、输出高低变化值(oStep)。
以目前要尝试的小车对象为例:
输入值为:速度,范围大约是 0 ~ 650 mm/s
输出值为:PWM,按目前程序设计,为 0 ~ 100,为避免控制饱和,无法真正获取峰值,将范围控制在 20 ~ 80 或更小。
为确定上述参数合适数值,应该先测试不同 PWM 下对应的速度,至少测试 30、50、70 三个 PWM 对应的速度。
可将 50 作为 outputStart, 20 作为oStep,这样震荡驱动为30、70;
(最高速度 - 最低速度)/2 作为setpoint;
(最高速度 - 最低速度)/10 作为Noiseband 。
2、setpoint、outputStart是在启动自整定过程的第一次初始化的,通过启动时的 input 、output 隐含确定,编程时最好通过显性方式给 input 、output 赋值。
其余参数有设置函数,比较容易理解。
3、注意:这个库算法是默认输出控制值和输入值成正比的,即计算后的输出增加,控制产生的输入也增加,如果你的控制对象不是如此,需要修改库函数中的相应计算!相关代码如下:
4、库函数中使用了 millis() 函数确定是否计算,所以必须保证函数返回的是 1ms,在 RTOS 中不能因修改Tick 频率而改变!如下程序,在系统时间间隔小于采样周期时,不计算。
PID 库也是同样,利用了 millis() 确定计算间隔!所以我需要修改程序,将Tick频率恢复为1000Hz,目前程序是 200Hz。要做相应修改!
5、计算周期是 sampleTime ,此值是隐性初始化的,在函数SetLookbackSec() 中确定,此函数确定回溯峰值的时间,最小值 1 秒。
如设置值小于 25 秒,对应 sampleTime 为 250ms,
回查次数为:时间 * 4
如设置值大于等于 25 秒,对应 sampleTime 为:时间*10 ms,
回查次数为:100
代码如下:
虽然设置函数限制最小值是 1 秒,但程序中对由设置产生的 nLookback 值有限制,必须大于 9 ,否则自整定总是返回 0 :
因此,如果直接使用此库,回溯时间必须大于等于 3!
按上述原理介绍所述,回溯机制是为了消除输入数据波动产生的假峰值,那么这个时间的确定是需要有点技巧的,应该是于约震荡的半周期、小于一个周期,小于半周期或许会找不到真正的峰值,大于一个周期,不清楚具体有什么结果,或许会多找出峰值。
从阅读理解情况看,这个库如果注意上述几点,应该可以使用,不清楚我的那个朋友具体遇到了哪些问题,导致其放弃库而自行编程实现,从文章的描述看,可能是第一点没有做,导致输入值、输出值、判定值和实际对象不匹配。
此外,自整定库内存占用不少,为了实现回溯峰值,设置了 101 单元的 Double 数组,以及很多的 Double 变量,估计要占用 1000 字节左右,使用STM32F103C8 估计问题不大,使用 Atmega328 有点勉强,我这个朋友是不是因此而失败?
希望我的尝试可以成功,至少能帮其它想尝试的朋友趟一条路。
四、设计
前面将电机相关的操作均归纳在驱动任务中,因为参数整定本身就是驱动任务的一部分,故继续放置在驱动任务中。
通过串口操作命令启动、停止自整定过程。
为了可以比较清晰的观察自整定过程的工作过程,拟实现速度和对应的PWM 采集和输出,用 PC 端程序谱图,这样,即便是“死”也“死个明白”。
测速周期为100ms,每秒只有 10 次数据,并不快,考虑每秒读一次,单片机机中设置 32 次环形缓冲区,存放速度和对应的PWM;通过存、取数指针控制读取,从而获得连续的速度和PWM变化曲线。
自整定过程和 PID 过程类似,在循环中定时调用计算函数完成。
首先修改程序中的 Tick 频率,以还原 millis() 函数的计数单位,因为似乎很多 Arduino 的库都会使用 millis() 函数,如果不改,后续学习过程中还会踩坑。
但任务内的 Tick 唤醒仍然使用 5ms 触发一次,在Tick 中断中计数5次后产生唤醒信号。这样修改后,应该会提升程序的响应时间,因为RTOS内部的任务切换速度提高了,但愿 MCU 能应付这么频繁的任务调度处理。
五、实施
调速参数自整定应该只需要一次处理一侧电机,无需两侧同时操作。
根据目前程序架构,设计如下操作命令。
5.1 PID自整定增加的操作命令
命令8:启动、停止 PID 参数自整定过程(key = 8, len = 9, Value:1字节电机ID 1字节PID模式 2字节输入判断中值 1字节回差1字节PWM 中值,1 字节 PWM 摆幅, 1字节回溯时间)
0x08 0x09 0x00 1字节电机ID 1字节PID模式 2字节输入判断中值 2字节回差 1字节PWM 中值,1 字节 PWM 摆幅, 1字节回溯时间
其中:
电机ID — 说明整定哪一侧电机,左侧 1, 右侧 2,为 0 则停止当前的自整定过程。
PID模式 — 确定输出结果是 PI 参数还是 PID 参数,0 - PI,1 – PID
输入判断中值 — 控制方向转换判断阈值中点,此处为电机速度值,单位 mm/s
回差 — 消除速度噪声用,实际的判断阈值为:中值 +/-回差,单位 mm/s
PWM 中值 — 控制输出值的中值,0 ~ 100,
PWM 摆幅 — 整定过程中,输出值在两个值之间变化,分别为 PWM中值 + PWM摆幅、PWM中值 - PWM摆幅。
回溯时间 — 确定峰值的回溯时间,单位:秒
应答内容:(暂时还是返回状态)
0x08 0x0E 0x00 左侧电机PWM(2字节) 右侧电机PWM(2字节)左侧剩余运行时间(2字节)右侧剩余运行时间(2字节) 左侧供电电压(1字节)右侧供电电压(1字节)左侧供电电流(2字节)右侧供电电流(2字节)
命令9:读整定结果(Key = 9, Len = 0)
0x09 0x00 0x00
应答内容:
0x09 0x28 0x00 4字节KpL 4字节KiL 4字节KdL 4字节KuL 4字节PuL 4字节KpR 4字节KiR 4字节KdR 4字节KuR 4字节PuR
使用3位小数的定点数返回,即:将浮点数*1000后取整。
命令10:读取速度及PWM(key = 10, len = 0)
0x0A 0x00 0x00
应答内容:
0x0A 0x?? 0x00 N 组数据(速度 2 字节、PWM 1 字节)
最多一次返回 32 组!
5.2 编写代码
因为原来的程序框架对命令的响应是用的 switch …… case …… 方式,所以很容易增加。
功能上增加了速度记录,设计了32单元的环形缓冲区,对应记录每次的测速值,同时设计了32单元的 PWM记录,以便对应速度的变化。这是为了能在PC机上画出速度及对应的PWM变化图,从而了解自整定的执行过程,以及自整定后,调速的效果。
因为此功能是调试使用,考虑到内存空间消耗,只设计了一路,即做速度及PWM记录时,只能是一侧电机运转,作为调试应该能满足。
对应的 PC 端程序 也增加了相应的功能代码。
详细如何实现的可以下载完整代码,慢慢琢磨,此处不再赘述。
完成代码后,首先测试不同 PWM 下实际速度值,利用 PC 图形显示。
通过图形发现,原来的测速算法有Bug,速度有非正常的波动。为此,重新编写了脉冲周期测量方式,原来是以前一个脉冲作为倍频的基础,因电机转动所涉及的机构比较简单,阻力会有变化,故速度变化较大,用一个脉冲的周期难免会有偏差。
现改为测量测速周期内的所有脉冲的周期,取其平均值作为倍频的基础。
将原来的在脉冲中断中计算周期改为到速度计算时处理,脉冲中断只记录对应的系统时间,设置16个环形缓冲,按目前码盘及电机转速水平,100ms 最多采集17个脉冲,取最近的16个作为平均值计算可以满足要求。
这样改变后,速度值的变化基本符合小车物理特性了:
根据PWM对应的速度特性,选择 50 作为中值,摆动幅度 20,即PWM 在自整定过程中的变化为 30、70。对应速度判断值选择330mm/s,回差30mm/s,判断线为360、300,回溯时间3秒。
启动自整定过程,电机是按期望的方式运行了,但一直在震荡,只好发命令停止。记录的波形如下:
从上图可以看出,目前的测速周期是 100ms(图中一个点对应一次记录),一个震荡周期大约1秒,只有10点数据。而按自整定库的算法,每250ms采样一次,回查12点(3*4),要均小于或大于当前值,才能确定为峰值,12点已经覆盖了完整一个波了,所以根本无法找到符合算法要求的峰值,也就无法停止了。
从此可以看出一个问题:
测速周期及自整定采样周期必须和对象的特性相匹配!
按目前对象的特性,100ms测速周期明显偏大,需要缩小。但考虑到码盘的分辨率及倍频方式,太小无法实现,故按以往经验,将测速周期改为 20ms,这样如果按上述 1 秒的周期,可以有近 50 点的数据。同时也要修改自整定库的采样时间及回查点数,以便和对象特征相配。
首先实现20ms测速。
因为码盘分辨率问题,20ms能记录的脉冲数在低速时只有1~2个,将会带来很大的计算误差,为此将测速算法改为滑窗式,仍然沿用100ms的测速周期,只是设置了5个脉冲计数器,错位20ms启动,这样可以得到20ms的速度刷新率,同时还起到了滤波的作用(详细实现方式见所附代码,代码中有详细注释,此处不再详述)。
修改后的20ms测速结果:
自整定库的修改主要涉及采样时间、回查次数两个关键数据。但修改的前提是对原来的代码有更清晰的了解,原作者编写时的构思很巧妙。
为了能彻底吃透并完善自整定库,干脆将它拷贝到我的源程序目录中,作为程序的一个模块。首先逐条理解消化原来的程序代码,并加上注释。
通过详细阅读代码,应该说比较透彻的理解了作者的构思,并发现了一些问题,基于我目前的需求,以及对程序中自整定算法的理解,作了如下修改:
1)修改程序中采样时间及回查次数的设置,将采样时间最小值从250ms缩小到20ms;回查次数范围 6 ~ 50次,减少对应的存放单元(注:因实际测试后发现周期只有300ms左右,一周期才15点,故将最小次数改为6次)。
2)将原来的 Double 变量根据数据特性改为 int 或 float,减少内存占用,并提高运行速度。
3)强制采集 12 次峰值(6峰6谷)后再执行计算。
4)将原来取整个整定过程中的最大、最小值作为 Ku 的计算依据,改为取 12 次采集中的后 9次峰峰值的平均值计算,头两次担心震荡不稳定不用。因为对象特征没有那么理想,会有一些波动,故用平均值可以更真实的反应对象特征。
5)原来程序确定 Pu 的峰值周期用的是最后两次的值,改为用 12 次峰值采集得到的 6 次周期的后 5 次平均值,道理同上。
6)将计算结果函数取消了,直接写在 Runtime 函数中,因为修改后只有一处计算结果,无需函数。
修改后的变量定义:
修改后的回溯时间设置函数:
这个函数应该根据对象实际情况加以调整,采样时间最好和输入值测量周期相同,回查次数不要大约整个周期的一半,假设此值为 N,它的作用是:峰值必须满足大于或小于前 N 次采集的输入,如果太少,容易将无效的误判,太多则找不到了,比如说点数大于一周,则不可能满足峰值条件。
此外库中最核心的就是计算函数 Runtime(),它包含几个部分:
1)采样时间间隔保护,这个其实可以通过外部调用控制实现。
2)首次进入的初始化:
3)输出值控制,这个比较简单,就是通过变换输出高低值,促使对象产生的输入值震荡,从而得到重复的对象阶跃响应特征,幅值关系反映了比例关系,周期反映了对象的时间常数。
4)有效峰值确定,这部分编写的很有技巧,也是最难理解的部分:
5)保存真实峰值、计算峰值周期,这部分就是我修改的主要部分:
6)结果计算,这部分改为采满12次数据后再计算:
5.3 尝试 PID 参数自整定
完成代码编写后,开始尝试自整定。
调试环境:
首先用 PWM 输出确定自整定的输入判断值,分别测试 PWM30、50、70对应的速度:
基于上述速度数据确定自整定参数:
启动自整定过程,速度和PWM波形如下:
计算结果:(从结果可以看出,前面由于采样周期长,采样点少,所得的的周期为 1秒,实际是300ms左右,已经明显失真了)
基于此结果的调速运行情况:
图中红色曲线为速度,红色直线为设定值,从图中看PID是起到作用了,没有进行任何优化,直接使用的自整定结果。
速度波动有几方面原因:
1)本身结构简单,转动阻力变化所导致
2)码盘分辨率偏低,测速波动导致
3)供电问题,这个应该是最主要的,小车供电是用的4节7号镍氢充电电池,逻辑电路部分是通过DC-DC升压到5V后,再降低到3.3V供电,电机是直接使用电池电压,但由于所用电池放置了快10年,内阻太大,激活了几次后勉强能用,但还是一带负载电压就跌落(短路电流只有约1.5A),无奈只好加了一块DC-DC升压模块,升压到5V,这才勉强工作,但转几次就稳不住了。
这一步的目的是尝试 PID 自整定,并非是完善小车的调速性能,故先不管它。
其实用于学习的平台性能不要太好,一是贵,二是反而会掩盖一些问题,要把不理想的对象做好,就需要动更多脑筋,从而学到更多内容。
从上述结果看,这种自整定方式是可行的!
否则,如此特殊的参数,从何处下手才能接近?我相信,在此基础上再做优化会容易很多。
六、结语
通过这次尝试,发现只有沉下心来深究,才会有所收获。
以前很多时候使用别人分享的内容,都是浅尝即止,有时效果不好,有时勉强能用,像是在碰运气。
从学习角度考虑,这样肯定不行!
对于分享的内容,应该本着消化、吸收的态度,将其作为示例,给自己以启发,而不是拿来主义。
而且应消化、完善后,再次分享,以求不断完善,这才是开源的价值所在。
希望我的这次改动,能起到抛砖引玉的作用,让自整定方式更接近实用。
————————————
文中程序下载:
链接:https://pan.baidu.com/s/1_718ALk7mD0_Ecytr5_WFQ
提取码:ya24文章来源:https://www.toymoban.com/news/detail-400605.html
文章来源地址https://www.toymoban.com/news/detail-400605.html
到了这里,关于掌上单片机实验室 – 实现PID自整定(11)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!