1. 程式人生 > 實用技巧 >【STM32F429】第11章 RL-TCPnet V7.X之TCP伺服器

【STM32F429】第11章 RL-TCPnet V7.X之TCP伺服器

最新教程下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=95243

第11章 RL-TCPnet之TCP伺服器

本章節為大家講解RL-TCPnet的TCP伺服器實現,學習本章節前,務必要優先學習第10章TCP傳輸控制協議基礎知識。有了這些基礎知識之後,再搞本章節會有事半功倍的效果。

11.1 初學者重要提示

11.2 TCP伺服器API函式

11.3 系統配置說明(Net_Config.c)

11.4 TCP配置說明(Net_Config_TCP.h)

11.5 乙太網配置說明(Net_Config_ETH.h)

11.6 網路除錯說明(Net_Debug.c)

11.7 TCP伺服器的實現方法

11.8 網路除錯助手和板子的除錯操作步驟

11.9 實驗例程說明(RTX5)

11.10 實驗例程說明(FreeRTOS)

11.11 總結

11.1 初學者重要提示

  1. 學習本章節前,務必保證已經學習了第10章的基礎知識。
  2. 本章要掌握的函式稍多,可以先學會基本的使用,然後再深入瞭解這些函式使用時的注意事項,爭取達到熟練使用。
  3. socket和監聽的關係:
  • 建立的一個socket只能建立一個監聽。
  • 建立的一個socket不能夠監聽多個 。
  • 建立多個socket可以建立多個監聽。
  • 建立多個socket可以僅建立一個監聽。

11.2 TCP伺服器API函式

使用如下幾個函式可以實現RL-TCPnet的TCP通訊:

  • netTCP_Abort
  • netTCP_Close
  • netTCP_Connect
  • netTCP_GetBuffer
  • netTCP_GetLocalPort
  • netTCP_GetMaxSegmentSize
  • netTCP_GetPeer
  • netTCP_GetSocket
  • netTCP_GetState
  • netTCP_GetTimer
  • netTCP_Listen
  • netTCP_ReleaseSocket
  • netTCP_ResetReceiveWindow
  • netTCP_Send
  • netTCP_SendReady
  • netTCP_SetOption

關於這幾個函式的講解及其使用方法可以看教程第 3 章 3.4 小節裡面說的參考資料檔案:

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

  • 這些函式都支援多工呼叫。
  • TCP介面函式通過TCP Socket做資料傳輸,主要用於將資料安全作為首選的場合。TCP Socket傳送完資料後會等待應答,任何資料包失敗都會重傳。

11.2.1 函式netTCP_cb_t

函式原型:

uint32_t(* netTCP_cb_t)(int32_t socket,      /* socket控制代碼 */
netTCP_Event event,  /* 事件型別 */
const NET_ADDR *addr,/* NET_ADDR型別變數,記錄IP地址,埠號*/
const uint8_t *buf,  /* 接收到的資料 */
uint32_t len)        /* 接收到的位元組數 */                

函式描述:

供TCP Socket使用的回撥函式,每個TCP Socket都可以定製自己的回撥函式。

函式引數:

  • 第1個引數是TCP socket控制代碼。
  • 第2個引數是事件型別,支援的事件型別如下:

  • 第3個引數NET_ADDR格式的結構體變數。
typedef struct net_addr {
  int16_t  addr_type;                   /* IP地址,NET_ADDR_IP4或者 NET_ADDR_IP6 */ 
  uint16_t port;                        /* 埠號 */
  uint8_t  addr[NET_ADDR_IP6_LEN];      /* IPv4或者IPv6 */
} NET_ADDR;
  • 第4個引數是接收資料的緩衝地址。
  • 第5個引數是接收到的資料個數,單位位元組。
  • 返回值,此函式的返回值僅適用於接收到事件netTCP_EventConnect時,當返回1時,表示接收遠端客戶端的連線請求,返回0時,表示拒絕遠端客戶端的連線請求。

使用舉例:

/*
*********************************************************************************************************
*    函 數 名: tcp_cb_server
*    功能說明: TCP Socket的回撥函式
*    形    參: socket  控制代碼
*             event   事件型別
*             addr    NET_ADDR型別變數,記錄IP地址,埠號。
*             buf     ptr指向的緩衝區記錄著接收到的TCP資料。
*             len     記錄接收到的資料個數。
*    返 回 值: 
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
                        const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
{
    switch (event) 
    {
        /*
            遠端客戶端連線訊息
            1、陣列ptr儲存遠端裝置的IP地址,par中儲存埠號。
            2、返回數值1允許連線,返回數值0禁止連線。
        */
        case netTCP_EventConnect:
            if (addr->addr_type == NET_ADDR_IP4) 
            {
                printf_debug("遠端客戶端請求連線IP: %d.%d.%d.%d  埠號:%d\r\n", 
                                                                addr->addr[0], 
                                                                addr->addr[1], 
                                                                addr->addr[2], 
                                                                addr->addr[3],         
                                                                addr->port);
                return (1);
            }
            else if (addr->addr_type == NET_ADDR_IP6)  
            {
                return (1);
            }
            
            return(0);

        /* Socket遠端連線已經建立 */
        case netTCP_EventEstablished:
            printf_debug("Socket is connected to remote peer\r\n");
            break;

        /* 連線斷開 */
        case netTCP_EventClosed:
            printf_debug("Connection has been closed\r\n");
            break;

        /* 連線終止 */
        case netTCP_EventAborted:
            break;

        /* 傳送的資料收到遠端裝置應答 */
        case netTCP_EventACK:
            break;

        /* 接收到TCP資料幀,ptr指向資料地址,par記錄資料長度,單位位元組 */
        case netTCP_EventData:
            printf_debug("Data length = %d\r\n", len);
            printf ("%.*s\r\n",len, buf);
            break;
    }
    return (0);
}

11.2.2 函式netTCP_GetSocket

函式原型:

int32_t netTCP_GetSocket(netTCP_cb_t cb_func)

函式描述:

函式netTCP_GetSocket用於獲取一個TCP Socket,並設定相關狀態變數到預設狀態。

  • 第1個引數是netTCP_cb_t型別的回撥函式。
  • 返回值
    • 如果大於等於0,表示成功獲得TCP Socket。
    • 如果小於0,表示申請失敗。申請失敗又分兩種情況,netInvalidParameter表示回撥函式無效,netError表示已經沒有可用的Socket。

注意事項:

  1. 呼叫TCP Socket任何其它函式前,務必要優先呼叫此函式。
  2. 如果用於伺服器模式,要呼叫監聽函式netTCP_Listen進行設定。

使用舉例:

/*
*********************************************************************************************************
*    函 數 名: tcp_cb_server
*    功能說明: TCP Socket的回撥函式
*    形    參: socket  控制代碼
*             event   事件型別
*             addr    NET_ADDR型別變數,記錄IP地址,埠號。
*             buf     ptr指向的緩衝區記錄著接收到的TCP資料。
*             len     記錄接收到的資料個數。
*    返 回 值: 
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
                        const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
{
    switch (event) 
    {
    
        case netTCP_EventConnect:
            return(0);

        /* Socket遠端連線已經建立 */
        case netTCP_EventEstablished:
            break;

        /* 連線斷開 */
        case netTCP_EventClosed:
            break;

        /* 連線終止 */
        case netTCP_EventAborted:
            break;

        /* 傳送的資料收到遠端裝置應答 */
        case netTCP_EventACK:
            break;

        /* 接收到TCP資料幀,ptr指向資料地址,par記錄資料長度,單位位元組 */
        case netTCP_EventData:
            break;
    }
    return (0);
}

/*
*********************************************************************************************************
*    函 數 名: TCPnetTest
*    功能說明: TCPnet應用
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/    
void TCPnetTest(void)
{
int32_t tcp_sock;    

    tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
    if (tcp_sock > 0) 
    {
        /* 建立成功 */
    }
}

11.2.3 函式netTCP_Listen

函式原型:

netStatus netTCP_Listen    ( int32_t     socket, /* TCP socket 控制代碼 */
uint16_t     port )  /* 監聽的埠號 */    

函式描述:

函式netTCP_Listen用於設定TCP伺服器的監聽埠。

函式引數:

  • 第1個引數是監聽的TCP Socket控制代碼。
  • 第2個引數是監聽埠號。
  • 返回值,返回netOK表示監聽成功,netInvalidParameter表示引數無效,netWrongState表示狀態錯誤(Socket not closed)。

注意事項:

  1. 埠號不允許設定為0,因為這個是系統保留的。
  2. RL-TCPnet伺服器類應用,比如Telnet Server,HTTP Server,務必要開啟一個TCP Socket用於監聽。

使用舉例:

int32_t tcp_sock;
 
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
    netTCP_Listen (tcp_sock, 2000);
}

11.2.4 函式netTCP_SendReady

函式原型:

bool netTCP_SendReady(int32_t socket)

函式描述:

函式netTCP_SendReady用於檢測是否可以傳送資料。此函式通過檢測TCP連線是否建立以及上次傳送的資料是否接收到遠端機器的應答來判斷是否可以傳送資料。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,可以傳送資料,返回true;不可以傳送資料,返回false。

使用舉例:

int32_t tcp_sock;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

if(netTCP_SendReady(tcp_sock) == true )
{

}

