LabVIEW FPGA PCIe開發(fā)講解-7.7節(jié):上位機(jī)PC端Memory應(yīng)用程序開發(fā)(LabVIEW/C調(diào)用DLL)

? ? ? ??當(dāng)FPGA硬件被系統(tǒng)識(shí)別成功后,我們就可以編寫一個(gè)上位機(jī)PC端的應(yīng)用程序來與之通信,比如用來監(jiān)控下位機(jī)FPGA前面板上的控件值或者下發(fā)控制指令給FPGA了。為了方便廣大用戶的使用,我們將2上2下共計(jì)4個(gè)通道的中間層Memory讀寫通道傳輸也封裝到前面給用戶介紹過的那個(gè)DLL動(dòng)態(tài)鏈接庫里面了,這樣對(duì)于使用不同編程語言(C\C++\C#\Python)開發(fā)上位機(jī)應(yīng)用程序的用戶來說,直接調(diào)用我們封裝好的DLL驅(qū)動(dòng)就可以了。這個(gè)DLL位于本書配套的云盤里面,如圖7-74所示。

? ? ? ??注意:不管是win7還是win10系統(tǒng),基本上都沒有ucrtbased.dll和vcruntime140d.dll,因此,如果用戶直接調(diào)用我們生成的“Xillybus_PC_for_LV_DLL_8Chs.dll”,win7系統(tǒng)會(huì)彈框提示你少了的庫的名稱,其實(shí)就是前面兩個(gè)依賴庫,但是win10系統(tǒng)不提示,直接報(bào)錯(cuò),從這一點(diǎn)看,win10系統(tǒng)的用戶體驗(yàn)做的太爛。因此,為了方便用戶使用,我們直接將這兩個(gè)系統(tǒng)依賴庫跟我們的“Xillybus_PC_for_LV_DLL_8Chs.dll”放在同一個(gè)目錄下,這樣用戶就不用操心了,無論通過什么軟件去調(diào)用dll都能正常了?。?!
?????? ?下面再回顧一下前面我們針對(duì)LabVIEW軟件封裝好的一個(gè)lvlib庫文件,對(duì)于熟悉LabVIEW編程的工程師來說,直接將lvlib里面的VI拖拽到自己的程序里面就可以完成跟下位機(jī)FPGA之間的PCIe通信了,一目了然。圖7-75顯示的是我們封裝好的上位機(jī)LabVIEW My FPGA PCIe Toolkit軟件工具包項(xiàng)目瀏覽器截圖。

? ? ? ? 里面主要包含兩大塊,分別是PCIe DMA FIFO相關(guān)的多態(tài)VI和Memory通道讀寫相關(guān)的多態(tài)VI;DMA FIFO相關(guān)函數(shù)我們?cè)谇懊?.3.3節(jié)已經(jīng)做了詳細(xì)介紹,本節(jié)先著重介紹一下我們封裝的lvlib庫里面的Memory相關(guān)函數(shù)(VI),這些函數(shù)位于這個(gè)虛擬文件夾“PC_Memory_RW_2Chs_Func”里面。
?????? ?為了提高M(jìn)emory讀寫的效率,我們特地有針對(duì)性的封裝,將Memory上行讀數(shù)據(jù)(FPGA-->Host)和下行寫數(shù)據(jù)(Host-->FPGA)的傳輸過程分成3個(gè)步驟,分別對(duì)應(yīng)6個(gè)子VI,也就是打開、讀寫、關(guān)閉,如圖7-76所示。

