1. 程式人生 > >【RL-TCPnet網路教程】第36章 RL-TCPnet之FTP伺服器

【RL-TCPnet網路教程】第36章 RL-TCPnet之FTP伺服器

第36章      RL-TCPnet之FTP伺服器

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

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

36.1  初學者重要提示

36.2  FTP函式

36.3  FTP配置說明(Net_Config.c)

36.4  FTP除錯說明(Net_Debug.c)

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

36.6  實驗例程說明(裸機)

36.7  實驗例程說明(RTX)

36.8  總結

36.1  初學者重要提示

  1. 學習本章節前,務必保證已經學習了第35章的基礎知識。
  2.  本章配套的例子是將開發板作為FTP伺服器,使用開發板上面的SD卡作為伺服器的儲存介質。所以測試本章節的例子,務必要準備一個SD卡。
  3. 由於配套例子的檔案系統是採用的RL-FlashFS,此檔案系統的檔名僅支援ASCII字元,不支援中文,特別注意!
  4. 具體FTP伺服器的訪問方法在本章的36.5小節有詳細說明。

36.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 檔案:

 

這裡我們重點的說以下 9個函式,因為本章節配套的例子使用的是這9個函式:

  •   ftp_fopen
  •   ftp_fclose
  •   ftp_fread
  •   ftp_fwrite
  •   ftp_fdelete
  •   ftp_frename
  •   ftp_ffind
  •   ftp_accept_host
  •   ftp_evt_notify

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

  1.   FTP的所有函式都不支援重入,也就是不支援多工呼叫。
  2.   以ftp_開頭的函式是用於FTP伺服器的。
  3.  以ftpc_開頭的函式是用於FTP客戶端的。

36.2.1    函式ftp_fopen

函式原型:

void *ftp_fopen (

    U8* fname,    /* 檔名地址 */

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

函式描述:

函式ftp_fopen用於開啟檔案。此函式在MDK的安裝目錄中的FTP_uif.c檔案裡面,屬於底層介面函式,使用者要在此函式裡面新增具體的操作。

  1. 第1個引數是檔名地址。
  2. 第2個引數是操作模式,可以是讀操作或者寫操作,具體支援的形參型別如下:
  3. 返回值,開啟檔案成功的話,返回指向此檔案的指標變數,否則返回NULL。

使用舉例:

複製程式碼

void *ftp_fopen (U8 *fname, U8 *mode) {

  /* 開啟檔案 */

  return (fopen ((const char *)fname, (const char *)mode));

}

複製程式碼

36.2.2   函式ftp_fclose

函式原型:

void *ftp_fclose (

FILE* file);  /* 檔案控制代碼地址 */

函式描述:

函式ftp_fclose用於關閉檔案。此函式在MDK的安裝目錄中的FTP_uif.c檔案裡面,屬於底層介面函式,使用者要在此函式裡面新增具體的操作。

  1. 第1個引數是要關閉的檔案控制代碼地址。
  2. 返回值,實際上此函式無需返回任何數值,寫成下面使用舉例中的形式即可。

使用舉例:

複製程式碼

void ftp_fclose (void *file) {

  /* 關閉檔案 */

  fclose (file);

}

複製程式碼

36.2.3   函式ftp_fread

函式原型:

複製程式碼

U16 ftp_fread (

    FILE* file,     /* 檔案控制代碼地址 */

    U8*   buf,      /* 資料緩衝地址 */

    U16   len );    /* 要讀取的位元組數 */

複製程式碼

函式描述:

函式ftp_fread用於從檔案中讀出len個位元組資料。此函式在MDK的安裝目錄中的FTP_uif.c檔案裡面,屬於底層介面函式,使用者要在此函式裡面新增具體的操作。

  1.   第1個引數是要讀取資料的檔案控制代碼地址。
  2.   第2個引數是資料緩衝地址,用於儲存讀取出來的資料。
  3.  第3個引數是要讀取出來的資料大小,單位位元組。
  4.   返回值,返回從檔案中實際讀出的位元組數。

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

  1. 設定讀取函式時,必須設定指定大小的位元組數。如果實際讀出的位元組數小於len,將停止讀取並關閉檔案,這種情況一般都是檔案已經讀取完畢。

使用舉例:

複製程式碼

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

 /* 讀取len位元組到buf中,當此函式的返回值,即實際讀取的位元組數小於len的時候,說明檔案已經讀取完畢,

檔案將被關閉*/

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

}

