第40章 RL-TCPnet之TFTP客戶端
本章節爲大家講解RL-TCPnet的TFTP客戶端應用,學習本章節前,務必要優先學習第38章的TFTP基礎知識。有了這些基礎知識之後,再搞本章節會有事半功倍的效果。
本章教程含STM32F407開發板和STM32F429開發板。
40.1 初學者重要提示
40.2 TFTP函數
40.3 TFTP服務器端軟件和板子的操作步驟
40.4 實驗例程說明(RTX)
40.5 總結
40.1 初學者重要提示
- 學習本章節前,務必保證已經學習了第38章的基礎知識。
- 本章配套的例子是將開發板作爲TFTP客戶端,使用開發板上面的SD卡作爲客戶端的存儲介質。所以測試本章節的例子,務必要準備一個SD卡。
- 由於配套例子的文件系統是採用的RL-FlashFS,此文件系統的文件名僅支持ASCII字符,不支持中文,特別注意!
- 具體電腦端TFTP服務器的創建方法和板子的操作步驟在本章的40.5小節有詳細說明。做本章節配套的實驗,必須要看!
40.2 TFTP函數
使用如下11個函數可以實現RL-TCPnet的TFTP:
- tftp_accept_host
- tftp_fclose
- tftp_fopen
- tftp_fread
- tftp_fwrite
- tftpc_fclose
- tftpc_fopen
- tftpc_fread
- tftpc_fwrite
- tftpc_get
- tftpc_put
關於這11個函數的講解及其使用方法可以看教程第 3 章 3.4 小節裏面說的參考資料 rlarm.chm 文件:
這裏我們重點的說以下6個函數,因爲本章節配套的例子使用的是這6個函數:
- tftpc_fclose
- tftpc_fopen
- tftpc_fread
- tftpc_fwrite
- tftpc_get
- tftpc_put
關於這些函數注意以下三點:
- TFTP的所有函數都不支持重入,也就是不支持多任務調用。
- 以tftp_開頭的函數是用於TFTP服務器的。
- 以tftpc_開頭的函數是用於TFTP客戶端的。
40.2.1 函數tftpc_fopen
函數原型:
void* tftpc_fopen ( U8* fname, /* 文件名地址 */ U8* mode ); /* 操作模式 */
函數描述:
函數tftpc_fopen用於打開本地文件(TFTP客戶端的文件)。此函數在MDK安裝目錄中的TFTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。
- 第1個參數是文件名地址。
- 第2個參數是操作模式,可以是讀操作或者寫操作,具體支持的形參類型如下:
- 返回值,打開文件成功的話,返回指向此文件的指針變量,否則返回NULL。
使用這個函數要注意以下問題:
- 此接口函數是用於TFTP客戶端的。
使用舉例:
void *tftpc_fopen (U8 *fname, U8 *mode) { /* 打開文件,如果返回NULL,表示打開失敗 */ return (fopen ((char *)fname, (char *)mode)); }
40.2.2 函數tftpc_fclose
函數原型:
void tftpc_fclose ( FILE* file ); /* 文件句柄地址 */
函數描述:
函數tftpc_fclose用於關閉文件。此函數在MDK安裝目錄中的TFTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。
- 第1個參數是要關閉的文件句柄地址。
使用這個函數要注意以下問題:
- 此接口函數是用於TFTP客戶端的。
使用舉例:
void tftpc_fclose (void *file) { /* 關閉文件 */ fclose (file); }
40.2.3 函數tftpc_fread
函數原型:
U16 tftpc_fread ( FILE* file, /* 文件句柄地址 */ U8* buf, /* 數據緩衝地址 */ U16 len ); /* 要讀取的字節數 */
函數描述:
函數tftpc_fread用於從文件中讀出len個字節數據。此函數在MDK安裝目錄中的TFTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。
- 第1個參數是要讀取數據的文件句柄地址。
- 第2個參數是數據緩衝地址,用於存儲讀取出來的數據。
- 第3個參數是要讀取出來的數據大小,單位字節。
- 返回值,返回從文件中實際讀出的字節數。
使用這個函數要注意以下問題:
- 設置讀取函數時,必須設置指定大小的字節數。如果實際讀出的字節數小於len,將停止讀取並關閉TFTP會話,這種情況一般都是文件已經讀取完畢。
- 此接口函數是用於TFTP客戶端的。
使用舉例:
U16 tftpc_fread (void *file, U8 *buf, U16 len) { /* 讀取len字節到buf中,返回值是實際讀取的字節數,返回數值小於len的話,表示文件已經讀取完畢, 文件將被關閉 */ return (fread (buf, 1, len, file)); }
40.2.4 函數tftpc_fwrite
函數原型:
U16 tftpc_fwrite ( FILE* file, /* 文件句柄地址 */ U8* buf, /* 數據緩衝地址 */ U16 len ); /* 要寫入的字節數 */
函數描述:
函數tftpc_fwrite用於往文件中寫入len個字節數據。此函數在MDK安裝目錄中的TFTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。
- 第1個參數是要寫入數據的文件句柄地址。
- 第2個參數是數據緩衝地址,存儲了要寫入的數據。
- 第3個參數是要寫入的數據大小,單位字節。
- 返回值,返回實際寫入文件的字節數。
使用這個函數要注意以下問題:
- 設置寫函數時,必須設置指定大小的字節數。如果實際寫入的字節數小於len,TFTP客戶端將停止寫入,終止數據傳輸並關閉TFTP會話,這種情況一般是寫操作出錯了。
- 此接口函數是用於TFTP客戶端的。
使用舉例:
U16 tftpc_fwrite (void *file, U8 *buf, U16 len) { /* 將buf中的len字節寫入到文件中,如果返回數值(實際寫入的字節數)不等於len,數據傳輸將終止 */ return (fwrite (buf, 1, len, file)); }
40.2.5 函數tftpc_get
函數原型:
BOOL tftpc_get ( U8* ipadr, /* 遠程TFTP服務器的IP地址 */ U16 port, /* 遠程TFTP服務器的端口號 */ const char *src, /* 遠程TFTP服務器上的文件名 */ const char *dst, /* 保存到本地的文件名 */ void (*cbfunc)(U8 event) ); /* 回調函數 */
函數描述:
函數tftpc_get用於啓動RL-TCPnet系統上的TFTP客戶端,將文件從遠程TFTP服務器下載到本地系統。 這樣TFTP客戶端就可以通過連接到UDP端口號爲port(本函數的第2個形參)的TFTP服務器來啓動TFTP會話。如果第2個參數的端口號填0,系統將使用TFTP服務器的標準端口號69進行連接。
- 第1個參數填TFTP服務器的IP地址。
- 第2個參數填TFTP服務器的端口號。
- 第3個參數是TFTP服務器上的文件名,即TFTP客戶端要下載的文件名。
- 第4個參數是TFTP客戶端上新建文件的文件名,用於存儲從TFTP客戶端下載的文件。如果這個參數填NULL,那麼此文件在TFTP服務器上的文件名是什麼,下載後還是什麼。
- 第5個參數填此函數的回調函數,當TFTP會話即將結束時,會調用這個函數。此回調函數只有一個形參,形參類型如下:
- 返回值,返回__TRUE表示TFTP客戶端啓動成功(注意,僅僅是客戶端啓動成功,並不是文件傳輸已經完成),返回__FALSE表示啓動失敗。
使用這個函數要注意以下問題:
- 標準TFTP的端口號是用的UDP端口69。
- 用戶是通過此函數啓動RL-TCPnet的TFTP客戶端下載TFTP服務器上的文件。
使用舉例:
/* ********************************************************************************************************* * 宏定義,遠程FTP服務器的IP和端口 ********************************************************************************************************* */ /* 要訪問的遠程FTP服務器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* FTP服務器,默認端口號是69,無需改動 */ /* ********************************************************************************************************* * 變量 ********************************************************************************************************* */ uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 數 名: ftpc_notify * 功能說明: 函數tftpc_put和tftpc_get的回調函數。 * 形 參: event 事件類型 * 返 回 值: 無 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件傳輸成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服務器響應超時,因此TFTP客戶端終止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 訪問的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服務器上找不到要訪問的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服務器的存儲器空間已經滿,無法再進行文件傳輸 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件傳輸過程中,TFTP服務器遇到一個錯誤 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定義 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 數 名: TCPnetTest * 功能說明: TCPent測試函數。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while (1) { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K2鍵按下,從TFTP服務器下載文件server.pdf,並重命名爲client.pdf */ case KEY2_BIT1: if (tftpc_get (ServerIP, PORT_NUM, "server.pdf", "client.pdf", tftpc_notify) == __FALSE) { printf_debug("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的鍵值不處理 */ default: break; } while (main_TcpNet() == __TRUE); } }
40.2.6 函數ftpc_put
函數原型:
BOOL tftpc_put ( U8* ipadr, /* 遠程TFTP服務器的IP地址. */ U16 port, /* 遠程TFTP服務器的IP地址. */ const char *src, /* 本地要上傳文件的文件名 */ const char *dst, /* 遠程TFTP服務器上文件的文件名 */ void (*cbfunc)(U8 event) ); /* 回調函數 */
函數描述:
函數tftpc_put用於啓動RL-TCPnet系統上的TFTP客戶端,將本地文件上傳到TFTP服務器。 這樣TFTP客戶端就可以通過連接到UDP端口號爲port(本函數的第2個形參)的TFTP服務器來啓動TFTP會話。如果第2個參數的端口號填0,系統將使用TFTP服務器的標準端口號69進行連接。
- 第1個參數填TFTP服務器的IP地址。
- 第2個參數填TFTP服務器的端口號。
- 第3個參數是TFTP客戶端上的文件名,此文件是要被上傳到TFTP服務器。
- 第4個參數是TFTP服務器上新建文件的文件名,用於存儲從TFTP客戶端上傳的文件。如果這個參數填NULL,那麼此文件在TFTP客戶端上文件名是什麼,上傳到TFTP服務器後還是什麼。
- 第5個參數填此函數的回調函數,當TFTP會話即將結束時,會調用這個函數。此回調函數只有一個形參,形參類型如下:
- 返回值,返回__TRUE表示TFTP客戶端啓動成功(注意,僅僅是客戶端啓動成功,並不是文件傳輸已經完成),返回__FALSE表示啓動失敗。
使用這個函數要注意以下問題:
- 標準TFTP的端口號是用的UDP端口69。
- 用戶是通過此函數啓動RL-TCPnet的TFTP客戶端上傳本地文件到TFTP服務器。
使用舉例:
/* ********************************************************************************************************* * 宏定義,遠程FTP服務器的IP和端口 ********************************************************************************************************* */ /* 要訪問的遠程FTP服務器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* FTP服務器,默認端口號是69,無需改動 */ /* ********************************************************************************************************* * 變量 ********************************************************************************************************* */ uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 數 名: ftpc_notify * 功能說明: 函數tftpc_put和tftpc_get的回調函數。 * 形 參: event 事件類型 * 返 回 值: 無 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件傳輸成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服務器響應超時,因此TFTP客戶端終止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 訪問的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服務器上找不到要訪問的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服務器的存儲器空間已經滿,無法再進行文件傳輸 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件傳輸過程中,TFTP服務器遇到一個錯誤 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定義 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 數 名: TCPnetTest * 功能說明: TCPent測試函數。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while (1) { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K3鍵按下,K2按鍵按下後下載的client.pdf文件上傳到TFTP服務器 */ case KEY3_BIT2: if (tftpc_put (ServerIP, PORT_NUM, "client.pdf", NULL, tftpc_notify) == __FALSE) { printf("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的鍵值不處理 */ default: break; } while (main_TcpNet() == __TRUE); } }
40.3 TFTP服務器端軟件和板子的操作步驟
本章節的測試稍麻煩些,需要大家配置工程,並且在電腦端建立一個TFTP服務器,而開發板是作爲客戶端,並且採用SD卡作爲存儲介質(測試前要準備好一個SD卡插到開發板上面),所以大家測試本章節配套的例子前,務必將這裏的操作步驟全部看完纔可以做測試!
另外有一點特別注意,我們使用的是RL-FlashFS文件系統,此文件系統的文件名僅支持ASCII字符,不支持中文,對於中文名的文件夾或者文件是無法操作的,因此,電腦端創建TFTP服務器的時候,使用的文件名也不要有中文。
40.3.1 獲取電腦的IP地址
獲取電腦IP地址的方法很多,可以在網上鄰居獲取,也可以通過輸入命令ipconfig獲取:
- WIN+R組合鍵打開“運行”窗口,輸入cmd。
- 彈出的命令窗口中,輸入ipconfig。
- 輸入ipconfig後,回車。
獲得電腦的IP地址是192.168.1.4。
40.3.2 在程序中配置要訪問的TFTP服務器IP地址和端口
根據剛獲得的IP地址,需要大家配置程序中app_tcpnet_lib.c文件開頭的宏定義:
/* ********************************************************************************************************* * 宏定義,遠程TFTP服務器的IP和端口 ********************************************************************************************************* */ /* 要訪問的遠程FTP服務器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* TFTP服務器,默認端口號是69,無需改動 */
40.3.3 TFTP服務器軟件安裝和設置
第1步:下載TFTP服務器軟件。
TFTP軟件推薦採用TFTPD32,客戶端和服務器都支持,分32bit和64bit兩個版本,大家根據自己電腦系統選擇相應版本進行安裝,另外推薦綠色版,無需安裝,使用起來簡單省事。下載地址:http://bbs.armfly.com/read.php?tid=32486 。
第2步:下載綠色版後,解壓出來就可以使用,打開軟件的效果如下(我的系統是WIN7 64bit,所以使用的是64位版本):
第3步:關閉不需要的功能,僅留下TFTP Server(不是必須的,僅剩下服務器功能,看着簡潔些)。
首先點擊settings:
在彈出的窗口裏面僅選擇TFTP Server:
設置後,點擊OK按鍵,彈出如下窗口,繼續點擊OK:
經過這麼設置後,就僅剩下TFTP服務器功能了,爲了使得設置的功能起作用,務必關閉軟件,然後重新打開。
設置完畢後,就可以測試文件的上傳和下載功能了。
40.3.4 開發板下載TFTP服務器上的文件
第1步:準備一個測試文件:
簡單的在電腦桌面上創建一個文件夾,起名爲good(任何其它地方均可,但建議不要有中文,防止測試不成功)
爲了方便查看上傳和下載文件的效果,找一個稍大些的文件放到此文件夾,這裏將我們之前做的FreeRTOS教程放到這個新建的文件夾裏面(已經將這個文件放在了本章節配套例子的Doc文件夾),起名爲server.pdf,務必且只能設置成此名字,因爲我們的程序中是配置成訪問此文件。
僅放這一個文件即可。
第2步:配置文件訪問路徑:
現在需要將good文件夾路徑添加到TFTP服務器軟件上。
第3步:選擇電腦端用於通信的網口IP:
設置完畢後就可以測試開發板下載TFTP服務器上的server.pdf文件了,首先需要用戶先將SD卡插到開發板上,因爲文件server.pdf是下載到開發板中的SD裏面,然後下載本章節配套的程序,程序下載後,串口調試助手會打印網絡初始化過程(波特率115200,數據位8,奇偶校驗位無,停止位1):
上面的6條信息都打印出來後,就可以按下K2按鍵了,之後就可以看到開發板從TFTP服務器下載文件的進度,速度有1MB/S左右。
下載完畢後,大家可以查看SD卡中是否有一個client.pdf文件(程序中將下載的server.pdf文件重命名成client.pdf),然後查看此文件是否可以正常打開並瀏覽,如果正常的話,說明下載成功,否則下載失敗。並且下載成功後,串口調試助手會打印如下信息:
40.3.5 開發板上傳文件到TFTP服務器
爲了方便測試,我們這裏直接將40.5.4小節中下載到開發板SD卡中的client.pdf文件上傳到電腦端。上傳後的名字不換,還叫client.pdf。對於TFTP服務器軟件,還繼續用之前設置好的,這裏什麼都不用修改,用戶僅需按下開發板上的K3按鍵,之後就可以看到如下上傳進度,速度1MB/S左右。
上傳完畢後,爲了驗證下載是否成功,需要大家查看之前創建的good文件夾中client.pdf文件是否可以正常打開並瀏覽,如果沒有問題,說明上傳成功,否則失敗。
並且上傳成功後,串口調試助手會打印如下信息:
至此,TFTP客戶端的文件上傳和下載功能就都測試完畢了。
40.4 實驗例程說明(RTX)
40.4.1 STM32F407開發板實驗
配套例子:
V5-1060_RL-TCPnet實驗_TFTP客戶端(RTX)
實驗目的:
- 學習RL-TCPnet的TFTP客戶端實現。
實驗內容:
- 強烈推薦將網線接到路由器或者交換機上面測試,因爲已經使能了DHCP,可以自動獲取IP地址。
- TFTP客戶端的存儲器是採用的SD卡,所以測試本例子前務必準備好一個SD卡並插上。
- 文件系統是採用的RL-FlashFS,此文件系統的文件名僅支持ASCII字符,不支持中文,特別注意!
- 遠程TFTP服務器的IP地址和端口號是在文件app_tcpnet_lib.c開頭的宏定義設置。
- 測試本例子,需要在電腦端先建立TFTP服務器,具體建立方法和本例子的測試步驟在本實例配套教程裏面有詳細講解,必看!!
- K2按鍵按下,將TFTP服務器上的server.pdf文件下載到開發板的SD卡中,重命名爲client.pdf。
- K3按鍵按下,將K2按鍵按下後下載的client.pdf文件上傳回TFTP服務器。所以務必要優先測試K2按鍵的文件下載功能。
實驗操作:
詳見本章節40.5小節。
配置嚮導文件設置(Net_Config.c):
詳見本章節40.3小節。
調試文件設置(Net_Debug.c):
詳見本章節40.4小節。
RTX配置:
RTX配置嚮導詳情如下:
Task Configuration
(1) Number of concurrent running tasks
允許創建6個任務,實際創建瞭如下5個任務:
AppTaskUserIF任務 :按鍵消息處理。
AppTaskLED任務 :LED閃爍。
AppTaskMsgPro任務 :按鍵檢測。
AppTaskTCPMain任務:RL-TCPnet測試任務。
AppTaskStart任務 :啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。
(2) Number of tasks with user-provided stack
創建的5個任務都是採用自定義堆棧方式。
(3)Run in privileged mode
設置任務運行在非特權級模式。
RTX任務調試信息:
程序設計:
任務棧大小分配:
static uint64_t AppTaskUserIFStk[1024/8]; /* 任務棧 */
static uint64_t AppTaskLEDStk[1024/8]; /* 任務棧 */
static uint64_t AppTaskMsgProStk[1024/8]; /* 任務棧 */
static uint64_t AppTaskTCPMainStk[4096/8]; /* 任務棧 */
static uint64_t AppTaskStartStk[1024/8]; /* 任務棧 */
將任務棧定義成uint64_t類型可以保證任務棧是8字節對齊的,8字節對齊的含義就是數組的首地址對8求餘等於0。如果不做8字節對齊的話,部分C語言庫函數、浮點運算和uint64_t類型數據運算會出問題。
系統棧大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: 標準c程序入口。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ int main (void) { /* 初始化外設 */ bsp_Init(); /* 創建啓動任務 */ os_sys_init_user (AppTaskStart, /* 任務函數 */ 5, /* 任務優先級 */ &AppTaskStartStk, /* 任務棧 */ sizeof(AppTaskStartStk)); /* 任務棧大小,單位字節數 */ while(1); }
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 由於ST固件庫的啓動文件已經執行了CPU系統時鐘的初始化,所以不必再次重複配置系統時鐘。 啓動文件配置了CPU主時鐘頻率、內部Flash訪問速度和可選的外部SRAM FSMC初始化。 系統時鐘缺省配置爲168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 優先級分組設置爲4,可配置0-15級搶佔式優先級,0級子優先級,即不存在子優先級。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); bsp_InitDWT(); /* 初始化DWT */ bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按鍵變量(必須在 bsp_InitTimer() 之前調用) */ bsp_InitLed(); /* 初始LED指示燈端口 */ MountSD(); /* 掛載SD卡 */ }
RTX任務創建:
/* ********************************************************************************************************* * 函 數 名: AppTaskCreate * 功能說明: 創建應用任務 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void AppTaskCreate (void) { HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任務函數 */ 1, /* 任務優先級 */ &AppTaskUserIFStk, /* 任務棧 */ sizeof(AppTaskUserIFStk)); /* 任務棧大小,單位字節數 */ HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任務函數 */ 2, /* 任務優先級 */ &AppTaskLEDStk, /* 任務棧 */ sizeof(AppTaskLEDStk)); /* 任務棧大小,單位字節數 */ HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任務函數 */ 3, /* 任務優先級 */ &AppTaskMsgProStk, /* 任務棧 */ sizeof(AppTaskMsgProStk)); /* 任務棧大小,單位字節數 */ HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain, /* 任務函數 */ 4, /* 任務優先級 */ &AppTaskTCPMainStk, /* 任務棧 */ sizeof(AppTaskTCPMainStk)); /* 任務棧大小,單位字節數 */ }
五個RTX任務的實現:
/* ********************************************************************************************************* * 函 數 名: AppTaskUserIF * 功能說明: 按鍵消息處理 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 1 (數值越小優先級越低,這個跟uCOS相反) ********************************************************************************************************* */ __task void AppTaskUserIF(void) { uint8_t ucKeyCode; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1鍵按下 */ case KEY_DOWN_K1: printf("K1鍵按下\r\n"); break; /* K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit1 */ case KEY_DOWN_K2: printf("K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit1被設置\r\n"); os_evt_set (KEY2_BIT1, HandleTaskTCPMain); break; /* K3鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit2 */ case KEY_DOWN_K3: printf("K3鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit2被設置\r\n"); os_evt_set (KEY3_BIT2, HandleTaskTCPMain); break; /* 其他的鍵值不處理 */ default: break; } } os_dly_wait(20); } } /* ********************************************************************************************************* * 函 數 名: AppTaskLED * 功能說明: LED閃爍。 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 2 ********************************************************************************************************* */ __task void AppTaskLED(void) { const uint16_t usFrequency = 500; /* 延遲週期 */ /* 設置延遲週期 */ os_itv_set(usFrequency); while(1) { bsp_LedToggle(2); /* os_itv_wait是絕對延遲,os_dly_wait是相對延遲。*/ os_itv_wait(); } } /* ********************************************************************************************************* * 函 數 名: AppTaskMsgPro * 功能說明: 按鍵檢測 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 3 ********************************************************************************************************* */ __task void AppTaskMsgPro(void) { while(1) { bsp_KeyScan(); os_dly_wait(10); } } /* ********************************************************************************************************* * 函 數 名: AppTaskTCPMain * 功能說明: RL-TCPnet測試任務 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while (1) { TCPnetTest(); } } /* ********************************************************************************************************* * 函 數 名: AppTaskStart * 功能說明: 啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 創建任務 */ AppTaskCreate(); os_itv_set (100); while(1) { os_itv_wait (); /* RL-TCPnet時間基準更新函數 */ timer_tick (); os_evt_set(0x0001, HandleTaskTCPMain); } }
RL-TCPnet功能測試
這裏專門創建了一個app_tcpnet_lib.c文件用於RL-TCPnet功能的測試,此文件主要實現開發板從TFTP服務器下載文件和上傳文件到TFTP服務器以及網絡主函數main_TcpNet的調用。
#include "includes.h" /* ********************************************************************************************************* * 用於本文件的調試 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 宏定義,遠程FTP服務器的IP和端口 ********************************************************************************************************* */ /* 要訪問的遠程FTP服務器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* FTP服務器,默認端口號是69,無需改動 */ /* ********************************************************************************************************* * 變量 ********************************************************************************************************* */ uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 數 名: ftpc_notify * 功能說明: 函數tftpc_put和tftpc_get的回調函數。 * 形 參: event 事件類型 * 返 回 值: 無 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件傳輸成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服務器響應超時,因此TFTP客戶端終止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 訪問的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服務器上找不到要訪問的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服務器的存儲器空間已經滿,無法再進行文件傳輸 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件傳輸過程中,TFTP服務器遇到一個錯誤 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定義 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 數 名: TCPnetTest * 功能說明: TCPent測試函數。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while (1) { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K2鍵按下,從TFTP服務器下載文件server.pdf,並重命名爲client.pdf */ case KEY2_BIT1: if (tftpc_get (ServerIP, PORT_NUM, "server.pdf", "client.pdf", tftpc_notify) == __FALSE) { printf_debug("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 接收到K3鍵按下,將K2按鍵按下後下載的client.pdf文件上傳到TFTP服務器 */ case KEY3_BIT2: if (tftpc_put (ServerIP, PORT_NUM, "client.pdf", NULL, tftpc_notify) == __FALSE) { printf("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的鍵值不處理 */ default: break; } while (main_TcpNet() == __TRUE); } }
TFTP用戶接口文件的實現
KEIL官網有提供TFTP的接口文件,名爲TFTPC_uif.c文件。我們就是在這個文件上修改。具體修改後的代碼如下:
#include <stdio.h> #include <Net_Config.h> /*---------------------------------------------------------------------------- * TFTP Client File Access Functions *---------------------------------------------------------------------------*/ /*--------------------------- tftpc_fopen -----------------------------------*/ void *tftpc_fopen (U8 *fname, U8 *mode) { /* Open local file for reading or writing. */ return (fopen ((char *)fname, (char *)mode)); } /*--------------------------- tftpc_fclose ----------------------------------*/ void tftpc_fclose (void *file) { /* Close a local file. */ fclose (file); } /*--------------------------- tftpc_read ------------------------------------*/ U16 tftpc_fread (void *file, U8 *buf, U16 len) { /* Read 'len' bytes from file to buffer 'buf'. Return number of bytes */ /* copied. The file will be closed, when the return value is < 'len' */ return (fread (buf, 1, len, file)); } /*--------------------------- tftpc_write -----------------------------------*/ U16 tftpc_fwrite (void *file, U8 *buf, U16 len) { /* Write data to file. Return number of bytes actually written. */ return (fwrite (buf, 1, len, file)); } /*---------------------------------------------------------------------------- * end of file *---------------------------------------------------------------------------*/
40.4.2 STM32F429開發板實驗
配套例子:
V6-1060_RL-TCPnet實驗_TFTP客戶端(RTX)
實驗目的:
- 學習RL-TCPnet的TFTP客戶端實現。
實驗內容:
- 強烈推薦將網線接到路由器或者交換機上面測試,因爲已經使能了DHCP,可以自動獲取IP地址。
- TFTP客戶端的存儲器是採用的SD卡,所以測試本例子前務必準備好一個SD卡並插上。
- 文件系統是採用的RL-FlashFS,此文件系統的文件名僅支持ASCII字符,不支持中文,特別注意!
- 遠程TFTP服務器的IP地址和端口號是在文件app_tcpnet_lib.c開頭的宏定義設置。
- 測試本例子,需要在電腦端先建立TFTP服務器,具體建立方法和本例子的測試步驟在本實例配套教程裏面有詳細講解,必看!!
- K2按鍵按下,將TFTP服務器上的server.pdf文件下載到開發板的SD卡中,重命名爲client.pdf。
- K3按鍵按下,將K2按鍵按下後下載的client.pdf文件上傳回TFTP服務器。所以務必要優先測試K2按鍵的文件下載功能。
實驗操作:
詳見本章節40.5小節。
配置嚮導文件設置(Net_Config.c):
詳見本章節40.3小節。
調試文件設置(Net_Debug.c):
詳見本章節40.4小節。
RTX配置:
RTX配置嚮導詳情如下:
Task Configuration
(1)Number of concurrent running tasks
允許創建6個任務,實際創建瞭如下5個任務:
AppTaskUserIF任務 :按鍵消息處理。
AppTaskLED任務 :LED閃爍。
AppTaskMsgPro任務 :按鍵檢測。
AppTaskTCPMain任務:RL-TCPnet測試任務。
AppTaskStart任務 :啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。
(2)Number of tasks with user-provided stack
創建的5個任務都是採用自定義堆棧方式。
(3)Run in privileged mode
設置任務運行在非特權級模式。
RTX任務調試信息:
程序設計:
任務棧大小分配:
static uint64_t AppTaskUserIFStk[1024/8]; /* 任務棧 */
static uint64_t AppTaskLEDStk[1024/8]; /* 任務棧 */
static uint64_t AppTaskMsgProStk[1024/8]; /* 任務棧 */
static uint64_t AppTaskTCPMainStk[4096/8]; /* 任務棧 */
static uint64_t AppTaskStartStk[1024/8]; /* 任務棧 */
將任務棧定義成uint64_t類型可以保證任務棧是8字節對齊的,8字節對齊的含義就是數組的首地址對8求餘等於0。如果不做8字節對齊的話,部分C語言庫函數、浮點運算和uint64_t類型數據運算會出問題。
系統棧大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 數 名: main * 功能說明: 標準c程序入口。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ int main (void) { /* 初始化外設 */ bsp_Init(); /* 創建啓動任務 */ os_sys_init_user (AppTaskStart, /* 任務函數 */ 5, /* 任務優先級 */ &AppTaskStartStk, /* 任務棧 */ sizeof(AppTaskStartStk)); /* 任務棧大小,單位字節數 */ while(1); }
硬件外設初始化
硬件外設的初始化是在 bsp.c 文件實現:
/* ********************************************************************************************************* * 函 數 名: bsp_Init * 功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次 * 形 參:無 * 返 回 值: 無 ********************************************************************************************************* */ void bsp_Init(void) { /* 由於ST固件庫的啓動文件已經執行了CPU系統時鐘的初始化,所以不必再次重複配置系統時鐘。 啓動文件配置了CPU主時鐘頻率、內部Flash訪問速度和可選的外部SRAM FSMC初始化。 系統時鐘缺省配置爲168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 優先級分組設置爲4,可配置0-15級搶佔式優先級,0級子優先級,即不存在子優先級。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); SystemCoreClockUpdate(); /* 根據PLL配置更新系統時鐘頻率變量 SystemCoreClock */ bsp_InitDWT(); /* 初始化DWT */ bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按鍵變量(必須在 bsp_InitTimer() 之前調用) */ bsp_InitExtIO(); /* FMC總線上擴展了32位輸出IO, 操作LED等外設必須初始化 */ bsp_InitLed(); /* 初始LED指示燈端口 */ MountSD(); /* 掛載SD卡 */ }
RTX任務創建:
/* ********************************************************************************************************* * 函 數 名: AppTaskCreate * 功能說明: 創建應用任務 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ static void AppTaskCreate (void) { HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任務函數 */ 1, /* 任務優先級 */ &AppTaskUserIFStk, /* 任務棧 */ sizeof(AppTaskUserIFStk)); /* 任務棧大小,單位字節數 */ HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任務函數 */ 2, /* 任務優先級 */ &AppTaskLEDStk, /* 任務棧 */ sizeof(AppTaskLEDStk)); /* 任務棧大小,單位字節數 */ HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任務函數 */ 3, /* 任務優先級 */ &AppTaskMsgProStk, /* 任務棧 */ sizeof(AppTaskMsgProStk)); /* 任務棧大小,單位字節數 */ HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain, /* 任務函數 */ 4, /* 任務優先級 */ &AppTaskTCPMainStk, /* 任務棧 */ sizeof(AppTaskTCPMainStk)); /* 任務棧大小,單位字節數 */ }
五個RTX任務的實現:
/* ********************************************************************************************************* * 函 數 名: AppTaskUserIF * 功能說明: 按鍵消息處理 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 1 (數值越小優先級越低,這個跟uCOS相反) ********************************************************************************************************* */ __task void AppTaskUserIF(void) { uint8_t ucKeyCode; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1鍵按下 */ case KEY_DOWN_K1: printf("K1鍵按下\r\n"); break; /* K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit1 */ case KEY_DOWN_K2: printf("K2鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit1被設置\r\n"); os_evt_set (KEY2_BIT1, HandleTaskTCPMain); break; /* K3鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit2 */ case KEY_DOWN_K3: printf("K3鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit2被設置\r\n"); os_evt_set (KEY3_BIT2, HandleTaskTCPMain); break; /* 其他的鍵值不處理 */ default: break; } } os_dly_wait(20); } } /* ********************************************************************************************************* * 函 數 名: AppTaskLED * 功能說明: LED閃爍。 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 2 ********************************************************************************************************* */ __task void AppTaskLED(void) { const uint16_t usFrequency = 500; /* 延遲週期 */ /* 設置延遲週期 */ os_itv_set(usFrequency); while(1) { bsp_LedToggle(2); /* os_itv_wait是絕對延遲,os_dly_wait是相對延遲。*/ os_itv_wait(); } } /* ********************************************************************************************************* * 函 數 名: AppTaskMsgPro * 功能說明: 按鍵檢測 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 3 ********************************************************************************************************* */ __task void AppTaskMsgPro(void) { while(1) { bsp_KeyScan(); os_dly_wait(10); } } /* ********************************************************************************************************* * 函 數 名: AppTaskTCPMain * 功能說明: RL-TCPnet測試任務 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while (1) { TCPnetTest(); } } /* ********************************************************************************************************* * 函 數 名: AppTaskStart * 功能說明: 啓動任務,也是最高優先級任務,這裏實現RL-TCPnet的時間基準更新。 * 形 參: 無 * 返 回 值: 無 * 優 先 級: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 創建任務 */ AppTaskCreate(); os_itv_set (100); while(1) { os_itv_wait (); /* RL-TCPnet時間基準更新函數 */ timer_tick (); os_evt_set(0x0001, HandleTaskTCPMain); } }
RL-TCPnet功能測試
這裏專門創建了一個app_tcpnet_lib.c文件用於RL-TCPnet功能的測試,此文件主要實現開發板從TFTP服務器下載文件和上傳文件到TFTP服務器以及網絡主函數main_TcpNet的調用。
#include "includes.h" /* ********************************************************************************************************* * 用於本文件的調試 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 宏定義,遠程FTP服務器的IP和端口 ********************************************************************************************************* */ /* 要訪問的遠程FTP服務器IP配置 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 4 #define PORT_NUM 69 /* FTP服務器,默認端口號是69,無需改動 */ /* ********************************************************************************************************* * 變量 ********************************************************************************************************* */ uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4}; /* ********************************************************************************************************* * 函 數 名: ftpc_notify * 功能說明: 函數tftpc_put和tftpc_get的回調函數。 * 形 參: event 事件類型 * 返 回 值: 無 ********************************************************************************************************* */ static void tftpc_notify (U8 event) { switch (event) { /* 文件傳輸成功 */ case TFTPC_EVT_SUCCESS: printf_debug ("File successfully transferred.\r\n"); break; /* TFTP服務器響應超時,因此TFTP客戶端終止操作 */ case TFTPC_EVT_TIMEOUT: printf_debug ("TFTP Server timeout.\r\n"); break; /* 訪問的是禁止操作的文件 */ case TFTPC_EVT_NOACCESS: printf_debug ("File access on TFTP server is not allowed for a specified file.\r\n"); break; /* 在TFTP服務器上找不到要訪問的文件 */ case TFTPC_EVT_NOTFOUND: printf_debug ("Requested file is not found on TFTP server.\r\n"); break; /* TFTP服務器的存儲器空間已經滿,無法再進行文件傳輸 */ case TFTPC_EVT_DISKFULL: printf_debug ("The TFTP server has run out of disk space.\r\n"); break; /* 文件傳輸過程中,TFTP服務器遇到一個錯誤 */ case TFTPC_EVT_ERROR: printf_debug ("The TFTP server has encountered an error during file transfer process.\r\n"); break; /* 其它未定義 */ default: printf_debug ("No Defined.\n"); break; } } /* ********************************************************************************************************* * 函 數 名: TCPnetTest * 功能說明: TCPent測試函數。 * 形 參: 無 * 返 回 值: 無 ********************************************************************************************************* */ void TCPnetTest(void) { OS_RESULT xResult; while (1) { os_evt_wait_or(0x0007, 0xFFFF); xResult = os_evt_get (); switch (xResult) { /* 接收到K2鍵按下,從TFTP服務器下載文件server.pdf,並重命名爲client.pdf */ case KEY2_BIT1: if (tftpc_get (ServerIP, PORT_NUM, "server.pdf", "client.pdf", tftpc_notify) == __FALSE) { printf_debug("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 接收到K3鍵按下,將K2按鍵按下後下載的client.pdf文件上傳到TFTP服務器 */ case KEY3_BIT2: if (tftpc_put (ServerIP, PORT_NUM, "client.pdf", NULL, tftpc_notify) == __FALSE) { printf("File transfer not started, TFTP Client not ready.\n"); } else { printf_debug("File transfer started.\n"); } break; /* 其他的鍵值不處理 */ default: break; } while (main_TcpNet() == __TRUE); } }
TFTP用戶接口文件的實現
KEIL官網有提供TFTP的接口文件,名爲TFTPC_uif.c文件。我們就是在這個文件上修改。具體修改後的代碼如下:
#include <stdio.h> #include <Net_Config.h> /*---------------------------------------------------------------------------- * TFTP Client File Access Functions *---------------------------------------------------------------------------*/ /*--------------------------- tftpc_fopen -----------------------------------*/ void *tftpc_fopen (U8 *fname, U8 *mode) { /* Open local file for reading or writing. */ return (fopen ((char *)fname, (char *)mode)); } /*--------------------------- tftpc_fclose ----------------------------------*/ void tftpc_fclose (void *file) { /* Close a local file. */ fclose (file); } /*--------------------------- tftpc_read ------------------------------------*/ U16 tftpc_fread (void *file, U8 *buf, U16 len) { /* Read 'len' bytes from file to buffer 'buf'. Return number of bytes */ /* copied. The file will be closed, when the return value is < 'len' */ return (fread (buf, 1, len, file)); } /*--------------------------- tftpc_write -----------------------------------*/ U16 tftpc_fwrite (void *file, U8 *buf, U16 len) { /* Write data to file. Return number of bytes actually written. */ return (fwrite (buf, 1, len, file)); } /*---------------------------------------------------------------------------- * end of file *---------------------------------------------------------------------------*/
40.5 總結
本章節就爲大家講解這麼多,其中TFTP的測試稍麻煩些,希望大家實際動手操作一遍,並將其熟練掌握。