推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

在 FPGA 上快速构建 PID 算法

   2023-08-09 网络整理佚名1080
核心提示:副标题:优秀的IC/FPGA开源项目(四)-使用HLS构建PID算法PID,因为国内应用VHDL较少,所以我们今天的实例是使用HLS构建我们的PID算法。实现的,可维护性高,可以快速构建一个我们需要的PID算法。

副标题:优秀IC/FPGA开源项目(四)——利用HLS构建PID算法

《优秀IC/FPGA开源项目》是一个旨在介绍单个项目的新系列,会比《优秀/FPGA开源项目》更加详细,包括但不限于综合、板测试等,两者相辅相成互相补充,相得益彰~

作为一名工程师,在项目的实施阶段,您或多或少会遇到需要使用控制理论的应用程序。

一种非常常见的算法是比例积分微分控制器 (--) 或 PID 控制器。 PID 算法用于控制各种应用中的变量,例如温度、压力、电机位置和流量。 我经常看到它的一个地方是高端图像处理系统(冷却红外),以减少图像中的噪声。 它使用热电冷却器或其他冷却系统来冷却图像传感器。 对于高端成像,较低的噪声会产生更好的图像。

介绍

PID控制算法实现起来并不困难,只需要加、乘、除、减(狗)。 然而,一旦算法实现,保证PID环路稳定性的三个系数可能需要一些额外的时间来获取。

PID主要使用三个术语。

每一项还有一个相关的增益 KI、KP 或 KD,帮助我们调整 PID 控制器算法的行为。 D项不是必须的,简单情况我们基本不用,使用PI控制器也很常见。

PID 通常使用浮点数来实现。 因此我们可以使用 VHDL Fix/Float 等库在 RTL 中实现。 或者,我们可以使用HLS来实现PID,因为国内VHDL的应用较少,所以我们今天的例子就是使用HLS来构建我们的PID算法。 使用 HLS 可以使用浮点或任意精度定点数。 HLS还可以通过#快速为IP添加通用控制接口(AXI)。

在纯FPGA中实现类似系统时,我们需要添加软核来控制IP。 在较小的Zynq-7000 SoC FPGA(7007、7010、7020等)中,可以通过硬核控制IP。 或者,如果我们不想在设计中使用处理器,我们可以设计传统的矢量接口。

源代码设计

PID 的实际源代码非常简单,如下所示。

#include "pid.h"
static data_type error_prev =0;
static data_type i_prev=0;
data_type PID (data_type set_point, data_type KP, data_type KI, data_type KD, data_type sample, data_type ts, data_type pmax)
{
#pragma HLS INTERFACE mode=s_axilite port=return
#pragma HLS INTERFACE mode=s_axilite port=sample
#pragma HLS INTERFACE mode=s_axilite port=KD
#pragma HLS INTERFACE mode=s_axilite port=KI
#pragma HLS INTERFACE mode=s_axilite port=KP
#pragma HLS INTERFACE mode=s_axilite port=set_point
#pragma HLS INTERFACE mode=s_axilite port=ts
#pragma HLS INTERFACE mode=s_axilite port=pmax
 data_type error, i, d, p;
 data_type temp;
 data_type op;
 error = set_point - sample;
 p = error * KP;
 i = i_prev + (error  * ts * KI);
 d = KD * ((error - error_prev) / ts);
 op = p+i+d;
 error_prev = error;
 if (op > pmax)  {
  i_prev = i_prev;
  op = pmax;
 }else{
  i_prev = i;
 }
 return op;
}

错误并已被声明为全局静态变量,以确保它们的值在迭代中保持不变。

在算法方面,用户可以在应用程序运行时动态加载KP、KI、KID、Ts和Pmax。 我们可以轻松地添加积分值或使用附加寄存器重新启动控制器。 这将允许 PID 用于多种实现。

为了测试和配置 PID,测试文件列出了远高于所需目标设定点的一系列温度值,并确保达到设定点。 本例中的 PID 旨在提供功率(以瓦为单位)来维持光学床的温度。 在这种情况下,我们需要加热,而不是冷却。