l? PCIe Memory讀通道初始化(FPGA_Memory_Read_Poly_Init_DLW30.vi)
l? PCIe Memory讀通道數(shù)據(jù)讀?。‵PGA_Memory_Read_Poly_Receive_DLW30.vi)
l? PCIe DMA讀通道關(guān)閉(FPGA_Memory_Read_Poly_Close_DLW30.vi)
?
l? PCIe Memory寫通道初始化(FPGA_Memory_Write_Poly_Init_DLW30.vi)
l? PCIe Memory寫通道數(shù)據(jù)下發(fā)(FPGA_Memory_Write_Poly_Send_DLW30.vi)
l? PCIe DMA寫通道關(guān)閉(FPGA_Memory_Write_Poly_Close_DLW30.vi)
?????? ?再次強(qiáng)調(diào):所有調(diào)用DLL的子VI都要設(shè)置成“任意線程”,切不可設(shè)置成“用戶線程”,如圖7-77所示。否則一旦DLL里面的函數(shù)出現(xiàn)讀寫阻塞之后,會(huì)導(dǎo)致LabVIEW界面無響應(yīng),卡死狀態(tài),更沒有辦法進(jìn)行探針調(diào)試,切記!

? ? ? ? 下面我們對(duì)這6個(gè)多態(tài)子VI分別進(jìn)行講解,之所以設(shè)計(jì)成多態(tài)VI,是為了方便用戶編程的時(shí)候,可以直接選擇切換通道。下面我們逐一給大家講解一下這6個(gè)PCIe Memory通道通信的VI含義和用法。
?????? ?1)FPGA_Memory_Read_Poly_Init_DLW30.vi
?????? ?這個(gè)子VI可以用來完成對(duì)指定的FPGA PCIe Memory讀通道進(jìn)行初始化,本質(zhì)上就是打開Xillybus里面的Memory通道文件名,如果Memory讀通道初始化成功,該VI會(huì)返回一個(gè)唯一的句柄“fd_read”,后面所有跟讀通道相關(guān)的函數(shù)VI都需要借助這個(gè)引用指針“fd_read”,如圖7-78所示。用戶可以根據(jù)實(shí)際情況,在左側(cè)的下拉列表“Pipe_Read_Name”里面選擇16位或者32位位寬的Memory通道作為讀取通道,其中,后綴mem0對(duì)應(yīng)16位位寬,mem1對(duì)應(yīng)32位位寬,也就是每次操作的數(shù)據(jù)是雙字節(jié)還是4字節(jié)。

? ? ? ? 2)FPGA_Memory_Read_Poly_Receive_DLW30.vi
?????? ?當(dāng)上行的Memory讀取通道打開成功之后,我們就可以利用這里的“FPGA_Memory_Read_Poly_Receive_DLW30.vi”函數(shù)按照指定的地址把下位機(jī)FPGA里面的數(shù)組或者M(jìn)emory元素讀取出來了,如圖7-79所示。該VI除了錯(cuò)誤簇外,還有4個(gè)連接端口,最上面的左右兩側(cè)分別是Memory讀通道打開后的引用句柄,另外就是地址輸入控件“address_read_U16”和數(shù)據(jù)輸出控件“data_read_U16”。就是用戶可以將FPGA里面的數(shù)組或者M(jìn)emory指定位置(地址)處的元素讀取到上位機(jī)來。

? ? ? ? 這個(gè)VI本質(zhì)上調(diào)用的是一個(gè)DLL函數(shù),里面實(shí)際上包含了兩個(gè)系統(tǒng)函數(shù),分別是lseek文件位置偏移函數(shù)和read文件內(nèi)容讀取函數(shù)。如果用戶不會(huì)LabVIEW的話,也可以使用其他語言,比如Python、C++、C#等直接調(diào)用系統(tǒng)里面的lseek和read函數(shù)就可以實(shí)現(xiàn)圖7-79這個(gè)VI一樣的功能和效果,因此我們開發(fā)的FPGA PCIe CLIP不限制操作系統(tǒng),無論是Windows還是Linux都支持。
?????? ?下面我們雙擊打開這個(gè)VI,看看程序框圖里面有沒有什么需要注意的地方,如圖7-80所示。