複製程式碼

36.2.4   函式ftp_fwrite

函式原型:

複製程式碼

U16 ftp_fwrite (

    FILE* file,     /* 檔案控制代碼地址 */

    U8*   buf,      /* 資料緩衝地址 */

U16   len );    /* 要寫入的位元組數 */

複製程式碼

函式描述:

函式ftp_fwrite用於往檔案中寫入len個位元組資料。此函式在MDK的安裝目錄中的FTP_uif.c檔案裡面,屬於底層介面函式,使用者要在此函式裡面新增具體的操作。

  1.   第1個引數是要寫入資料的檔案控制代碼地址。
  2.   第2個引數是資料緩衝地址,儲存要寫入的資料。
  3.   第3個引數是要寫入的資料大小,單位位元組。
  4.   返回值,返回實際寫入檔案的位元組數。

使用舉例:

複製程式碼

U16 ftp_fwrite (FILE *file, U8 *buf, U16 len) {

  /* 將buf中的len位元組寫入到檔案中 */

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

}

複製程式碼

36.2.5   函式ftp_fdelete

函式原型:

BOOL ftp_fdelete (

    U8* fname );   /* 檔名 */

函式描述:

函式ftp_fdelete用於刪除檔案或資料夾。此函式在MDK的安裝目錄中的FTP_uif.c檔案裡面,屬於底層介面函式,使用者要在此函式裡面新增具體的操作。

  1. 第1個引數要刪除的檔案或者資料夾名字。
  2. 返回值,返回__TRUE表示刪除成功,返回__FALSE表示刪除失敗。

使用舉例:

複製程式碼

BOOL ftp_fdelete (U8 *fname) {

  /* 刪除檔案,返回__TRUE表示刪除成功 */

  if (fdelete((char *)fname) == 0) {

    return (__TRUE);

  }

  /* 返回__FALSE表示刪除失敗 */

  return (__FALSE);

}

複製程式碼

36.2.6   函式ftp_frename

函式原型:

BOOL ftp_frename (

    U8* fname,      /* 老檔名 */

U8* newn );     /* 新檔名 */

函式描述:

函式ftp_frename用於檔案或者資料夾的重新命名。要操作的檔案或者資料夾名字fname必須存在,而新命名的名字newn必須是當前目錄不存在的。

  1. 第1個引數是要替換的檔案或者資料夾名字,即老的檔名。
  2. 第2個引數是新命名檔案或者資料夾名字,即新的檔名。
  3. 返回值,返回__TRUE表示重新命名成功,返回__FALSE表示重新命名失敗。

使用舉例:

複製程式碼

BOOL ftp_frename (U8 *fname, U8 *newn) {

  /* 檔案重新命名,返回__TRUE表示重新命名成功 */

  if (frename((char *)fname, (char *)newn) == 0) {

    return (__TRUE);

  }

  /* 返回__FALSE表示重新命名失敗 */

  return (__FALSE);

}

複製程式碼

36.2.7   函式ftp_ffind

函式原型:

複製程式碼

U16 ftp_ffind (

    U8  code,      /* 請求型別 */

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

    U8* mask,      /* 檢索的字元 */

    U16 buflen );  /* 輸出緩衝區大小,單位位元組 */

複製程式碼

函式描述:

函式ftp_ffind用於在檔案系統目錄中搜索與引數mask匹配的檔案。匹配的檔案資訊會被儲存到資料緩衝區buf中。輸出資料必須以FTP資料夾列表格式進行儲存。

  1.  第1個引數是請求型別:
  2.  第2個引數是輸出緩衝區地址,用於儲存檔案資訊和資料。
  3.   第3個引數用於檔案檢索的字元,類似於我們在電腦端輸入關鍵字元進行檔案或者資料夾的檢索。
  4.  第4個引數是輸出緩衝區的大小,單位位元組。
  5. 返回值,返回寫入到輸出緩衝區的位元組數。