11.2.5 函式netTCP_GetMaxSegmentSize

函式原型:

uint32_t netTCP_GetMaxSegmentSize(int32_t socket)

函式描述:

函式netTCP_GetMaxSegmentSize用於獲得當前可以傳送的最大報文長度(MSS,Maximum Segment Size)。在配置嚮導中,預設配置的MSS是1440位元組,然而在實際建立連線後,此值會被動態調整,但一定是小於等於1440位元組的。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回本次可以傳送的最大報文長度,單位位元組。

注意事項:

  1. 這個函式只能在呼叫了函式netTCP_Listen或netTCP_Connect後,才可以使用。

使用舉例:

int32_t tcp_sock;
uint32_t maxlen;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

if(netTCP_SendReady(tcp_sock) == true )
{
    maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
}

11.2.6 函式netTCP_GetBuffer

函式原型:

uint8_t * netTCP_GetBuffer(uint32_t size )

函式描述:

函式netTCP_GetBuffer用於獲取TCP傳送緩衝區,使用者將要傳送的資料存到這個緩衝區中,然後通過函式netTCP_Send傳送。傳送完畢後要等待遠端主機的應答,收到應答後,會在函式netTCP_Send中釋放申請的傳送緩衝區。

函式引數:

  • 第1個引數是要申請的緩衝區大小。
  • 返回值,返回獲取的緩衝區地址。

注意事項:

  1. 每次傳送都需要呼叫此函式獲取傳送緩衝區地址。
  2. 申請的傳送緩衝區大小不可超過最大報文長度(MSS,Maximum Segment Size),即1440位元組。
  3. 操作緩衝區的時候,切不可超過申請的緩衝區大小。

使用舉例:

int32_t tcp_sock;
uint32_t maxlen;
uint8_t *sendbuf;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

if(netTCP_SendReady(tcp_sock) == true )
{
    maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
    sendbuf = netTCP_GetBuffer (maxlen);
}

11.2.7 函式netTCP_Send

函式原型:

netStatus netTCP_Send    ( int32_t     socket, /* TCP socket 控制代碼 */
uint8_t *     buf,    /* 資料緩衝區地址 */
uint32_t     len )   /* 要傳送的資料個數,單位位元組 */

函式描述:

函式netTCP_Send用於資料包傳送。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 第2個引數是函式netTCP_GetBuffer獲取的緩衝區地址。
  • 第3個引數是傳送資料個數,單位位元組。
  • 返回值:
    • netOK: 資料傳送成功。
    • netInvalidParameter: 引數無效或者引數不支援。
    • netWrongState: 狀態錯誤,Socket未連線或者關閉中。
    • netBusy: 前面傳送的資料還沒有收到應答。
    • netError: 資料傳送失敗。

注意事項:

  1. 不管函式netTCP_Send傳送成功還是失敗,都會釋放通過函式netTCP_GetBuffer獲取的緩衝區。
  2. 以下兩種情況不可使用函式netTCP_Send傳送資料包

    (1) TCP連線還未建立。

    (2) 傳送給遠端機器的資料包還未收到應答。

  3. 呼叫函式netTCP_Send前務必要呼叫函式netTCP_GetBuffer獲得緩衝區。
  4. 申請的傳送緩衝區大小不可超過最大報文長度(MSS,Maximum Segment Size),即1440位元組。
  5. netTCP_Send不會發送長度為0的資料包,如果使用者設定為0,可以用來釋放緩衝區。
  6. 操作緩衝區的時候,切不可超過申請的緩衝區大小。
  7. 不可以在TCP Socket的回撥函式裡面呼叫netTCP_Send。

使用舉例:

int32_t tcp_sock;
uint32_t maxlen;
uint8_t *sendbuf;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

if(netTCP_SendReady(tcp_sock) == true )
{
    maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
    sendbuf = netTCP_GetBuffer (maxlen);

    /* 必須使用申請的記憶體空間 */
    netTCP_Send (tcp_sock, sendbuf, maxlen);
}

11.2.8 函式netTCP_GetState

函式原型:

netTCP_State netTCP_GetState(int32_t socket)

函式描述:

函式netTCP_GetState用於獲取TCP Socket的當前狀態。使用者應用程式可以通過此函式監控TCP Socket的連線、斷開等狀態。最有用的狀態值是netTCP_StateCLOSED, netTCP_StateLISTEN和netTCP_StateESTABLISHED。

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回以下幾種狀態值:

使用舉例:

int32_t tcp_sock;
uint32_t maxlen;
uint8_t *sendbuf;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
if(netTCP_SendReady(tcp_sock) == true )
{
        maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
        sendbuf = netTCP_GetBuffer (maxlen);

            /* 必須使用申請的記憶體空間 */
        netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}

11.2.9 函式netTCP_Abort

函式原型:

netStatus netTCP_Abort(int32_t socket)

函式描述:

用於立即終止TCP通訊。此函式通過傳送帶RESET標誌的TCP幀給遠端裝置來關閉連線。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回以下幾種狀態值:
    • netOK: 終止成功
    • netInvalidParameter: 引數錯誤
    • netWrongState: 狀態錯誤。

注意事項:

  1. 當遠端客戶端終止了連線,TCP Socket才會呼叫監聽回撥函式。如果是自己呼叫的終止連線,那麼不會呼叫回撥函式。

使用舉例:

int32_t tcp_sock;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

/* 終止socket */
netTCP_Abort(tcp_sock);

11.2.10 函式netTCP_Close

函式原型:

netStatus netTCP_Close( int32_t socket)

函式描述:

用於關閉TCP通訊,完成關閉需要一點時間。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回以下幾種狀態值:
    • netOK: 關閉成功
    • netInvalidParameter: 引數錯誤
    • netWrongState: 狀態錯誤。

注意事項:

  1. 當遠端客戶端關閉了連線,TCP Socket才會呼叫監聽回撥函式。如果是自己呼叫的關閉連線,那麼不會呼叫回撥函式。
  2. 如果呼叫了監聽函式netTCP_Listen,那麼首次呼叫函式netTCP_Close並不會關閉連線,只會關閉當前處於連線狀態的TCP,關閉後依然可以監聽新的連線。要真正關閉,需要使用者再次呼叫netTCP_Close。
  3. 呼叫了函式netTCP_Close後,還沒有釋放TCP Socket佔用的空間,需要大家呼叫函式netTCP_ReleaseSocket來釋放。

使用舉例:

int32_t tcp_sock;

tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
if (tcp_sock > 0) 
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}

/* 關閉socket */
netTCP_Close(tcp_sock);

11.2.11 函式netTCP_GetPeer

函式原型:

netStatus netTCP_GetPeer(int32_t     socket,
NET_ADDR *     addr,
uint32_t     addr_len )    

函式描述:

用於獲取遠端客戶端的IP和埠號。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 第2個引數用於儲存獲取的IP和埠號。
  • 第3個引數是用於填寫第2個引數的結構體大小,單位位元組。
  • 返回值,返回以下幾種狀態值:
    • netOK: 獲取成功
    • netInvalidParameter: 引數錯誤
    • netWrongState: 狀態錯誤。

使用舉例:

NET_ADDR peer;
char ip_ascii[40];
 
netTCP_GetPeer (tcp_sock, &peer, sizeof(peer));
netIP_ntoa (peer.addr_type, peer.addr, ip_ascii, sizeof (ip_ascii));
printf ("Peer address: %s\n", ip_ascii);
printf ("Peer port: %d\n", peer.port);

11.2.12 函式netTCP_GetTimer

函式原型:

uint32_t netTCP_GetTimer(int32_t socket)

函式描述:

用於獲取TCP連線的溢位時間或者當前的保活值(keep alive),如果溢位時間到了,協議棧會關閉連線或者傳送一個keep-alive值。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回溢位時間或者keep-alive值,單位秒,如果返回0表示引數錯了或者無效狀態。

使用舉例:

int32_t tcp_sock;

printf ("Socket %d will close in %d seconds\n", socket, netTCP_GetTimer (socket));

11.2.13 函式netTCP_ReleaseSocket

函式原型:

netStatus netTCP_ReleaseSocket(int32_t socket)

函式描述:

用於釋放建立TCP Socket時申請的內容空間。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回溢位時間或者keep-alive值,單位秒,如果返回0表示引數錯了或者無效狀態。
  • 返回值,返回以下幾種狀態值:
    • netOK: 是否TCP Socket成功。
    • netInvalidParameter: 引數錯誤。
    • netWrongState: 狀態錯誤。

注意事項:

  1. 如果不再使用TCP Socket了,務必記得呼叫此函式釋放TCP Socket佔用的空間。
  2. 釋放後,還在再次申請使用。

使用舉例:

int32_t tcp_sock;

/*關閉Socket */
netTCP_Close (tcp_sock);

/*釋放 Socket */
netTCP_ReleaseSocket (tcp_sock);

11.2.14 函式netTCP_ResetReceiveWindow

函式原型:

netStatus netTCP_ResetReceiveWindow(int32_t socket)

函式描述:

