作為一名工程師,在項目實施階段多多少少會遇到需要使用控制理論的應用程序。
一種非常常用的算法是比例積分微分控制器(proportional-integral-derivative control)或 PID 控制器。PID 算法用于控制各種應用中溫度、壓力、電機位置和流量等變量。我經常看到的一個地方是高端圖像處理系統(制冷型紅外),為了減少圖像中的噪點。它使用熱電冷卻器或其他冷卻系統來冷卻圖像傳感器。對于高端成像,較低的噪聲可以帶來更好的圖像。
介紹
PID 控制算法實現起來并不難,因為它只需要加法、乘法、除法和減法(dog)。但是,一旦算法實施,確保 PID 回路穩定的三個系數可能需要一點額外的時間來獲取。
PID 主要使用三個術語。
- 比例(Proportional) -測量期望值和測量值之間的差異。比例值是當前位置的量度。
- 積分(Integral) -會隨著時間的推移對誤差進行積分。積分項是誤差的歷史累積值。隨著誤差的消除,積分項停止增長。
- 導數(Derivative) -計算變化率并預測誤差的未來趨勢。
每個術語還具有相關的增益 KI、KP 或 KD,可以幫助我們調整 PID 控制器算法的行為。D 項不是必須的,而且簡單的情況下我們基本不使用,使用 PI 控制器也很常見。
PID經常使用浮點數來實現。因此,我們可以使用諸如 VHDL Fixed/Float 之類的庫在 RTL 中實現。或者,我們可以使用HLS來實現 PID,因為國內應用VHDL較少,所以我們今天的實例是使用HLS構建我們的PID算法。使用HLS能夠使用浮點或任意精度的定點數。HLS還能通過#pragma 快速的為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;
}
已將previous error和previous integral聲明為全局靜態變量,以確保它們在迭代時候其值保持不變。
在算法方面,用戶可以在應用程序運行時動態加載 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 仿真和協同仿真,結果完全符合預期。
算法按照預期運行,下一步是綜合和導出 IP,最后就是添加到我們的 Vivado 項目中。這次我們使用的是ZYNQ FPGA。
延遲性能和資源消耗下面的完整框圖反映了添加到Vivado項目中情況。
框圖
總設計資源
PID 資源構建完成上面的Vivado項目,接下來就是導出硬件(XSA)到 Vitis 中開發驅動。
在 Vitis 中開發驅動時候,我重用了 HLS 仿真文件中的幾個元素。
由于我們使用的是 AXI 接口,Vitis HLS 在導出IP時候地為我們提供了一個可以在 Vitis 中用于驅動 IP 核的驅動程序。但是,當在 IP 內核中使用浮點輸入時,驅動程序則期望它們為 U32。如果我們在開發驅動時候從浮點數轉換為 U32,我們將失去準確性。因此,解決這個問題的方法是使用指針(pointers)和強制轉換。
本質上,我們將變量聲明為浮點數,然后在函數中調用設置一個指向浮點變量地址的 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算法。
完整項目在下面鏈接里。
參考
https://www.cnki.com.cn/Article/CJFDTOTAL-CAIZ201316188.htm
https://www.adiuvoengineering.com/
總結
雖然上面的流程很簡單,但是HLS在調整資源和速度方面還是需要一些時間,并且浪費的資源還是比純HDL多。
最后在說一下該方式的缺點,PID需要進行浮點運算,而FPGA則不能進行浮點運算,如果想把上面的算法在邏輯中運行,則需要自己進行量化,但是如果像上面例程的方式在內核(硬核)中運行算法,則該方式簡單且優雅~
-
控制器
+關注
關注
112文章
16336瀏覽量
177818 -
噪聲
+關注
關注
13文章
1120瀏覽量
47400 -
PID
+關注
關注
35文章
1472瀏覽量
85484 -
圖像處理器
+關注
關注
1文章
104瀏覽量
15496
發布評論請先 登錄
相關推薦
評論