Microchip官方针对数字环路控制的应用,专门开发了一套函数库,供程序员直接调用。笔者用的是“smps_control_library_v2018_02_28.zip”这个版本。该函数库中的函数支持C语言和汇编调用。先解压zip文件,得到如下目录:
doc目录是帮助文件,lib目录是官方已经编译好的库文件,mplabx目录是函数库的mplab工程文件,src是用汇编代码写的算法函数,最后smps_control.h是库中函数的结构体和函数声明。
库中的函数,按调用方式分为:C语言调用和汇编语言调用;按算法实现方式又分为:基于软件的函数和硬件加速函数。
首先要表明一点,即使是C语言调用的库函数也是用汇编代码编写的,只不过是最终编译成为*.a的集成库,供C语言调用。另外,上面所说的硬件加速函数与基于软件的函数的区别主要是:在调用该函数时,硬件加速函数使用了dsp硬件的context切换功能,节省了寄存器压栈和出栈的时间,而基于软件的函数没有这个功能。至于context寄存器切换功能在后面的文章中会涉及,这里主要分析如何实现函数的调用。
1.用C语言的方式调用:
将头文件smps_control.h添加到工程文件的头文件中,然后再将lib目录中的*.a库引入工程文件的库中,如下图:
右键点击Libraries,然后选择“Add Library/Object File...”,在lib目录中的库文件有两种,一个是dspic33E系列,另一个是dspic33F系列。对应不同的dsp选择不同的文件,这里用libsmps_control_dspic33e-elf.X.a这个库文件。选择“libsmps_control_dspic33e-elf.X.a”。如下图:
然后在工程文件属性里,如下图设置:
下面简要说说C语言中不同文件中的变量是如何引用的:
设在C语言的工程文件中包含:a.c、a.h、b.c、b.h
如果在a.c中的某个全局变量int16_t Var,要在b.c中调用,就需要在b.c的开头用
extern int16_t Var;
先声明,才可以在b.c中使用,但一般不这样做。通常的做法是把a.c中可以被其它*.c文件引用的变量用extern关键字声明,放入对应的头文件中。如:可以把
extern int16_t Var;
这个声明放入到a.h头文件中,然后在b.c中用
#include "a.h"
的方式就将变量Var的外部引用声明包含了,那么在b.c中就可以引用Var变量了。这样做的好处是:程序员通过查看对应C文件的头文件就可以知道该C文件中哪些变量是可以被其它文件使用的。这对于结构体和函数等也同样适用。所以,一般会看到在a.c文件对应的a.h文件中,会把该a.c文件中允许外部引用的变量、数组、结构体、指针和函数等全部声明出来。为了防止变量或函数被重复声明,在a.h中只声明a.c中允许引用的变量或函数,如果a.c要引用其它C文件的变量或函数时,请在a.c开头用#include "xx.h"的方式包含含有该变量的头文件,而不要在a.h文件中包含xx.h头文件。
libsmps_control_dspic33e-elf.X.a是集成库,是由汇编代码直按生成的,没有C文件,只有一个smps_control.h头文件表明库函数里面的哪些函数和结构体是可以被外界调用的。如果不提供汇编的原代码,函数的使用者就不可能了解函数的实现过程,从而保护了原作者的知识产权。
官方的参考设计中,一般都会调用硬件加速函数,而且相对基于软件的函数,调用硬件加速函数更简单。下面就以PID算法为例,说说如何用C代码的方式调用硬件加速函数:
1)首先,已经将*.a文件和smps_control.h文件加入到工程文件中了
2)可以新建一个文件,如:compensators.c。先定义一个结构体
SMPS_Controller_Options_T smps_controller_options;
3)然后在compensators.c文件中,分别定义数据空间X和Y区域中的1个数组:
volatile int16_t controllerPIDCoefficients[3]__attribute__ ((section (".xbss")));
volatile int16_t controllerPIDErrorHistory[3]__attribute__ ((space (ymemory), far));
X和Y区间是什么?这涉及到dsp内部的数据存储器的结构,因为mac等dsp指令需要X和Y空间的地址做运算,这里不再深入了,对此感兴趣的读者可以参考dspic33系列参考手册第3章(DS70595C_CN)第3.2节。
4)接着,定义要切换到的context寄存器阵列中W0至W14的值或指向变量的指针(并不是所有W0至W14都用到),这方面的内容后面还会提到。
5)初始化上面在数据空间的X和Y区域中定义的两个数组,controllerPIDErrorHistory[3]都初始化为0,controllerPIDCoefficients[3]就是PID算法的Kp、Ki和Kd,当然不是完全对应的关系,后面会说明。
6)还要设置采样时间。我们用的是平均电流型控制方式,所以在占空比一半时触发采样,才能采到电感电流的平均值。将smps_controller_options结构体中的成员triggerSelectFlag设为1,成员trigger和period分别指向PWM2的触发寄存器和周期寄存器。
7)调用前用汇编指令“CTXTSWP”切换到第4)步中初始化的工作寄存器中,然后
SMPS_ControllerPIDUpdate_HW_Accel();
进行PID差分方程的求解,并更新占空比,还有PWM2下次触发ADC采样的时间。
2.用汇编语言的方式调用:
用汇编代码调用库函数是官方参考设计中比较常用的方式。在16位dspic系列单片机中,汇编文件是.s或.S为扩展名的。.s汇编文件是纯粹的汇编代码,用编译器直接编译。.S汇编文件是带有C预处理指令的汇编代码,其中可以包含
#include "xxx.h"
#define ..........
等C预处理指令,当进行汇编前,调用C编译器先处理其中的预处理指令,然后再进行汇编。再说一个细节,//和;都可以用来表示注释行。
这里涉及到汇编与C代码的混合编程。我们需要知道汇编与C是如何相互引用对方的变量和函数的。其实也简单。
C引用汇编中的变量和函数:
汇编代码中的变量或函数应该在汇编文件中用.global关键字声明,在C文件的开头,再用extern声明一遍。然后就可以在C中使用了。
汇编引用C中的变量和函数:
在C文件中已经定义的全局变量,可在在汇编文件中直接引用。还有一个细节要注意:如果一个变量在C中是Var,在汇编中,一定要写成_Var,前面的下划线一定要有,否则编译时会报错。关于C与汇编混合编程可以参考《MPALB XC16 C编译器用户指南(ds50002071E_CN)》第16章。
所以,引用库函数时,只需要将对应的汇编文件添加进工程中,然后用extern声明后,就可以使用了。
以PID算法为例,介绍汇编是如何调用函数的:
1)要将smps_pid_dspic_v2.s加入工程文件中,注意不是smps_pid_dspic.s!!后缀v2表示是硬件加速函数。
2)初始化context。
3)定义X和Y空间中的2个数组。还要声明SMPS_ControllerPIDUpdate_HW_Accel()函数。
extern void SMPS_ControllerPIDUpdate_HW_Accel(void);
4)ADC采样触发时间,可以在汇编中直接修改,不用再定义一个结构体了。会比C简单些,后面会提到如何修改。
5)切换context,调用SMPS_ControllerPIDUpdate_HW_Accel。完成PID差分方程求解,并更新占空比和PWM2采样ADC触发时间。
总结:
以上都是调用库函数前的准备工作,官方的设计参考中,硬件加速函数都用汇编的方式进行调用,这样做步骤相对简单一些。
在笔者使用PID库函数过程中,发现PID函数的代码好像有点问题,在下一节中,我们就以smps_pid_dspic_v2.s汇编代码的例,分析一下PID的具体是如何求解差分方程的。