#include "pid.h"
#include 
#define iterations 40
int main(void)
{
data_type set_point = -80.0;
data_type sample[iterations] = {-90.000,-88.988,-87.977,-86.966,-85.955,-84.946,-83.936,-82.928,-81.920,-80.912,-80.283,-79.926,-79.784,-79.774,-79.829,-79.898,-79.955,-79.993,-80.011,-80.017,-80.016,-80.010,-80.005,-80.002,-80.000,-79.999,-79.999,-79.999,-79.999,-80.000,-80.000,-80.000,-80.000,-80.000,-80.000,-80.000,-79.999,-80.000,-80.001,-80.000};
data_type kp = 19.6827; // w/k
data_type ki = 0.7420; // w/k/s
data_type kd = 0.0;
data_type op;
printf("testing cpp\r\n");
for (int i =0; i

Vitis HLS中PID算法的C仿真和联合仿真完全符合预期。

延迟性能和资源消耗

下面的完整框图反映了项目中添加的内容。

框图

pid算法c语言实现_实现算法的程序设计语言_算法程序语言

总设计资源

PID资源

构建完上述项目后,下一步是将硬件(XSA)导出到 Vitis 来开发驱动程序。

在 Vitis 中开发驱动程序时,我重用了 HLS 模拟文件中的多个元素。

由于我们使用的是 AXI 接口,Vitis HLS 为我们提供了一个驱动程序,可用于在导出 IP 时驱动 Vitis 中的 IP 核。 然而,当 IP 核中使用浮点输入时,驱动程序期望它们为 U32。 如果我们在开发驱动程序时从 float 转换为 U32,我们将失去准确性。 所以解决这个问题的方法是使用()和强制转换。

本质上,我们将变量声明为浮点数,然后在函数中调用设置一个U32指针指向浮点变量的地址,并使用间接运算符读取值。

XPid_Set_set_point ( & pid ,  * ( ( u32 * ) & set_point ) ) ;

整个应用程序是

#include 
#include "platform.h"
#include "xil_printf.h"
#include "xpid.h"
#define iterations 40
typedef float data_type;
data_type set_point = -80.0;
data_type sample[iterations] = {-90.000,-88.988,-87.977,-86.966,-85.955,-84.946,-83.936,-82.928,-81.920,-80.912,-80.283,-79.926,-79.784,-79.774,-79.829,-79.898,-79.955,-79.993,-80.011,-80.017,-80.016,-80.010,-80.005,-80.002,-80.000,-79.999,-79.999,-79.999,-79.999,-80.000,-80.000,-80.000,-80.000,-80.000,-80.000,-80.000,-79.999,-80.000,-80.001,-80.000};
data_type kp = 19.6827; // w/k
data_type ki = 0.7420; // w/k/s
data_type kd = 0.0;
data_type ts = 12.5;
data_type pmax = 40;
u32 op;
XPid pid;
int main()
{
    float result;
    init_platform();
    disable_caches();
    print("Adiuvo PID Example\n\r");
    XPid_Initialize(&pid,XPAR_XPID_0_DEVICE_ID);
    XPid_Set_set_point(&pid, *((u32*)&set_point ));
    XPid_Set_KP(&pid,  *((u32*)&kp));
    XPid_Set_KI(&pid,  *((u32*)&ki));
    XPid_Set_KD(&pid,  *((u32*)&kd));
    XPid_Set_ts(&pid,  *((u32*)&ts));
    XPid_Set_pmax(&pid,  *((u32*)&pmax));
    u32 tst = XPid_Get_set_point(&pid);
    for (int i =0; i

运行它并得到以下结果。

正如预期的那样,硬件中的实现方式与软件中的实现方式相同。

当然,针对不同的应用,我们需要重新定义应用可用的KP、KI和KD变量。

这样做的真正美妙之处在于,因为它是用 C 实现的,所以可维护性很高,可以快速构建我们需要的 PID 算法。

完整的项目位于下面的链接。

参考

总结

虽然上面的过程很简单,但是HLS仍然需要一些时间来调整资源和速度,比纯HDL浪费更多的资源。

最后说一下这种方法的缺点。 PID需要进行浮点运算,但FPGA无法进行浮点运算。 如果你想在逻辑上运行上面的算法,你需要自己量化,但如果像上面的例程一样在内核(硬核)中运行算法,方法就简单优雅了~

项目文本示例:

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
Powered By DESTOON