使用舉例:

複製程式碼

U16 ftp_ffind (U8 code, U8 *buf, U8 *mask, U16 len) {

  /* Find file names and other file information. */

  static FINFO info;

  U32 rlen,v;

  U8 *tp;

 

  if (code < 4) {

    /* First call to ffind, initialize the info. */

    info.fileID = 0;

  }

 

  rlen = 0;

next:

  if (ffind ((char *)mask, &info) == 0) {

    /* File found, print file information. */

    if (info.name[0] == '.') {

      if ((info.name[1] == 0) || (info.name[1] == '.' && info.name[2]) == 0) {

        /* Ignore the '.' and '..' folders. */

        goto next;

      }

    }

    switch (code) {

      case 0:

        /* Return file size as decimal number. */

        rlen = sprintf ((char *)buf,"%d\r\n", info.size);

        break;

 

      case 1:

        /* Return last-modified time in format "YYYYMMDDhhmmss". */

        rlen  = sprintf ((char *)buf,"%04d%02d%02d",

                         info.time.year, info.time.mon, info.time.day);

        rlen += sprintf ((char *)&buf[rlen],"%02d%02d%02d\r\n",

                         info.time.hr, info.time.min, info.time.sec);

        break;

 

      case 2:

      case 4:

        /* List file names only. */

        rlen = sprintf ((char *)buf,"%s\r\n", info.name);

        break;

 

      case 3:

      case 5:

        /* List directory in extended format. */

        rlen  = sprintf ((char *)buf,"%02d-%02d-%02d",

                         info.time.mon, info.time.day, info.time.year%100);

        /* Convert time to "AM/PM" format. */

        v = info.time.hr % 12;

        if (v == 0) v = 12;

        if (info.time.hr < 12) tp = "AM";

        else                   tp = "PM";

        rlen += sprintf ((char *)&buf[rlen],"  %02d:%02d%s",v,info.time.min,tp);

        if (info.attrib & ATTR_DIRECTORY) {

          rlen += sprintf ((char *)&buf[rlen],"%-21s","       <DIR>");

        }

        else {

          rlen += sprintf ((char *)&buf[rlen],"%21d", info.size);

        }

        rlen += sprintf ((char *)&buf[rlen]," %s\r\n", info.name);

        break;

    }

  }

  return (rlen);

}

複製程式碼

36.2.8   函式ftp_accept_host

函式原型:

BOOL ftp_accept_host (

    U8* rem_ip,      /* 遠端裝置IP地址 */

U16 rem_port );  /* 遠端裝置埠號 */

函式描述:

函式ftp_accept_host用於設定是否接受遠端連線,使用者可以通過此函式選擇允許哪些裝置可以連線,哪些不可以連線。

  1. 第1個引數是遠端裝置的IP地址。
  2. 第2個引數是遠端裝置的埠號。
  3. 返回值,返回__TRUE表示允許此遠端連線,返回__FALSE表示不允許此遠端連線。

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

  1. 此函式是可選的,如果大家在工程中沒有寫這個函式,RL-TCPnet庫會呼叫預設的函式,允許所有的連線請求,如果在工程中寫了此函式,會執行新寫的這個函式。

使用舉例:

複製程式碼

BOOL ftp_accept_host (U8 *rem_ip, U16 rem_port) {

 

  if (rem_ip[0] == 192  &&

      rem_ip[1] == 168  &&

      rem_ip[2] == 1    &&

      rem_ip[3] == 1) {

    /* 接受此連線. */

    return (__TRUE);

  }

  /* 拒絕此連線 */

  return (__FALSE);

}

複製程式碼

36.2.9   函式ftp_evt_notify

函式原型:

void ftp_evt_notify (U8 evt); /* 事件型別 */

函式描述:

函式ftp_evt_notify用於通知使用者應用程式:FTP伺服器中的事件。通過這些事件訊息可以做很多有用的事情,比如伺服器的韌體升級,接收到訊息FTP_EVT_LOGOUT後,表示伺服器已經接收到韌體,可以更新韌體了。

  1. 第1個引數是FTP伺服器的事件型別。

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

  1. 此函式是可選的,一般正常的FTP伺服器操作無需此函式。

使用舉例:

複製程式碼

void ftp_evt_notify (U8 evt) {

  /* Notify the user application about events in FTP server.*/

 

  switch (evt) {

    case FTP_EVT_LOGIN:

      /* User logged in, FTP session is busy. */

      break;

 

    case FTP_EVT_LOGOUT;

      /* User logged out, session is idle. */

      break;

 

    case FTP_EVT_LOGFAIL:

      /* User login failed (invalid credentials). */

      break;

 

    case FTP_EVT_DOWNLOAD:

      /* File download ended. */

      break;

 

    case FTP_EVT_UPLOAD:

      /* File upload ended. */

      break;

 

    case FTP_EVT_DELETE:

      /* File deleted. */

      break;

 

    case FTP_EVT_RENAME:

      /* File or directory renamed. */

      break;

 

    case FTP_EVT_MKDIR:

      /* Directory created. */

      break;

 

    case FTP_EVT_RMDIR:

      /* Directory removed. */

      break;

 

    case FTP_EVT_ERRLOCAL:

      /* Local file operation error. */

      break;

 

    case FTP_EVT_DENIED:

      /* Requested file operation denied. */

      break;

 

    case FTP_EVT_ERROR:

      /* Generic file operation or protocol error. */

      break;

  }

複製程式碼

36.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 Server

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

(1)  Number of FTP Sessions

同時可以連線的會話個數,即可以連線的FTP客戶端個數。

範圍1-10。

(2)Port Number

FTP伺服器的監聽埠號。

範圍1-65535。

(3) Welcome Message

登入FTP伺服器後的歡迎訊息,如果使用者沒有配置此選項,將使用預設的歡迎訊息,如果配置了,將使用使用者配置的。

(4) Idle Session Timeout in seconds

空閒連線溢位時間,如果連線後,這段時間內無操作,FTP伺服器將斷開客戶端的連線。

配置為數值0,將禁止超時斷開連線,即一直保持連線。

範圍0-3600,單位秒。

(5) Enable User Authentication

使用者認證,如果此選項打上對勾的話,將使能使用者認證。

Authentication Username 是使用者名稱。

Authentication Password 是使用者密碼。

36.4 FTP除錯說明(Net_Debug.c)

(重要說明,RL-TCPnet的除錯是通過串列埠打印出來的)

RL-TCPnet的除錯功能是通過配置檔案Net_Debug.c實現。在MDK工程中開啟檔案Net_Debug.c,可以看到下圖所示的工程配置嚮導:

Print Time Stamp

勾選了此選項的話,列印訊息時,前面會附帶時間資訊。

其它所有的選項

預設情況下,所有的除錯選項都關閉了,每個選項有三個除錯級別可選擇,這裡我們以FTP Server Debug為例,點選下拉列表,可以看到裡面有Off,Errors only和Full debug三個除錯級別可供選擇,每個除錯選項裡面都是這三個級別。

Off:表示關閉此選項的除錯功能。

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

Full debug:表示此選項的全功能除錯。

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

36.5 FTP伺服器的訪問方法和板子的操作步驟

我們這裡介紹三種訪問FTP伺服器的方法,對於本實驗實現的FTP伺服器,支援的操作如下:

  • 支援檔案的上傳和下載。
  •  支援檔案和資料夾的刪除。
  • 支援檔案和資料夾的重新命名。
  • 支援新建檔案和資料夾。

另外,本章節配套的例子是用開發板做FTP伺服器,SD卡做為伺服器的儲存介質,所以務必準備好一個SD卡插到開發板上面。

最後,特別注意一點,我們使用的是RL-FlashFS檔案系統,此檔案系統的檔名僅支援ASCII字元,不支援中文,對於中文名的資料夾或者檔案是無法操作的。

36.5.1 使用FTP客戶端軟體訪問

  • 第1步:下載FTP客戶端軟體
  • 第2步:下載後,安裝此軟體,安裝完畢後,開啟軟體的效果如下

  • 第3步:輸入地址,賬號和埠號

主機:armfly (備註,因為使能了NetBIOS Name,這裡可以直接寫armfly或者實際的IP地址)。

使用者名稱:admin

密碼:123456

埠:21

如果開發板已經上電,就可以點選快速連線進行訪問了。

  • 第4步:點選“快速連線”按鈕就可以訪問了

訪問後效果如下:

訪問FTP伺服器成功後,這個客戶端軟體操作也比較簡單,大家摸索下就熟練了,檔案的上傳下載、檔案和資料夾的建立、刪除、重新命名等都支援。這裡主要說上傳和下載檔案的方法。

  • 第5步:檔案的上傳方法

點選了上傳後,狀態列裡面會有上傳速度和上傳進度:

  •  第6步:檔案的下載方法

點選了下載後,狀態列裡面會有下載速度和下載進度:

36.5.2 “網路”位址列輸入地址訪問

這種方法訪問也非常方便,大家開啟電腦端的“網路”或者“計算機”圖示,早期的XP系統是叫“我的電腦”和“網路上的芳鄰”。

  • 第2步:輸入地址後,點選鍵盤迴車鍵,彈出如下視窗 
  • 第3步:點選登入按鈕後,就可以看到FTP伺服器中SD卡的內容了 

剩下的操作就很簡單了,電腦端怎麼操作檔案的,這裡就怎麼操作(特別注意,不支援中文檔名,僅支援ASCII字元的檔名)。大家可以實際測試下檔案的複製貼上、檔案和資料夾的建立、刪除、重新命名等功能。

另外這種訪問方式,有一種情況要注意,如果FTP伺服器中有某個檔案了,重複上傳會彈出一個視窗,提示正在計算上傳檔案所需時間,這個視窗不用管它,直接點選“是”或者“全是”按鈕即可。

36.5.3 瀏覽器訪問

瀏覽器訪問FTP伺服器也比較簡單,這裡以谷歌瀏覽器為例,按照下圖所標註的四步即可登入:

登入成功後的介面如下:

不過這種方式僅支援檔案的瀏覽,其它的任何操作均不支援。

36.6 實驗例程說明(RTX)

36.6.1 STM32F407開發板實驗

配套例子:

V5-1051_RL-TCPnet實驗_FTP伺服器(RTX)

實驗目的:

  1. 學習RL-TCPnet的FTP伺服器實現。

實驗內容:

  1. 強烈推薦將網線接到路由器或者交換機上面測試,因為已經使能了DHCP,可以自動獲取IP地址。
  2. FTP伺服器的儲存器是採用的SD卡,所以測試本例子前務必準備好一個SD卡並插上。
  3. 檔案系統是採用的RL-FlashFS,此檔案系統的檔名僅支援ASCII字元,不支援中文,特別注意!
  4. FTP伺服器的訪問方法在本例項配套教程裡面有詳細講解。可以使用FTP客戶端軟體訪問,也可以在“我的電腦”位址列輸入ftp://armfly進行訪問。
  5. FTP伺服器的使用者名稱admin,密碼123456。

實驗操作:

詳見本章節36.5小節。

配置嚮導檔案設定(Net_Config.c):

詳見本章節36.3小節。

除錯檔案設定(Net_Debug.c):

詳見本章節36.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鍵按下 */

                  case KEY_DOWN_K2:

                       printf("K2鍵按下\r\n");        

                       break;

                  

                   /* K3鍵按下 */

                   case KEY_DOWN_K3:

                       printf("K3鍵按下 \r\n");

                       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功能的測試,此檔案主要實現網路主函式main_TcpNet的呼叫。

複製程式碼

#include "includes.h"

 

 

 

/*

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

*    函 數 名: TCPnetTest

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

*    形    參: 無

*    返 回 值: 無

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

*/

void TCPnetTest(void)

{

    

     while (1)

     {

         os_evt_wait_or(0x0001, 0xFFFF); 

        

         /* RL-TCPnet主處理函式 */       

         while (main_TcpNet() == __TRUE);

     }

}

複製程式碼

FTP使用者介面檔案的實現

KEIL官網有提供FTP的介面檔案,名為FTP_uif.c檔案。我們就是在這個檔案上修改。具體修改後的程式碼如下:

複製程式碼

#include <Net_Config.h>

#include <File_Config.h>

#include <stdio.h>

 

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

 * FTP Server File Access Functions

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

 

/*--------------------------- ftp_fopen -------------------------------------*/

 

void *ftp_fopen (U8 *fname, U8 *mode) {

  /* Open file 'fname' for reading or writing. Return file handle. */

  return (fopen ((const char *)fname, (const char *)mode));

}

 

 

/*--------------------------- ftp_fclose ------------------------------------*/

 

void ftp_fclose (void *file) {

  /* Close the file opened for reading or writing. */

  fclose (file);

}

 

 

/*--------------------------- ftp_fread -------------------------------------*/

 

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

  /* Read 'len' bytes from file to buffer 'buf'. The file will be closed, */

  /* when the number of bytes read is less than 'len'. */

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

}

 

 

