操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    )摘自【正點(diǎn)原子】領(lǐng)航者 ZYNQ 之嵌入式開發(fā)指南

    2)實(shí)驗(yàn)平臺:正點(diǎn)原子領(lǐng)航者ZYNQ開發(fā)板
    3)平臺購買地址:https://item.taobao.com/item.htm?&id=606160108761
    4)全套實(shí)驗(yàn)源碼+手冊+視頻下載:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
    5)對正點(diǎn)原子FPGA感興趣的同學(xué)可以加群討論:876744900
    6)關(guān)注正點(diǎn)原子公眾號,獲取最新資料

    第三十五章基于lwip的tftp server實(shí)驗(yàn)


    文件傳輸是網(wǎng)絡(luò)環(huán)境中的一項(xiàng)基本應(yīng)用,其作用是將一臺電子設(shè)備中的文件傳輸?shù)搅硪慌_可能相距很遠(yuǎn)的電子設(shè)備中。TFTP作為TCP/IP協(xié)議族中的一個用來在客戶機(jī)與服務(wù)器之間進(jìn)行文件傳輸?shù)膮f(xié)議,常用于無盤工作站、路由器以及遠(yuǎn)程測控設(shè)備從主機(jī)上獲取引導(dǎo)配置文件,實(shí)現(xiàn)遠(yuǎn)程升級。由于TFTP簡單且易實(shí)現(xiàn),本實(shí)驗(yàn)我們使用lwip協(xié)議棧實(shí)現(xiàn)TFTP Server的功能。本章包括以下幾個部分:
    3535.1簡介
    35.2實(shí)驗(yàn)任務(wù)
    35.3硬件設(shè)計(jì)
    35.4軟件設(shè)計(jì)
    35.5下載驗(yàn)證
    35.1簡介
    一、TFTP簡介(基于RFC1350版本)
    簡單文件傳輸協(xié)議TFTP (Trivial File Transfer Protocol) 是TCP/IP協(xié)議族中的一個用來在客戶機(jī)與服務(wù)器之間進(jìn)行簡單文件傳輸,基于UDP實(shí)現(xiàn)的應(yīng)用層協(xié)議,提供不復(fù)雜、開銷不大的文件傳輸服務(wù),端口號為 69。為了保證文件可靠傳輸TFTP有自己的差錯改正措施。TFTP 只支持文件傳輸、不支持交互、沒有龐大的命令集,也沒有目錄列表功能,以及不能對用戶進(jìn)行身份鑒別。
    與常用的文件傳送協(xié)議 FTP (File Transfer Protocol) 相比,F(xiàn)TP基于TCP協(xié)議,提供交互式的訪問,允許客戶指明文件的類型與格式、允許執(zhí)行對目錄和文件的訪問,并且可以完成特定類型的目錄操作以及需要進(jìn)行身份驗(yàn)證。
    可以說FTP是完整的、面向會話、常規(guī)用途的文件傳輸協(xié)議,而TFTP相當(dāng)于用作特殊目的簡化版的FTP。
    TFTP的主要優(yōu)點(diǎn)有兩個。
    第一,TFTP可用于UDP環(huán)境。例如,當(dāng)需要將程序或文件同時(shí)向許多機(jī)器下載時(shí)就往往需要使用TFTP。
    第二,TFTP代碼所占的內(nèi)存較小。這對較小的計(jì)算機(jī)或某些特殊用途的設(shè)備(如無盤工作站等)是很重要的。這些設(shè)備不需要硬盤,只需要固化了TFTP、UDP和IP的小容量只讀存儲器即可。當(dāng)接通電源后,設(shè)備執(zhí)行只讀存儲器中的代碼,在網(wǎng)絡(luò)上廣播一個TFTP請求。網(wǎng)絡(luò)上的TFTP服務(wù)器就發(fā)送響應(yīng),其中包括可執(zhí)行二進(jìn)制程序。設(shè)備收到此文件后將其放入內(nèi)存,然后開始運(yùn)行程序。這種方式增加了靈活性,也減少了開銷。
    TFTP的主要特點(diǎn)如下:
    (1)每次傳送的數(shù)據(jù)報(bào)文中有512字節(jié)的數(shù)據(jù),但最后一次可不足512字節(jié)。
    (2)數(shù)據(jù)報(bào)文按序編號,從1開始。
    (3)支持ASCII碼或二進(jìn)制傳送。
    (4)可對文件進(jìn)行讀或?qū)憽?br>(5)使用很簡單的首部。
    (6)實(shí)現(xiàn)簡單而不是高的系統(tǒng)吞吐量
    二、TFTP的五種報(bào)文
    TFTP的報(bào)文格式如圖 35.1.1所示,可以看到TFTP有五種報(bào)文,每種報(bào)文有不同的操作碼,這五種報(bào)文分別是:RRQ、WRQ、DATA、ACK和ERROR報(bào)文。下面我們簡單的介紹下這五種報(bào)文。
    RRQ/WRQ報(bào)文
    模式字段中,包含兩種字符串中的一種,"netascii"表示ASCII文件,"octet"表示二進(jìn)制文件。對于RRQ,客戶向TFTP服務(wù)器發(fā)送讀請求后,服務(wù)器返回一個塊編號為1的DATA報(bào)文。而對于WRQ,客戶向TFTP服務(wù)器發(fā)送寫請求后,服務(wù)器返回的是塊編號為1的ACK報(bào)文。總之,不管是RRQ還是WRQ,接收DATA數(shù)據(jù)的一方發(fā)送ACK確認(rèn),而發(fā)送DATA數(shù)據(jù)的一方只負(fù)責(zé)發(fā)送數(shù)據(jù)。

    圖 35.1.1 TFTP報(bào)文格式


    a)DATA報(bào)文
    發(fā)送方用于傳送數(shù)據(jù)塊。所有的塊都用數(shù)字順序編碼,從1開始。在所有的DATA報(bào)文中,這個塊必須準(zhǔn)確地等于512Byte,但最后一個塊可以小于或等于512Byte。當(dāng)發(fā)送的DATA報(bào)文中數(shù)據(jù)部分的長度小于512Byte,表示DATA報(bào)文發(fā)送完畢,所以小于數(shù)據(jù)部分512Byte的DATA數(shù)據(jù)報(bào)可以作為文件結(jié)束的標(biāo)志。特殊的情況是,當(dāng)文件中的數(shù)據(jù)正好是512Byte的整數(shù)倍時(shí),那么發(fā)送端必須再發(fā)送一個具有數(shù)據(jù)部分為0Byte的額外的DATA數(shù)據(jù)塊以表示傳輸?shù)慕Y(jié)束。數(shù)據(jù)可以采用ASCII碼或二進(jìn)制來傳送。
    b)ACK報(bào)文
    塊號表示它所收到的塊號(不是下一個期待的塊號,這與TCP中的ACK序號不同)。特殊情況是,當(dāng)客戶向服務(wù)器發(fā)送一個WRQ請求后,服務(wù)器返回給客戶的是一個塊號為0的ACK報(bào)文,表示服務(wù)器已經(jīng)準(zhǔn)備好了接收來自客戶的數(shù)據(jù)報(bào)。
    c)EEROR報(bào)文(差錯報(bào)文)
    ERROR報(bào)文既可以由客戶發(fā)送,也可以由服務(wù)器發(fā)送,當(dāng)一條連接(如讀連接或?qū)戇B接)不能建立或在數(shù)據(jù)傳輸中出現(xiàn)問題時(shí)使用。差錯碼定義了差錯的類型,差錯信息是一個可變字節(jié),包含原文中的差錯數(shù)據(jù)。
    從上面的報(bào)文格式中可以看出,TFTP報(bào)文沒有差錯檢驗(yàn)和字段,所以接收端檢驗(yàn)數(shù)據(jù)是否出現(xiàn)差錯的唯一方法是通過該TFTP數(shù)據(jù)報(bào)的UDP首部中的檢驗(yàn)和字段。
    三、TFTP傳輸過程
    以TFTP客戶端向 TFTP 服務(wù)器發(fā)送寫請求為例,說明整個過程。
    1)服務(wù)器使用默認(rèn)端口號69被動打開連接;
    2)客戶主動打開連接,向服務(wù)器進(jìn)程發(fā)送WRQ報(bào)文,報(bào)文中包含寫入文件的文件名;
    3)TFTP服務(wù)器進(jìn)程選擇一個新的端口和TFTP客戶進(jìn)程進(jìn)行通信,并向TFTP客戶進(jìn)程發(fā)送塊編號為0的的ACK報(bào)文;
    4)客戶端收到服務(wù)器的ACK報(bào)文后發(fā)送DATA報(bào)文,數(shù)據(jù)段為512Byte,少于512Byte表明是文件的最后的數(shù)據(jù),塊編號逐次遞增;
    5)TFTP服務(wù)器校驗(yàn)收到的DATA報(bào)文的塊編號,如果校驗(yàn)正確則將數(shù)據(jù)寫入文件,并發(fā)送ACK報(bào)文表明已接收到數(shù)據(jù),ACK報(bào)文的塊編號為本次接收的DATA報(bào)文的塊編號。另外還判斷數(shù)據(jù)段長度是否小于512 Byte,小于則表明文件傳輸完成,關(guān)閉連接,如果等于512Byte,則重復(fù)步驟4-5,直到所有請求的數(shù)據(jù)發(fā)送完畢。
    從上面的傳輸過程可以看出,TFTP 是一種類似于停止等待協(xié)議(不是真正的停止等待協(xié)議,在停止等待協(xié)議中,接收方發(fā)送的 ack 表示期望收到的下一個分組,而在 TFTP 的 ACK 報(bào)文中,ACK的塊號表示的是本次成功收到的數(shù)據(jù)塊,而不是下一個期望的下一個數(shù)據(jù)塊)。TFTP 客戶端只有收到服務(wù)器的確認(rèn)報(bào)文ACK后才會接著向服務(wù)器發(fā)送新的數(shù)據(jù)。
    另外需要注意的是TFTP 協(xié)議中,用于讀文件的連接和用于寫文件的連接的建立方式不同:建立讀連接的時(shí)候,客戶首先向服務(wù)器發(fā)送 RRQ 讀報(bào)文,服務(wù)器收到該報(bào)文后,直接發(fā)回給該客戶 DATA 報(bào)文,并且包含第一個數(shù)據(jù)塊(塊號為 1)。而建立寫連接的時(shí)候,客戶首先先服務(wù)器發(fā)送 WRQ 寫報(bào)文,服務(wù)器收到該報(bào)文后,則發(fā)回給客戶 ACK 報(bào)文,使用的塊號為 0;當(dāng)然上面兩種情況如果遇到請求報(bào)文出錯時(shí),均會發(fā)回 ERROR 報(bào)文作為響應(yīng)。
    35.2實(shí)驗(yàn)任務(wù)
    本章的實(shí)驗(yàn)任務(wù)是使用LWIP協(xié)議棧搭建TFTP服務(wù)器,PC電腦上的客戶端可以從TFTP服務(wù)器讀取文件也可向TFTP服務(wù)器寫入文件,文件存放在SD卡中。
    35.3硬件設(shè)計(jì)
    根據(jù)實(shí)驗(yàn)任務(wù)我們可以畫出本次實(shí)驗(yàn)的系統(tǒng)框圖,如下圖所示:

    圖 35.3.1 系統(tǒng)框圖


    在圖 5.3.1中,UART用于打印程序相關(guān)的信息,LWIP通過以太網(wǎng)傳輸數(shù)據(jù),SD用于存放文件,包括服務(wù)器創(chuàng)建的文件和客戶端寫入的文件。
    step1:創(chuàng)建Vivado工程
    本次實(shí)驗(yàn)的硬件設(shè)計(jì)可以在《LWIP echo server》實(shí)驗(yàn)的基礎(chǔ)上添加SD卡。
    1-1 我們先打開《LWIP echo server》實(shí)驗(yàn)的Vivado工程,打開后將工程另存為 “l(fā)wip_tftp_server”工程,然后點(diǎn)擊“OK”按鈕。
    step2:使用IP Integrator創(chuàng)建Processing System
    2-1 在Vivado界面左側(cè)的Flow Navigator中,點(diǎn)擊IP INTEGRATOR下的Open Block Design以打開Diagram窗口。
    2-2 在打開的下圖Diagram窗口,雙擊打開ZYNQ7 Processing System重定義窗口。

    圖 35.3.2 重定義ZYNQ7 Processing System


    2-3 在下圖所示的重定義窗口,如同《SD卡讀寫TXT文本實(shí)驗(yàn)》那樣配置SD卡。點(diǎn)擊左側(cè)的MIO Configuration,在右側(cè)的界面中展開“I/O Peripherals”,勾選“SD 0”,在“IO”列選擇SD 0的IO為“MIO40…45”,如下圖所示。

    圖 35.3.3 PS以太網(wǎng)接口配置界面


    2-4 由于不需要添加其它IP,按Ctrl+S快捷鍵保存Diagram。此時(shí)我們的第二步完成,進(jìn)入第三步
    step3:生成頂層HDL
    在sources面板中,右鍵點(diǎn)擊Block Design設(shè)計(jì)文件“system.bd”,然后依次執(zhí)行“Generate Output Products”和“Create HDL Wrapper”。
    step4:生成Bitstream文件并導(dǎo)出到SDK
    由于本實(shí)驗(yàn)未用到PL部分,所以無需生成Bitstream文件,只需導(dǎo)出到SDK即可。如果使用到PL,則需要添加引腳約束以及對該系統(tǒng)進(jìn)行綜合、實(shí)現(xiàn)并生成Bitstream文件。
    4-1 導(dǎo)出硬件。
    在菜單欄中選擇 File > Export > Export hardware。
    并在彈出的對話框中,取消勾選“Include bitstream”,直接點(diǎn)擊“OK”按鈕。
    因?yàn)槭窃谇耙还こ痰幕A(chǔ)上建立的,還保留著前一工程的結(jié)果,所以會彈出“Module Already Exported”對話框,我們點(diǎn)擊“Yes”按鈕。
    4-2 硬件導(dǎo)出完成后,選擇菜單File->Launch SDK,啟動SDK開發(fā)環(huán)境。
    35.4軟件設(shè)計(jì)
    下面步驟操作比較麻煩,實(shí)際意義也不大,可以直接使用我們提供的例程里的SDK軟件工程。
    此處我們刪除《LWIP echo server》實(shí)驗(yàn)的應(yīng)用工程,保留bsp工程。下面我們開始第五步——創(chuàng)建應(yīng)用工程。下面我們開始第五步——創(chuàng)建應(yīng)用工程
    step5:在SDK中創(chuàng)建應(yīng)用工程
    5-1在菜單欄中選擇“File->New->Application Project”,
    在彈出的界面中,輸入工程名“l(fā)wip_tftp_server”,然后選擇“Next >”,在下一界面選擇“Empty Application”,然后點(diǎn)擊“Finish”按鈕。
    5-2 在Project Explorer中,鼠標(biāo)右鍵點(diǎn)擊“l(fā)wip_tftp_server _bsp”,在彈出的菜單中選擇“Board Support Package Settings”,如下圖所示:
    彈出對BSP的設(shè)置界面,勾選“l(fā)wip202”和“xilffs”以啟用lwip和文件系統(tǒng),如圖 35.4.1所示。
    如果沒有開啟DHCP服務(wù)可以開啟DHCP服務(wù),點(diǎn)擊standalone下的lwip202,在右側(cè)界面中到“dhcp_options”,將其下的兩個選項(xiàng)的“Value”設(shè)置為“true”,如圖 35.4.2所示。

    圖 35.4.1 BSP的設(shè)置界面


    圖 35.4.2 開啟DHCP
    5-3 由于Xilinx提供的lwip例程里有TFTP server的源代碼,所以我們無需自己手動編寫,直接添加即可。
    雙擊打開“l(fā)wip_tftp_server”目錄下的system.mss文件。在system.mss文件的底部單擊“Import Example”,如下圖所示。

    圖 35.4.3 Import lwip Example


    5-4 在彈出的下圖所示界面中,點(diǎn)擊下方的“Examples Directory”。

    圖 35.4.4 platform_config.h文件內(nèi)容


    5-5 打開例程所在文件的目錄,里面有Xilinx關(guān)于lwip的全部例程源文件。我們選擇本次實(shí)驗(yàn)需要的源文件,如圖 35.4.6所示,并單擊鼠標(biāo)右鍵選擇復(fù)制。復(fù)制完成后,在打開的圖 35.4.5界面中,點(diǎn)擊“Cancel”退出。

    圖 35.4.6 例程所在文件的目錄


    5-6 單擊SDK軟件的lwip_tftp_server/src目錄,按下粘貼快捷鍵“Ctrl-v”,將復(fù)制的文件粘貼到該src目錄下,如下圖所示。

    圖 35.4.7 src目錄


    5-7 為了方便分析,我們將剛才復(fù)制到src目錄的源文件重命名,主要是刪除不需要的前綴,其中“l(fā)wip_example_tftpserver_common.h”改為“l(fā)wip_tftp_server.h”,如下圖所示:

    圖 35.4.8 刪除不相關(guān)文件后的src文件夾內(nèi)容


    5-8 修復(fù)錯誤。
    由于重命名了“l(fā)wip_example_tftpserver_common.h”,所以需要將lwip_tftp_server.c源文件的 #include "lwip_tftpserver_common.h"修改為#include "lwip_tftp_server.h",如下圖所示:

    圖 35.4.9 修改為#include "lwip_tftp_server.h"


    此處為了方便顯示,將其注釋,實(shí)際在文件內(nèi)直接修改第34行即可。
    打開platform_fs.c源文件,添加BYTE work[FF_MAX_SS](第9行),修改f_mkfs函數(shù)的調(diào)用,第15行,修改后的內(nèi)容如下:

    1. 1 #include "ff.h"
    2. 2 #include "xil_printf.h"
    3. 3
    4. 4 int platform_init_fs()
    5. 5 {
    6. 6 static FATFS fatfs;
    7. 7 FRESULT Res;
    8. 8 TCHAR *Path="0:/";
    9. 9 BYTE work[FF_MAX_SS];
    10. 10
    11. 11 /* Try to mount FAT file system */
    12. 12 Res=f_mount(&fatfs, Path, 1);
    13. 13 if (Res !=FR_OK) {
    14. 14 xil_printf("Volume is not FAT formated; formating FAT\r\n");
    15. 15 Res=f_mkfs(Path, FM_FAT32, 0, work, sizeof work);
    16. 16 if (Res !=FR_OK) {
    17. 17 xil_printf("Unable to format FATfs\r\n");
    18. 18 return -1;
    19. 19 }
    20. 20
    21. 21 Res=f_mount(&fatfs, Path, 1);
    22. 22 if (Res !=FR_OK) {
    23. 23 xil_printf("Unable to mount FATfs\r\n");
    24. 24 return -1;
    25. 25 }
    26. 26 }
    27. 27 xil_printf("File system initialization successful\r\n");
    28. 28
    29. 29 return 0;
    30. 30 }



    該文件在初始化平臺時(shí)由init_platform函數(shù)調(diào)用,用于掛載SD卡,掛載不成功就將SD卡格式化成FAT32,格式化成功后再嘗試掛載。
    5-9 現(xiàn)在我們打開main.c文件,為了方便分析源代碼,在main.c文件中將帶有下圖箭頭所指的預(yù)編譯指令刪除。

    圖 35.4.10 刪除不需要的預(yù)編譯指令


    刪除不適用的預(yù)編譯指令后的main.c代碼與我們《lwip echo server實(shí)驗(yàn)》的main.c代碼基本相同,區(qū)別在于本次TFTP server實(shí)驗(yàn)沒有使用IPv6,所以沒有IPv6的預(yù)編譯指令,其他完全相同,main.c代碼講解見《lwip echo server實(shí)驗(yàn)》。
    5-10本實(shí)驗(yàn)可以說是在《lwip echo server實(shí)驗(yàn)》的基礎(chǔ)上增加了文件系統(tǒng),然后將Echo server的實(shí)現(xiàn)文件echo.c文件改寫成了TFTP Server的實(shí)現(xiàn)文件。因而本實(shí)驗(yàn)的主要代碼是TFTP Server的實(shí)現(xiàn),該實(shí)現(xiàn)在lwip_tftp_server.h和lwip_tftp_server.c中,由于這兩個文件的總代碼有近500行,因此我們挑選部分代碼進(jìn)行講解。此處以客戶端寫文件為例講解lwip_tftp_server.c中的寫文件實(shí)現(xiàn)源碼。講解以函數(shù)調(diào)用順序進(jìn)行。
    首先我們看main函數(shù)中調(diào)用的start_application函數(shù),該函數(shù)實(shí)現(xiàn)如下:

    1. 342 void start_application()
    2. 343 {
    3. 344 struct udp_pcb *pcb;
    4. 345 err_t err;
    5. 346
    6. 347 //創(chuàng)建測試文件用于客戶端讀取
    7. 348 err=tftp_create_test_file();
    8. 349 if (err) {
    9. 350 xil_printf("Unable to create test file\r\n");
    10. 351 return;
    11. 352 }
    12. 353
    13. 354 //創(chuàng)建新的UDP PCB
    14. 355 pcb=udp_new();
    15. 356 if (!pcb) {
    16. 357 xil_printf("Error creating PCB. Out of Memory\r\n");
    17. 358 return;
    18. 359 }
    19. 360
    20. 361 //綁定端口
    21. 362 err=udp_bind(pcb, IP_ADDR_ANY, TFTP_PORT);
    22. 363 if (err !=ERR_OK) {
    23. 364 xil_printf("Unable to bind to port %d; err %d\r\n",
    24. 365 TFTP_PORT, err);
    25. 366 udp_remove(pcb);
    26. 367 return;
    27. 368 }
    28. 369 //設(shè)置接收回調(diào)函數(shù)
    29. 370 udp_recv(pcb, (udp_recv_fn) tftp_server_recv_cb, NULL);
    30. 371 }



    可以看到該函數(shù)首先通過調(diào)用tftp_create_test_file函數(shù)創(chuàng)建了測試文件,用于tftp客戶端讀取tftp服務(wù)器的文件數(shù)據(jù),測試文件名為sample#.txt,其中“#”為數(shù)字1、2、3中的任一值,其文件內(nèi)容為“----- This is a test file for TFTP server application -----”。如果不執(zhí)行客戶端的讀取文件請求,可刪除該函數(shù)的調(diào)用及其實(shí)現(xiàn)。
    由于TFTP基于UDP協(xié)議,從start_application函數(shù)可以看到lwip中使用UDP協(xié)議很簡單。首先通過udp_new函數(shù)創(chuàng)建一個新的UDP PCB,然后調(diào)用udp_bind函數(shù)綁定端口號,IP_ADDR_ANY表明為任意本地地址,TFTP_PORT是在lwip_tftp_server.h宏定義的端口號,其值為69,即TFTP的默認(rèn)端口。最后調(diào)用udp_recv函數(shù)設(shè)置接收回調(diào)函數(shù)就完成了UDP服務(wù)的創(chuàng)建,服務(wù)端的功能幾TFTP協(xié)議由回調(diào)函數(shù)實(shí)現(xiàn)。回調(diào)函數(shù)代碼如下:

    1. 260 //UDP接收回調(diào)函數(shù)
    2. 261 static void tftp_server_recv_cb(void *arg, struct udp_pcb *upcb, struct pbuf *p,
    3. 262 ip_addr_t *ip, u16_t port)
    4. 263 {
    5. 264 tftp_opcode op=tftp_get_opcode(p->payload);
    6. 265 char fname[512];
    7. 266 struct udp_pcb *pcb;
    8. 267 err_t err;
    9. 268
    10. 269 pcb=udp_new();
    11. 270 if (!pcb) {
    12. 271 xil_printf("Error creating PCB. Out of Memory\r\n");
    13. 272 goto cleanup;
    14. 273 }
    15. 274
    16. 275 //綁定到端口0以接收下一個可用的空閑端口
    17. 276 err=udp_bind(pcb, IP_ADDR_ANY, 0);
    18. 277 if (err !=ERR_OK) {
    19. 278 xil_printf("Unable to bind to port %d; err %d\r\n", port, err);
    20. 279 goto cleanup;
    21. 280 }
    22. 281
    23. 282 switch (op) {
    24. 283 case TFTP_RRQ:
    25. 284 //從payload中獲取文件名
    26. 285 strcpy(fname, p->payload + FIL_NAME_OFFSET);
    27. 286 printf("TFTP RRQ (read request): %s\r\n", fname);
    28. 287 tftp_process_read(pcb, ip, port, fname);
    29. 288 break;
    30. 289 case TFTP_WRQ:
    31. 290 strcpy(fname, p->payload + FIL_NAME_OFFSET);
    32. 291 printf("TFTP WRQ (write request): %s\r\n", fname);
    33. 292 tftp_process_write(pcb, ip, port, fname);
    34. 293 break;
    35. 294 default:
    36. 295 //發(fā)送訪問沖突消息
    37. 296 tftp_send_error_packet(pcb, ip, port, TFTP_ERR_ILLEGALOP);
    38. 297 printf("TFTP unknown request op: %d\r\n\r\n", op);
    39. 298 udp_remove(pcb);
    40. 299 break;
    41. 300 }
    42. 301
    43. 302 cleanup:
    44. 303 pbuf_free(p);
    45. 304 }



    當(dāng)TFTP客戶端發(fā)起寫入或讀取文件的請求后,lwip協(xié)議棧調(diào)用回調(diào)函數(shù)tftp_server_recv_cb。該回調(diào)函數(shù)通過tftp_get_opcode宏獲取客戶端發(fā)送報(bào)文的操作碼,不同的操作碼執(zhí)行該函數(shù)switch分支中的不同的case,如對于寫入文件請求,則執(zhí)行“case TFTP_WRQ”分支語句,該分支語句調(diào)用TFTP處理寫文件請求函數(shù)tftp_process_write,該函數(shù)實(shí)現(xiàn)如下:

    1. 223 //TFTP處理寫文件請求
    2. 224 static int tftp_process_write(struct udp_pcb *pcb, ip_addr_t *ip, int port,
    3. 225 char *fname)
    4. 226 {
    5. 227 tftp_connection_args *conn;
    6. 228 FIL w_fil;
    7. 229 FRESULT Res;
    8. 230
    9. 231 Res=f_open(&w_fil, fname, FA_CREATE_ALWAYS | FA_WRITE);
    10. 232 if (Res) {
    11. 233 xil_printf("Unable to open file %s for writing %d\r\n", fname,
    12. 234 Res);
    13. 235 tftp_send_error_packet(pcb, ip, port, TFTP_ERR_DISKFULL);
    14. 236 udp_remove(pcb);
    15. 237 return -1;
    16. 238 }
    17. 239
    18. 240 conn=mem_malloc(sizeof *conn);
    19. 241 if (!conn) {
    20. 242 xil_printf("Unable to allocate memory for tftp conn\r\n");
    21. 243 tftp_send_error_packet(pcb, ip, port, TFTP_ERR_DISKFULL);
    22. 244 udp_remove(pcb);
    23. 245 return -1;
    24. 246 }
    25. 247
    26. 248 memcpy(&conn->fil, &w_fil, sizeof(w_fil));
    27. 249 conn->block=0;
    28. 250
    29. 251 //為該pcb設(shè)置接收回調(diào)
    30. 252 udp_recv(pcb, (udp_recv_fn) tftp_server_write_req_recv_cb, conn);
    31. 253
    32. 254 //通過發(fā)送第一個ACK來啟動傳輸
    33. 255 tftp_send_ack_packet(pcb, ip, port, conn->block);
    34. 256
    35. 257 return 0;
    36. 258 }



    該函數(shù)首先在文件系統(tǒng)中創(chuàng)建一個文件,文件名為客戶端寫入的文件名,然后為新創(chuàng)建的UDP PCB設(shè)置接收回調(diào)函數(shù),用于處理后面接收客戶端傳入的文件,最后發(fā)送塊編號為0的ACK報(bào)文以應(yīng)答客戶端啟動傳輸。TFTP寫入請求的接收回調(diào)函數(shù)實(shí)現(xiàn)如下:

    1. 181 //TFTP寫入請求的接收回調(diào)函數(shù)
    2. 182 static void tftp_server_write_req_recv_cb(void *_args, struct udp_pcb *upcb,
    3. 183 struct pbuf *p, ip_addr_t *addr, u16_t port)
    4. 184 {
    5. 185 ip_addr_t ip=*addr;
    6. 186 tftp_connection_args *args=(tftp_connection_args *)_args;
    7. 187
    8. 188 if (p->len !=p->tot_len) {
    9. 189 xil_printf("TFTP_WRQ: Tftp server does not support "
    10. 190 "chained pbufs\r\n");
    11. 191 pbuf_free(p);
    12. 192 return;
    13. 193 }
    14. 194
    15. 195 //確保數(shù)據(jù)塊是我們所期望的
    16. 196 if ((p->len >=TFTP_PACKET_HDR_LEN) &&
    17. 197 (tftp_get_block_value(p->payload)==(u16_t) (args->block + 1))) {
    18. 198 //將接收的數(shù)據(jù)寫入文件
    19. 199 unsigned int n;
    20. 200 f_write(&args->fil, p->payload + TFTP_PACKET_HDR_LEN,
    21. 201 p->len - TFTP_PACKET_HDR_LEN, &n);
    22. 202 if (n !=p->len - TFTP_PACKET_HDR_LEN) {
    23. 203 xil_printf("TFTP_WRQ: Write to file error\r\n");
    24. 204 tftp_send_error_packet(upcb, &ip, port,
    25. 205 TFTP_ERR_DISKFULL);
    26. 206 pbuf_free(p);
    27. 207 return tftp_cleanup(upcb, args);
    28. 208 }
    29. 209 args->block++;
    30. 210 }
    31. 211
    32. 212 tftp_send_ack_packet(upcb, &ip, port, args->block);
    33. 213
    34. 214 //如果接收到的數(shù)據(jù)段長度小于指定的字節(jié)數(shù),則表明已經(jīng)接收了整個文件,因此可以退出
    35. 215 if (p->len < TFTP_DATA_PACKET_MSG_LEN) {
    36. 216 xil_printf("TFTP_WRQ: Transfer completed\r\n\r\n");
    37. 217 return tftp_cleanup(upcb, args);
    38. 218 }
    39. 219
    40. 220 pbuf_free(p);
    41. 221 }



    從該回調(diào)函數(shù)可以看到,TFTP服務(wù)端對客戶端發(fā)送的數(shù)據(jù)報(bào)文的塊編號進(jìn)行校驗(yàn),如果不是我們期望的塊編號就重發(fā)上一次發(fā)送的ACK報(bào)文,如果是期望的塊編號,就將數(shù)據(jù)寫入文件中,然后遞增塊編號,并發(fā)送ACK報(bào)文給客戶端以確認(rèn)收到數(shù)據(jù)。
    在該函數(shù)的最后判斷接收到的數(shù)據(jù)段長度是否小于指定的字節(jié)數(shù)TFTP_DATA_PACKET_MSG_LEN,如果是,則表明已經(jīng)接收了整個文件,因此可以結(jié)束連接。TFTP_DATA_PACKET_MSG_LEN在lwip_tftp_server.h宏定義為512。
    以上大概的講解了TFTP Server接收客戶端寫入文件的實(shí)現(xiàn)。下面我們進(jìn)行實(shí)際操作,看看TFTP客戶端是否能向服務(wù)器寫入文件。
    35.5下載驗(yàn)證
    首先我們將下載器與領(lǐng)航者底板上的JTAG接口連接,下載器另外一端與電腦連接。然后使用Mini USB連接線將USB UART接口與電腦連接,用于串口通信。使用網(wǎng)線一端連接領(lǐng)航者開發(fā)板的以太網(wǎng)接口,另一端與電腦或路由器連接。連接完成后,在開發(fā)板上插入SD 卡或者插入帶卡套(適配器)的 TF 卡(SD 卡插槽位于開發(fā)板背面)。最后連接開發(fā)板的電源,并打開電源開關(guān)。如下圖所示:

    圖 35.5.1領(lǐng)航者ZYNQ開發(fā)板實(shí)物圖


    現(xiàn)在進(jìn)入最后一步。
    step6:板級驗(yàn)證
    6-1 在SDK軟件的下方的SDK Terminal窗口中點(diǎn)擊右上角的加號連接串口。
    6-2 下載程序。下載完成后,可以看到串口打印的結(jié)果如下:

    圖 35.5.2 顯示打印結(jié)果


    其中“File system initialization successful”表明SD卡可以正常工作。打印的最后一句表明了該實(shí)驗(yàn)如何使用。由于是TFTP服務(wù)器實(shí)驗(yàn),所以我們需要TFTP客戶端,可以從網(wǎng)上下載,也可以使用Windows系統(tǒng)的CMD命令行界面,如果開啟了TFTP客戶端,開啟方法見步驟6-6。
    6-3 下面我們先創(chuàng)建一個文件用來傳輸?shù)絋FTP服務(wù)器。文件存放位置任意,文件內(nèi)容任意。
    我們在Vivado工程目錄新建一個名為“test”的文件夾,里面新建一個名為testfile.txt的文件,文件內(nèi)容為“這只是一個測試文件。”,如下圖所示:

    圖 35.5.3 新建一個名為test_file.txt的文件


    6-4 我們打開電腦的CMD(按win+r鍵后輸入cmd),然后輸入命令“cd /D F:\ZYNQ\Embedded_System\lwip_tftp_server\test”切換到 “F:\ZYNQ\Embedded_System\lwip_tftp_server\test”目錄下,如下圖所示:

    圖35.5.4 切換到上傳文件所在的目錄


    然后輸入“tftp -i 192.168.1.10 PUT testfile.txt”命令,回車,會顯示傳輸成功字樣,如下圖所示:

    圖 35.5.5 進(jìn)行tftp連接


    此時(shí)SDK串口終端也會打印如下信息:

    圖 35.5.6 串口終端打印寫入完成信息


    如果回車后出現(xiàn)像下圖所示界面所示“tftp不是內(nèi)部或外部命令,也不是可運(yùn)行的程序或批處理文件”,則表明未開啟Windows的tftp客戶端功能,開啟方式見6-5。

    圖 35.5.7 未啟用tftp客戶端時(shí)的界面


    向服務(wù)器寫入文件剛才測試完成了,現(xiàn)在測試從服務(wù)器端讀取文件,可以讀取剛才寫入的文件,也可以讀取服務(wù)器程序創(chuàng)建的測試文件。下面我們以讀取服務(wù)器程序創(chuàng)建的測試文件為例,進(jìn)行讀取文件測試。
    在CMD中輸入“tftp -i 192.168.1.10 GET sample1.txt”命令,然后回車,會顯示傳輸成功字樣,如下圖所示:

    圖 35.5.8 輸入讀取文件命令


    此時(shí)SDK串口終端也會打印如下信息:

    圖 35.5.9 讀取成功


    此時(shí)我們打開test文件夾,會看到其中新增了sample1.txt,雙擊打開,其內(nèi)容如下:

    圖 35.5.10 讀取的sample1.txt文件


    可以看到讀取文件測試成功。現(xiàn)在我們把SD卡插到電腦上,查看其內(nèi)容如下:

    圖 35.5.11 SD卡上的文件


    可以看到客戶端上傳給TFTP服務(wù)器的文件確實(shí)寫到SD卡中。
    6-5 下面我們介紹一下如何開啟Windows的tftp客戶端功能。在Win10或Win7系統(tǒng)中,按“Win+r”快捷鍵后,在下圖所示界面中輸入“control”。

    圖 35.5.12 打開控制面板界面


    進(jìn)入下圖所示控制面板界面,將查看方式設(shè)置為“類別”,單擊“程序”下的“卸載程序”,如下圖所示:

    圖 35.5.13 點(diǎn)擊進(jìn)入“程序和功能”界面


    在彈出的界面中,單擊“啟用或關(guān)閉Windows功能”,如下圖所示:

    圖 35.5.14 點(diǎn)擊“啟用或關(guān)閉Windows功能”


    在彈出的“Windows功能”界面中,找到“Tftp Client”,并勾選,如下圖所示:


    圖 35.5.15 勾選tftp client


    單擊確定后,如果出現(xiàn)“Windows需要重啟電腦才能完成安裝所請求的更改”字樣,重新啟動電腦即可。現(xiàn)在 Windows的tftp客戶端服務(wù)已啟用。
    至此,本實(shí)驗(yàn)完成。

    1)摘自【正點(diǎn)原子】領(lǐng)航者 ZYNQ 之嵌入式開發(fā)指南

    2)實(shí)驗(yàn)平臺:正點(diǎn)原子領(lǐng)航者ZYNQ開發(fā)板
    3)平臺購買地址:https://item.taobao.com/item.htm?&id=606160108761
    4)全套實(shí)驗(yàn)源碼+手冊+視頻下載:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
    5)對正點(diǎn)原子FPGA感興趣的同學(xué)可以加群討論:876744900
    6)關(guān)注正點(diǎn)原子公眾號,獲取最新資料

    第三十五章基于lwip的tftp server實(shí)驗(yàn)


    文件傳輸是網(wǎng)絡(luò)環(huán)境中的一項(xiàng)基本應(yīng)用,其作用是將一臺電子設(shè)備中的文件傳輸?shù)搅硪慌_可能相距很遠(yuǎn)的電子設(shè)備中。TFTP作為TCP/IP協(xié)議族中的一個用來在客戶機(jī)與服務(wù)器之間進(jìn)行文件傳輸?shù)膮f(xié)議,常用于無盤工作站、路由器以及遠(yuǎn)程測控設(shè)備從主機(jī)上獲取引導(dǎo)配置文件,實(shí)現(xiàn)遠(yuǎn)程升級。由于TFTP簡單且易實(shí)現(xiàn),本實(shí)驗(yàn)我們使用lwip協(xié)議棧實(shí)現(xiàn)TFTP Server的功能。本章包括以下幾個部分:
    3535.1簡介
    35.2實(shí)驗(yàn)任務(wù)
    35.3硬件設(shè)計(jì)
    35.4軟件設(shè)計(jì)
    35.5下載驗(yàn)證
    35.1簡介
    一、TFTP簡介(基于RFC1350版本)
    簡單文件傳輸協(xié)議TFTP (Trivial File Transfer Protocol) 是TCP/IP協(xié)議族中的一個用來在客戶機(jī)與服務(wù)器之間進(jìn)行簡單文件傳輸,基于UDP實(shí)現(xiàn)的應(yīng)用層協(xié)議,提供不復(fù)雜、開銷不大的文件傳輸服務(wù),端口號為 69。為了保證文件可靠傳輸TFTP有自己的差錯改正措施。TFTP 只支持文件傳輸、不支持交互、沒有龐大的命令集,也沒有目錄列表功能,以及不能對用戶進(jìn)行身份鑒別。
    與常用的文件傳送協(xié)議 FTP (File Transfer Protocol) 相比,F(xiàn)TP基于TCP協(xié)議,提供交互式的訪問,允許客戶指明文件的類型與格式、允許執(zhí)行對目錄和文件的訪問,并且可以完成特定類型的目錄操作以及需要進(jìn)行身份驗(yàn)證。
    可以說FTP是完整的、面向會話、常規(guī)用途的文件傳輸協(xié)議,而TFTP相當(dāng)于用作特殊目的簡化版的FTP。
    TFTP的主要優(yōu)點(diǎn)有兩個。
    第一,TFTP可用于UDP環(huán)境。例如,當(dāng)需要將程序或文件同時(shí)向許多機(jī)器下載時(shí)就往往需要使用TFTP。
    第二,TFTP代碼所占的內(nèi)存較小。這對較小的計(jì)算機(jī)或某些特殊用途的設(shè)備(如無盤工作站等)是很重要的。這些設(shè)備不需要硬盤,只需要固化了TFTP、UDP和IP的小容量只讀存儲器即可。當(dāng)接通電源后,設(shè)備執(zhí)行只讀存儲器中的代碼,在網(wǎng)絡(luò)上廣播一個TFTP請求。網(wǎng)絡(luò)上的TFTP服務(wù)器就發(fā)送響應(yīng),其中包括可執(zhí)行二進(jìn)制程序。設(shè)備收到此文件后將其放入內(nèi)存,然后開始運(yùn)行程序。這種方式增加了靈活性,也減少了開銷。
    TFTP的主要特點(diǎn)如下:
    (1)每次傳送的數(shù)據(jù)報(bào)文中有512字節(jié)的數(shù)據(jù),但最后一次可不足512字節(jié)。
    (2)數(shù)據(jù)報(bào)文按序編號,從1開始。
    (3)支持ASCII碼或二進(jìn)制傳送。
    (4)可對文件進(jìn)行讀或?qū)憽?br>(5)使用很簡單的首部。
    (6)實(shí)現(xiàn)簡單而不是高的系統(tǒng)吞吐量
    二、TFTP的五種報(bào)文
    TFTP的報(bào)文格式如圖 35.1.1所示,可以看到TFTP有五種報(bào)文,每種報(bào)文有不同的操作碼,這五種報(bào)文分別是:RRQ、WRQ、DATA、ACK和ERROR報(bào)文。下面我們簡單的介紹下這五種報(bào)文。
    RRQ/WRQ報(bào)文
    模式字段中,包含兩種字符串中的一種,"netascii"表示ASCII文件,"octet"表示二進(jìn)制文件。對于RRQ,客戶向TFTP服務(wù)器發(fā)送讀請求后,服務(wù)器返回一個塊編號為1的DATA報(bào)文。而對于WRQ,客戶向TFTP服務(wù)器發(fā)送寫請求后,服務(wù)器返回的是塊編號為1的ACK報(bào)文。總之,不管是RRQ還是WRQ,接收DATA數(shù)據(jù)的一方發(fā)送ACK確認(rèn),而發(fā)送DATA數(shù)據(jù)的一方只負(fù)責(zé)發(fā)送數(shù)據(jù)。

    圖 35.1.1 TFTP報(bào)文格式


    a)DATA報(bào)文
    發(fā)送方用于傳送數(shù)據(jù)塊。所有的塊都用數(shù)字順序編碼,從1開始。在所有的DATA報(bào)文中,這個塊必須準(zhǔn)確地等于512Byte,但最后一個塊可以小于或等于512Byte。當(dāng)發(fā)送的DATA報(bào)文中數(shù)據(jù)部分的長度小于512Byte,表示DATA報(bào)文發(fā)送完畢,所以小于數(shù)據(jù)部分512Byte的DATA數(shù)據(jù)報(bào)可以作為文件結(jié)束的標(biāo)志。特殊的情況是,當(dāng)文件中的數(shù)據(jù)正好是512Byte的整數(shù)倍時(shí),那么發(fā)送端必須再發(fā)送一個具有數(shù)據(jù)部分為0Byte的額外的DATA數(shù)據(jù)塊以表示傳輸?shù)慕Y(jié)束。數(shù)據(jù)可以采用ASCII碼或二進(jìn)制來傳送。
    b)ACK報(bào)文
    塊號表示它所收到的塊號(不是下一個期待的塊號,這與TCP中的ACK序號不同)。特殊情況是,當(dāng)客戶向服務(wù)器發(fā)送一個WRQ請求后,服務(wù)器返回給客戶的是一個塊號為0的ACK報(bào)文,表示服務(wù)器已經(jīng)準(zhǔn)備好了接收來自客戶的數(shù)據(jù)報(bào)。
    c)EEROR報(bào)文(差錯報(bào)文)
    ERROR報(bào)文既可以由客戶發(fā)送,也可以由服務(wù)器發(fā)送,當(dāng)一條連接(如讀連接或?qū)戇B接)不能建立或在數(shù)據(jù)傳輸中出現(xiàn)問題時(shí)使用。差錯碼定義了差錯的類型,差錯信息是一個可變字節(jié),包含原文中的差錯數(shù)據(jù)。
    從上面的報(bào)文格式中可以看出,TFTP報(bào)文沒有差錯檢驗(yàn)和字段,所以接收端檢驗(yàn)數(shù)據(jù)是否出現(xiàn)差錯的唯一方法是通過該TFTP數(shù)據(jù)報(bào)的UDP首部中的檢驗(yàn)和字段。
    三、TFTP傳輸過程
    以TFTP客戶端向 TFTP 服務(wù)器發(fā)送寫請求為例,說明整個過程。
    1)服務(wù)器使用默認(rèn)端口號69被動打開連接;
    2)客戶主動打開連接,向服務(wù)器進(jìn)程發(fā)送WRQ報(bào)文,報(bào)文中包含寫入文件的文件名;
    3)TFTP服務(wù)器進(jìn)程選擇一個新的端口和TFTP客戶進(jìn)程進(jìn)行通信,并向TFTP客戶進(jìn)程發(fā)送塊編號為0的的ACK報(bào)文;
    4)客戶端收到服務(wù)器的ACK報(bào)文后發(fā)送DATA報(bào)文,數(shù)據(jù)段為512Byte,少于512Byte表明是文件的最后的數(shù)據(jù),塊編號逐次遞增;
    5)TFTP服務(wù)器校驗(yàn)收到的DATA報(bào)文的塊編號,如果校驗(yàn)正確則將數(shù)據(jù)寫入文件,并發(fā)送ACK報(bào)文表明已接收到數(shù)據(jù),ACK報(bào)文的塊編號為本次接收的DATA報(bào)文的塊編號。另外還判斷數(shù)據(jù)段長度是否小于512 Byte,小于則表明文件傳輸完成,關(guān)閉連接,如果等于512Byte,則重復(fù)步驟4-5,直到所有請求的數(shù)據(jù)發(fā)送完畢。
    從上面的傳輸過程可以看出,TFTP 是一種類似于停止等待協(xié)議(不是真正的停止等待協(xié)議,在停止等待協(xié)議中,接收方發(fā)送的 ack 表示期望收到的下一個分組,而在 TFTP 的 ACK 報(bào)文中,ACK的塊號表示的是本次成功收到的數(shù)據(jù)塊,而不是下一個期望的下一個數(shù)據(jù)塊)。TFTP 客戶端只有收到服務(wù)器的確認(rèn)報(bào)文ACK后才會接著向服務(wù)器發(fā)送新的數(shù)據(jù)。
    另外需要注意的是TFTP 協(xié)議中,用于讀文件的連接和用于寫文件的連接的建立方式不同:建立讀連接的時(shí)候,客戶首先向服務(wù)器發(fā)送 RRQ 讀報(bào)文,服務(wù)器收到該報(bào)文后,直接發(fā)回給該客戶 DATA 報(bào)文,并且包含第一個數(shù)據(jù)塊(塊號為 1)。而建立寫連接的時(shí)候,客戶首先先服務(wù)器發(fā)送 WRQ 寫報(bào)文,服務(wù)器收到該報(bào)文后,則發(fā)回給客戶 ACK 報(bào)文,使用的塊號為 0;當(dāng)然上面兩種情況如果遇到請求報(bào)文出錯時(shí),均會發(fā)回 ERROR 報(bào)文作為響應(yīng)。
    35.2實(shí)驗(yàn)任務(wù)
    本章的實(shí)驗(yàn)任務(wù)是使用LWIP協(xié)議棧搭建TFTP服務(wù)器,PC電腦上的客戶端可以從TFTP服務(wù)器讀取文件也可向TFTP服務(wù)器寫入文件,文件存放在SD卡中。
    35.3硬件設(shè)計(jì)
    根據(jù)實(shí)驗(yàn)任務(wù)我們可以畫出本次實(shí)驗(yàn)的系統(tǒng)框圖,如下圖所示:

    圖 35.3.1 系統(tǒng)框圖


    在圖 5.3.1中,UART用于打印程序相關(guān)的信息,LWIP通過以太網(wǎng)傳輸數(shù)據(jù),SD用于存放文件,包括服務(wù)器創(chuàng)建的文件和客戶端寫入的文件。
    step1:創(chuàng)建Vivado工程
    本次實(shí)驗(yàn)的硬件設(shè)計(jì)可以在《LWIP echo server》實(shí)驗(yàn)的基礎(chǔ)上添加SD卡。
    1-1 我們先打開《LWIP echo server》實(shí)驗(yàn)的Vivado工程,打開后將工程另存為 “l(fā)wip_tftp_server”工程,然后點(diǎn)擊“OK”按鈕。
    step2:使用IP Integrator創(chuàng)建Processing System
    2-1 在Vivado界面左側(cè)的Flow Navigator中,點(diǎn)擊IP INTEGRATOR下的Open Block Design以打開Diagram窗口。
    2-2 在打開的下圖Diagram窗口,雙擊打開ZYNQ7 Processing System重定義窗口。

    圖 35.3.2 重定義ZYNQ7 Processing System


    2-3 在下圖所示的重定義窗口,如同《SD卡讀寫TXT文本實(shí)驗(yàn)》那樣配置SD卡。點(diǎn)擊左側(cè)的MIO Configuration,在右側(cè)的界面中展開“I/O Peripherals”,勾選“SD 0”,在“IO”列選擇SD 0的IO為“MIO40…45”,如下圖所示。

    圖 35.3.3 PS以太網(wǎng)接口配置界面


    2-4 由于不需要添加其它IP,按Ctrl+S快捷鍵保存Diagram。此時(shí)我們的第二步完成,進(jìn)入第三步
    step3:生成頂層HDL
    在sources面板中,右鍵點(diǎn)擊Block Design設(shè)計(jì)文件“system.bd”,然后依次執(zhí)行“Generate Output Products”和“Create HDL Wrapper”。
    step4:生成Bitstream文件并導(dǎo)出到SDK
    由于本實(shí)驗(yàn)未用到PL部分,所以無需生成Bitstream文件,只需導(dǎo)出到SDK即可。如果使用到PL,則需要添加引腳約束以及對該系統(tǒng)進(jìn)行綜合、實(shí)現(xiàn)并生成Bitstream文件。
    4-1 導(dǎo)出硬件。
    在菜單欄中選擇 File > Export > Export hardware。
    并在彈出的對話框中,取消勾選“Include bitstream”,直接點(diǎn)擊“OK”按鈕。
    因?yàn)槭窃谇耙还こ痰幕A(chǔ)上建立的,還保留著前一工程的結(jié)果,所以會彈出“Module Already Exported”對話框,我們點(diǎn)擊“Yes”按鈕。
    4-2 硬件導(dǎo)出完成后,選擇菜單File->Launch SDK,啟動SDK開發(fā)環(huán)境。
    35.4軟件設(shè)計(jì)
    下面步驟操作比較麻煩,實(shí)際意義也不大,可以直接使用我們提供的例程里的SDK軟件工程。
    此處我們刪除《LWIP echo server》實(shí)驗(yàn)的應(yīng)用工程,保留bsp工程。下面我們開始第五步——創(chuàng)建應(yīng)用工程。下面我們開始第五步——創(chuàng)建應(yīng)用工程
    step5:在SDK中創(chuàng)建應(yīng)用工程
    5-1在菜單欄中選擇“File->New->Application Project”,
    在彈出的界面中,輸入工程名“l(fā)wip_tftp_server”,然后選擇“Next >”,在下一界面選擇“Empty Application”,然后點(diǎn)擊“Finish”按鈕。
    5-2 在Project Explorer中,鼠標(biāo)右鍵點(diǎn)擊“l(fā)wip_tftp_server _bsp”,在彈出的菜單中選擇“Board Support Package Settings”,如下圖所示:
    彈出對BSP的設(shè)置界面,勾選“l(fā)wip202”和“xilffs”以啟用lwip和文件系統(tǒng),如圖 35.4.1所示。
    如果沒有開啟DHCP服務(wù)可以開啟DHCP服務(wù),點(diǎn)擊standalone下的lwip202,在右側(cè)界面中到“dhcp_options”,將其下的兩個選項(xiàng)的“Value”設(shè)置為“true”,如圖 35.4.2所示。

    圖 35.4.1 BSP的設(shè)置界面


    圖 35.4.2 開啟DHCP
    5-3 由于Xilinx提供的lwip例程里有TFTP server的源代碼,所以我們無需自己手動編寫,直接添加即可。
    雙擊打開“l(fā)wip_tftp_server”目錄下的system.mss文件。在system.mss文件的底部單擊“Import Example”,如下圖所示。

    圖 35.4.3 Import lwip Example


    5-4 在彈出的下圖所示界面中,點(diǎn)擊下方的“Examples Directory”。

    圖 35.4.4 platform_config.h文件內(nèi)容


    5-5 打開例程所在文件的目錄,里面有Xilinx關(guān)于lwip的全部例程源文件。我們選擇本次實(shí)驗(yàn)需要的源文件,如圖 35.4.6所示,并單擊鼠標(biāo)右鍵選擇復(fù)制。復(fù)制完成后,在打開的圖 35.4.5界面中,點(diǎn)擊“Cancel”退出。

    圖 35.4.6 例程所在文件的目錄


    5-6 單擊SDK軟件的lwip_tftp_server/src目錄,按下粘貼快捷鍵“Ctrl-v”,將復(fù)制的文件粘貼到該src目錄下,如下圖所示。

    圖 35.4.7 src目錄


    5-7 為了方便分析,我們將剛才復(fù)制到src目錄的源文件重命名,主要是刪除不需要的前綴,其中“l(fā)wip_example_tftpserver_common.h”改為“l(fā)wip_tftp_server.h”,如下圖所示:

    圖 35.4.8 刪除不相關(guān)文件后的src文件夾內(nèi)容


    5-8 修復(fù)錯誤。
    由于重命名了“l(fā)wip_example_tftpserver_common.h”,所以需要將lwip_tftp_server.c源文件的 #include "lwip_tftpserver_common.h"修改為#include "lwip_tftp_server.h",如下圖所示:

    圖 35.4.9 修改為#include "lwip_tftp_server.h"


    此處為了方便顯示,將其注釋,實(shí)際在文件內(nèi)直接修改第34行即可。
    打開platform_fs.c源文件,添加BYTE work[FF_MAX_SS](第9行),修改f_mkfs函數(shù)的調(diào)用,第15行,修改后的內(nèi)容如下:

    1. 1 #include "ff.h"
    2. 2 #include "xil_printf.h"
    3. 3
    4. 4 int platform_init_fs()
    5. 5 {
    6. 6 static FATFS fatfs;
    7. 7 FRESULT Res;
    8. 8 TCHAR *Path="0:/";
    9. 9 BYTE work[FF_MAX_SS];
    10. 10
    11. 11 /* Try to mount FAT file system */
    12. 12 Res=f_mount(&fatfs, Path, 1);
    13. 13 if (Res !=FR_OK) {
    14. 14 xil_printf("Volume is not FAT formated; formating FAT\r\n");
    15. 15 Res=f_mkfs(Path, FM_FAT32, 0, work, sizeof work);
    16. 16 if (Res !=FR_OK) {
    17. 17 xil_printf("Unable to format FATfs\r\n");
    18. 18 return -1;
    19. 19 }
    20. 20
    21. 21 Res=f_mount(&fatfs, Path, 1);
    22. 22 if (Res !=FR_OK) {
    23. 23 xil_printf("Unable to mount FATfs\r\n");
    24. 24 return -1;
    25. 25 }
    26. 26 }
    27. 27 xil_printf("File system initialization successful\r\n");
    28. 28
    29. 29 return 0;
    30. 30 }



    該文件在初始化平臺時(shí)由init_platform函數(shù)調(diào)用,用于掛載SD卡,掛載不成功就將SD卡格式化成FAT32,格式化成功后再嘗試掛載。
    5-9 現(xiàn)在我們打開main.c文件,為了方便分析源代碼,在main.c文件中將帶有下圖箭頭所指的預(yù)編譯指令刪除。

    圖 35.4.10 刪除不需要的預(yù)編譯指令


    刪除不適用的預(yù)編譯指令后的main.c代碼與我們《lwip echo server實(shí)驗(yàn)》的main.c代碼基本相同,區(qū)別在于本次TFTP server實(shí)驗(yàn)沒有使用IPv6,所以沒有IPv6的預(yù)編譯指令,其他完全相同,main.c代碼講解見《lwip echo server實(shí)驗(yàn)》。
    5-10本實(shí)驗(yàn)可以說是在《lwip echo server實(shí)驗(yàn)》的基礎(chǔ)上增加了文件系統(tǒng),然后將Echo server的實(shí)現(xiàn)文件echo.c文件改寫成了TFTP Server的實(shí)現(xiàn)文件。因而本實(shí)驗(yàn)的主要代碼是TFTP Server的實(shí)現(xiàn),該實(shí)現(xiàn)在lwip_tftp_server.h和lwip_tftp_server.c中,由于這兩個文件的總代碼有近500行,因此我們挑選部分代碼進(jìn)行講解。此處以客戶端寫文件為例講解lwip_tftp_server.c中的寫文件實(shí)現(xiàn)源碼。講解以函數(shù)調(diào)用順序進(jìn)行。
    首先我們看main函數(shù)中調(diào)用的start_application函數(shù),該函數(shù)實(shí)現(xiàn)如下:

    1. 342 void start_application()
    2. 343 {
    3. 344 struct udp_pcb *pcb;
    4. 345 err_t err;
    5. 346
    6. 347 //創(chuàng)建測試文件用于客戶端讀取
    7. 348 err=tftp_create_test_file();
    8. 349 if (err) {
    9. 350 xil_printf("Unable to create test file\r\n");
    10. 351 return;
    11. 352 }
    12. 353
    13. 354 //創(chuàng)建新的UDP PCB
    14. 355 pcb=udp_new();
    15. 356 if (!pcb) {
    16. 357 xil_printf("Error creating PCB. Out of Memory\r\n");
    17. 358 return;
    18. 359 }
    19. 360
    20. 361 //綁定端口
    21. 362 err=udp_bind(pcb, IP_ADDR_ANY, TFTP_PORT);
    22. 363 if (err !=ERR_OK) {
    23. 364 xil_printf("Unable to bind to port %d; err %d\r\n",
    24. 365 TFTP_PORT, err);
    25. 366 udp_remove(pcb);
    26. 367 return;
    27. 368 }
    28. 369 //設(shè)置接收回調(diào)函數(shù)
    29. 370 udp_recv(pcb, (udp_recv_fn) tftp_server_recv_cb, NULL);
    30. 371 }



    可以看到該函數(shù)首先通過調(diào)用tftp_create_test_file函數(shù)創(chuàng)建了測試文件,用于tftp客戶端讀取tftp服務(wù)器的文件數(shù)據(jù),測試文件名為sample#.txt,其中“#”為數(shù)字1、2、3中的任一值,其文件內(nèi)容為“----- This is a test file for TFTP server application -----”。如果不執(zhí)行客戶端的讀取文件請求,可刪除該函數(shù)的調(diào)用及其實(shí)現(xiàn)。
    由于TFTP基于UDP協(xié)議,從start_application函數(shù)可以看到lwip中使用UDP協(xié)議很簡單。首先通過udp_new函數(shù)創(chuàng)建一個新的UDP PCB,然后調(diào)用udp_bind函數(shù)綁定端口號,IP_ADDR_ANY表明為任意本地地址,TFTP_PORT是在lwip_tftp_server.h宏定義的端口號,其值為69,即TFTP的默認(rèn)端口。最后調(diào)用udp_recv函數(shù)設(shè)置接收回調(diào)函數(shù)就完成了UDP服務(wù)的創(chuàng)建,服務(wù)端的功能幾TFTP協(xié)議由回調(diào)函數(shù)實(shí)現(xiàn)。回調(diào)函數(shù)代碼如下:

    1. 260 //UDP接收回調(diào)函數(shù)
    2. 261 static void tftp_server_recv_cb(void *arg, struct udp_pcb *upcb, struct pbuf *p,
    3. 262 ip_addr_t *ip, u16_t port)
    4. 263 {
    5. 264 tftp_opcode op=tftp_get_opcode(p->payload);
    6. 265 char fname[512];
    7. 266 struct udp_pcb *pcb;
    8. 267 err_t err;
    9. 268
    10. 269 pcb=udp_new();
    11. 270 if (!pcb) {
    12. 271 xil_printf("Error creating PCB. Out of Memory\r\n");
    13. 272 goto cleanup;
    14. 273 }
    15. 274
    16. 275 //綁定到端口0以接收下一個可用的空閑端口
    17. 276 err=udp_bind(pcb, IP_ADDR_ANY, 0);
    18. 277 if (err !=ERR_OK) {
    19. 278 xil_printf("Unable to bind to port %d; err %d\r\n", port, err);
    20. 279 goto cleanup;
    21. 280 }
    22. 281
    23. 282 switch (op) {
    24. 283 case TFTP_RRQ:
    25. 284 //從payload中獲取文件名
    26. 285 strcpy(fname, p->payload + FIL_NAME_OFFSET);
    27. 286 printf("TFTP RRQ (read request): %s\r\n", fname);
    28. 287 tftp_process_read(pcb, ip, port, fname);
    29. 288 break;
    30. 289 case TFTP_WRQ:
    31. 290 strcpy(fname, p->payload + FIL_NAME_OFFSET);
    32. 291 printf("TFTP WRQ (write request): %s\r\n", fname);
    33. 292 tftp_process_write(pcb, ip, port, fname);
    34. 293 break;
    35. 294 default:
    36. 295 //發(fā)送訪問沖突消息
    37. 296 tftp_send_error_packet(pcb, ip, port, TFTP_ERR_ILLEGALOP);
    38. 297 printf("TFTP unknown request op: %d\r\n\r\n", op);
    39. 298 udp_remove(pcb);
    40. 299 break;
    41. 300 }
    42. 301
    43. 302 cleanup:
    44. 303 pbuf_free(p);
    45. 304 }



    當(dāng)TFTP客戶端發(fā)起寫入或讀取文件的請求后,lwip協(xié)議棧調(diào)用回調(diào)函數(shù)tftp_server_recv_cb。該回調(diào)函數(shù)通過tftp_get_opcode宏獲取客戶端發(fā)送報(bào)文的操作碼,不同的操作碼執(zhí)行該函數(shù)switch分支中的不同的case,如對于寫入文件請求,則執(zhí)行“case TFTP_WRQ”分支語句,該分支語句調(diào)用TFTP處理寫文件請求函數(shù)tftp_process_write,該函數(shù)實(shí)現(xiàn)如下:

    1. 223 //TFTP處理寫文件請求
    2. 224 static int tftp_process_write(struct udp_pcb *pcb, ip_addr_t *ip, int port,
    3. 225 char *fname)
    4. 226 {
    5. 227 tftp_connection_args *conn;
    6. 228 FIL w_fil;
    7. 229 FRESULT Res;
    8. 230
    9. 231 Res=f_open(&w_fil, fname, FA_CREATE_ALWAYS | FA_WRITE);
    10. 232 if (Res) {
    11. 233 xil_printf("Unable to open file %s for writing %d\r\n", fname,
    12. 234 Res);
    13. 235 tftp_send_error_packet(pcb, ip, port, TFTP_ERR_DISKFULL);
    14. 236 udp_remove(pcb);
    15. 237 return -1;
    16. 238 }
    17. 239
    18. 240 conn=mem_malloc(sizeof *conn);
    19. 241 if (!conn) {
    20. 242 xil_printf("Unable to allocate memory for tftp conn\r\n");
    21. 243 tftp_send_error_packet(pcb, ip, port, TFTP_ERR_DISKFULL);
    22. 244 udp_remove(pcb);
    23. 245 return -1;
    24. 246 }
    25. 247
    26. 248 memcpy(&conn->fil, &w_fil, sizeof(w_fil));
    27. 249 conn->block=0;
    28. 250
    29. 251 //為該pcb設(shè)置接收回調(diào)
    30. 252 udp_recv(pcb, (udp_recv_fn) tftp_server_write_req_recv_cb, conn);
    31. 253
    32. 254 //通過發(fā)送第一個ACK來啟動傳輸
    33. 255 tftp_send_ack_packet(pcb, ip, port, conn->block);
    34. 256
    35. 257 return 0;
    36. 258 }



    該函數(shù)首先在文件系統(tǒng)中創(chuàng)建一個文件,文件名為客戶端寫入的文件名,然后為新創(chuàng)建的UDP PCB設(shè)置接收回調(diào)函數(shù),用于處理后面接收客戶端傳入的文件,最后發(fā)送塊編號為0的ACK報(bào)文以應(yīng)答客戶端啟動傳輸。TFTP寫入請求的接收回調(diào)函數(shù)實(shí)現(xiàn)如下:

    1. 181 //TFTP寫入請求的接收回調(diào)函數(shù)
    2. 182 static void tftp_server_write_req_recv_cb(void *_args, struct udp_pcb *upcb,
    3. 183 struct pbuf *p, ip_addr_t *addr, u16_t port)
    4. 184 {
    5. 185 ip_addr_t ip=*addr;
    6. 186 tftp_connection_args *args=(tftp_connection_args *)_args;
    7. 187
    8. 188 if (p->len !=p->tot_len) {
    9. 189 xil_printf("TFTP_WRQ: Tftp server does not support "
    10. 190 "chained pbufs\r\n");
    11. 191 pbuf_free(p);
    12. 192 return;
    13. 193 }
    14. 194
    15. 195 //確保數(shù)據(jù)塊是我們所期望的
    16. 196 if ((p->len >=TFTP_PACKET_HDR_LEN) &&
    17. 197 (tftp_get_block_value(p->payload)==(u16_t) (args->block + 1))) {
    18. 198 //將接收的數(shù)據(jù)寫入文件
    19. 199 unsigned int n;
    20. 200 f_write(&args->fil, p->payload + TFTP_PACKET_HDR_LEN,
    21. 201 p->len - TFTP_PACKET_HDR_LEN, &n);
    22. 202 if (n !=p->len - TFTP_PACKET_HDR_LEN) {
    23. 203 xil_printf("TFTP_WRQ: Write to file error\r\n");
    24. 204 tftp_send_error_packet(upcb, &ip, port,
    25. 205 TFTP_ERR_DISKFULL);
    26. 206 pbuf_free(p);
    27. 207 return tftp_cleanup(upcb, args);
    28. 208 }
    29. 209 args->block++;
    30. 210 }
    31. 211
    32. 212 tftp_send_ack_packet(upcb, &ip, port, args->block);
    33. 213
    34. 214 //如果接收到的數(shù)據(jù)段長度小于指定的字節(jié)數(shù),則表明已經(jīng)接收了整個文件,因此可以退出
    35. 215 if (p->len < TFTP_DATA_PACKET_MSG_LEN) {
    36. 216 xil_printf("TFTP_WRQ: Transfer completed\r\n\r\n");
    37. 217 return tftp_cleanup(upcb, args);
    38. 218 }
    39. 219
    40. 220 pbuf_free(p);
    41. 221 }



    從該回調(diào)函數(shù)可以看到,TFTP服務(wù)端對客戶端發(fā)送的數(shù)據(jù)報(bào)文的塊編號進(jìn)行校驗(yàn),如果不是我們期望的塊編號就重發(fā)上一次發(fā)送的ACK報(bào)文,如果是期望的塊編號,就將數(shù)據(jù)寫入文件中,然后遞增塊編號,并發(fā)送ACK報(bào)文給客戶端以確認(rèn)收到數(shù)據(jù)。
    在該函數(shù)的最后判斷接收到的數(shù)據(jù)段長度是否小于指定的字節(jié)數(shù)TFTP_DATA_PACKET_MSG_LEN,如果是,則表明已經(jīng)接收了整個文件,因此可以結(jié)束連接。TFTP_DATA_PACKET_MSG_LEN在lwip_tftp_server.h宏定義為512。
    以上大概的講解了TFTP Server接收客戶端寫入文件的實(shí)現(xiàn)。下面我們進(jìn)行實(shí)際操作,看看TFTP客戶端是否能向服務(wù)器寫入文件。
    35.5下載驗(yàn)證
    首先我們將下載器與領(lǐng)航者底板上的JTAG接口連接,下載器另外一端與電腦連接。然后使用Mini USB連接線將USB UART接口與電腦連接,用于串口通信。使用網(wǎng)線一端連接領(lǐng)航者開發(fā)板的以太網(wǎng)接口,另一端與電腦或路由器連接。連接完成后,在開發(fā)板上插入SD 卡或者插入帶卡套(適配器)的 TF 卡(SD 卡插槽位于開發(fā)板背面)。最后連接開發(fā)板的電源,并打開電源開關(guān)。如下圖所示:

    圖 35.5.1領(lǐng)航者ZYNQ開發(fā)板實(shí)物圖


    現(xiàn)在進(jìn)入最后一步。
    step6:板級驗(yàn)證
    6-1 在SDK軟件的下方的SDK Terminal窗口中點(diǎn)擊右上角的加號連接串口。
    6-2 下載程序。下載完成后,可以看到串口打印的結(jié)果如下:

    圖 35.5.2 顯示打印結(jié)果


    其中“File system initialization successful”表明SD卡可以正常工作。打印的最后一句表明了該實(shí)驗(yàn)如何使用。由于是TFTP服務(wù)器實(shí)驗(yàn),所以我們需要TFTP客戶端,可以從網(wǎng)上下載,也可以使用Windows系統(tǒng)的CMD命令行界面,如果開啟了TFTP客戶端,開啟方法見步驟6-6。
    6-3 下面我們先創(chuàng)建一個文件用來傳輸?shù)絋FTP服務(wù)器。文件存放位置任意,文件內(nèi)容任意。
    我們在Vivado工程目錄新建一個名為“test”的文件夾,里面新建一個名為testfile.txt的文件,文件內(nèi)容為“這只是一個測試文件。”,如下圖所示:

    圖 35.5.3 新建一個名為test_file.txt的文件


    6-4 我們打開電腦的CMD(按win+r鍵后輸入cmd),然后輸入命令“cd /D F:\ZYNQ\Embedded_System\lwip_tftp_server\test”切換到 “F:\ZYNQ\Embedded_System\lwip_tftp_server\test”目錄下,如下圖所示:

    圖35.5.4 切換到上傳文件所在的目錄


    然后輸入“tftp -i 192.168.1.10 PUT testfile.txt”命令,回車,會顯示傳輸成功字樣,如下圖所示:

    圖 35.5.5 進(jìn)行tftp連接


    此時(shí)SDK串口終端也會打印如下信息:

    圖 35.5.6 串口終端打印寫入完成信息


    如果回車后出現(xiàn)像下圖所示界面所示“tftp不是內(nèi)部或外部命令,也不是可運(yùn)行的程序或批處理文件”,則表明未開啟Windows的tftp客戶端功能,開啟方式見6-5。

    圖 35.5.7 未啟用tftp客戶端時(shí)的界面


    向服務(wù)器寫入文件剛才測試完成了,現(xiàn)在測試從服務(wù)器端讀取文件,可以讀取剛才寫入的文件,也可以讀取服務(wù)器程序創(chuàng)建的測試文件。下面我們以讀取服務(wù)器程序創(chuàng)建的測試文件為例,進(jìn)行讀取文件測試。
    在CMD中輸入“tftp -i 192.168.1.10 GET sample1.txt”命令,然后回車,會顯示傳輸成功字樣,如下圖所示:

    圖 35.5.8 輸入讀取文件命令


    此時(shí)SDK串口終端也會打印如下信息:

    圖 35.5.9 讀取成功


    此時(shí)我們打開test文件夾,會看到其中新增了sample1.txt,雙擊打開,其內(nèi)容如下:

    圖 35.5.10 讀取的sample1.txt文件


    可以看到讀取文件測試成功。現(xiàn)在我們把SD卡插到電腦上,查看其內(nèi)容如下:

    圖 35.5.11 SD卡上的文件


    可以看到客戶端上傳給TFTP服務(wù)器的文件確實(shí)寫到SD卡中。
    6-5 下面我們介紹一下如何開啟Windows的tftp客戶端功能。在Win10或Win7系統(tǒng)中,按“Win+r”快捷鍵后,在下圖所示界面中輸入“control”。

    圖 35.5.12 打開控制面板界面


    進(jìn)入下圖所示控制面板界面,將查看方式設(shè)置為“類別”,單擊“程序”下的“卸載程序”,如下圖所示:

    圖 35.5.13 點(diǎn)擊進(jìn)入“程序和功能”界面


    在彈出的界面中,單擊“啟用或關(guān)閉Windows功能”,如下圖所示:

    圖 35.5.14 點(diǎn)擊“啟用或關(guān)閉Windows功能”


    在彈出的“Windows功能”界面中,找到“Tftp Client”,并勾選,如下圖所示:


    圖 35.5.15 勾選tftp client


    單擊確定后,如果出現(xiàn)“Windows需要重啟電腦才能完成安裝所請求的更改”字樣,重新啟動電腦即可。現(xiàn)在 Windows的tftp客戶端服務(wù)已啟用。
    至此,本實(shí)驗(yàn)完成。

網(wǎng)站首頁   |    關(guān)于我們   |    公司新聞   |    產(chǎn)品方案   |    用戶案例   |    售后服務(wù)   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區(qū)    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權(quán)所有