libsmps_control_dspic33e-elf库函数的原代码是以汇编的形式给出的,需要有一定的汇编程序的编程基础,笔者以smps_pid_dspic_v2.s原代码为例,阐述一下PID算法的实现原理。分2部分:1.计算参数,2.代码分析。在附件中,有经过笔者修改的PID算法的代码,可以下载学习。
1)先计算PID函数中各个参数值:
下图为PID算法的结构框图。
从上面的图可知,PID函数求解的差分方程为:
而PID的标准式为:
由此可知:
为了正确计算PID的差分方程的解,还要按照要求设置工作寄存器
以平均电流型控制电流内环环路的PI差分方程为例:
所以:KA=3.2116 KB=-3.125 代入前面的公式,可得:
在累加器中要做小数的乘法运算,且小数是大于1的,为了方便累加器的小数计算,应该把大于1的小数都化成小于1的小数,具体做法就是从KA、KB和KC中选出一个绝对值最大的数,然后每项都除以这个数。还有一点,这个除数应该比最大值还要再大一点,否则会出现1,也是无法用dsp的小数格式表示。上式中3.2116最大,可以选择3.2117做为除数,即
要注意,u[n-1]是上一次计算的结果,没有进行postShift和postScalar,所以不需要再除3.2117。
要将Ka和Kb、Kc做为系数填入到数组controllerPIDCoefficients[3]中,这一过程是在数组初始化时完成的。
controllerPIDCoefficients[0]=Ka;
controllerPIDCoefficients[1]=Kb;
controllerPIDCoefficients[2]=Kc;
并且在初始化工作寄存器时,还要将w9寄存器做为指针,指向controllerPIDCoefficientABC[3]数组的地址。
进行完一次差分方程的计算后,结果还要乘回3.2117这个数,才是差分方程解的最终值。因为1<3.2117<4(2^2),是无法用Q15格式表示的,所以
w6寄存器中的值为:
还要将ACCA寄存器的结果向左移2位,所以w7寄存器的值应填-2,负号表示向左移,因为都是向左移位,w7寄存器的值总是负值。
w11和w12寄存器是限制PID计算结果最小最大值的。
w10寄存器要做为指针指向controllerPIDErrorHistory[3]数组的地址,用来保存当前误差和前两次误差的结果。
w0是电流环路的参考值,也是PFC电压外环的PID输出值。
w1是ADC电感电流采样。w2是指向占空比寄存器的指针。
因为ADC采样是12位的,为了用满15位,所以要左移3位,w13要填3
2)分析PID算法的代码:
汇编程序以.text为程序的开头,以.end为程序的结尾。
.global _SMPS_ControllerPIDUpdate_HW_Accel
先将函数声明为全局函数,以供其它文件调用。然后是定义该函数,并编写函数的内容,注意函数名前都有下划线。
进入函数后,首先保存调用该函数前的CORCON寄存器,然后用w3、w14和w5三个寄存器将上次计算出的u[n-1]的值载入到ACCA累加器中,然后通过w4寄存器,设置本函数环境中需要用到的CORCON,主要是设置ACCA累加器的工作模式等参数。在这里,ACCA累加器被设置为普通饱和模式(1.31)且累加器ACCA是小数乘法运算。
接着计算误差e[n],并存储在w10指向的数组中。然后从w9和w10两个寄存器中获取两个数组的地址,然后是计算差分方程解的过程。在用mac指令进行乘法运算时,乘法的源操作数只能是w4、w5、w6和w7这4个寄存器,用其它的寄存器都会报错。另外,在mac指令预取操作时,x空间只能用w8和w9做指针;y空间只能用w10和w11做指针。也只有把两个不同的数定义在不同的x和y空间,才能做到在一个mac指令中预取2个数,所以要把controllerPIDCoefficients[3]和controllerPIDErrorHistory[3]分别定义在x空间和y空间。因为ACCA寄存器是40位的,所以用3个16位寄存器w3、w14和w5来保存,为了下一次计算PID差分方程时做为u[n-1]使用。
接下来的
sac.r a, w4
其实就是将ACCAH的内容保存在w4寄存器中。w4内容就是PID差分方程的解,但是在做PID算法之前,我们将系数KA、KB和KC都除了3.2117,因此在这个地方,要重新乘回来才是正确结果。w6和w7寄存器的值保存的就是3.2117这个数的dsp小数格式。
mpy w4*w6, a
sftac a, w7
sac.r a, w4
mpy是纯乘法指令,指明用ACCA累加器保存计算结果。如同前面文章中提到的,对于大于1的小数,先做乘法,再向左位移。然后再次将结果(ACCAH)保存在w4寄存器中。
接着做最大最小值的嵌位,最后把结果传送给PID算法的输出寄存器指针w2。对于本例来说就是PDC2。下面要把e[n]、e[n-1]和e[n-2]都保存起来,同时把w9和w10这两个指针复位,供下次PID计算时指向正确的地址。
这里上面一段官方原代码中,好像有点问题!此处不应该用w14寄存器,因为在前面的代码中,用到了w14来保存u[n-1],下图所示:
如果存储e[n]、e[n-1]和e[n-2]过程中,再用w14寄存器,w14原来的值会被覆盖!即u[n-1]的值会被覆盖,则下次计算PID时会出现错误。所以是不对的,笔者把w14改为w8,经过测试可以实现PID功能。
在原代码中,w8是指向结构体SMPS_Controller_Options_T的指针,这个结构体的成员triggerSelectFlag用来选择触发时间的计算方式,成员trigger是指向TRIG2寄存器的指针,成员period是指向PWM2周期寄存器的指针。在笔者的代码中,完全舍弃了这个结构体,将计算触发时间的代码以C语言的方式,写到本函数的外面。所以将下图中的263行至297行,全部删除。
然后提一下如何计算一下PWM2触发ADC的时间,即计算TRIG2寄存器的值。对于平均电流型控制模式,我们只要在达到占空比一半的时间点时触发ADC采样,采到的值就一定是被测量的平均值,这可以说是数字电源对比模拟电源的一个优点,因为模拟电源采集平均值需要进行低通滤波,会有一定的延时,而数字电源几乎是瞬时的。
TRIG2的计算方法:(其中Delay是指从PWM2发出波形到MOS管输出PWM波形的延时时间)
再说明一下,在本函数中,并没有进行触发时间的计算,而是在函数的外面以C语言的方式编写代码,在下一节中会有具体说明。
总结:
PID差分方程的求解计算主要是应用累加器实现的,如何整定函数的参数,本文给出了详细的步骤,并指出了原代码中出现的错误,以及解决方法。里面涉及到了汇编代码的编程,需要对汇编指令有比较深入的了解,其中的一些细节更是无法一一提及,还需读者阅读相关的资料。推荐阅读《16位MCU和DSP程序员参考手册》(DS70157F_CN)。
在下一节中,会以半无桥PFC为例,具体说说如何编写代码。