/*--------------------------- ftp_fwrite ------------------------------------*/

 

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

  /* Write 'len' bytes from buffer 'buf' to a file. */

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

}

 

 

/*--------------------------- ftp_fdelete -----------------------------------*/

 

BOOL ftp_fdelete (U8 *fname) {

  /* Delete a file, return __TRUE on success. */

  if (fdelete((char *)fname) == 0) {

    return (__TRUE);

  }

  return (__FALSE);

}

 

 

/*--------------------------- ftp_frename -----------------------------------*/

 

BOOL ftp_frename (U8 *fname, U8 *newn) {

  /* Rename a file, return __TRUE on success. */

  if (frename((char *)fname, (char *)newn) == 0) {

    return (__TRUE);

  }

  return (__FALSE);

}

 

 

/*--------------------------- ftp_ffind -------------------------------------*/

 

U16 ftp_ffind (U8 code, U8 *buf, U8 *mask, U16 buflen) {

  /* This function is called by the FTP server to find file names and other */

  /* file information. The output data is stored in ascii format to output  */

  /* buffer 'buf' Parameter 'code' specifies requested file information.    */

  /* Values for 'code':                                                     */

  /*    0 - read file size                                                  */

  /*    1 - read last-modified time of a file                               */

  /*    2 - list file names only (first call)                               */

  /*    3 - list file directory in extended format (first call)             */

  /*    4 - list file names only (repeated call)                            */

  /*    5 - list file directory in extended format (repeated call)          */

  static FINFO info;

  U32 rlen,v;

  U8 *tp;

 

  if (code < 4) {

    /* First call to ffind, initialize the info. */

    info.fileID = 0;

  }

 

  rlen = 0;

next:

  if (ffind ((char *)mask, &info) == 0) {

    /* File found, print file information. */

    if (info.name[0] == '.') {

      if ((info.name[1] == 0) || (info.name[1] == '.' && info.name[2] == 0)) {

        /* Ignore the '.' and '..' folders. */

        goto next;

      }

    }

    switch (code) {

      case 0:

        /* Return file size as decimal number. */

        rlen = sprintf ((char *)buf,"%d\r\n", info.size);

        break;

 

      case 1:

        /* Return last-modified time in format "YYYYMMDDhhmmss". */

        rlen  = sprintf ((char *)buf,"%04d%02d%02d",

                         info.time.year, info.time.mon, info.time.day);

        rlen += sprintf ((char *)&buf[rlen],"%02d%02d%02d\r\n",

                         info.time.hr, info.time.min, info.time.sec);

        break;

 

      case 2:

      case 4:

        /* List file names only. */

        rlen = sprintf ((char *)buf,"%s\r\n", info.name);

        break;

 

      case 3:

      case 5:

        /* List directory in extended format. */

        rlen  = sprintf ((char *)buf,"%02d-%02d-%02d",

                         info.time.mon, info.time.day, info.time.year%100);

        /* Convert time to "AM/PM" format. */

        v = info.time.hr % 12;

        if (v == 0) v = 12;

        if (info.time.hr < 12) tp = "AM";

        else                   tp = "PM";

        rlen += sprintf ((char *)&buf[rlen],"  %02d:%02d%s",v,info.time.min,tp);

        if (info.attrib & ATTR_DIRECTORY) {

          rlen += sprintf ((char *)&buf[rlen],"%-21s","       <DIR>");

        }

        else {

          rlen += sprintf ((char *)&buf[rlen],"%21d", info.size);

        }

        rlen += sprintf ((char *)&buf[rlen]," %s\r\n", info.name);

        break;

    }

  }

  return (rlen);

}

 

 