用於復位TCP接收視窗大小到預設值。預設值是由Net_Config_TCP.h檔案裡面TCP_RECEIVE_WIN_SIZE定義的。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回溢位時間或者keep-alive值,單位秒,如果返回0表示引數錯了或者無效狀態。
  • 返回值,返回以下幾種狀態值:
    • netOK: 復位接收視窗成功。
    • netInvalidParameter: 引數錯誤或者TCP流控制沒有使能。
    • netWrongState: 狀態錯誤,Socket連線還沒有建立。

注意事項:

  1. 此函式只能用在使能了TCP流控制的Socket上。
  2. 通過函式netTCP_SetOption的netTCP_OptionFlowControl引數來使能流控制,這樣以來,接收的時候就可以使用滑動視窗協議了。
  3. 在流控制模式下,每個接收到一次資料包,都將調整接收視窗大小,即減去接收到的資料包位元組數, 直到視窗大小變得很小或為0,此時遠端主機停止傳送資料,並等待視窗更新。處理完接收到的資料後,我們可以呼叫netTCP_ResetReceiveWindow函式來重新開啟接收視窗,繼續接收資料。
  4. 根據使用者呼叫此函式的上下文,呼叫此函式有如下幾種效果
  • 如果TCP Socket沒有處於netTCP_StateESTABLISHED狀態或者沒有使能引數netTCP_OptionFlowControl,呼叫此函式沒有任何效果。
  • 如果程式其它地方呼叫了此函式,會復位視窗大小,併發送一個Window Update包。
  • 如果在TCP Socket的回撥函式裡面呼叫此函式,當回撥函式返回時,視窗大小會在TCP生成的確認資料包中更改。

使用舉例:

uint8_t uart_buf[TCP_RECEIVE_WIN_SIZE];
 
uint32_t tcp_cb_func (int32_t socket, netTCP_Event event,
                      const NET_ADDR *addr, const uint8_t *buf, uint32_t len) {
  switch (event) {
case netTCP_EventConnect:
      return (1);
 
    case netTCP_EventData:
      /* 接收到的資料放到緩衝裡面 */
      memcpy (&uart_buf[head], buf, len);
      head += len;
      break;
  }
  return (0);
}
 
int main (void) {
  int32_t tcp_sock;
  uint32_t head, tail;
 
  /* 核心初始化 */
  netInitialize ();
  tcp_sock = netTCP_GetSocket (tcp_cb_func);
  if (tcp_sock >= 0) {
    /* 開始監聽 */ 
    netTCP_Listen (tcp_sock, 8080);
  }
 
  /* 乙太網轉串列埠 */
  head = 0;
  tail = 0;
  while (1) {
    if (uart_busy () || head == tail) {
      /* 串列埠忙,或者緩衝空 */
      continue;
    }
    /* 向串列埠傳送資料 */
    send_uart (uart_buf[tail++]);
    if (tail == head) {
      /* 緩衝空 */
      tail = 0;
      head = 0;
      netTCP_ResetReceiveWindow (tcp_sock);
    }
  }
}

11.2.15 函式netTCP_GetLocalPort

函式原型:

uint16_t netTCP_GetLocalPort(int32_t socket)

函式描述:

用於獲取TCP Socket的埠號。如果使用者在使用netTCP_Connect時,未指定埠,將使用系統自動分配的,可以使用此函式獲取。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 返回值,返回0表示無效狀態或者無效引數,返回其它表示成功獲取的埠號

使用舉例:

int32_t tcp_sock;
 
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
  netTCP_Connect (tcp_sock, addr, 0);
  printf ("Local port is %d\n", netTCP_GetLocalPort (tcp_sock));
}

11.2.16 函式netTCP_SetOption

函式原型:

netStatus netTCP_SetOption(int32_t     socket,
netTCP_Option     option,
uint32_t     val )    

函式描述:

用於TCP Socket的一些選項配置。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 第2個引數是配置選項,當前支援的選項如下:

  • 第3個引數對於上面列表的第3列,前兩個選擇預設取0即可,一般不用。
  • 返回值,返回以下幾種狀態值:
    • netOK: 設定TCP Socket成功。
    • netInvalidParameter: 引數錯誤。
    • netWrongState: 狀態錯誤。

使用舉例:

int32_t tcp_sock;
 
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
  /* 設定30秒內,如果沒有資料通訊,將斷開連線 */
  netTCP_SetOption (tcp_sock, netTCP_OptionTimeout, 30);
  /* 使能Keep Alive */
  netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
}

11.2.17 函式netTCP_Connect

函式原型:

netStatus netTCP_Connect(int32_t     socket,
const NET_ADDR *  addr,
uint16_t     local_port )    

函式描述:

用於連線遠端伺服器。

函式引數:

  • 第1個引數是TCP Socket控制代碼。
  • 第2個引數NET_ADDR型別結構體變數,用於設定要連線的遠端伺服器IP地址和埠號。
  • 第3個引數用於設定本地埠號,如果設定為0的話,系統將自動分配一個埠號。自動分配的埠號,可以通過函式netTCP_GetLocalPort獲取。
  • 返回值,返回以下幾種狀態值:
    • netOK: 設定TCP Socket成功。
    • netInvalidParameter: 引數錯誤。
    • netWrongState: 狀態錯誤。

使用舉例:

NET_ADDR4 addr = { NET_ADDR_IP4, 2000, 192, 168, 0, 1 };
 
netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, 0);

for (;;) {
  switch (netTCP_GetState (tcp_sock)) {
    case netTCP_StateUNUSED:
    case netTCP_StateCLOSED:
      /* 連線失敗 */
      return;
    case netTCP_StateESTABLISHED:
      /* 連線成功 */
      break;
  }
  osDelay (10);
}

11.3 系統配置說明(Net_Config.c)

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

Network System Settings

  • Local Host Name

區域網域名。

這裡起名為armfly,使用區域網域名限制為15個字元。

  • Memory Pool size

引數範圍1536-262144位元組。

記憶體池大小配置,單位位元組。

  • Start System Services

開啟系統服務。如果使能了此選項(打上對勾表示使能),系統將自動開啟系統服務,比如HTTP, FTP, TFTP server等。如果沒有使能,需要使用者呼叫專門的API使能。

  • OS Resource Settings
  • Core Thread Stack Size

RL-TCPnet核心任務需要的棧大小,單位位元組,範圍512到65535。

  • NET_THREAD_PRIORITY

RL-TCPnet核心任務的優先順序。

這個選擇在配置嚮導裡面沒有展示,需要大家點選上圖左下角的Text Editor按鈕檢視巨集定義修改。

11.4 TCP配置說明(Net_Config_TCP.h)

TCP配置檔案:

TCP Sockets

  • Number of TCP Sockets

範圍1-20。

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

  • Number of Retries

範圍0-20。

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

  • Retry Timeout in seconds

範圍1-10,單位秒。

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

  • Default Connect Timeout in seconds

範圍1-600,單位秒。

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

  • Maximum Segment Size

範圍536-1440,單位位元組。

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

  • Receive Window Size

範圍536-65535,單位位元組。

TCP接收視窗大小。

11.5 乙太網配置說明(Net_Config_ETH_0.h)

乙太網涉及到的配置選項比較多:

Ethernet Network Interface 0

Connect to hardware via Driver_ETH#

用於指定驅動號,這個一般不需要使用者去設定,比如RTE建立的檔名是Net_Config_ETH_0.h,就會自動將此引數設定為0。

VLAN

虛擬區域網。

  • ETH0_VLAN_ID

VLAN的ID號,12bit數值,範圍1到4093。

MAC Address

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

注意,MAC地址的第1個位元組的最後一個bit一定要是0。

IP Address

IP地址。

Subnet mask

子網掩碼。

Default Gateway

預設閘道器。

Primary DNS Server

首選DNS伺服器地址。

Secondary DNS Server

備選DNS伺服器地址。

IP Fragmentation

使用傳送IP報文的分片處理和接收IP報文的重組。

  • MTU Size

範圍576-1500位元組。

最大的傳輸單元。

ARP Address Resolution

地址解析協議

  • Cache Table size

ARP Cache表大小。

  • Cache Timeout in seconds

Cache表超時時間。

  • Number of Retries

嘗試解析IP地址的次數

  • Resend Timeout in seconds

每次解析請求的時間間隔。

  • Send Notification on Address changes

啟用此選項後,嵌入式主機將在啟動時或裝置IP地址已更改時傳送ARP通知。

IGMP Group Management

IGMP分組管理。

  • Membership Table size

此主機可以加入的分組數。

NetBIOS Name Service

NetBIOS區域網域名服務,這裡打上對勾就使能了。這樣我們就可以通過Net_Config.c檔案配置的Local Host Name區域網域名進行訪問,而不需要通過IP地址訪問了。

Dynaminc Host Configuration

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

  • Vendor Class Identifier

廠商ID,如果設定了的話,會將其加到DHCP的請求訊息中,用於識別網路裝置的不同廠商。

  • Bootfile Name

從DHCP 伺服器獲取的引導檔名。

  • NTP Servers

從DCHP伺服器獲得NTP伺服器列表。

11.6 網路除錯說明(Net_Debug.c)

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

  • Print Time Stamp

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

  • 其它所有的選項

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

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

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

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

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

11.7 TCP伺服器的實現方法

有了本章節前面小節的配置後,剩下的問題就是TCP伺服器的建立和TCP伺服器資料收發的實現。

11.7.1 建立TCP伺服器