? ? ? ? 為了讓用戶在執(zhí)行讀取操作的時(shí)候,地址是連續(xù)的,也就是地址0對(duì)應(yīng)的U16或者U32數(shù)組的第1個(gè)元素,地址1對(duì)應(yīng)第2個(gè)元素,地址2對(duì)應(yīng)第3個(gè)元素,以此類推,而不需要考慮什么雙字節(jié)或者4字節(jié)的倍數(shù)整除問題,為此,我們特地在這個(gè)VI里面加入了一個(gè)轉(zhuǎn)換公式,如圖7-81~7-82所示,其中,16位位寬的Memory地址,我們下發(fā)之前給FPGA的地址做了×2+1偏移;對(duì)于32位位寬的Memory地址做了×4+3偏移。


? ? ? ? 最后需要注意的一點(diǎn)是:PCIe Memory跟FIFO一樣,對(duì)超過8位位寬的數(shù)據(jù),存在大小端格式轉(zhuǎn)換問題,在前面7.6節(jié)已經(jīng)強(qiáng)調(diào)過了,為了減少FPGA資源的消耗,鑒于Memory讀寫速度很慢,我們特地將大小端格式轉(zhuǎn)換代碼放在了上位機(jī),也就是本節(jié)介紹的Memory通道讀寫VI里面,如圖7-83所示。將讀出來的字節(jié)數(shù)組進(jìn)行翻轉(zhuǎn),然后再利用“強(qiáng)制類型轉(zhuǎn)換”函數(shù)變成U16或者U32就可以了。

? ? ? ? 3)FPGA_Memory_Read_Poly_Close_DLW30.vi
?????? ?當(dāng)我們需要退出上位機(jī)應(yīng)用程序之前,需要利用這里的“FPGA_Memory_Read_Poly_Close_DLW30.vi”函數(shù)來把先前打開的PCIe Memory讀通道引用句柄指針關(guān)掉,如圖7-84所示。防止出現(xiàn)內(nèi)存泄露,更重要的是,如果不執(zhí)行關(guān)閉的話,下次再打開這個(gè)通道的時(shí)候,會(huì)提示這個(gè)通道被占用了,所以務(wù)必要檢查一下之前開啟的通道句柄是否銷毀了。另外,為了保證Memory通道引用被強(qiáng)制關(guān)閉,我們?cè)谳斎脲e(cuò)誤簇之前加入一個(gè)“清除錯(cuò)誤簇”函數(shù),防止上游的錯(cuò)誤導(dǎo)致該VI不執(zhí)行關(guān)閉功能。

? ? ? ? 4)FPGA_Memory_Write_Poly_Init_DLW30.vi
?????? ?這個(gè)子VI可以用來完成對(duì)指定的FPGA PCIe Memory寫通道進(jìn)行初始化,本質(zhì)上就是打開Xillybus里面的Memory通道文件名,如果Memory寫通道初始化成功,該VI會(huì)返回一個(gè)唯一的句柄“fd_write”,后面所有跟寫通道相關(guān)的函數(shù)VI都需要借助這個(gè)引用指針“fd_write”,如圖7-85所示。用戶可以根據(jù)實(shí)際情況,在左側(cè)的下拉列表“Pipe_Write_Name”里面選擇16位或者32位位寬的Memory通道作為寫入通道,其中,后綴mem0對(duì)應(yīng)16位位寬,mem1對(duì)應(yīng)32位位寬,也就是每次操作的數(shù)據(jù)是雙字節(jié)還是4字節(jié)。

? ? ? ? 5)FPGA_Memory_Write_Poly_Send_DLW30.vi
?????? ?當(dāng)下行的Memory寫取通道打開成功之后,我們就可以利用這里的“FPGA_Memory_Write_Poly_Send_DLW30.vi”函數(shù)將上位機(jī)的地址和數(shù)據(jù)下發(fā)給下位機(jī)FPGA,按照索引值(地址)將數(shù)組或者M(jìn)emory里面的元素覆蓋更新,如圖7-86所示。該VI除了錯(cuò)誤簇外,還有4個(gè)連接端口,最上面的左右兩側(cè)分別是Memory寫通道打開后的引用句柄,另外就是地址輸入控件“address_write_U16”和數(shù)據(jù)輸入控件“data_write_U16”。就是用戶可以將上位機(jī)的數(shù)據(jù)按照給定的地址對(duì)FPGA里面的數(shù)組或者M(jìn)emory指定位置(地址)處的元素進(jìn)行覆蓋更新。