/*--------------------------- ftp_accept_host -------------------------------*/

#if 0

BOOL ftp_accept_host (U8 *rem_ip, U16 rem_port) {

  /* This function checks if a connection from remote host is accepted or  */

  /* not. If this function is missing, all remote hosts are accepted.      */

 

  if (rem_ip[0] == 192  &&

      rem_ip[1] == 168  &&

      rem_ip[2] == 1    &&

      rem_ip[3] == 1) {

    /* Accept a connection. */

    return (__TRUE);

  }

  /* Deny a connection. */

  return (__FALSE);

}

#endif

 

 

/*--------------------------- ftp_evt_notify --------------------------------*/

#if 0

void ftp_evt_notify (U8 evt) {

  /* This function notifies the user application about events in FTP server.*/

 

  switch (evt) {

    case FTP_EVT_LOGIN:

      /* User logged in, FTP session is busy. */

      break;

 

    case FTP_EVT_LOGOUT;

      /* User logged out, session is idle. */

      break;

 

    case FTP_EVT_LOGFAIL:

      /* User login failed (invalid credentials). */

      break;

 

    case FTP_EVT_DOWNLOAD:

      /* File download ended. */

      break;

 

    case FTP_EVT_UPLOAD:

      /* File upload ended. */

      break;

 

    case FTP_EVT_DELETE:

      /* File deleted. */

      break;

 

    case FTP_EVT_RENAME:

      /* File or directory renamed. */

      break;

 

    case FTP_EVT_MKDIR:

      /* Directory created. */

      break;

 

    case FTP_EVT_RMDIR:

      /* Directory removed. */

      break;

 

    case FTP_EVT_ERRLOCAL:

      /* Local file operation error. */

      break;

 

    case FTP_EVT_DENIED:

      /* Requested file operation denied. */

      break;

 

    case FTP_EVT_ERROR:

      /* Generic file operation or protocol error. */

      break;

  }

}

#endif

 

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

 * end of file

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

複製程式碼

36.6.2 STM32F429開發板實驗

配套例子:

V6-1051_RL-TCPnet實驗_FTP伺服器(RTX)

實驗目的:

  1. 學習RL-TCPnet的FTP伺服器實現。

實驗內容:

  1. 強烈推薦將網線接到路由器或者交換機上面測試,因為已經使能了DHCP,可以自動獲取IP地址。
  2. FTP伺服器的儲存器是採用的SD卡,所以測試本例子前務必準備好一個SD卡並插上。
  3. 檔案系統是採用的RL-FlashFS,此檔案系統的檔名僅支援ASCII字元,不支援中文,特別注意!
  4. FTP伺服器的訪問方法在本例項配套教程裡面有詳細講解。可以使用FTP客戶端軟體訪問,也可以在“我的電腦”位址列輸入ftp://armfly進行訪問。
  5. FTP伺服器的使用者名稱admin,密碼123456。

實驗操作:

詳見本章節36.5小節。

配置嚮導檔案設定(Net_Config.c):

詳見本章節36.3小節。

除錯檔案設定(Net_Debug.c):

詳見本章節36.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  (數值越小優先順序越低,這個跟uC