增量式 PID 算法的 STM32 实现
宋人梁2018-07-20 22:17:2935366收藏369
虽然PID这不是一件好事,但我真诚地希望那些刚接触过这个领域的人能尽快进入这个状态。特别分享一些你自己的过程。 先说增量式PID关系到的公式MCU实际上,两个公式的写法是通过同一个公式来转换的,不同的是系数的差异。 资料较多的是: 另一种算法是: 这里主要介绍第二个,具体分析比例、积分、微分三个环节的作用。 控制系统的控制对象是4个带光电编码器的空心杯直流电机,可以反馈速度波形。电机驱动模块是普通的L298N模块。芯片型号,STM32F103ZET6
PWM输出:TIM3.4路不占空比可直接输出PWM波 PWM捕获:STM32除了TIM6 TIM其余7都有捕获功能,使用TIM1 TIM2 TIM4 TIM54个定时器捕获4个反馈信号 PID的采样和处理:使用了基本定时器TIM6.溢出时间是我的采样周期。理论上,T越小,效果越好。这里我拿20ms,根据控制对象,如果控制水温,采样周期将是几秒钟或几分钟。 上面的PWM定时器的设置有输出和捕获的例程。我在这里是这样的: TIM3输出四路PWM,在引脚 C 的 GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9输出 四路捕获分别是TIM4TIM1TIM2TIM5 ,对应的引脚是:PB7 PE11 PB3 PA1 高级定时器tim1的初始化略有不同,其中断名称不同于通用定时器。请看我分享的代码。程序.zip 主要讲解PID部分
- floatKp = 0.32; ///比例常数
- floatTi = 0.09 ; ///积分时间常数
- float Td = 0.0028 ;///微分时间常数
- #define T 0.02 ///采样周期
- #define Ki Kp*(T/Ti) // Kp Ki Kd 三个主要参数
- #define Kd Kp*(Td/T)
复制代码
- void PIDperiodinit(u16 arr,u16 psc); //PID 采样定时器设定
- void incPIDinit(void); ///初始化,参数清零清零
- int incPIDcalc(PIDtypedef*PIDx,u16 nextpoint); //PID计算
- void PID_setpoint(PIDtypedef*PIDx,u16 setvalue);//设定 PID预期值
- void PID_set(float pp,float ii,float dd);//设定PIDkp ki kd三个参数
- void set_speed(float W1,float W2,float W3,float W4);///设置四个电机的目标转速
复制代码
岔开:这里我控制的是电机的转速w,事实上,电机反馈波形的频率f、电机转速w、控制信号PWM占空比a是大致线性的正比关系。这里强调的目的是 因为楼主一直不明白我前期控制的速度和速度TIM4输出的PWM将占空比联系起来,然后想清楚里面的联系,然后通过公式计算出每个系数。 主题:控制过程是这样的。首先,我设置我需要的速度(对应四个轮子的速度),然后PID它开始响应。首先采样电机转速,得到偏差值E,带入PID计算公式,调整量最终更改PWM不断调整占空比,直到速度在稳态小范围内上下浮动。 上述调整量是增量PID的公式:
- int incPIDcalc(PIDtypedef *PIDx,u16 nextpoint)
- {
- int iError,iincpid;
- iError=PIDx->setpoint-nextpoint;///当前误差
- /*iincpid= ///增量计算
- PIDx->proportion*iError //e[k]项
- -PIDx->integral*PIDx->last_error nbsp; //e[k-1]
- +PIDx->derivative*PIDx->prev_error;//e[k-2]
- */
- iincpid= //增量计算
- PIDx->proportion*(iError-PIDx->last_error)
- +PIDx->integral*iError
- +PIDx->derivative*(iError-2*PIDx->last_error+PIDx->prev_error);
- PIDx->prev_error=PIDx->last_error; //存储误差,便于下次计算
- PIDx->last_error=iError;
- return(iincpid) ;
- }
复制代码
注释掉的是第一种写法,没注释的是第二种以Kp KI kd为系数的写法,实际结果是一样的。 处理过程放在了TIM6,溢出周期时间就是是PID里面采样周期(区分于反馈信号的采样,反馈信号采样是1M的频率) 相关代码:
- void TIM6_IRQHandler(void) // 采样时间到,中断处理函数
- {
- if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)//更新中断
- {
- frequency1=1000000/period_TIM4 ; //通过捕获的波形的周期算出频率
- frequency2=1000000/period_TIM1 ;
- frequency3=1000000/period_TIM2 ;
- frequency4=1000000/period_TIM5 ;
- /********PID1处理**********/
- PID1.sum_error+=(incPIDcalc(&PID1,frequency1)); //计算增量并累加
- pwm1=PID1.sum_error*4.6875 ; //pwm1 代表将要输出PWM的占空比
- frequency1=0; //清零
- period_TIM4=0;
- /********PID2处理**********/
- PID2.sum_error+=(incPIDcalc(&PID2,frequency2)); //计算增量并累加 Y=Y+Y'
- pwm2=PID2.sum_error*4.6875 ; //将要输出PWM的占空比
- frequency2=0;
- period_TIM1=0;
- /********PID3处理**********/
- PID3.sum_error+=(incPIDcalc(&PID3,frequency3)); //常规PID控制
- pwm3=PID3.sum_error*4.6875 ; //将要输出PWM的占空比
- frequency3=0;
- period_TIM2=0;
- /********PID4处理**********/
- PID4.sum_error+=(incPIDcalc(&PID4,frequency4)); //计算增量并累加
- pwm4=PID4.sum_error*4.6875 ; //将要输出PWM的占空比
- frequency4=0;
- period_TIM5=0;
- }
-
TIM_SetCompare(pwm1,pwm2,pwm3,pwm4); //重新设定PWM值
- TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除中断标志位
- }
复制代码
TIM_SetCompare ()函数:
办法有不少,这里用的是先KP,再TI,再TD,在微调。其他的办法特别是有个尼古拉斯法我发现不适合我这个控制对象。 先Kp,就是消除积分和微分部分的影响,这里我纠结过到底是让Ti 等于一个很大的值让Ki=Kp*(T/Ti)里面的KI接近零,还是直接定义KI=0,TI=0. 然后发现前者没法找到KP使系统震荡的临界值,第二个办法可以得到预期的效果:即KP大了会产生震荡,小了会让系统稳定下来,当然这个时候是有稳态误差的。 随后把积分部分加进去,KI=Kp*(T/Ti)这个公式用起来,并且不断调节TI 。TI太大系统稳定时间比较长。 然后加上Kd =Kp*(Td/T),对于系统响应比较滞后的情况效果好像好一些,我这里的电机反映挺快的,所以Td值很小。 最后就是几个参数调节一下,让波形好看一点。这里的波形实际反映的是采集回来的转速值,用STM32的DAC功能输出和转速对应的电压,用示波器采集的。 最后的波形是这样的: 内容转自某莫 当然最后还是要分享一下最终的PID文件 pid.zip
最近两天在考虑一般控制算法的C语言实现问题,发现网络上尚没有一套完整的比较体系的讲解。于是总结了几天,整理一套思路分享给大家。
在工业应用中PID及其衍生算法是应用最广泛的算法之一,是当之无愧的万能算法,如果能够熟练掌握PID算法的设计与实现过程,对于一般的研发人员来讲,应该是足够应对一般研发问题了,而难能可贵的是,在我所接触的控制算法当中,PID控制算法又是最简单,最能体现反馈思想的控制算法,可谓经典中的经典。经典的未必是复杂的,经典的东西常常是简单的,而且是最简单的,想想牛顿的力学三大定律吧,想想爱因斯坦的质能方程吧,何等的简单!简单的不是原始的,简单的也不是落后的,简单到了美的程度。先看看PID算法的一般形式:
PID的流程简单到了不能再简单的程度,通过误差信号控制被控量,而控制器本身就是比例、积分、微分三个环节的加和。这里我们规定(在t时刻):
1.输入量为rin(t);
2.输出量为rout(t);
3.偏差量为err(t)=rin(t)-rout(t);
pid的控制规律为
理解一下这个公式,主要从下面几个问题着手,为了便于理解,把控制环境具体一下:
1.规定这个流程是用来为直流电机调速的;
2.输入量rin(t)为电机转速预定值;
3.输出量rout(t)为电机转速实际值;
4.执行器为直流电机;
6.直流电机采用PWM调速 转速用单位 表示;
不难看出以下结论:
1.输入量rin(t)为电机转速预定值(转/min);
2. 输出量rout(t)为电机转速实际值(转/min);
3.偏差量为预定值和实际值之差(转/min);
那么以下几个问题需要弄清楚:
1.通过PID环节之后的 U(k) 是什么值呢?
2.通过调节 PWM 的电压占空比来调节电机的转速。
3.那么U(k)与控制电机的 PWM 之间存在怎样的联系呢?
看到有不少人问到底如何让UK值与PWM占空比值对应,进而实现占空比输出和输出控制电压对应。
(注意,我这里讨论的前提是输出控制的是电压,不是PWM方波。PWM输出后要经过滤波整形再输出控制。)
前提条件:
输出电压控制电压范围是0-10V。
给定、反馈、输出电压采样输入电压范围是0-5V(经过运放)。
使用单片机AD为10位AD芯片。
那么10位AD芯片电压采集得到的数据范围就是0-1024。
PWM为 8位可调占空比方波,0对应输出占空比为0的方波,255对应输出占空比100%的方波,127对应输出50%的方波。
比如当前给定是2.5V,反馈电压是1V。(KP,KI,KD等系数略,关于PID算法的整数实现我在前文中有论述如何实现)。
那么经过AD采样
1、给定2.5V对应为 512
2、反馈1V对应为 205
假定经过PID计算得到的UK为400
也就意味着输出电压应当为(400*(UPWM峰值电压))/1024
那么UK对应的PWM占空比是多少呢?
我们知道,UK=1024对应占空比为100,也就是PWM的占空比系数为255。可知,PWM系数 = UK/4;
那么400就应当对应系数 400/4=100。
也就是输出电压=400*10/1024=3.9V
同时,由于采样精度以及PWM输出占空比精度控制的问题,将导致输出电压和期望值不是那么线性,所以,我在项目内加入了输出电压采样的控制。
采样AD输入为0-5V,所以,对于输出0-10V有一个缩小的比例。
输出10V则采样值对应为255
输出5V则采样之对应127
可知,3.9V对应AD结果为97
采样输出电压值,可以针对性的调整一下占空比输出,从而得到误差允许范围内的一个控制输出电压。