TCP伺服器的建立比較簡單,呼叫函式netTCP_GetSocket即可,此函式的使用和注意事項在本章的11.2.2小節有講解:

/*
*********************************************************************************************************
*                                      巨集定義
*********************************************************************************************************
*/
#define PORT_NUM       1001    /* TCP伺服器監聽埠號 */


/*
*********************************************************************************************************
*                                         變數
*********************************************************************************************************
*/
int32_t tcp_sock;


/*
*********************************************************************************************************
*    函 數 名: tcp_cb_server
*    功能說明: TCP Socket的回撥函式
*    形    參: socket  控制代碼
*             event   事件型別
*             addr    NET_ADDR型別變數,記錄IP地址,埠號。
*             buf     ptr指向的緩衝區記錄著接收到的TCP資料。
*             len     記錄接收到的資料個數。
*    返 回 值: 
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
                        const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
{
    switch (event) 
    {
        /*
            遠端客戶端連線訊息
            1、陣列ptr儲存遠端裝置的IP地址,par中儲存埠號。
            2、返回數值1允許連線,返回數值0禁止連線。
        */
        case netTCP_EventConnect:
            if (addr->addr_type == NET_ADDR_IP4) 
            {
                printf_debug("遠端客戶端請求連線IP: %d.%d.%d.%d  埠號:%d\r\n", 
                                                                addr->addr[0], 
                                                                addr->addr[1], 
                                                                addr->addr[2], 
                                                                addr->addr[3],         
                                                                addr->port);
                return (1);
            }
            else if (addr->addr_type == NET_ADDR_IP6)  
            {
                return (1);
            }
            
            return(0);

        /* Socket遠端連線已經建立 */
        case netTCP_EventEstablished:
            printf_debug("Socket is connected to remote peer\r\n");
            break;

        /* 連線斷開 */
        case netTCP_EventClosed:
            printf_debug("Connection has been closed\r\n");
            break;

        /* 連線終止 */
        case netTCP_EventAborted:
            break;

        /* 傳送的資料收到遠端裝置應答 */
        case netTCP_EventACK:
            break;

        /* 接收到TCP資料幀,ptr指向資料地址,par記錄資料長度,單位位元組 */
        case netTCP_EventData:
            printf_debug("Data length = %d\r\n", len);
            printf ("%.*s\r\n",len, buf);
            break;
    }
    return (0);
}

/*
*********************************************************************************************************
*    函 數 名: TCPnetTest
*    功能說明: TCPnet應用
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/    
void TCPnetTest(void)
{
    int32_t iCount;
    uint8_t *sendbuf;
    uint32_t maxlen;
    netStatus res;
    const uint16_t usMaxBlockTime = 2; /* 延遲週期 */
    uint32_t EvtFlag;
    
    tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
    if (tcp_sock > 0) 
    {
        res = netTCP_Listen (tcp_sock, PORT_NUM);
        printf_debug("tcp listen res = %s\r\n", ReVal_Table[res]);
        
        /* 使能TCP_TYPE_KEEP_ALIVE,會一直保持連線 */
        netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
    }

    /* 省略 */

}

11.7.2 TCP資料傳送

TCP Socket的資料傳送一定要注意各個函式呼叫順序和使用方法,非常重要!否則,資料傳送很容易失敗。資料傳送所用到函式的使用方法和注意事項在本章節的11.2小節有講解。下面的程式碼中對資料傳送專門做了處理,支援任意位元組大小的資料傳送,僅需修改計數變數iCount的初始值即可,初始值是多少,就是傳送多少位元組。下面的程式碼是測試傳送8位元組,1024位元組和5MB:

1.    /*
2.    ******************************************************************************************************
3.    *    函 數 名: TCPnetTest
4.    *    功能說明: TCPnet應用
5.    *    形    參: 無
6.    *    返 回 值: 無
7.    ******************************************************************************************************
8.    */    
9.    void TCPnetTest(void)
10.    {
11.        int32_t iCount;
12.        uint8_t *sendbuf;
13.        uint32_t maxlen;
14.        netStatus res;
15.        const uint16_t usMaxBlockTime = 2; /* 延遲週期 */
16.        uint32_t EvtFlag;
17.        
18.        tcp_sock = netTCP_GetSocket (tcp_cb_server);
19.        
20.        if (tcp_sock > 0) 
21.        {
22.            res = netTCP_Listen (tcp_sock, PORT_NUM);
23.            printf_debug("tcp listen res = %s\r\n", ReVal_Table[res]);
24.            
25.            /* 使能TCP_TYPE_KEEP_ALIVE,會一直保持連線 */
26.            netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
27.        }
28.        
29.        while (1) 
30.        {
31.            
32.            EvtFlag = osThreadFlagsWait(0x00000007U, osFlagsWaitAny, usMaxBlockTime);
33.            
34.            /* 按鍵訊息的處理 */
35.            if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
36.            {
37.                switch (EvtFlag)
38.                {
39.                    /* 接收到K1鍵按下,給遠端TCP客戶端傳送8位元組資料 */
40.                    case KEY1_BIT0:              
41.                        iCount = 8;
42.                        do
43.                        {
44.                            if(netTCP_SendReady(tcp_sock) == true )
45.                            {
46.                                maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
47.    
48.                                iCount -= maxlen;
49.                                
50.                                if(iCount < 0)
51.                                {
52.                                    /* 這麼計算沒問題的 */
53.                                    maxlen = iCount + maxlen;
54.                                }
55.                                
56.                                sendbuf = netTCP_GetBuffer (maxlen);
57.                                sendbuf[0] = '1';
58.                                sendbuf[1] = '2';
59.                                sendbuf[2] = '3';
60.                                sendbuf[3] = '4';
61.                                sendbuf[4] = '5';
62.                                sendbuf[5] = '6';
63.                                sendbuf[6] = '7';
64.                                sendbuf[7] = '8';
65.                                
66.                                /* 必須使用申請的記憶體空間 */
67.                                netTCP_Send (tcp_sock, sendbuf, maxlen);
68.                            }
69.                            
70.                        }while(iCount > 0);
71.                        break;
72.    
73.                    /* 接收到K2鍵按下,給遠端TCP客戶端傳送1024位元組的資料 */
74.                    case KEY2_BIT1:    
75.                        iCount = 1024;
76.                        do
77.                        {
78.                            if(netTCP_SendReady(tcp_sock) == true )
79.                            {
80.                                maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
81.    
82.                                iCount -= maxlen;
83.                                
84.                                if(iCount < 0)
85.                                {
86.                                    /* 這麼計算沒問題的 */
87.                                    maxlen = iCount + maxlen;
88.                                }
89.                                
90.                                sendbuf = netTCP_GetBuffer (maxlen);
91.                                sendbuf[0] = '1';
92.                                sendbuf[1] = '2';
93.                                sendbuf[2] = '3';
94.                                sendbuf[3] = '4';
95.                                sendbuf[4] = '5';
96.                                sendbuf[5] = '6';
97.                                sendbuf[6] = '7';
98.                                sendbuf[7] = '8';
99.                                
100.                                /* 必須使用申請的記憶體空間 */
101.                                netTCP_Send (tcp_sock, sendbuf, maxlen);
102.                            }
103.                            
104.                        }while(iCount > 0);
105.                        break;                    
106.                        
107.                        
108.                    /* 接收到K3鍵按下,給遠端TCP客戶端傳送5MB資料 */
109.                    case KEY3_BIT2:              
110.                        iCount = 5*1024*1024;                
111.                        do
112.                        {
113.                            if(netTCP_SendReady(tcp_sock) == true )
114.                            {
115.                                maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);
116.    
117.                                iCount -= maxlen;
118.                                
119.                                if(iCount < 0)
120.                                {
121.                                    /* 這麼計算沒問題的 */
122.                                    maxlen = iCount + maxlen;
123.                                }
124.                                
125.                                sendbuf = netTCP_GetBuffer (maxlen);
126.                                sendbuf[0] = '1';
127.                                sendbuf[1] = '2';
128.                                sendbuf[2] = '3';
129.                                sendbuf[3] = '4';
130.                                sendbuf[4] = '5';
131.                                sendbuf[5] = '6';
132.                                sendbuf[6] = '7';
133.                                sendbuf[7] = '8';
134.                                
135.                                /* 必須使用申請的記憶體空間 */
136.                                netTCP_Send (tcp_sock, sendbuf, maxlen);
137.                            }
138.                            
139.                        }while(iCount > 0);
140.                        break;
141.                    
142.                     /* 其他的鍵值不處理 */
143.                    default:                     
144.                        break;
145.                }
146.            }
147.        }
148.    }
  • 第41行,通過變數iCount設定要傳送的位元組數,這裡是傳送8位元組資料。
  • 第42到70行,do while語句中的流程很重要:
    • 傳送前務必要呼叫函式netTCP_SendReady檢視傳送是否就緒。
    • 函式netTCP_GetMaxSegmentSize,netTCP_GetBuffer和netTCP_Send務必要依次呼叫,一個都不能少。
  • 第75行,通過變數iCount設定要傳送的位元組數,這裡是傳送1024位元組資料。
  • 第110行,通過變數iCount設定要傳送的位元組數,這裡是傳送5MB資料。

11.7.3 TCP資料接收

