欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    )摘自【正點原子】領航者 ZYNQ 之嵌入式開發指南

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

    第三十五章基于lwip的tftp server實驗


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

    圖 35.1.1 TFTP報文格式


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

    圖 35.3.1 系統框圖


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

    圖 35.3.2 重定義ZYNQ7 Processing System


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

    圖 35.3.3 PS以太網接口配置界面


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

    圖 35.4.1 BSP的設置界面


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

    圖 35.4.3 Import lwip Example


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

    圖 35.4.4 platform_config.h文件內容


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

    圖 35.4.6 例程所在文件的目錄


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

    圖 35.4.7 src目錄


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

    圖 35.4.8 刪除不相關文件后的src文件夾內容


    5-8 修復錯誤。
    由于重命名了“lwip_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"


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

    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 }



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

    圖 35.4.10 刪除不需要的預編譯指令


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

    1. 342 void start_application()
    2. 343 {
    3. 344 struct udp_pcb *pcb;
    4. 345 err_t err;
    5. 346
    6. 347 //創建測試文件用于客戶端讀取
    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 //創建新的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 //設置接收回調函數
    29. 370 udp_recv(pcb, (udp_recv_fn) tftp_server_recv_cb, NULL);
    30. 371 }



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

    1. 260 //UDP接收回調函數
    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 //發送訪問沖突消息
    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 }



    當TFTP客戶端發起寫入或讀取文件的請求后,lwip協議棧調用回調函數tftp_server_recv_cb。該回調函數通過tftp_get_opcode宏獲取客戶端發送報文的操作碼,不同的操作碼執行該函數switch分支中的不同的case,如對于寫入文件請求,則執行“case TFTP_WRQ”分支語句,該分支語句調用TFTP處理寫文件請求函數tftp_process_write,該函數實現如下:

    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設置接收回調
    30. 252 udp_recv(pcb, (udp_recv_fn) tftp_server_write_req_recv_cb, conn);
    31. 253
    32. 254 //通過發送第一個ACK來啟動傳輸
    33. 255 tftp_send_ack_packet(pcb, ip, port, conn->block);
    34. 256
    35. 257 return 0;
    36. 258 }



    該函數首先在文件系統中創建一個文件,文件名為客戶端寫入的文件名,然后為新創建的UDP PCB設置接收回調函數,用于處理后面接收客戶端傳入的文件,最后發送塊編號為0的ACK報文以應答客戶端啟動傳輸。TFTP寫入請求的接收回調函數實現如下:

    1. 181 //TFTP寫入請求的接收回調函數
    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 //確保數據塊是我們所期望的
    16. 196 if ((p->len >=TFTP_PACKET_HDR_LEN) &&
    17. 197 (tftp_get_block_value(p->payload)==(u16_t) (args->block + 1))) {
    18. 198 //將接收的數據寫入文件
    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 //如果接收到的數據段長度小于指定的字節數,則表明已經接收了整個文件,因此可以退出
    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 }



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

    圖 35.5.1領航者ZYNQ開發板實物圖


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

    圖 35.5.2 顯示打印結果


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

    圖 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 進行tftp連接


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

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


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

    圖 35.5.7 未啟用tftp客戶端時的界面


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

    圖 35.5.8 輸入讀取文件命令


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

    圖 35.5.9 讀取成功


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

    圖 35.5.10 讀取的sample1.txt文件


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

    圖 35.5.11 SD卡上的文件


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

    圖 35.5.12 打開控制面板界面


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

    圖 35.5.13 點擊進入“程序和功能”界面


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

    圖 35.5.14 點擊“啟用或關閉Windows功能”


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


    圖 35.5.15 勾選tftp client


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

    1)摘自【正點原子】領航者 ZYNQ 之嵌入式開發指南

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

    第三十五章基于lwip的tftp server實驗


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

    圖 35.1.1 TFTP報文格式


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

    圖 35.3.1 系統框圖


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

    圖 35.3.2 重定義ZYNQ7 Processing System


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

    圖 35.3.3 PS以太網接口配置界面


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

    圖 35.4.1 BSP的設置界面


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

    圖 35.4.3 Import lwip Example


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

    圖 35.4.4 platform_config.h文件內容


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

    圖 35.4.6 例程所在文件的目錄


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

    圖 35.4.7 src目錄


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

    圖 35.4.8 刪除不相關文件后的src文件夾內容


    5-8 修復錯誤。
    由于重命名了“lwip_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"


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

    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 }



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

    圖 35.4.10 刪除不需要的預編譯指令


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

    1. 342 void start_application()
    2. 343 {
    3. 344 struct udp_pcb *pcb;
    4. 345 err_t err;
    5. 346
    6. 347 //創建測試文件用于客戶端讀取
    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 //創建新的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 //設置接收回調函數
    29. 370 udp_recv(pcb, (udp_recv_fn) tftp_server_recv_cb, NULL);
    30. 371 }



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

    1. 260 //UDP接收回調函數
    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 //發送訪問沖突消息
    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 }



    當TFTP客戶端發起寫入或讀取文件的請求后,lwip協議棧調用回調函數tftp_server_recv_cb。該回調函數通過tftp_get_opcode宏獲取客戶端發送報文的操作碼,不同的操作碼執行該函數switch分支中的不同的case,如對于寫入文件請求,則執行“case TFTP_WRQ”分支語句,該分支語句調用TFTP處理寫文件請求函數tftp_process_write,該函數實現如下:

    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設置接收回調
    30. 252 udp_recv(pcb, (udp_recv_fn) tftp_server_write_req_recv_cb, conn);
    31. 253
    32. 254 //通過發送第一個ACK來啟動傳輸
    33. 255 tftp_send_ack_packet(pcb, ip, port, conn->block);
    34. 256
    35. 257 return 0;
    36. 258 }



    該函數首先在文件系統中創建一個文件,文件名為客戶端寫入的文件名,然后為新創建的UDP PCB設置接收回調函數,用于處理后面接收客戶端傳入的文件,最后發送塊編號為0的ACK報文以應答客戶端啟動傳輸。TFTP寫入請求的接收回調函數實現如下:

    1. 181 //TFTP寫入請求的接收回調函數
    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 //確保數據塊是我們所期望的
    16. 196 if ((p->len >=TFTP_PACKET_HDR_LEN) &&
    17. 197 (tftp_get_block_value(p->payload)==(u16_t) (args->block + 1))) {
    18. 198 //將接收的數據寫入文件
    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 //如果接收到的數據段長度小于指定的字節數,則表明已經接收了整個文件,因此可以退出
    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 }



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

    圖 35.5.1領航者ZYNQ開發板實物圖


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

    圖 35.5.2 顯示打印結果


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

    圖 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 進行tftp連接


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

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


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

    圖 35.5.7 未啟用tftp客戶端時的界面


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

    圖 35.5.8 輸入讀取文件命令


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

    圖 35.5.9 讀取成功


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

    圖 35.5.10 讀取的sample1.txt文件


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

    圖 35.5.11 SD卡上的文件


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

    圖 35.5.12 打開控制面板界面


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

    圖 35.5.13 點擊進入“程序和功能”界面


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

    圖 35.5.14 點擊“啟用或關閉Windows功能”


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


    圖 35.5.15 勾選tftp client


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

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

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

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