【RL-TCPnet網絡教程】第37章 RL-TCPnet之FTP客戶端

第37章      RL-TCPnet之FTP客戶端

本章節爲大家講解RL-TCPnet的FTP客戶端應用,學習本章節前,務必要優先學習第35章的FTP基礎知識。有了這些基礎知識之後,再搞本章節會有事半功倍的效果。

本章教程含STM32F407開發板和STM32F429開發板。

37.1  初學者重要提示

37.2  FTP函數

37.3  FTP配置說明(Net_Config.c)

37.4  FTP調試說明(Net_Debug.c)

37.5  FTP訪問方法和板子的操作步驟

37.6  實驗例程說明(裸機)

37.7  實驗例程說明(RTX)

37.8  總結

37.1  初學者重要提示

  1.   學習本章節前,務必保證已經學習了第35章的基礎知識。
  2.   本章配套的例子是將開發板作爲FTP客戶端,使用開發板上面的SD卡作爲客戶端的存儲介質。所以測試本章節的例子,務必要準備一個SD卡。
  3.   由於配套例子的文件系統是採用的RL-FlashFS,此文件系統的文件名僅支持ASCII字符,不支持中文,特別注意!
  4.   具體電腦端FTP服務器的創建方法和板子的操作步驟在本章的37.5小節有詳細說明。做本章節配套的實驗,必須要看!

37.2  FTP函數

使用如下18個函數可以實現RL-TCPnet的FTP:

  •   ftp_accept_host
  •   ftp_check_account
  •   ftp_fclose
  •   ftp_evt_notify
  •   ftp_fdelete
  •   ftp_ffind
  •   ftp_file_access
  •   ftp_fopen
  •   ftp_fread
  •   ftp_frename Server
  •   ftp_fwrite Server
  •   ftp_get_user_id
  •   ftpc_cbfunc
  •   ftpc_connect
  •   ftpc_fclose
  •   ftpc_fopen
  •   ftpc_fread
  •   ftpc_fwrite

關於這18個函數的講解及其使用方法可以看教程第 3 章 3.4 小節裏面說的參考資料 rlarm.chm 文件:

這裏我們重點的說以下6個函數,因爲本章節配套的例子使用的是這6個函數:

  •   ftpc_cbfunc
  •   ftpc_connect
  •   ftpc_fclose
  •   ftpc_fopen
  •   ftpc_fread
  •   ftpc_fwrite

關於這些函數注意以下三點:

  1.   FTP的所有函數都不支持重入,也就是不支持多任務調用。
  2.   以ftp_開頭的函數是用於FTP服務器的。
  3.   以ftpc_開頭的函數是用於FTP客戶端的。

37.2.1    函數ftpc_fopen

函數原型:

void *ftpc_fopen (

    U8* mode);    /* 操作模式 */

函數描述:

函數ftpc_fopen用於打開本地文件(FTP客戶端的文件)。此函數在MDK的安裝目錄中的FTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。

  1.  第1個參數是操作模式,可以是讀操作或者寫操作,具體支持的形參類型如下:
  1. 返回值,打開文件成功的話,返回指向此文件的指針變量,否則返回NULL。

使用這個函數要注意以下問題:

  1. 此接口函數是用於FTP客戶端的。

使用舉例:

void *ftpc_fopen (U8 *mode) {

  /* 打開文件,如果返回NULL,表示打開失敗 */

  return (fopen (LOCAL_FILE, (char *)mode));

}

37.2.2   函數ftpc_fclose

函數原型:

void *ftpc_fclose (

    FILE* file);  /* 文件句柄地址 */

函數描述:

函數ftpc_fclose用於關閉文件。此函數在MDK的安裝目錄中的FTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。

  1.  第1個參數是要關閉的文件句柄地址。
  2.  返回值,實際上此函數無需返回任何數值,寫成下面使用舉例中的形式即可。

使用這個函數要注意以下問題:

  1. 此接口函數是用於FTP客戶端的。

使用舉例:

void ftpc_fclose (void *file) {

  /* 關閉文件 */

  fclose (file);

}

37.2.3   函數ftpc_fread

函數原型:

U16 ftpc_fread (

    FILE* file,     /* 文件句柄地址 */

    U8*   buf,      /* 數據緩衝地址 */

    U16   len );    /* 要讀取的字節數 */

函數描述:

函數ftpc_fread用於從文件中讀出len個字節數據。此函數在MDK的安裝目錄中的FTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。

  1.   第1個參數是要讀取數據的文件句柄地址。
  2.   第2個參數是數據緩衝地址,用於存儲讀取出來的數據。
  3.   第3個參數是要讀取出來的數據大小,單位字節。
  4.   返回值,返回從文件中實際讀出的字節數。

使用這個函數要注意以下問題:

  1. 設置讀取函數時,必須設置指定大小的字節數。如果實際讀出的字節數小於len,將停止讀取並關閉文件,這種情況一般都是文件已經讀取完畢。
  2. 此接口函數是用於FTP客戶端的。

使用舉例:

U16 ftpc_fread (void *file, U8 *buf, U16 len) {

  /* 讀取len字節到buf中,返回值是實際讀取的字節數,返回0的話,表示文件已經讀取完畢,文件將被關閉 */

  return (fread (buf, 1, len, file));

}

37.2.4   函數ftpc_fwrite

函數原型:

U16 ftpc_fwrite (

    FILE* file,     /* 文件句柄地址 */

    U8*   buf,      /* 數據緩衝地址 */

    U16   len );    /* 要寫入的字節數 */

函數描述:

函數ftpc_fwrite用於往文件中寫入len個字節數據。此函數在MDK的安裝目錄中的FTPC_uif.c文件裏面,屬於底層接口函數,用戶要在此函數裏面添加具體的操作。

  1. 第1個參數是要寫入數據的文件句柄地址。
  2. 第2個參數是數據緩衝地址,存儲要寫入的數據。
  3. 第3個參數是要寫入的數據大小,單位字節。
  4. 返回值,返回實際寫入文件的字節數。

