【STM32H7】第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)

配套例子:

V7-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)
{
    
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
       - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
       - 设置NVIV优先级分组为4。
     */
    HAL_Init();

    /* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并开启 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif

}

/*
*********************************************************************************************************
*    函 数 名: bsp_Init
*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
    bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
    bsp_InitLed();        /* 初始化LED */    
    bsp_InitTimer();      /* 初始化滴答定时器 */
}

  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)

配套例子:

V7-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)
{
    
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
       - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
       - 设置NVIV优先级分组为4。
     */
    HAL_Init();

    /* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并开启 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif

}

/*
*********************************************************************************************************
*    函 数 名: bsp_Init
*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       
    bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
    bsp_InitLed();        /* 初始化LED */    
    bsp_InitTimer();      /* 初始化滴答定时器 */
}

  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函数的使用。

 

posted @ 2020-09-22 16:45  硬汉嵌入式  阅读(285)  评论(0编辑  收藏  举报