? ? ? ? 這個(gè)VI本質(zhì)上調(diào)用的是一個(gè)DLL函數(shù),里面實(shí)際上包含了兩個(gè)系統(tǒng)函數(shù),分別是lseek文件位置偏移函數(shù)和write文件內(nèi)容寫入函數(shù)。如果用戶不會(huì)LabVIEW的話,也可以使用其他語言,比如Python、C++、C#等直接調(diào)用系統(tǒng)里面的lseek和write函數(shù)就可以實(shí)現(xiàn)圖7-86這個(gè)VI一樣的功能和效果,因此我們開發(fā)的FPGA PCIe CLIP不限制操作系統(tǒng),無論是Windows還是Linux都支持。
?????? ?下面我們雙擊打開這個(gè)VI,看看程序框圖里面有沒有什么需要注意的地方,如圖7-87所示。

? ? ? ? 為了讓用戶在執(zhí)行寫入操作的時(shí)候,地址是連續(xù)的,也就是地址0對(duì)應(yīng)的U16或者U32數(shù)組的第1個(gè)元素,地址1對(duì)應(yīng)第2個(gè)元素,地址2對(duì)應(yīng)第3個(gè)元素,以此類推,而不需要考慮什么雙字節(jié)或者4字節(jié)的倍數(shù)整除問題,為此,我們特地在這個(gè)VI里面加入了一個(gè)轉(zhuǎn)換公式,如圖7-88~7-89所示,其中,16位位寬的Memory地址,我們下發(fā)之前給FPGA的地址做了×2+1偏移;對(duì)于32位位寬的Memory地址做了×4+3偏移。


? ? ? ? 最后需要注意的一點(diǎn)是:PCIe Memory跟FIFO一樣,對(duì)超過8位位寬的數(shù)據(jù),存在大小端格式轉(zhuǎn)換問題,在前面7.6節(jié)已經(jīng)強(qiáng)調(diào)過了,為了減少FPGA資源的消耗,鑒于Memory讀寫速度很慢,我們特地將大小端格式轉(zhuǎn)換代碼放在了上位機(jī),也就是本節(jié)介紹的Memory通道讀寫VI里面,如圖7-90所示。先利用“強(qiáng)制類型轉(zhuǎn)換”函數(shù)將U16或者U32轉(zhuǎn)換成字節(jié)數(shù)組,再利用“反轉(zhuǎn)一維數(shù)組”函數(shù)進(jìn)行反轉(zhuǎn)就可以了。

? ? ? ? 6)FPGA_Memory_Write_Poly_Close_DLW30.vi
?????? ?當(dāng)我們需要退出上位機(jī)應(yīng)用程序之前,需要利用這里的“FPGA_Memory_Write_Poly_Close_DLW30.vi”函數(shù)來把先前打開的PCIe Memory寫通道引用句柄指針關(guān)掉,如圖7-91所示。防止出現(xiàn)內(nèi)存泄露,更重要的是,如果不執(zhí)行關(guān)閉的話,下次再打開這個(gè)通道的時(shí)候,會(huì)提示這個(gè)通道被占用了,所以務(wù)必要檢查一下之前開啟的通道句柄是否銷毀了。另外,為了保證Memory通道引用被強(qiáng)制關(guān)閉,可以在輸入錯(cuò)誤簇前面加一個(gè)“清除錯(cuò)誤簇”函數(shù),防止上游的錯(cuò)誤導(dǎo)致該VI不執(zhí)行關(guān)閉功能。

?????? ?總結(jié):用戶在熟悉和掌握了上面介紹的這6個(gè)PCIe Memory子函數(shù)(VI)的含義和用法之后,就可以大刀闊斧的開始編寫PCIe Memory上位機(jī)應(yīng)用程序了。關(guān)于這部分內(nèi)容,我們會(huì)在后面的實(shí)驗(yàn)68里面,做詳細(xì)的講解。