TCP資料接收主要是通過函式的回撥函式實現(RTX5和FreeRTOS是一樣的):

/*
*********************************************************************************************************
*    函 數 名: tcp_cb_server
*    功能說明: TCP Socket的回撥函式
*    形    參: socket  控制代碼
*             event   事件型別
*             addr    NET_ADDR型別變數,記錄IP地址,埠號。
*             buf     ptr指向的緩衝區記錄著接收到的TCP資料。
*             len     記錄接收到的資料個數。
*    返 回 值: 
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
                        const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
{
    switch (event) 
    {
        /*
            遠端客戶端連線訊息
            1、陣列ptr儲存遠端裝置的IP地址,par中儲存埠號。
            2、返回數值1允許連線,返回數值0禁止連線。
        */
        case netTCP_EventConnect:
            if (addr->addr_type == NET_ADDR_IP4) 
            {
                printf_debug("遠端客戶端請求連線IP: %d.%d.%d.%d  埠號:%d\r\n", 
                                                                addr->addr[0], 
                                                                addr->addr[1], 
                                                                addr->addr[2], 
                                                                addr->addr[3],         
                                                                addr->port);
                return (1);
            }
            else if (addr->addr_type == NET_ADDR_IP6)  
            {
                return (1);
            }
            
            return(0);

        /* Socket遠端連線已經建立 */
        case netTCP_EventEstablished:
            printf_debug("Socket is connected to remote peer\r\n");
            break;

        /* 連線斷開 */
        case netTCP_EventClosed:
            printf_debug("Connection has been closed\r\n");
            break;

        /* 連線終止 */
        case netTCP_EventAborted:
            break;

        /* 傳送的資料收到遠端裝置應答 */
        case netTCP_EventACK:
            break;

        /* 接收到TCP資料幀,ptr指向資料地址,par記錄資料長度,單位位元組 */
        case netTCP_EventData:
            printf_debug("Data length = %d\r\n", len);
            printf ("%.*s\r\n",len, buf);
            break;
    }
    return (0);
}
  • TCP伺服器的資料接收主要是通過回撥函式的訊息netTCP_EventData實現,進入訊息後,指標變數buf是接收資料緩衝區首地址,變數len記錄接收到的資料長度,單位位元組。

11.8 網路除錯助手和板子的除錯操作步驟

我們這裡使用下面這款除錯助手,任何其它網路除錯助手均可,不限制:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=1568

11.8.1 獲取板子IP地址

首先,強烈推薦將網線接到路由器或者交換機上面測試,因為已經使能了DHCP,可以自動獲取IP地址,而且在前面的配置中使能了局域網域名NetBIOS,使用者只需在電腦端ping armfly就可以獲取板子的IP地址。測試方法如下:

  • WIN+R組合鍵開啟“執行”視窗,輸入cmd。

  • 彈出的命令視窗中,輸入ping armfly。

  • 輸入ping armfly後,回車。

獲得IP地址是192.168.1.5。

11.8.2 網路除錯助手建立TCP客戶端

  • 開啟除錯助手,點選左上角建立連線:

  • 彈出如下介面,型別選擇TCP,目標IP設定為192.168.1.5,埠號1001,最後點選建立:

特別說明,我們這裡直接填區域網域名armfly也是沒有問題的,即下面這樣:

  • 建立後的介面效果如下:

  • 點選連線,連線後的介面效果如下:

連線上後,串列埠軟體也會打印出如下資訊(波特率115200,資料位8,奇偶校驗位無,停止位1):

11.8.3 TCP伺服器傳送資料

板子和網路除錯助手建立連線後就可以相互收發資料了。對於傳送資料。程式中建立了三種大小的資料傳送測試。

  • K1按鍵按下,傳送了8個字元,從1到8。

  • K2按鍵按下,傳送1024位元組,每次傳送資料包的前8個位元組設定了字元a到字元h,後面未做設定。

  • K3按鍵按下,傳送5*1024*1024 = 5242880位元組,即5MB。每次傳送資料包的前8個位元組設定了字元a到字元h,後面都未做設定。

11.8.4 TCP伺服器接收資料

TCP伺服器接收資料的測試也比較方便,我們這裡通過網路除錯助手給板子傳送0到9,共10個字元:

點擊發送後,可以看到串列埠軟體打印出接收到的10個字元:

測試也是沒問題的。

11.9 實驗例程說明(RTX5)

配套例子:

V6-1006_RL-TCPnet V7.X實驗_TCP伺服器(RTX5)

實驗目的:

  1. 學習RL-TCPnet的TCP伺服器建立和資料收發。

實驗內容:

  1. 強烈推薦將網線接到路由器或者交換機上面測試,因為已經使能了DHCP,可以自動獲取IP地址。
  2. 建立了一個TCP Server,而且使能了局域網域名NetBIOS,使用者只需在電腦端ping armfly就可以獲得板子的IP地址,埠號1001。
  3. 使用者可以在電腦端用網路除錯軟體建立TCP Client連線此伺服器端。
  4. 按鍵K1按下,傳送8位元組的資料給TCP Client。
  5. 按鍵K2按下,傳送1024位元組的資料給TCP Client。
  6. 按鍵K3按下,傳送5MB位元組的資料給TCP Client。

實驗操作:

詳見本章節11.8小節。

系統配置說明(Net_Config.c):

詳見本章節11.3小節。

TCP配置說明(Net_Config_TCP.h):

詳見本章節11.4小節。

乙太網配置說明(Net_Config_ETH_0.h):

詳見本章節11.5小節。

網路除錯說明(Net_Debug.c):

詳見本章節11.6小節。

RTX5配置:

RTX5配置嚮導詳情如下:

System Configuration

系統配置

  • Global Dynamic Memory size

全域性動態記憶體大小,單位位元組。

當前配置為20480位元組。

  • Kernel Tick Frequency

核心滴答時鐘頻率。

當前配置為1KHz

  • Round-Robin Thread switching

使能時間片排程,並把時間片設定為5個,即5ms。

  • OS_ISR_FIFO_QUEUE

中斷服務程式裡面呼叫RTX5的API,需要用到這個FIFO佇列,當前FIFO大小設定為16個。

  • OS_ISR_FIFO_QUEUE

中斷服務程式裡面呼叫RTX

Thread Configuration

任務配置。

  • Default Thread Stack size

預設的任務棧大小,單位位元組。

  • Idle Thread Stack size

空閒任務棧大小,單位位元組。

  • Idle Thread Stack size

空閒任務棧大小,單位位元組。

  • Stack overrun checking

使能棧溢位檢測。

  • Stack usage watermark

棧使用率。

  • Privileged mode

使能特權模式。

RTX5任務除錯資訊:

RL-TCPnet協議棧除錯資訊:

程式設計:

任務分配:

AppTaskUserIF任務 : 按鍵訊息處理。

AppTaskLED任務 : LED閃爍。

AppTaskMsgPro任務 : TCPnet應用任務。

AppTaskEthCheck : 網線插拔狀態檢測。

AppTaskStart任務 : 啟動任務,也是最高優先順序任務,這裡用作BSP驅動包處理。

netCore_Thread任務 : TCPnet核心任務。

netEth0_Thread任務 : TCPnet乙太網介面任務。

osRtxTimerThread任務: 定時器任務,TCPnet時間基準。

系統棧大小分配:

RTX5初始化

/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: 標準c程式入口。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
int main (void) 
{    
    /* HAL庫,MPU,Cache,時鐘等系統初始化 */
    System_Init();

    /* 核心開啟前關閉HAL的時間基準 */
    HAL_SuspendTick();
    
    /* 核心初始化 */
    osKernelInitialize();                                  

    /* 建立啟動任務 */
    ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);  

    /* 開啟多工 */
    osKernelStart();
    
    while(1);
}

硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實現:

/*
*********************************************************************************************************
*    函 數 名: System_Init
*    功能說明: 系統初始化,主要是MPU,Cache和系統時鐘配置
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void System_Init(void)
{
    /* 
       STM32H429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鐘:
       - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先順序分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到168MHz
       - 切換使用HSE。
       - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V6開發板使用者手冊第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
}

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串列埠 */
    bsp_InitExtIO();   /* 初始化擴充套件IO */
    bsp_InitLed();        /* 初始化LED */    
}

RTX任務建立:

/*
*********************************************************************************************************
*    函 數 名: AppTaskCreate
*    功能說明: 建立應用任務
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
    ThreadIdTaskEthCheck = osThreadNew(AppTaskEthCheck, NULL, &ThreadEthCheck_Attr);      
    ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);  
    ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);  
}

幾個RTX任務的實現:

/*
*********************************************************************************************************
*    函 數 名: AppTaskUserIF
*    功能說明: 按鍵訊息處理        
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal (數值越小優先順序越低,這個跟uCOS相反)
*********************************************************************************************************
*/
void AppTaskUserIF(void *argument)
{
    uint8_t ucKeyCode;

    while(1)
    {
        ucKeyCode = bsp_GetKey();
        
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                /* K1鍵按下,直接傳送事件標誌給任務AppTaskTCPMain,設定bit0 */
                case KEY_DOWN_K1:
                    printf("K1鍵按下,直接傳送事件標誌給任務ThreadIdTaskMsgPro,bit0被設定\r\n");
                    osThreadFlagsSet(ThreadIdTaskMsgPro, KEY1_BIT0);                    
                    break;    

                /* K2鍵按下,直接傳送事件標誌給任務AppTaskTCPMain,設定bit1 */
                case KEY_DOWN_K2:
                    printf("K2鍵按下,直接傳送事件標誌給任務ThreadIdTaskMsgPro,bit1被設定\r\n");
                    osThreadFlagsSet(ThreadIdTaskMsgPro, KEY2_BIT1);
                    break;
                
                /* K3鍵按下,直接傳送事件標誌給任務AppTaskTCPMain,設定bit2 */
                case KEY_DOWN_K3:
                    printf("K3鍵按下,直接傳送事件標誌給任務ThreadIdTaskMsgPro,bit2被設定\r\n");
                    osThreadFlagsSet(ThreadIdTaskMsgPro, KEY3_BIT2);
                    break;

                /* 其他的鍵值不處理 */
                default:                     
                    break;
            }
        }
        
        osDelay(20);
    }
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskLED
*    功能說明: LED閃爍。
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal1 
*********************************************************************************************************
*/
void AppTaskLED(void *argument)
{
    const uint16_t usFrequency = 200; /* 延遲週期 */
    uint32_t tick;

    /* 獲取當前時間 */
    tick = osKernelGetTickCount(); 
    
    while(1)
    {
        bsp_LedToggle(2);
        /* 相對延遲 */
        tick += usFrequency;                          
        osDelayUntil(tick);
    }
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskMsgPro
*    功能說明: TCPnet應用任務
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal2  
*********************************************************************************************************
*/
void AppTaskMsgPro(void *argument)
{
    while(1)
    {
        TCPnetTest();
    }    
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskEthCheck
*    功能說明: 檢查網線插拔狀態。
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal3  
*********************************************************************************************************
*/
void AppTaskEthCheck(void *argument)
{

    /* 初始化變數 */
    ThreadIdTaskMsgPro = NULL;
    g_ucEthLinkStatus = 0;
    
    /* 初始化網路 */
    netInitialize();
    
    while(1)
    {
        /* 網線插拔處理,方便移植,大家也可以根據需要傳送任務事件標誌做處理 */
        switch (g_ucEthLinkStatus)
        {
            /* 插拔臨時狀態,無需處理 */
            case 0:
            case 1:    
                break;    

            /* 網線插入,創應用任務 */
            case 2:
                if(ThreadIdTaskMsgPro == NULL)
                {    
                    printf_taskdbg("網線插入,建立應用任務\r\n");
                    ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
                }
                break;
            
            /* 網線拔掉,復位網路,刪除應用任務 */
            case 3:
                printf_taskdbg("網線拔掉,復位網路,刪除應用任務\r\n");

                /* 釋放所有網路資源,含TCPnet核心任務和ETH介面任務 */    
                netUninitialize();
            
                printf_taskdbg("netUninitialize\r\n");

                /* 刪除TCPnet應用任務 */
                osThreadTerminate(ThreadIdTaskMsgPro);

                ThreadIdTaskMsgPro = NULL;
                g_ucEthLinkStatus = 0;
            
                /* 重新初始化 */
                netInitialize();
            
                printf_taskdbg("netInitialize\r\n");

            /* 其他的鍵值不處理 */
            default:                     
                break;
        }
        
        osDelay(10);
    }    
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskStart
*    功能說明: 啟動任務,這裡用作BSP驅動包處理。
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal4  
*********************************************************************************************************
*/
void AppTaskStart(void *argument)
{
    const uint16_t usFrequency = 1; /* 延遲週期 */
    uint32_t tick;
    
    /* 初始化外設 */
    HAL_ResumeTick();
    bsp_Init();

    /* 建立任務 */
    AppTaskCreate();

    /* 獲取當前時間 */
    tick = osKernelGetTickCount(); 
    
    while(1)
    {
        /* 需要週期性處理的程式,對應裸機工程呼叫的SysTick_ISR */
        bsp_ProPer1ms();
        
        /* 相對延遲 */
        tick += usFrequency;                          
        osDelayUntil(tick);
    }
}

RL-TCPnet功能測試

這裡專門建立了一個app_tcpnet_lib.c檔案用於RL-TCPnet功能的測試,主要功能是建立了一個TCP Server。

/*
*********************************************************************************************************
*                                      用於本檔案的除錯
*********************************************************************************************************
*/
#if 1
    #define printf_debug printf
#else
    #define printf_debug(...)
#endif


/*
*********************************************************************************************************
*                                      巨集定義
*********************************************************************************************************
*/
#define PORT_NUM       1001    /* TCP伺服器監聽埠號 */


/*
*********************************************************************************************************
*                                         變數
*********************************************************************************************************
*/
int32_t tcp_sock;

/* TCPnet API的返回值 */
static const char * ReVal_Table[]= 
{
    "netOK: Operation succeeded",
    "netBusy: Process is busy",
    "netError: Unspecified error",
    "netInvalidParameter: Invalid parameter specified",
    "netWrongState: Wrong state error",
    "netDriverError: Driver error",
    "netServerError: Server error",
    "netAuthenticationFailed: User authentication failed",
    "netDnsResolverError: DNS host resolver failed",
    "netFileError: File not found or file r/w error",
    "netTimeout: Operation timeout",
};

/*
*********************************************************************************************************
*    函 數 名: tcp_cb_server
*    功能說明: TCP Socket的回撥函式
*    形    參: socket  控制代碼
*             event   事件型別
*             addr    NET_ADDR型別變數,記錄IP地址,埠號。
*             buf     ptr指向的緩衝區記錄著接收到的TCP資料。
*             len     記錄接收到的資料個數。
*    返 回 值: 
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
                        const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
{
    switch (event) 
    {
        /*
            遠端客戶端連線訊息
            1、陣列ptr儲存遠端裝置的IP地址,par中儲存埠號。
            2、返回數值1允許連線,返回數值0禁止連線。
        */
        case netTCP_EventConnect:
            if (addr->addr_type == NET_ADDR_IP4) 
            {
                printf_debug("遠端客戶端請求連線IP: %d.%d.%d.%d  埠號:%d\r\n", 
                                                                addr->addr[0], 
                                                                addr->addr[1], 
                                                                addr->addr[2], 
                                                                addr->addr[3],         
                                                                addr->port);
                return (1);
            }
            else if (addr->addr_type == NET_ADDR_IP6)  
            {
                return (1);
            }
            
            return(0);

        /* Socket遠端連線已經建立 */
        case netTCP_EventEstablished:
            printf_debug("Socket is connected to remote peer\r\n");
            break;

        /* 連線斷開 */
        case netTCP_EventClosed:
            printf_debug("Connection has been closed\r\n");
            break;

        /* 連線終止 */
        case netTCP_EventAborted:
            break;

        /* 傳送的資料收到遠端裝置應答 */
        case netTCP_EventACK:
            break;

        /* 接收到TCP資料幀,ptr指向資料地址,par記錄資料長度,單位位元組 */
        case netTCP_EventData:
            printf_debug("Data length = %d\r\n", len);
            printf ("%.*s\r\n",len, buf);
            break;
    }
    return (0);
}

/*
*********************************************************************************************************
*    函 數 名: TCPnetTest
*    功能說明: TCPnet應用
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/    
void TCPnetTest(void)
{
    int32_t iCount;
    uint8_t *sendbuf;
    uint32_t maxlen;
    netStatus res;
    const uint16_t usMaxBlockTime = 2; /* 延遲週期 */
    uint32_t EvtFlag;
    
    tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
    if (tcp_sock > 0) 
    {
        res = netTCP_Listen (tcp_sock, PORT_NUM);
        printf_debug("tcp listen res = %s\r\n", ReVal_Table[res]);
        
        /* 使能TCP_TYPE_KEEP_ALIVE,會一直保持連線 */
        netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
    }
    
    while (1) 
    {
        
        EvtFlag = osThreadFlagsWait(0x00000007U, osFlagsWaitAny, usMaxBlockTime);
        
        /* 按鍵訊息的處理 */
        if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
        {
            switch (EvtFlag)
            {
                /* 接收到K1鍵按下,給遠端TCP客戶端傳送8位元組資料 */
                case KEY1_BIT0:              
                    iCount = 8;
                    do
                    {
                        if(netTCP_SendReady(tcp_sock) == true )
                        {
                            maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);

                            iCount -= maxlen;
                            
                            if(iCount < 0)
                            {
                                /* 這麼計算沒問題的 */
                                maxlen = iCount + maxlen;
                            }
                            
                            sendbuf = netTCP_GetBuffer (maxlen);
                            sendbuf[0] = '1';
                            sendbuf[1] = '2';
                            sendbuf[2] = '3';
                            sendbuf[3] = '4';
                            sendbuf[4] = '5';
                            sendbuf[5] = '6';
                            sendbuf[6] = '7';
                            sendbuf[7] = '8';
                            
                            /* 必須使用申請的記憶體空間 */
                            netTCP_Send (tcp_sock, sendbuf, maxlen);
                        }
                        
                    }while(iCount > 0);
                    break;

                /* 接收到K2鍵按下,給遠端TCP客戶端傳送1024位元組的資料 */
                case KEY2_BIT1:    
                    iCount = 1024;
                    do
                    {
                        if(netTCP_SendReady(tcp_sock) == true )
                        {
                            maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);

                            iCount -= maxlen;
                            
                            if(iCount < 0)
                            {
                                /* 這麼計算沒問題的 */
                                maxlen = iCount + maxlen;
                            }
                            
                            sendbuf = netTCP_GetBuffer (maxlen);
                            sendbuf[0] = '1';
                            sendbuf[1] = '2';
                            sendbuf[2] = '3';
                            sendbuf[3] = '4';
                            sendbuf[4] = '5';
                            sendbuf[5] = '6';
                            sendbuf[6] = '7';
                            sendbuf[7] = '8';
                            
                            /* 必須使用申請的記憶體空間 */
                            netTCP_Send (tcp_sock, sendbuf, maxlen);
                        }
                        
                    }while(iCount > 0);
                    break;                    
                    
                    
                /* 接收到K3鍵按下,給遠端TCP客戶端傳送5MB資料 */
                case KEY3_BIT2:              
                    iCount = 5*1024*1024;                
                    do
                    {
                        if(netTCP_SendReady(tcp_sock) == true )
                        {
                            maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);

                            iCount -= maxlen;
                            
                            if(iCount < 0)
                            {
                                /* 這麼計算沒問題的 */
                                maxlen = iCount + maxlen;
                            }
                            
                            sendbuf = netTCP_GetBuffer (maxlen);
                            sendbuf[0] = '1';
                            sendbuf[1] = '2';
                            sendbuf[2] = '3';
                            sendbuf[3] = '4';
                            sendbuf[4] = '5';
                            sendbuf[5] = '6';
                            sendbuf[6] = '7';
                            sendbuf[7] = '8';
                            
                            /* 必須使用申請的記憶體空間 */
                            netTCP_Send (tcp_sock, sendbuf, maxlen);
                        }
                        
                    }while(iCount > 0);
                    break;
                
                 /* 其他的鍵值不處理 */
                default:                     
                    break;
            }
        }
    }
}