使用這個函數要注意以下問題:

  1. 設置寫函數時,必須設置指定大小的字節數。如果實際寫入的字節數小於len,FTP客戶端將停止寫入,終止數據傳輸並關閉FTP會話,這種情況一般是寫操作出錯了。
  2. 此接口函數是用於FTP客戶端的。

使用舉例:

U16 ftpc_fwrite (void *file, U8 *buf, U16 len) {

  /* 將buf中的len字節寫入到文件中,如果返回數值(實際寫入的字節數)不等於len,數據傳輸將終止 */

  return (fwrite (buf, 1, len, file));

}

37.2.5   函數ftpc_cbfunc

函數原型:

U16 ftpc_cbfunc (

    U8   code,      /* 消息類型 */

    U8*  buf,       /* 輸出緩衝區地址 */

U16  buflen );  /* 輸出緩衝區大小 */

函數描述:

函數ftpc_cbfunc是FTP客戶端的回調函數,用於爲FTP客戶端會話提供額外參數,如登錄FTP服務器的用戶名和密碼、本地和遠程文件名等。FTP客戶端多次調用此函數纔可完成文件操作請求。

  1. 第1個參數是FTP客戶端所需的附加信息(用戶,密碼,文件名等)類型,具體支持的參數類型如下:
  1.  第2個參數是輸出緩衝區地址,用於將請求的數據寫入到輸出緩衝區。
  2. 第3個參數是輸出緩衝區大小,單位字節。
  3. 返回值,返回使用的輸出緩衝區大小,單位字節。

使用這個函數要注意以下問題:

  1. 每次通信,數據緩衝區的大小會有所不同,因爲它是由TCP Socket的MSS最大報文段協商決定的,局域網中一般是1400字節左右。
  2. 寫到輸出緩衝區的數據,不可以超過第三個參數buflen的大小,否則會造成內存指針鏈表的損壞,從而很容易造成系統崩潰。

使用舉例:

#define FTPC_USERNAME   "armfly"       /* 遠程FTP服務器的賬號 */

#define FTPC_PASSWORD   "123456"       /* 遠程FTP服務器的密碼 */

#define FTPC_PATH       ""             /* 要訪問的子文件夾,FTP電腦端設置後是沒有路徑的,填空 */

#define FTPC_FILENAME   "server.pdf"   /* 要訪問的文件 */

#define FTPC_NEWNAME    "renamed.pdf"  /* 使用這裏定義的文件字重命名宏定義FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 創建名字爲New_Folder的文件夾或者刪除名字爲New_Folder的文件夾 */

#define FTPC_LISTNAME   "*"            /* 瀏覽宏定義FTPC_PATH路徑下所有文件 */

 

#define LOCAL_FILE      "client.pdf"   /* 開發板SD卡里面要上傳的文件,或者從FTP服務器下載文件後,

                                          文件會被設置成此名字 */

 

 

U16 ftpc_cbfunc (U8 code, U8 *buf, U16 buflen) {

  /* This function is called by the FTP client to get parameters. It returns*/

  /* the number of bytes written to buffer 'buf'. This function should NEVER*/

  /* write more than 'buflen' bytes to this buffer.                         */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 = Username for FTP authentication                        */

  /*             1 = Password for FTP authentication                        */

  /*             2 = Working directory path for all commands                */

  /*             3 = Filename for PUT, GET, APPEND, DELETE, RENAME command  */

  /*             4 - New filename for RENAME command                        */

  /*             5 - Directory name for MKDIR, RMDIR command                */

  /*             6 - File filter/mask for LIST command (wildcards allowed)  */

  /*             7 = Received directory listing on LIST command             */

  /*   buf    - transmit/receive buffer                                     */

  /*   buflen - length of this buffer                                       */

  /*             on transmit, it specifies size of 'buf'                    */

  /*             on receive, specifies the number of bytes received         */

  U32 i,len = 0;

 

  switch (code) {

    case 0:

      /* Enter Username for login. */

      len = str_copy (buf, FTPC_USERNAME);

      break;

 

    case 1:

      /* Enter Password for login. */

      len = str_copy (buf, FTPC_PASSWORD);

      break;

 

    case 2:

      /* Enter a working directory path. */

      len = str_copy (buf, FTPC_PATH);

      break;

 

    case 3:

      /* Enter a filename for file operations. */

      len = str_copy (buf, FTPC_FILENAME);

      break;

 

    case 4:

      /* Enter a new name for rename command. */

      len = str_copy (buf, FTPC_NEWNAME);

      break;

 

    case 5:

      /* Enter a directory name to create or remove. */

      len = str_copy (buf, FTPC_DIRNAME);

      break;

 

    case 6:

      /* Enter a file filter for list command. */

      len = str_copy (buf, FTPC_LISTNAME);

      break;

 

    case 7:

      /* Process received directory listing in raw format. */

      /* Function return value is don't care.              */

      for (i = 0; i < buflen; i++) {

        putchar (buf[i]);

      }

      break;

  }

  return ((U16)len);

}

37.2.6   函數ftpc_connect

函數原型:

BOOL ftpc_connect (

    U8*   ipadr,                   /* FTP服務器的IP地址 */

    U16   port,                    /* FTP服務器的端口號 */

    U8    command,                 /* FTP命令 */

    void (*cbfunc)(U8 event) );    /* 回調函數 */

函數描述:

函數ftpc_connect用於啓動RL-TCPnet的FTP客戶端登錄FTP服務器,可以對FTP服務器進行文件管理,比如上傳下載、刪除、重命名等操作,FTP服務器的地址和端口號由此函數的前兩個形參決定。如果第2個參數的端口號填0,系統將使用FTP服務器的標準端口號21進行連接。

  1. 第1個參數填FTP服務器的IP地址。
  2. 第2個參數填FTP服務器的端口號。
  3. 第3個參數填FTP命令,具體支持的命令如下,這些命令是客戶端發給服務器的:
  1. 第4個參數填此函數的回調函數,當FTP會話即將結束時,會調用這個函數。此回調函數只有一個形參,形參類型如下:
  1. 返回值,返回__TRUE表示FTP客戶端啓動成功(注意,僅僅是客戶端啓動成功,並不是命令成功執行),返回__FALSE表示啓動失敗。