11.10 實驗例程說明(FreeRTOS)

配套例子:

V6-1007_RL-TCPnet V7.X實驗_TCP伺服器(FreeRTOS)

實驗目的:

  1. 學習RL-TCPnet的TCP伺服器建立和資料收發。

實驗內容:

  1. 強烈推薦將網線接到路由器或者交換機上面測試,因為已經使能了DHCP,可以自動獲取IP地址。
  2. 建立了一個TCP Server,而且使能了局域網域名NetBIOS,使用者只需在電腦端ping armfly就可以獲得板子的IP地址,埠號1001。
  3. 使用者可以在電腦端用網路除錯軟體建立TCP Client連線此伺服器端。
  4. 按鍵K1按下,傳送8位元組的資料給TCP Client。
  5. 按鍵K2按下,傳送1024位元組的資料給TCP Client。
  6. 按鍵K3按下,傳送5MB位元組的資料給TCP Client。

實驗操作:

詳見本章節11.8小節。

系統配置說明(Net_Config.c):

詳見本章節11.3小節。

TCP配置說明(Net_Config_TCP.h):

詳見本章節11.4小節。

乙太網配置說明(Net_Config_ETH_0.h):

詳見本章節11.5小節。

網路除錯說明(Net_Debug.c):

詳見本章節11.6小節。

FreeRTOS配置:

FreeRTOS配置嚮導詳情如下:

  • Minimal stack size

最小任務棧大小,主要是空閒任務,單位字(4個位元組)。

當前設定的是512位元組。

  • Total heap size

FreeRTOS總的堆大小,單位位元組。

當前設定的30960位元組。

  • Kernel tick frequency

FreeRTOS的系統時鐘節拍。

當前設定的是1KHz。

  • Timer task stack depth

定時器任務棧大小,單位字(4位元組)。

當前設定的2048位元組。

  • Timer task priority

定時器任務優先順序。

當前設定的48。

  • Timer queue length

定時器訊息佇列大小。

  • Use time slicing

使能時間片排程,這個選項非常重要,RL-TCPnet V7.X用於FreeRTOS版要用到。

FreeRTOS任務除錯資訊:

RL-TCPnet協議棧除錯資訊:

程式設計:

任務分配:

AppTaskUserIF任務 : 按鍵訊息處理。

AppTaskLED任務 : LED閃爍。

AppTaskMsgPro任務 : TCPnet應用任務。

AppTaskEthCheck : 網線插拔狀態檢測。

AppTaskStart任務 : 啟動任務,也是最高優先順序任務,這裡用作BSP驅動包處理。

netCore_Thread任務 : TCPnet核心任務。

netEth0_Thread任務 : TCPnet乙太網介面任務。

osRtxTimerThread任務: 定時器任務,TCPnet時間基準。

系統棧大小分配:

FreeRTOS初始化

/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: 標準c程式入口。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
int main (void) 
{    
    /* HAL庫,MPU,Cache,時鐘等系統初始化 */
    System_Init();

    /* 核心開啟前關閉HAL的時間基準 */
    HAL_SuspendTick();
    
    /* 核心初始化 */
    osKernelInitialize();                                  

    /* 建立啟動任務 */
    ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);  

    /* 開啟多工 */
    osKernelStart();
    
    while(1);
}

硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實現:

/*
*********************************************************************************************************
*    函 數 名: System_Init
*    功能說明: 系統初始化,主要是MPU,Cache和系統時鐘配置
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void System_Init(void)
{
    /* 
       STM32H429 HAL 庫初始化,此時系統用的還是F429自帶的16MHz,HSI時鐘:
       - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先順序分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到168MHz
       - 切換使用HSE。
       - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V6開發板使用者手冊第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
}

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串列埠 */
    bsp_InitExtIO();   /* 初始化擴充套件IO */
    bsp_InitLed();        /* 初始化LED */    
}

FreeRTOS任務建立:

/*
*********************************************************************************************************
*    函 數 名: AppTaskCreate
*    功能說明: 建立應用任務
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
    ThreadIdTaskEthCheck = osThreadNew(AppTaskEthCheck, NULL, &ThreadEthCheck_Attr);      
    ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);  
    ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);  
}

幾個FreeRTOS任務的實現:

/*
*********************************************************************************************************
*    函 數 名: AppTaskUserIF
*    功能說明: 按鍵訊息處理        
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal (數值越小優先順序越低,這個跟uCOS相反)
*********************************************************************************************************
*/
void AppTaskUserIF(void *argument)
{
    uint8_t ucKeyCode;

    while(1)
    {
        ucKeyCode = bsp_GetKey();
        
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                /* K1鍵按下,直接傳送事件標誌給任務AppTaskTCPMain,設定bit0 */
                case KEY_DOWN_K1:
                    printf("K1鍵按下,直接傳送事件標誌給任務ThreadIdTaskMsgPro,bit0被設定\r\n");
                    osThreadFlagsSet(ThreadIdTaskMsgPro, KEY1_BIT0);                    
                    break;    

                /* K2鍵按下,直接傳送事件標誌給任務AppTaskTCPMain,設定bit1 */
                case KEY_DOWN_K2:
                    printf("K2鍵按下,直接傳送事件標誌給任務ThreadIdTaskMsgPro,bit1被設定\r\n");
                    osThreadFlagsSet(ThreadIdTaskMsgPro, KEY2_BIT1);
                    break;
                
                /* K3鍵按下,直接傳送事件標誌給任務AppTaskTCPMain,設定bit2 */
                case KEY_DOWN_K3:
                    printf("K3鍵按下,直接傳送事件標誌給任務ThreadIdTaskMsgPro,bit2被設定\r\n");
                    osThreadFlagsSet(ThreadIdTaskMsgPro, KEY3_BIT2);
                    break;

                /* 其他的鍵值不處理 */
                default:                     
                    break;
            }
        }
        
        osDelay(20);
    }
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskLED
*    功能說明: LED閃爍。
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal1 
*********************************************************************************************************
*/
void AppTaskLED(void *argument)
{
    const uint16_t usFrequency = 200; /* 延遲週期 */
    uint32_t tick;

    /* 獲取當前時間 */
    tick = osKernelGetTickCount(); 
    
    while(1)
    {
        bsp_LedToggle(2);
        /* 相對延遲 */
        tick += usFrequency;                          
        osDelayUntil(tick);
    }
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskMsgPro
*    功能說明: TCPnet應用任務
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal2  
*********************************************************************************************************
*/
void AppTaskMsgPro(void *argument)
{
    while(1)
    {
        TCPnetTest();
    }    
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskEthCheck
*    功能說明: 檢查網線插拔狀態。
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal3  
*********************************************************************************************************
*/
void AppTaskEthCheck(void *argument)
{

    /* 初始化變數 */
    ThreadIdTaskMsgPro = NULL;
    g_ucEthLinkStatus = 0;
    
    /* 初始化網路 */
    netInitialize();
    
    while(1)
    {
        /* 網線插拔處理,方便移植,大家也可以根據需要傳送任務事件標誌做處理 */
        switch (g_ucEthLinkStatus)
        {
            /* 插拔臨時狀態,無需處理 */
            case 0:
            case 1:    
                break;    

            /* 網線插入,創應用任務 */
            case 2:
                if(ThreadIdTaskMsgPro == NULL)
                {    
                    printf_taskdbg("網線插入,建立應用任務\r\n");
                    ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
                }
                break;
            
            /* 網線拔掉,復位網路,刪除應用任務 */
            case 3:
                printf_taskdbg("網線拔掉,復位網路,刪除應用任務\r\n");

                /* 釋放所有網路資源,含TCPnet核心任務和ETH介面任務 */    
                netUninitialize();
            
                printf_taskdbg("netUninitialize\r\n");

                /* 刪除TCPnet應用任務 */
                osThreadTerminate(ThreadIdTaskMsgPro);

                ThreadIdTaskMsgPro = NULL;
                g_ucEthLinkStatus = 0;
            
                /* 重新初始化 */
                netInitialize();
            
                printf_taskdbg("netInitialize\r\n");

            /* 其他的鍵值不處理 */
            default:                     
                break;
        }
        
        osDelay(10);
    }    
}

/*
*********************************************************************************************************
*    函 數 名: AppTaskStart
*    功能說明: 啟動任務,這裡用作BSP驅動包處理。
*    形    參: 無
*    返 回 值: 無
*   優 先 級: osPriorityNormal4  
*********************************************************************************************************
*/
void AppTaskStart(void *argument)
{
    const uint16_t usFrequency = 1; /* 延遲週期 */
    uint32_t tick;
    
    /* 初始化外設 */
    HAL_ResumeTick();
    bsp_Init();

    /* 建立任務 */
    AppTaskCreate();

    /* 獲取當前時間 */
    tick = osKernelGetTickCount(); 
    
    while(1)
    {
        /* 需要週期性處理的程式,對應裸機工程呼叫的SysTick_ISR */
        bsp_ProPer1ms();
        
        /* 相對延遲 */
        tick += usFrequency;                          
        osDelayUntil(tick);
    }
}

RL-TCPnet功能測試

這裡專門建立了一個app_tcpnet_lib.c檔案用於RL-TCPnet功能的測試,主要功能是建立了一個TCP Server。

/*
*********************************************************************************************************
*                                      用於本檔案的除錯
*********************************************************************************************************
*/
#if 1
    #define printf_debug printf
#else
    #define printf_debug(...)
#endif


/*
*********************************************************************************************************
*                                      巨集定義
*********************************************************************************************************
*/
#define PORT_NUM       1001    /* TCP伺服器監聽埠號 */


/*
*********************************************************************************************************
*                                         變數
*********************************************************************************************************
*/
int32_t tcp_sock;

/* TCPnet API的返回值 */
static const char * ReVal_Table[]= 
{
    "netOK: Operation succeeded",
    "netBusy: Process is busy",
    "netError: Unspecified error",
    "netInvalidParameter: Invalid parameter specified",
    "netWrongState: Wrong state error",
    "netDriverError: Driver error",
    "netServerError: Server error",
    "netAuthenticationFailed: User authentication failed",
    "netDnsResolverError: DNS host resolver failed",
    "netFileError: File not found or file r/w error",
    "netTimeout: Operation timeout",
};

/*
*********************************************************************************************************
*    函 數 名: tcp_cb_server
*    功能說明: TCP Socket的回撥函式
*    形    參: socket  控制代碼
*             event   事件型別
*             addr    NET_ADDR型別變數,記錄IP地址,埠號。
*             buf     ptr指向的緩衝區記錄著接收到的TCP資料。
*             len     記錄接收到的資料個數。
*    返 回 值: 
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
                        const NET_ADDR *addr, const uint8_t *buf, uint32_t len) 
{
    switch (event) 
    {
        /*
            遠端客戶端連線訊息
            1、陣列ptr儲存遠端裝置的IP地址,par中儲存埠號。
            2、返回數值1允許連線,返回數值0禁止連線。
        */
        case netTCP_EventConnect:
            if (addr->addr_type == NET_ADDR_IP4) 
            {
                printf_debug("遠端客戶端請求連線IP: %d.%d.%d.%d  埠號:%d\r\n", 
                                                                addr->addr[0], 
                                                                addr->addr[1], 
                                                                addr->addr[2], 
                                                                addr->addr[3],         
                                                                addr->port);
                return (1);
            }
            else if (addr->addr_type == NET_ADDR_IP6)  
            {
                return (1);
            }
            
            return(0);

        /* Socket遠端連線已經建立 */
        case netTCP_EventEstablished:
            printf_debug("Socket is connected to remote peer\r\n");
            break;

        /* 連線斷開 */
        case netTCP_EventClosed:
            printf_debug("Connection has been closed\r\n");
            break;

        /* 連線終止 */
        case netTCP_EventAborted:
            break;

        /* 傳送的資料收到遠端裝置應答 */
        case netTCP_EventACK:
            break;

        /* 接收到TCP資料幀,ptr指向資料地址,par記錄資料長度,單位位元組 */
        case netTCP_EventData:
            printf_debug("Data length = %d\r\n", len);
            printf ("%.*s\r\n",len, buf);
            break;
    }
    return (0);
}

/*
*********************************************************************************************************
*    函 數 名: TCPnetTest
*    功能說明: TCPnet應用
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/    
void TCPnetTest(void)
{
    int32_t iCount;
    uint8_t *sendbuf;
    uint32_t maxlen;
    netStatus res;
    const uint16_t usMaxBlockTime = 2; /* 延遲週期 */
    uint32_t EvtFlag;
    
    tcp_sock = netTCP_GetSocket (tcp_cb_server);
    
    if (tcp_sock > 0) 
    {
        res = netTCP_Listen (tcp_sock, PORT_NUM);
        printf_debug("tcp listen res = %s\r\n", ReVal_Table[res]);
        
        /* 使能TCP_TYPE_KEEP_ALIVE,會一直保持連線 */
        netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
    }
    
    while (1) 
    {
        
        EvtFlag = osThreadFlagsWait(0x00000007U, osFlagsWaitAny, usMaxBlockTime);
        
        /* 按鍵訊息的處理 */
        if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
        {
            switch (EvtFlag)
            {
                /* 接收到K1鍵按下,給遠端TCP客戶端傳送8位元組資料 */
                case KEY1_BIT0:              
                    iCount = 8;
                    do
                    {
                        if(netTCP_SendReady(tcp_sock) == true )
                        {
                            maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);

                            iCount -= maxlen;
                            
                            if(iCount < 0)
                            {
                                /* 這麼計算沒問題的 */
                                maxlen = iCount + maxlen;
                            }
                            
                            sendbuf = netTCP_GetBuffer (maxlen);
                            sendbuf[0] = '1';
                            sendbuf[1] = '2';
                            sendbuf[2] = '3';
                            sendbuf[3] = '4';
                            sendbuf[4] = '5';
                            sendbuf[5] = '6';
                            sendbuf[6] = '7';
                            sendbuf[7] = '8';
                            
                            /* 必須使用申請的記憶體空間 */
                            netTCP_Send (tcp_sock, sendbuf, maxlen);
                        }
                        
                    }while(iCount > 0);
                    break;

                /* 接收到K2鍵按下,給遠端TCP客戶端傳送1024位元組的資料 */
                case KEY2_BIT1:    
                    iCount = 1024;
                    do
                    {
                        if(netTCP_SendReady(tcp_sock) == true )
                        {
                            maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);

                            iCount -= maxlen;
                            
                            if(iCount < 0)
                            {
                                /* 這麼計算沒問題的 */
                                maxlen = iCount + maxlen;
                            }
                            
                            sendbuf = netTCP_GetBuffer (maxlen);
                            sendbuf[0] = '1';
                            sendbuf[1] = '2';
                            sendbuf[2] = '3';
                            sendbuf[3] = '4';
                            sendbuf[4] = '5';
                            sendbuf[5] = '6';
                            sendbuf[6] = '7';
                            sendbuf[7] = '8';
                            
                            /* 必須使用申請的記憶體空間 */
                            netTCP_Send (tcp_sock, sendbuf, maxlen);
                        }
                        
                    }while(iCount > 0);
                    break;                    
                    
                    
                /* 接收到K3鍵按下,給遠端TCP客戶端傳送5MB資料 */
                case KEY3_BIT2:              
                    iCount = 5*1024*1024;                
                    do
                    {
                        if(netTCP_SendReady(tcp_sock) == true )
                        {
                            maxlen  = netTCP_GetMaxSegmentSize (tcp_sock);

                            iCount -= maxlen;
                            
                            if(iCount < 0)
                            {
                                /* 這麼計算沒問題的 */
                                maxlen = iCount + maxlen;
                            }
                            
                            sendbuf = netTCP_GetBuffer (maxlen);
                            sendbuf[0] = '1';
                            sendbuf[1] = '2';
                            sendbuf[2] = '3';
                            sendbuf[3] = '4';
                            sendbuf[4] = '5';
                            sendbuf[5] = '6';
                            sendbuf[6] = '7';
                            sendbuf[7] = '8';
                            
                            /* 必須使用申請的記憶體空間 */
                            netTCP_Send (tcp_sock, sendbuf, maxlen);
                        }
                        
                    }while(iCount > 0);
                    break;
                
                 /* 其他的鍵值不處理 */
                default:                     
                    break;
            }
        }
    }

11.11 總結

本章節就為大家講解這麼多,希望大家多做測試,爭取可以熟練掌握這些API函式的使用。