使用這個函數要注意以下問題:

  1. 標準FTP的端口號是用的TCP端口21。
  2. 用戶是通過此函數啓動RL-TCPnet的FTP客戶端登錄FTP服務器進行文件管理。

使用舉例:

/*

*********************************************************************************************************

*                                          變量

*********************************************************************************************************

*/

/*

*********************************************************************************************************

*                              宏定義,遠程FTP服務器的IP和端口

*********************************************************************************************************

*/

/* 要訪問的遠程FTP服務器IP配置 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            4

 

#define PORT_NUM       21  /* FTP服務器,默認端口號是21,無需改動 */

 

 

/*

*********************************************************************************************************

*                                         變量

*********************************************************************************************************

*/

uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4};

 

 

/*

*********************************************************************************************************

*    函 數 名: ftpc_notify

*    功能說明: 函數ftpc_connect的回調函數。

*    形    參: event  事件類型

*    返 回 值: 無

*********************************************************************************************************

*/

static void ftpc_notify (U8 event)

{

     switch (event)

     {

          /* 文件操作成功 */

         case FTPC_EVT_SUCCESS:

              printf_debug ("Command successful\n");

              break;

 

         /* 失敗,因爲FTP服務器響應時間溢出,因此FTP客戶端終止操作 */

         case FTPC_EVT_TIMEOUT:

              printf_debug ("Failed, timeout expired\n");

              break;

 

         /* 失敗,FTP客戶端登陸FTP服務器失敗 */

         case FTPC_EVT_LOGINFAIL:

              printf ("Failed, username/password invalid\n");

              break;

 

         /* 失敗,禁止操作此文件 */

         case FTPC_EVT_NOACCESS:

              printf_debug ("Failed, operation not allowed\n");

              break;

 

         /* 失敗,在FTP服務器上找不到請求的文件 */

         case FTPC_EVT_NOTFOUND:

              printf_debug ("Failed, file or path not found\n");

              break;

 

         /* 失敗,在FTP服務器上找不到工作目錄路徑 */

         case FTPC_EVT_NOPATH:

              printf ("Failed, working directory not found\n");

              break;

 

         /* 失敗,文件打開或者寫入出錯,即FTP客戶端操作SD卡或者其它存儲介質出錯 */

         case FTPC_EVT_ERRLOCAL:

              printf_debug ("Failed, local file open/write error\n");

              break;

 

         /* 失敗,未指定的協議錯誤,或者說在文件操作過程中遇到錯誤 */

         case FTPC_EVT_ERROR:

              printf_debug ("Failed, unspecified protocol error\n");

              break;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: TCPnetTest

*    功能說明: TCPent測試函數。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

    

     while (1)

     {

         os_evt_wait_or(0x003F, 0xFFFF); 

        

         xResult = os_evt_get ();

         switch (xResult)

         {

              /* 接收到K2鍵按下,發送FTPC_CMD_GET命令,表示從FTP服務器下載文件 */

              case KEY2_BIT1:   

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_GET, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

                  

              /* 接收到K3鍵按下,發送FTPC_CMD_PUT命令,表示向FTP服務器上傳文件 */

              case KEY3_BIT2:              

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_PUT, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

             

              /* 接收到搖桿上鍵按下,發送FTPC_CMD_LIST命令,表示列出當前目錄下所有文件詳細信息 */

              case JOY_U_BIT3:             

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_LIST, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

             

              /* 接收到搖桿左鍵按下,發送FTPC_CMD_MKDIR命令,表示創建一個文件夾 */

              case JOY_L_BIT4:

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_MKDIR, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

             

         /* 接收到搖桿右鍵按下,發送FTPC_CMD_RMDIR命令,表示刪除一個文件夾,文件夾爲空的時候纔可以刪除 */

              case JOY_R_BIT5:  

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_RMDIR, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

             

               /* 其他的鍵值不處理 */

              default:                    

                   break;

         }

             

         while (main_TcpNet() == __TRUE);

     }

}

37.3 FTP配置說明(Net_Config.c)

(本章節配套例子的配置與本小節的說明相同)

RL-TCPnet的配置工作是通過配置文件Net_Config.c實現。在MDK工程中打開文件Net_Config.c,可以看到下圖所示的工程配置嚮導:

RL-TCPnet要配置的選項非常多,我們這裏把幾個主要的配置選項簡單介紹下。

System Definitions

(1)Local Host Name

局域網域名。

這裏起名爲armfly,使用局域網域名限制爲15個字符。

(2)Memory Pool size

參數範圍1536-262144字節。

內存池大小配置,單位字節。另外注意一點,配置嚮導這裏顯示的單位是字節,如果看原始定義,MDK會做一個自動的4字節倍數轉換,比如我們這裏配置的是8192字節,那麼原始定義是#define MEM_SIZE  2048,也就是8192/4 = 2048。

(3)Tick Timer interval

可取10,20,25,40,50,100,200,單位ms。

系統滴答時鐘間隔,也就是網絡協議棧的系統時間基準,默認情況下,取值100ms。

Ethernet Network Interface

以太網接口配置,勾選了此選項就可以配置了,如果沒有使能DHCP的話,將使用這裏配置的固定IP

(1)  MAC Address

局域網內可以隨意配置,只要不跟局域網內其它設備的MAC地址衝突即可。

(2) IP Address

IP地址。

(3) Subnet mask

子網掩碼。

(4) Default Gateway

默認網關。

Ethernet Network Interface

以太網接口配置,這個配置裏面還有如下兩項比較重要的配置需要說明。

(1)  NetBIOS Name Service

NetBIOS局域網域名服務,這裏打上對勾就使能了。這樣我們就可以通過前面配置的Local Host Name局域網域名進行訪問,而不需要通過IP地址訪問了。

(2)  Dynaminc Host Configuration

即DHCP,這裏打上對勾就使能了。使能了DHCP後,RL-TCPnet就可以從外接的路由器上獲得動態IP地址。

UDP Sockets

UDP Sockets配置,打上對勾就使能了此項功能

(1)  Number of UDP Sockets

用於配置可創建的UDP Sockets數量,這裏配置了5個。

範圍1 – 20。

TCP Sockets

TCP Sockets配置,打上對勾就使能了此項功能

(1)  Number of TCP Sockets

用於配置可創建的TCP Sockets數量。

(2)  Number of Retries

範圍0-20。

用於配置重試次數,TCP數據傳輸時,如果在設置的重試時間內得不到應答,算一次重試失敗,這裏就是配置的最大重試次數。

(3) Retry Timeout in seconds

範圍1-10,單位秒。

重試時間。如果發送的數據在重試時間內得不到應答,將重新發送數據。

(4) Default Connect Timeout in seconds

範圍1-600,單位秒。

用於配置默認的保持連接時間,即我們常說的Keep Alive時間,如果時間到了將斷開連接。常用於HTTP Server,Telnet Server等。

(5)  Maximum Segment Size

範圍536-1460,單位字節。

MSS定義了TCP數據包能夠傳輸的最大數據分段。

(6) Receive Window Size

範圍536-65535,單位字節。

TCP接收窗口大小。

FTP Client

FTP 配置,打上對勾就使能了此項功能

(1)  Response Timeout in seconds

FTP客戶端等待FTP服務器響應時間,如果溢出,客戶端將終止操作。

範圍1-120,單位秒。

(2) Passive mode

被動模式,客戶端發起到服務器的數據連接,詳看第35章的35.3.5小節說明。

37.4 FTP調試說明(Net_Debug.c)

(重要說明,RL-TCPnet的調試是通過串口打印出來的)

RL-TCPnet的調試功能是通過配置文件Net_Debug.c實現。在MDK工程中打開文件Net_Debug.c,可以看到下圖所示的工程配置嚮導:

Print Time Stamp

勾選了此選項的話,打印消息時,前面會附帶時間信息。

其它所有的選項

默認情況下,所有的調試選項都關閉了,每個選項有三個調試級別可選擇,這裏我們以FTP Client Debug爲例,點擊下拉列表,可以看到裏面有Off,Errors only和Full debug三個調試級別可供選擇,每個調試選項裏面都是這三個級別。

Off:表示關閉此選項的調試功能。

Errors only:表示僅在此選項出錯時,將其錯誤打印出來。

Full debug:表示此選項的全功能調試。

具體測試,我們這裏就不做了,大家可以按照第11章講解的調試方法進行測試。

37.5 FTP服務器的建立方法和板子的操作步驟

本章節的測試稍麻煩些,需要大家配置工程,並且在電腦端建立一個FTP服務器,而開發板是作爲客戶端,並且採用SD卡作爲存儲介質(測試前要準備好一個SD卡插到開發板上面),所以大家測試本章節配套的例子前,務必將這裏的操作步驟全部看完纔可以做測試!

另外有一點特別注意,我們使用的是RL-FlashFS文件系統,此文件系統的文件名僅支持ASCII字符,不支持中文,對於中文名的文件夾或者文件是無法操作的,因此,電腦端創建FTP服務器的時候,使用的文件夾和文件名也不要有中文。

37.5.1 獲取電腦的IP地址

獲取電腦IP地址的方法很多,可以在網上鄰居獲取,也可以通過輸入命令ipconfig獲取:

  • WIN+R組合鍵打開“運行”窗口,輸入cmd。
  • 彈出的命令窗口中,輸入ipconfig。
  • 輸入ipconfig後,回車。

獲得電腦的IP地址是192.168.1.4。

37.5.2 在程序中配置要訪問的FTP服務器IP地址和端口

根據剛獲得的IP地址,需要大家配置程序中app_tcpnet_lib.c文件開頭的宏定義:

/*

*********************************************************************************************************

*                              宏定義,遠程FTP服務器的IP和端口

*********************************************************************************************************

*/

/* 要訪問的遠程FTP服務器IP配置 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            4

 

#define PORT_NUM       21  /* FTP服務器,默認端口號是21,無需改動 */

37.5.3 在程序中配置FTP服務器的賬號和文件管理參數

FTP服務器的賬號和文件管理參數已經在FTPC_uif.c文件開頭的宏定義中配置好,大家做測試是無需修改的。建議當前的功能測試完畢後,修改相關參數,做一些新的測試,以此來熟練掌握這些配置所代表的含義:

#define FTPC_USERNAME   "armfly"        /* 遠程FTP服務器的賬號 */

#define FTPC_PASSWORD   "123456"        /* 遠程FTP服務器的密碼 */

#define FTPC_PATH       ""              /* 要訪問的子文件夾,FTP電腦端設置後是沒有路徑的,填空 */

#define FTPC_FILENAME   "server.pdf"    /* 要訪問的文件 */

#define FTPC_NEWNAME    "renamed.pdf"   /* 使用這裏定義的文件字重命名宏定義FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 創建名字爲New_Folder的文件夾或者刪除名字爲New_Folder的文件夾 */

#define FTPC_LISTNAME   "*"             /* 瀏覽宏定義FTPC_PATH路徑下所有文件 */

 

#define LOCAL_FILE      "client.pdf"    /* 開發板SD卡里面要上傳的文件,或者從FTP服務器下載文件後,

                                           文件會被設置成此名字 */

37.5.4 電腦端創建FTP服務器

第1步:下載FTP服務器軟件

FTP軟件推薦採用FileZilla Server,下載地址:http://bbs.armfly.com/read.php?tid=32586 。建議下載帖子裏面二樓的中文版。

第2步:下載後,安裝此軟件,安裝完畢後,打開軟件的效果如下

使用默認參數,不用做任何修改,點擊確定,彈出如下界面:

第3步:設置一個可供外部訪問的FTP服務器賬戶

點擊編輯->用戶:

彈出如下界面:

點擊確定後,設置密碼:

第4步:創建要分享的文件夾

簡單的在電腦桌面上創建一個文件夾,起名爲good(任何其它地方均可,但建議不要有中文,防止測試不成功)。

爲了方便查看上傳和下載文件的效果,找一個稍大些的文件放到此文件夾,這裏將我們之前做的FreeRTOS教程放到這個新建的文件夾裏面(已經將這個文件放在了本章節配套例子的Doc文件夾),起名爲server.pdf,務必且只能設置成此名字,因爲我們的程序中是配置成訪問此文件。

僅放這一個文件即可。現在就可以將這個good文件夾路徑添加到FTP服務器軟件上。

添加完畢路徑後,配置權限,將所有權限都使能:

至此,就可以進行開發板的功能測試了。

37.5.5 開發板訪問FTP服務器操作說明

FTP服務器創建完畢後,開發板訪問FTP服務器就比較簡單了。

第1步:將本章節配套的例子下載到開發板後,務必將SD卡插上,因爲從FTP服務器上傳或者下載文件要用到。

第2步:務必優先測試下載功能(第3步還要將下載的這個文件上傳),按下開發板的K2按鍵,會將FTP服務器分享的server.pdf文件下載到SD卡中,下載後的文件起名叫client.pdf,文件是一樣的,只是換了個名字做區分。

下載成功後,串口調試助手打印出如下信息(波特率115200,數據位8,奇偶校驗位無,停止位1):

第3步:測試上傳功能,爲方便起見,需要大家將電腦端分享的server.pdf文件刪掉,然後按下開發板上的K3按鍵,實現將第2步下載的server.pdf文件重新上傳到FTP服務器:

上傳成功後,串口調試助手打印出如下信息:

第4步:測試完畢上傳下載功能後,按下搖桿上鍵,可以將good文件夾中的所有文件及其詳情列出來,下面是串口調試助手顯示的信息:

上面打印出來的是FTP服務器端的server.pdf文件詳情。

第5步:按下開發板上的搖桿左鍵,可以給good文件夾中創建一個新的文件夾New_Folder:

第6步:按下開發板上的搖桿右鍵,可以將新創建的New_Folder文件夾刪除:

至此,開發板實現的幾個功能已經都實現了,大家還可以測試下文件重命名、文件刪除等功能,不過最主要的還是文件的上傳和下載功能。大家測試的時候,別忘了實際打開文件看下,查看文件是否被損壞,以此來保證文件被成功傳輸了。

37.6 實驗例程說明(RTX)

37.6.1 STM32F407開發板實驗

配套例子:

V5-1054_RL-TCPnet實驗_FTP客戶端(RTX)

實驗目的:

  1. 學習RL-TCPnet的FTP客戶端實現。

實驗內容:

  1. 強烈推薦將網線接到路由器或者交換機上面測試,因爲已經使能了DHCP,可以自動獲取IP地址。
  2. FTP客戶端的存儲器是採用的SD卡,所以測試本例子前務必準備好一個SD卡並插上。
  3. 文件系統是採用的RL-FlashFS,此文件系統的文件名僅支持ASCII字符,不支持中文,特別注意!
  4. 遠程FTP服務器的IP地址和端口號是在app_tcpnet_lib.c文件開頭的宏定義設置。
  5. 需要上傳下載的文件、文件夾的創建和刪除、文件夾瀏覽等配置是在FTPC_uif.c文件開頭的宏定義設置。
  6. 測試本例子,需要在電腦端先建立FTP服務器,具體建立方法和本例子的測試步驟在本實例配套教程裏面有詳細講解,必看!
  7. K2按鍵按下,從FTP服務器下載文件到開發板的SD卡。
  8. K3按鍵按下,將開發板SD卡里面的文件上傳到FTP服務器。
  9. 搖桿上鍵按下,瀏覽FTP服務器當前目錄下的所有文件詳情。
  10. 搖桿左鍵按下,創建一個文件夾。
  11. 搖桿右鍵按下,刪除一個文件夾。

實驗操作:

詳見本章節37.5小節。

配置嚮導文件設置(Net_Config.c):

詳見本章節37.3小節。

調試文件設置(Net_Debug.c):

詳見本章節37.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;

                  

                   /* 搖桿上鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit3 */

                   case JOY_DOWN_U:

                       printf("搖桿上鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit3被設置\r\n");

                       os_evt_set (JOY_U_BIT3, HandleTaskTCPMain);

                       break;                

 

                   /* 搖桿左鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit4 */

                   case JOY_DOWN_L:

                       printf("搖桿左鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit4被設置\r\n");

                       os_evt_set (JOY_L_BIT4, HandleTaskTCPMain);

                       break;

                  

                   /* 搖桿右鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit5 */

                   case JOY_DOWN_R:

                       printf("搖桿右鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit5被設置\r\n");

                       os_evt_set (JOY_R_BIT5, 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功能的測試,此文件主要實現開發板發給FTP服務器的文件操作命令和網絡主函數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       21  /* FTP服務器,默認端口號是21,無需改動 */

 

 

/*

*********************************************************************************************************

*                                         變量

*********************************************************************************************************

*/

uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4};

 

 

/*

*********************************************************************************************************

*    函 數 名: ftpc_notify

*    功能說明: 函數ftpc_connect的回調函數。

*    形    參: event  事件類型

*    返 回 值: 無

*********************************************************************************************************

*/

static void ftpc_notify (U8 event)

{

     switch (event)

     {

         /* 文件操作成功 */

         case FTPC_EVT_SUCCESS:

              printf_debug ("Command successful\n");

              break;

 

         /* 失敗,因爲FTP服務器響應時間溢出,因此FTP客戶端終止操作 */

         case FTPC_EVT_TIMEOUT:

              printf_debug ("Failed, timeout expired\n");

              break;

 

         /* 失敗,FTP客戶端登陸FTP服務器失敗 */

         case FTPC_EVT_LOGINFAIL:

              printf ("Failed, username/password invalid\n");

              break;

 

         /* 失敗,禁止操作此文件 */

         case FTPC_EVT_NOACCESS:

              printf_debug ("Failed, operation not allowed\n");

              break;

 

         /* 失敗,在FTP服務器上找不到請求的文件 */

         case FTPC_EVT_NOTFOUND:

              printf_debug ("Failed, file or path not found\n");

              break;

 

         /* 失敗,在FTP服務器上找不到工作目錄路徑 */

         case FTPC_EVT_NOPATH:

              printf ("Failed, working directory not found\n");

              break;

 

         /* 失敗,文件打開或者寫入出錯,即FTP客戶端操作SD卡或者其它存儲介質出錯 */

         case FTPC_EVT_ERRLOCAL:

              printf_debug ("Failed, local file open/write error\n");

              break;

 

         /* 失敗,未指定的協議錯誤,或者說在文件操作過程中遇到錯誤 */

         case FTPC_EVT_ERROR:

              printf_debug ("Failed, unspecified protocol error\n");

              break;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: TCPnetTest

*    功能說明: TCPent測試函數。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

    

     while (1)

     {

         os_evt_wait_or(0x003F, 0xFFFF); 

        

         xResult = os_evt_get ();

         switch (xResult)

         {

              /* 接收到K2鍵按下,發送FTPC_CMD_GET命令,表示從FTP服務器下載文件 */

              case KEY2_BIT1:   

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_GET, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

                  

              /* 接收到K3鍵按下,發送FTPC_CMD_PUT命令,表示向FTP服務器上傳文件 */

              case KEY3_BIT2:              

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_PUT, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

             

              /* 接收到搖桿上鍵按下,發送FTPC_CMD_LIST命令,表示列出當前目錄下所有文件詳細信息 */

              case JOY_U_BIT3:             

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_LIST, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

             

              /* 接收到搖桿左鍵按下,發送FTPC_CMD_MKDIR命令,表示創建一個文件夾 */

              case JOY_L_BIT4:

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_MKDIR, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

             

         /* 接收到搖桿右鍵按下,發送FTPC_CMD_RMDIR命令,表示刪除一個文件夾,文件夾爲空的時候纔可以刪除 */

              case JOY_R_BIT5:  

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_RMDIR, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

             

               /* 其他的鍵值不處理 */

              default:                    

                   break;

         }

             

         while (main_TcpNet() == __TRUE);

     }

}

FTP用戶接口文件的實現

KEIL官網有提供FTP的接口文件,名爲FTPC_uif.c文件。我們就是在這個文件上修改。具體修改後的代碼如下:

#include <Net_Config.h>

#include <stdio.h>

 

#define FTPC_USERNAME   "armfly"       /* 遠程FTP服務器的賬號 */

#define FTPC_PASSWORD   "123456"       /* 遠程FTP服務器的密碼 */

#define FTPC_PATH       ""             /* 要訪問的子文件夾,FTP電腦端設置後是沒有路徑的,填空 */

#define FTPC_FILENAME   "server.pdf"   /* 要訪問的文件 */

#define FTPC_NEWNAME    "renamed.pdf"  /* 使用這裏定義的文件字重命名宏定義FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 創建名字爲New_Folder的文件夾或者刪除名字爲New_Folder的文件夾 */

#define FTPC_LISTNAME   "*"            /* 瀏覽宏定義FTPC_PATH路徑下所有文件 */

 

#define LOCAL_FILE      "client.pdf"   /* 開發板SD卡里面要上傳的文件,或者從FTP服務器下載文件後,

                                          文件會被設置成此名字 */

 

 

/*----------------------------------------------------------------------------

 *      FTP Client File Access and Data CallBack Functions

 *---------------------------------------------------------------------------*/

 

/*--------------------------- ftpc_fopen ------------------------------------*/

 

void *ftpc_fopen (U8 *mode) {

  /* Open local file for reading or writing. If the return value is NULL, */

  /* processing of FTP Client commands PUT, APPEND or GET is cancelled.   */

  return (fopen (LOCAL_FILE, (char *)mode));

}

 

 

/*--------------------------- ftpc_fclose -----------------------------------*/

 

void ftpc_fclose (void *file) {

  /* Close a local file. */

  fclose (file);

}

 

 

/*--------------------------- ftpc_fread ------------------------------------*/

 

U16 ftpc_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 0.         */

  /* For optimal performance the return value should be 'len'             */

  return (fread (buf, 1, len, file));

}

 

 

/*--------------------------- ftpc_fwrite -----------------------------------*/

 

U16 ftpc_fwrite (void *file, U8 *buf, U16 len) {

  /* Write 'len' bytes from buffer 'buf' to a file. Data transfer will be */

  /* aborted, if the return value is not equal 'len'.                     */

  return (fwrite (buf, 1, len, file));

}

 

 

/*--------------------------- ftpc_cbfunc -----------------------------------*/

 

U16 ftpc_cbfunc (U8 code, U8 *buf, U16 buflen) {

  /* This function is called by the FTP client to get parameters. It returns*/

  /* the number of bytes written to buffer 'buf'. This function should NEVER*/

  /* write more than 'buflen' bytes to this buffer.                         */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 = Username for FTP authentication                        */

  /*             1 = Password for FTP authentication                        */

  /*             2 = Working directory path for all commands                */

  /*             3 = Filename for PUT, GET, APPEND, DELETE, RENAME command  */

  /*             4 - New filename for RENAME command                        */

  /*             5 - Directory name for MKDIR, RMDIR command                */

  /*             6 - File filter/mask for LIST command (wildcards allowed)  */

  /*             7 = Received directory listing on LIST command             */

  /*   buf    - transmit/receive buffer                                     */

  /*   buflen - length of this buffer                                       */

  /*             on transmit, it specifies size of 'buf'                    */

  /*             on receive, specifies the number of bytes received         */

  U32 i,len = 0;

 

  switch (code) {

    case 0:

      /* Enter Username for login. */

      len = str_copy (buf, FTPC_USERNAME);

      break;

 

    case 1:

      /* Enter Password for login. */

      len = str_copy (buf, FTPC_PASSWORD);

      break;

 

    case 2:

      /* Enter a working directory path. */

      len = str_copy (buf, FTPC_PATH);

      break;

 

    case 3:

      /* Enter a filename for file operations. */

      len = str_copy (buf, FTPC_FILENAME);

      break;

 

    case 4:

      /* Enter a new name for rename command. */

      len = str_copy (buf, FTPC_NEWNAME);

      break;

 

    case 5:

      /* Enter a directory name to create or remove. */

      len = str_copy (buf, FTPC_DIRNAME);

      break;

 

    case 6:

      /* Enter a file filter for list command. */

      len = str_copy (buf, FTPC_LISTNAME);

      break;

 

    case 7:

      /* Process received directory listing in raw format. */

      /* Function return value is don't care.              */

      for (i = 0; i < buflen; i++) {

        putchar (buf[i]);

      }

      break;

  }

  return ((U16)len);

}

 

 

/*----------------------------------------------------------------------------

 * end of file

 *---------------------------------------------------------------------------*/

37.6.2 STM32F429開發板實驗

配套例子:

V6-1054_RL-TCPnet實驗_FTP客戶端(RTX)

實驗目的:

  1. 學習RL-TCPnet的FTP客戶端實現。

實驗內容:

  1. 強烈推薦將網線接到路由器或者交換機上面測試,因爲已經使能了DHCP,可以自動獲取IP地址。
  2. FTP客戶端的存儲器是採用的SD卡,所以測試本例子前務必準備好一個SD卡並插上。
  3. 文件系統是採用的RL-FlashFS,此文件系統的文件名僅支持ASCII字符,不支持中文,特別注意!
  4. 遠程FTP服務器的IP地址和端口號是在app_tcpnet_lib.c文件開頭的宏定義設置。
  5. 需要上傳下載的文件、文件夾的創建和刪除、文件夾瀏覽等配置是在FTPC_uif.c文件開頭的宏定義設置。
  6. 測試本例子,需要在電腦端先建立FTP服務器,具體建立方法和本例子的測試步驟在本實例配套教程裏面有詳細講解,必看!!
  7. K2按鍵按下,從FTP服務器下載文件到開發板的SD卡。
  8. K3按鍵按下,將開發板SD卡里面的文件上傳到FTP服務器。
  9. 搖桿上鍵按下,瀏覽FTP服務器當前目錄下的所有文件詳情。
  10. 搖桿左鍵按下,創建一個文件夾。
  11. 搖桿右鍵按下,刪除一個文件夾。

實驗操作:

詳見本章節37.5小節。

配置嚮導文件設置(Net_Config.c):

詳見本章節37.3小節。

調試文件設置(Net_Debug.c):

詳見本章節37.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;

                  

                   /* 搖桿上鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit3 */

                   case JOY_DOWN_U:

                       printf("搖桿上鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit3被設置\r\n");

                       os_evt_set (JOY_U_BIT3, HandleTaskTCPMain);

                       break;                

 

                   /* 搖桿左鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit4 */

                   case JOY_DOWN_L:

                       printf("搖桿左鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit4被設置\r\n");

                       os_evt_set (JOY_L_BIT4, HandleTaskTCPMain);

                       break;

                  

                   /* 搖桿右鍵按下,直接發送事件標誌給任務AppTaskTCPMain,設置bit5 */

                   case JOY_DOWN_R:

                       printf("搖桿右鍵按下,直接發送事件標誌給任務AppTaskTCPMain,bit5被設置\r\n");

                       os_evt_set (JOY_R_BIT5, 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功能的測試,此文件主要實現開發板發給FTP服務器的文件操作命令和網絡主函數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       21  /* FTP服務器,默認端口號是21,無需改動 */

 

 

/*

*********************************************************************************************************

*                                         變量

*********************************************************************************************************

*/

uint8_t ServerIP[4] = {IP1, IP2, IP3, IP4};

 

 

/*

*********************************************************************************************************

*    函 數 名: ftpc_notify

*    功能說明: 函數ftpc_connect的回調函數。

*    形    參: event  事件類型

*    返 回 值: 無

*********************************************************************************************************

*/

static void ftpc_notify (U8 event)

{

     switch (event)

     {

         /* 文件操作成功 */

         case FTPC_EVT_SUCCESS:

              printf_debug ("Command successful\n");

              break;

 

         /* 失敗,因爲FTP服務器響應時間溢出,因此FTP客戶端終止操作 */

         case FTPC_EVT_TIMEOUT:

              printf_debug ("Failed, timeout expired\n");

              break;

 

         /* 失敗,FTP客戶端登陸FTP服務器失敗 */

         case FTPC_EVT_LOGINFAIL:

              printf ("Failed, username/password invalid\n");

              break;

 

         /* 失敗,禁止操作此文件 */

         case FTPC_EVT_NOACCESS:

              printf_debug ("Failed, operation not allowed\n");

              break;

 

         /* 失敗,在FTP服務器上找不到請求的文件 */

         case FTPC_EVT_NOTFOUND:

              printf_debug ("Failed, file or path not found\n");

              break;

 

         /* 失敗,在FTP服務器上找不到工作目錄路徑 */

         case FTPC_EVT_NOPATH:

              printf ("Failed, working directory not found\n");

              break;

 

         /* 失敗,文件打開或者寫入出錯,即FTP客戶端操作SD卡或者其它存儲介質出錯 */

         case FTPC_EVT_ERRLOCAL:

              printf_debug ("Failed, local file open/write error\n");

              break;

 

         /* 失敗,未指定的協議錯誤,或者說在文件操作過程中遇到錯誤 */

         case FTPC_EVT_ERROR:

              printf_debug ("Failed, unspecified protocol error\n");

              break;

     }

}

 

/*

*********************************************************************************************************

*    函 數 名: TCPnetTest

*    功能說明: TCPent測試函數。

*    形    參: 無

*    返 回 值: 無

*********************************************************************************************************

*/

void TCPnetTest(void)

{

     OS_RESULT xResult;

    

     while (1)

     {

         os_evt_wait_or(0x003F, 0xFFFF); 

        

         xResult = os_evt_get ();

         switch (xResult)

         {

              /* 接收到K2鍵按下,發送FTPC_CMD_GET命令,表示從FTP服務器下載文件 */

              case KEY2_BIT1:   

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_GET, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

                  

              /* 接收到K3鍵按下,發送FTPC_CMD_PUT命令,表示向FTP服務器上傳文件 */

              case KEY3_BIT2:              

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_PUT, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

             

              /* 接收到搖桿上鍵按下,發送FTPC_CMD_LIST命令,表示列出當前目錄下所有文件詳細信息 */

              case JOY_U_BIT3:             

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_LIST, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }

                   break;

             

              /* 接收到搖桿左鍵按下,發送FTPC_CMD_MKDIR命令,表示創建一個文件夾 */

              case JOY_L_BIT4:

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_MKDIR, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

             

         /* 接收到搖桿右鍵按下,發送FTPC_CMD_RMDIR命令,表示刪除一個文件夾,文件夾爲空的時候纔可以刪除 */

              case JOY_R_BIT5:  

                   if (ftpc_connect (ServerIP, PORT_NUM, FTPC_CMD_RMDIR, ftpc_notify) == 0)

                   {

                       printf_debug("Connect failed, the client is busy.\n");

                   }

                   else

                   {

                       printf_debug("FTP client started.\n");

                   }                 

                   break;

             

               /* 其他的鍵值不處理 */

              default:                    

                   break;

         }

             

         while (main_TcpNet() == __TRUE);

     }

}

FTP用戶接口文件的實現

KEIL官網有提供FTP的接口文件,名爲FTPC_uif.c文件。我們就是在這個文件上修改。具體修改後的代碼如下:

#include <Net_Config.h>

#include <stdio.h>

 

#define FTPC_USERNAME   "armfly"       /* 遠程FTP服務器的賬號 */

#define FTPC_PASSWORD   "123456"       /* 遠程FTP服務器的密碼 */

#define FTPC_PATH       ""             /* 要訪問的子文件夾,FTP電腦端設置後是沒有路徑的,填空 */

#define FTPC_FILENAME   "server.pdf"   /* 要訪問的文件 */

#define FTPC_NEWNAME    "renamed.pdf"  /* 使用這裏定義的文件字重命名宏定義FTPC_FILENAME的文件名 */

#define FTPC_DIRNAME    "New_Folder"    /* 創建名字爲New_Folder的文件夾或者刪除名字爲New_Folder的文件夾 */

#define FTPC_LISTNAME   "*"            /* 瀏覽宏定義FTPC_PATH路徑下所有文件 */

 

#define LOCAL_FILE      "client.pdf"   /* 開發板SD卡里面要上傳的文件,或者從FTP服務器下載文件後,

                                          文件會被設置成此名字 */

 

 

/*----------------------------------------------------------------------------

 *      FTP Client File Access and Data CallBack Functions

 *---------------------------------------------------------------------------*/

 

/*--------------------------- ftpc_fopen ------------------------------------*/

 

void *ftpc_fopen (U8 *mode) {

  /* Open local file for reading or writing. If the return value is NULL, */

  /* processing of FTP Client commands PUT, APPEND or GET is cancelled.   */

  return (fopen (LOCAL_FILE, (char *)mode));

}

 

 

/*--------------------------- ftpc_fclose -----------------------------------*/

 

void ftpc_fclose (void *file) {

  /* Close a local file. */

  fclose (file);

}

 

 

/*--------------------------- ftpc_fread ------------------------------------*/

 

U16 ftpc_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 0.         */

  /* For optimal performance the return value should be 'len'             */

  return (fread (buf, 1, len, file));

}

 

 

/*--------------------------- ftpc_fwrite -----------------------------------*/

 

U16 ftpc_fwrite (void *file, U8 *buf, U16 len) {

  /* Write 'len' bytes from buffer 'buf' to a file. Data transfer will be */

  /* aborted, if the return value is not equal 'len'.                     */

  return (fwrite (buf, 1, len, file));

}

 

 

/*--------------------------- ftpc_cbfunc -----------------------------------*/

 

U16 ftpc_cbfunc (U8 code, U8 *buf, U16 buflen) {

  /* This function is called by the FTP client to get parameters. It returns*/

  /* the number of bytes written to buffer 'buf'. This function should NEVER*/

  /* write more than 'buflen' bytes to this buffer.                         */

  /* Parameters:                                                            */

  /*   code   - function code with following values:                        */

  /*             0 = Username for FTP authentication                        */

  /*             1 = Password for FTP authentication                        */

  /*             2 = Working directory path for all commands                */

  /*             3 = Filename for PUT, GET, APPEND, DELETE, RENAME command  */

  /*             4 - New filename for RENAME command                        */

  /*             5 - Directory name for MKDIR, RMDIR command                */

  /*             6 - File filter/mask for LIST command (wildcards allowed)  */

  /*             7 = Received directory listing on LIST command             */

  /*   buf    - transmit/receive buffer                                     */

  /*   buflen - length of this buffer                                       */

  /*             on transmit, it specifies size of 'buf'                    */

  /*             on receive, specifies the number of bytes received         */

  U32 i,len = 0;

 

  switch (code) {

    case 0:

      /* Enter Username for login. */

      len = str_copy (buf, FTPC_USERNAME);

      break;

 

    case 1:

      /* Enter Password for login. */

      len = str_copy (buf, FTPC_PASSWORD);

      break;

 

    case 2:

      /* Enter a working directory path. */

      len = str_copy (buf, FTPC_PATH);

      break;

 

    case 3:

      /* Enter a filename for file operations. */

      len = str_copy (buf, FTPC_FILENAME);

      break;

 

    case 4:

      /* Enter a new name for rename command. */

      len = str_copy (buf, FTPC_NEWNAME);

      break;

 

    case 5:

      /* Enter a directory name to create or remove. */

      len = str_copy (buf, FTPC_DIRNAME);

      break;

 

    case 6:

      /* Enter a file filter for list command. */

      len = str_copy (buf, FTPC_LISTNAME);

      break;

 

    case 7:

      /* Process received directory listing in raw format. */

      /* Function return value is don't care.              */

      for (i = 0; i < buflen; i++) {

        putchar (buf[i]);

      }

      break;

  }

  return ((U16)len);

}

 

 

/*----------------------------------------------------------------------------

 * end of file

 *---------------------------------------------------------------------------*/

37.7 總結

本章節就爲大家講解這麼多,其中FTP的測試稍麻煩些,希望大家實際動手操作一遍,並將其熟練掌握。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章