【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

时间:2023-03-09 02:39:19
【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

第17章      RL-TCPnet之UDP通信

本章节为大家讲解RL-TCPnet的UDP通信实现,学习本章节前,务必要优先学习第16章UDP用户数据报协议基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

本章教程含STM32F407开发板和STM32F429开发板。

17.1  初学者重要提示

17.2  UDP的API函数

17.3  特别注意UDP个数配置

17.4  UDP配置说明(Net_Config.c)

17.5  UDP调试说明(Net_Debug.c)

17.6  UDP通信的丢包问题说明

17.7  UDP通信的实现方法

17.8  网络调试助手和板子的操作步骤

17.9  实验例程说明(裸机)

17.10  实验例程说明(RTX)

17.11  总结

17.1  初学者重要提示

  1. 学习本章节前,务必保证已经学习了第16章的基础知识。
  2. 本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取达到熟练运用。
  3. 对于UDP通讯时的丢包问题在本章节的17.6小节有特别说明。
  4. 本章节使用“野人网络调试助手”,前面几个章节使用的网络调试助手在UDP测试方面丢包稍微高一点。具体看本章节的17.8小节。

17.2  UDP的API函数

使用如下7个函数可以实现RL-TCPnet的UDP通信:

  • udp_get_socket
  • udp_open
  • udp_close
  • udp_release_socket
  • udp_get_buf
  • udp_send
  • udp_mcast_ttl

关于这7个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

这里我们重点的说以下 4个函数,因为本章节配套的例子使用的是这4个函数:

  • udp_get_socket
  • udp_open
  • udp_get_buf
  • udp_send

关于这些函数注意以下两点:

  1. 这些函数都不支持重入,也就是不支持多任务调用。
  2. UDP接口函数通过UDP Socket做数据传输,主要用于不把数据可靠传输作为首选的场合。因为UDP没有确认机制,会有丢包问题。

17.2.1  函数udp_get_socket

函数原型:

U8 udp_get_socket (

    U8   tos,           /* UDP Socket服务类型 */

    U8   opt,           /* 校验和选项 */

    U16 (*listener)(    /* 回调函数 */

        U8  socket,     /* UDP Socket句柄 */

        U8* remip,      /* 远程设备的IP地址 */

        U16 port,       /* 远程设备的端口号. */

        U8* buf,        /* 接收到的数据地址 */

        U16 len ));     /* 接收到的数据长度 */                   

函数描述:

函数udp_get_socket用于获取一个UDP Socket。

1、第1个参数用于指定服务类型,默认取零即可

2、第2个参数是校验和选项,有如下两种可选。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

用户可以通过或操作将发送和接收校验和都选上UDP_OPT_CHK_CS | UDP_OPT_SEND_CS。如果这两个选项都不使用的话,设置此参数为0即可,这样一定程度上可以加快系统响应时间。

3、第3个参数是回调函数,用于事件监听。

(1)回调函数第1个参数,UDP Socket的句柄,也就是函数tcp_get_socket的返回值。
(2)回调函数第2个参数,远程设备的IP地址。
(3)回调函数第3个参数,远程设备的端口号。
(4)回调函数第4个参数,接收到的数据地址。
(5)回调函数第5个参数,接收到的数据长度。

4、返回值,如果获取成功,返回TCP Socket句柄,如果获取失败,返回0。

使用这个函数要注意以下问题:

  1. 调用UDP Socket任何其它函数前,务必要调用此函数udp_get_socket。
  2. 以太网数据包受到以太网CRC的保护。
  3. 传输的数据包通过路由器、代理服务器、网关等,数据包是可以被修改的。
  4. 使用函数udp_get_socket,第3个参数的回调函数务必要设置。

使用举例:

/*

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

*                                         变量

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

*/

uint8_t udp_soc;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("IP:%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

          udp_open (udp_soc, LocalPort_NUM);

     }

    /* 省略 */

}

17.2.2 函数udp_open

函数原型:

BOOL udp_open (

    U8  socket,      /* UDP Socket句柄 */

    U16 locport);    /* 端口号 */       

函数描述:

函数udp_open用于打开UDP通信。

  • 第1个参数是设置要监听的UDP Socket句柄。
  • 第2个参数是UDP端口号。
  • 返回值,打开成功返回__TRUE,打开失败返回__FALSE。

使用这个函数要注意以下问题:

  1. 如果第二个参数填0的话,系统将为其自动分配一个未使用的UDP端口号。

使用举例:

/*

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

*                                         变量

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

*/

uint8_t udp_soc;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("IP:%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

    /* 省略 */

}

17.2.3 函数udp_get_buf

函数原型:

U8* udp_get_buf (

    U16 size);    /* 申请缓冲区大小,单位字节 */

函数描述:

函数udp_get_buf用于获取UDP发送缓冲区,用户将要发送的数据存到这个缓冲区中,然后通过函数udp_send发送。发送完毕后会释放申请的发送缓冲区。

  • 第1个参数是要申请的缓冲区大小,单位字节。
  • 返回值,返回获取的缓冲区地址。如果缓冲区申请失败,RL-TCPnet会调用函数sys_error,并触发里面的错误类型ERR_MEM_ALLOC。对于RL-TCPnet V4.60及其以上版本,如果用户将此函数的形参与0x8000进行或操作,即最高位设置为1,那么此函数申请失败的话会返回空指针,即数值0,并触发函数sys_error的调用。

使用这个函数要注意以下问题:

  1. 每次发送都需要调用此函数获取发送缓冲区地址。
  2. 申请的发送缓冲区大小不可超过最大数据包大小UDP Maximum Packet Size,即1472字节。
  3. 操作缓冲区的时候,切不可超过申请的缓冲区大小,否则会造成RL-TCPnet崩溃。

使用举例:

/*

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

*                                      用于本文件的调试

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

*/

#define PORT_NUM       1001    /* TCP服务器监听端口号 */

/*

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

*                                         变量

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

*/

uint8_t udp_soc;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("IP:%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

         /* TCP轮询 */

         tcpnet_poll();

         /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程UDP设备发送8字节数据 */

                   case KEY_DOWN_K1:

                       /* 用于设置发送次数 */

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();  

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}

17.2.4 函数udp_send

函数原型:

BOOL udp_send (

    U8  socket,     /* UDP socket句柄 */

    U8* remip,      /* 远程设备的IP地址 */

    U16 remport,    /* 远程设备的端口号 */

    U8* buf,        /* 要发送数据的地址 */

    U16 dlen );     /* 要发送数据的大小,单位字节 */

函数描述:

函数udp_send用于发送数据包给远程设备。

  • 第1个参数是UDP Socket句柄。
  • 第2个参数是远程设备的IP地址。
  • 第3个参数是远程设备的端口号。
  • 第4个参数是要发送数据的地址。
  • 第5个参数是要发送数据的大小,单位字节。
  • 返回值,发送成功返回__TRUE,发送失败返回__FALSE。

使用这个函数要注意以下问题:

  1. 调用函数udp_send前务必要调用函数udp_get_buf获得缓冲区。
  2. 数据通信前,务必要通过函数udp_open打开。
  3. 同一个端口号,同一个UDP Socket可以与多个远程设备通信,但需要用户管理好多个设备通信时的数据发送和接收。

使用举例:

/*

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

*                                      用于本文件的调试

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

*/

#define PORT_NUM       1001    /* TCP服务器监听端口号 */

/*

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

*                                         变量

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

*/

uint8_t udp_soc;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("IP:%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

         /* TCP轮询 */

         tcpnet_poll();

         /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程UDP设备发送8字节数据 */

                   case KEY_DOWN_K1:

                       /* 用于设置发送次数 */

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();  

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}

17.3 特别注意UDP个数配置

使用UDP要特别注意UDP的个数配置,因为除了用户自己使用的UDP,还要考虑BSD Socket,TFTP,TFTPC,DNS,SNTP,SNMP,DHCP和NetBIOS也都要使用UDP Socket(下面的代码在Net_lib.c文件中定义):

/* Check number of UDP sockets available. */

#define __UDPNS    ((BSD_ENABLE  * BSD_NUMSOCKS)   + \

                    (TFTP_ENABLE * __TFTP_NSOCKS)  + \

                    (TFTPC_ENABLE* )              + \

                    (DNS_ENABLE  * )              + \

                    (SNMP_ENABLE * )              + \

                    (SNTP_ENABLE * )              + \

                    (DHCP_ENABLE * ETH_ENABLE)     + \

                    (NBNS_ENABLE * ETH_ENABLE))

#if (__UDPNS > UDP_NUMSOCKS)

 #error Number of UDP Sockets too small

#endif

本章节配套的例子就使用了DHCP和NetBIOS了,所以这两个就得占用两个UDP Socket,外接测试UDP通信也需要占用一个,所以至少要在Net_Config.c文件中配置3个UDP Socket供使用。本章配套的例子是配置了5个UDP Socket。

17.4 UDP配置说明(Net_Config.c)

(本章节配套例子的配置与本小节的说明相同)

RL-TCPnet的配置工作是通过配置文件Net_Config.c实现。在MDK工程中打开文件Net_Config.c,可以看到下图所示的工程配置向导:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

RL-TCPnet要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

System Definitions

(1)Local Host Name

局域网域名。

这里起名为armfly,使用局域网域名限制为15个字符。

(2)Memory Pool size

参数范围1536-262144字节。

内存池大小配置,单位字节。另外注意一点,配置向导这里显示的单位是字节,如果看原始定义,MDK会做一个自动的4字节倍数转换,比如我们这里配置的是8192字节,那么原始定义是#define MEM_SIZE  2048,也就是8192/4 = 2048。

(3)Tick Timer interval

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

系统滴答时钟间隔,也就是网络协议栈的系统时间基准,默认情况下,取值100ms。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

Ethernet Network Interface

以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP

(1)MAC Address

局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。

(2)IP Address

IP地址。

(3)Subnet mask

子网掩码。

(4)Default Gateway

默认网关。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

Ethernet Network Interface

以太网接口配置,这个配置里面还有如下两项比较重要的配置需要说明。

(1)NetBIOS Name Service

NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。

(2)Dynaminc Host Configuration

即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

UDP Sockets

UDP Sockets配置,打上对勾就使能了此项功能

(1)Number of UDP Sockets

用于配置可创建的UDP Sockets数量,这里配置了5个,特别注意本章节前面17.3小节的说明。

范围1 – 20。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

TCP Sockets

TCP Sockets配置,打上对勾就使能了此项功能

(1)Number of TCP Sockets

用于配置可创建的TCP Sockets数量。

(2)Number of Retries

范围0-20。

用于配置重试次数,TCP数据传输时,如果在设置的重试时间内得不到应答,算一次重试失败,这里就是配置的最大重试次数。

(3)Retry Timeout in seconds

范围1-10,单位秒。

重试时间。如果发送的数据在重试时间内得不到应答,将重新发送数据。

(4)Default Connect Timeout in seconds

范围1-600,单位秒。

用于配置默认的保持连接时间,即我们常说的Keep Alive时间,如果时间到了将断开连接。常用于HTTP Server,Telnet Server等。

(5)Maximum Segment Size

范围536-1460,单位字节。

MSS定义了TCP数据包能够传输的最大数据分段。

(6)Receive Window Size

范围536-65535,单位字节。

TCP接收窗口大小。

17.5 UDP调试说明(Net_Debug.c)

(重要说明,RL-TCPnet的调试是通过串口打印出来的)

RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

Print Time Stamp

勾选了此选项的话,打印消息时,前面会附带时间信息。

其它所有的选项

默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以Memory Management Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

Off:表示关闭此选项的调试功能。

Errors only:表示仅在此选项出错时,将其错误打印出来。

Full debug:表示此选项的全功能调试。

具体测试,我们这里就不做了,大家可以按照第11章讲解的调试方法进行测试。

17.6 UDP通信的丢包问题说明

使用本章节配套的例子做大批量数据量通信的时候,出现UDP丢包是正常的,因为UDP通信不需要建立连接就可以直接把数据包丢出去,而且没有应答机制,发送失败了也没有重传,所以出现丢包是正常的。如此一来,可靠性机制只能用户自己在应用层去实现。

对于本章节配套的例子,我们是使用板子跟电脑端的网络调试助手通信,没法做应用层的可靠性机制,所以在大批量数据包发送的时候,简单的在每个数据包发送之间加个延迟,从而保证网络调试助手可以接收到。

17.7 UDP通信的实现方法

有了本章节17.4小节的配置后,剩下的问题就是UDP的创建和UDP数据收发的实现。

17.7.1 DHCP和ARP状态获取

不像TCP,UDP不需要建立连接就可以收发数据,而且也没有重发、应答、流控制等保证数据可靠发送的机制,对于这种情况,程序中做了一个特别处理,在创建了UDP Socket后就检测DHCP是否获取了IP地址(如果使能了DHCP)以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。

具体实现代码如下(具体DHCP和ARP的知识会在后面章节为大家讲解):

/*

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

*    函 数 名: DCHP_ARP_Check

*    功能说明: 检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。

*    形    参: 无

*    返 回 值: 无

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

*/

void DCHP_ARP_Check(void)

{

     if(DHCP_Status == __FALSE) 

     {

         if(mem_test(localm[NETIF_ETH].IpAdr, , ) == __FALSE)

         {

              DHCP_Status = __TRUE;

              printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");          

         }

     }

     if(CacheARP_Status == __FALSE)  

     {

         if(arp_cache_ip (Rem_IP, ARP_FIXED_IP) == __FALSE)

         {

              CacheARP_Status = __TRUE;

              printf_debug("通过IP地址可以解析出MAC\r\n");                   

         }

     }   

}

17.7.2 创建UDP Socket

UDP通信的创建比较简单,调用函数udp_get_socket即可,此函数的使用方法和注意事项在本章的17.2.1小节有讲解:

/*

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

*                                         变量

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

*/

uint8_t udp_soc;

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("IP:%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

    /* 省略 */

}

17.7.3 UDP数据发送

UDP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在本章节的17.2小节有讲解。下面的代码中对数据发送专门做了处理,支持任意字节大小的数据发送,仅需修改计数变量iCount的初始值即可,初始值是多少,就发送多少次数据包,具体每次发送的数据包大小由函数udp_get_buf和udp_send决定。下面的代码是裸机方式的,测试发送8字节,10240字节和2MB:

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer())

     {

         bsp_LedToggle();

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();//--------------(1)

         DCHP_ARP_Check();

     }

     main_TcpNet ();//--------------(2)

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

         /* TCP轮询 */

         tcpnet_poll();

         /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程UDP设备发送8字节数据 */

                   case KEY_DOWN_K1:

                       /* 用于设置发送次数 */

                       iCount = ; //--------------(3)

                       do //--------------(4)

                       {

                            tcpnet_poll();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )  //--------------(5)

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();   //--------------(6)

                       }while(iCount > );

                       break;

                   /* K2键按下,给远程UDP设备发送10240字节数据 */

                   case KEY_DOWN_K2: 

                        /* 用于设置发送次数,每次1024字节 */                  

                       iCount = ; //--------------(7)

                       do

                       {

                            tcpnet_poll();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , ); //--------------(8)

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */                         

                            bsp_DelayMS();

                       }while(iCount > );

                       break;

                   /* K3键按下,给远程UDP设备发送2MB数据 */

                   case KEY_DOWN_K3:

                       /* 用于设置发送次数,每次发送1024字节 */                   

                       iCount = ; //--------------(9)

                       do

                       {

                            tcpnet_poll();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}
  1. 函数timer_tick用于实现网络时间基准,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms。
  2. 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
  3. 通过变量iCount设置要发送的次数,这里是发送1次。
  4. do while语句中的流程很重要:

    (1)    函数tcpnet_poll一定要实时调用着。

    (2)    函数udp_get_buf和udp_send务必要依次调用,一个都不能少。

  5. 一定要保证发送成功了,发送次数才可以减一,但这里发送成功并不保证远程设备接收成功。
  6. 由于UDP没有重发、应答、流控制等机制,这里简单的做个延迟,保证远程设备可以接收到数据。
  7. 通过变量iCount设置要发送的次数,这里是发送10次,每次发送1024字节。
  8. 将申请到的1024字节数据全部清零,因为后面的代码仅初始化了前8个字节,RL-TCPnet不负责对申请的空间清零,申请的空间依然保存着上次数据包或者其它应用时的数值。
  9. 通过变量iCount设置要发送的次数,这里是发送2048次,每次发送1024字节。

说完了裸机方式,下面说说RTOS方式的数据发送,这里我们以RTX操作系统为例进行说明(其它的uCOS-III和FreeRTOS的思路是一样的)。RTX操作系统与裸机方式的主要不同是为RL-TCPnet专门配套了两个任务,一个是RL-TCPnet主任务,另一个是网络系统时间基准更新任务。

网络系统时间更新任务:

/*

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

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

特别注意,这里的网络时间基准函数timer_tick,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms,所以这里的延迟一定要匹配。

RL-TCPnet主任务,TCP数据收发在这个任务里面实现:

/*

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

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

函数TCPnetTest的具体实现如下:

/*

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

*                                      宏定义

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

*/

#define PORT_NUM       1001    /* TCP服务器监听端口号 */

/*

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

*                                         变量

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

*/

uint8_t socket_tcp;

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t tcp_status;

     uint16_t maxlen;

     uint8_t res;

     OS_RESULT xResult;

     const uint16_t usMaxBlockTime = ; /* 延迟周期 */

     /*

        创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。

        但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。

     */

    socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback);

     if(socket_tcp != )

     {

         res = tcp_listen (socket_tcp, PORT_NUM);

         printf_debug("tcp listen res = %d\r\n", res);

     }

     while ()

     {

         /* RL-TCPnet处理函数 */

         main_TcpNet();  //--------------(1)

         /* 用于网线插拔的处理 */

         tcp_status = TCP_StatusCheck();

         /* 按键消息的处理 */

         if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) //------(2)

         {

              xResult = os_evt_get ();

              switch (xResult)

              {

                   /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */

                   case KEY1_BIT0:              

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       iCount = ; //--------------(3)

                       do

                       {

                            main_TcpNet();//--------------(4)

                            if (tcp_check_send (socket_tcp) == __TRUE)  

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 iCount -= maxlen;

                                 if(iCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = iCount + maxlen;

                                 }

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(iCount > );

                       break;

                   /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */

                   case KEY2_BIT1:       

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       iCount = ; //--------------(5)

                       do

                       {

                            main_TcpNet();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 iCount -= maxlen;

                                 if(iCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = iCount + maxlen;

                                 }

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(iCount > );                      

                       break;

                   /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */

                   case KEY3_BIT2:              

                       printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp));

                       iCount = **; //--------------(6)

                       do

                       {

                            main_TcpNet();

                            if (tcp_check_send (socket_tcp) == __TRUE)

                            {

                                 maxlen = tcp_max_dsize (socket_tcp);

                                 iCount -= maxlen;

                                 if(iCount < )

                                 {

                                     /* 这么计算没问题的 */

                                     maxlen = iCount + maxlen;

                                 }

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf = tcp_get_buf(maxlen);

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 /* 测试发现只能使用获取的内存 */

                                 tcp_send (socket_tcp, sendbuf, maxlen);

                            }

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}
  1. 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
  2. 这里使用了事件标志组,溢出时间设置为了2毫秒。这样一方面保证了函数main_TcpNet的周期性执行,另一方面用来等待按键发送事件标志消息。
  3. 通过变量iCount设置要发送的字节数,这里是发送8字节数据。
  4. do while语句中的流程很重要:

    (1)    函数main_TcpNet一定要实时调用着。

    (2)    发送前务必要调用函数tcp_check_send查看发送是否就绪。

    (3)    函数tcp_max_dsize,tcp_get_buf和tcp_send务必要依次调用,一个都不能少。

  5. 通过变量iCount设置要发送的字节数,这里是发送1024字节数据。
  6. 通过变量iCount设置要发送的字节数,这里是发送5MB数据。

17.7.4 UDP数据接收

TCP数据接收主要是通过函数udp_get_socket的回调函数实现(裸机,RTX,uCOS-III和FreeRTOS是一样的):

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("IP:%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

相比TCP Socket中函数tcp_get_socket的回调函数,UDP Socket的回调就简单很多了。接收到数据后,都会进入到这个回调函数中。在回调函数中可以获得数据来源IP地址和端口号,以及数据和数据大小。

17.8 网络调试助手和板子的操作步骤

由于前面TCP通信章节使用的网络调试助手做UDP测试效果不好,丢包稍微高一些,所以本章节UDP通信使用“野人网络调试助手”:http://bbs.armfly.com/read.php?tid=30223

17.8.1 获取板子IP地址

首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。测试方法如下:

(1)WIN+R组合键打开“运行”窗口,输入cmd。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

(2)弹出的命令窗口中,输入ping armfly。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

(3)输入ping armfly后,回车。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

获得IP地址是192.168.1.5。

17.8.2 获取电脑的IP地址

获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取,方法跟上面17.8.1小节中的方式一样:

(1)WIN+R组合键打开“运行”窗口,输入cmd。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

(2)弹出的命令窗口中,输入ipconfig。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

(3)输入ipconfig后,回车。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

获得电脑的IP地址是192.168.1.2。

17.8.3 网络调试助手打开UDP端口

1、打开调试助手,可以自动识别出电脑的IP地址,我们这里仅需配置成UDP模式,端口号1001并且勾选“显示接收时间”功能:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

2、点击“打开”按钮后,就可以使用了。“打开”按钮会变成如下状态:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

17.8.4 程序中配置远程IP地址和端口

据前面17.8.2小节获取的电脑端IP地址和17.8.3小节给网络调试助手设置的端口号,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义,其中IP地址填前面获取的192.168.1.2,大家要根据电脑实际的IP地址填写。而端口号,配置为1001,跟网络调试助手中设置的端口号要统一:

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_NUM         1001

17.8.5 DHCP和ARP状态

对于UDP通信,程序中创建了UDP Socket后就检测DHCP 是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

17.8.6 UDP发送数据

将板子上电,并且网络调试助手的UDP通信也打开后就可以相互收发数据了。对于发送数据,程序中创建了三种数据大小的数据发送测试。

(1)K1按键按下,发送了8个字符,从1到8。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

(2)K2按键按下,发送了10次数据包,每次发1024字节,每个数据包的前8个字节设置了字符a到字符h,后面都未做设置。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

(3)K3按键按下,发送了2048次,每次1024字节,共计发送2048*1024 = 2097152字节,即2MB。这里仅设置了每个数据包的前8个字节为字符a到字符h,后面都未做设置。

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

17.8.7 UDP接收数据

UDP接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送0到9,共10个字符:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

点击发送后,可以看到串口软件打印出发送此消息的远程设备IP地址和端口号以及接收到的10个字符:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

字符0对应的ASCII值就是48,其它字符数值依次增加。测试也是没问题的。

17.9 实验例程说明(裸机)

17.9.1 STM32F407开发板实验

配套例子:

V5-1020_RL-TCPnet实验_UDP通信(裸机)

实验目的:

  1. 学习RL-TCPnet的UDP通信创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):

    #define IP1            192

    #define IP2            168

    #define IP3            1

    #define IP4            2

    #define PORT_NUM       1001

  3. 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
  4. 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
  5. 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。

    (1)printf_debug("通过IP地址可以解析出MAC\r\n");

    (2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");

  6. 按键K1按下,发送8字节的数据给电脑端UDP端口。
  7. 按键K2按下,发送10240字节的数据给电脑端UDP端口。
  8. 按键K3按下,发送2MB字节的数据给电脑端UDP端口。

实验操作:

详见本章节17.8小节。

配置向导文件设置(Net_Config.c):

详见本章节17.4小节。

调试文件设置(Net_Debug.c):

详见本章节17.5小节。

程序设计:

主函数初始化

在main.c文件实现:

/*

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

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 进入RL-TCPnet测试函数 */

     TCPnetTest();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

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

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

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

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     bsp_InitUart();    /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitLed();     /* 初始LED指示灯端口 */

     bsp_InitTimer();   /* 初始化滴答定时器 */

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,这里是创建了一个UDP Socket。

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_NUM         1001

/* 这个是本地端口 */

#define LocalPort_NUM    1024

/*

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

*                                         变量

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

*/

extern LOCALM localm[];

uint8_t udp_soc;

uint8_t DHCP_Status = __FALSE;

uint8_t CacheARP_Status = __FALSE;

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

/*

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

*    函 数 名: DCHP_ARP_Check

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void DCHP_ARP_Check(void)

{

     if(DHCP_Status == __FALSE) 

     {

         if(mem_test(localm[NETIF_ETH].IpAdr, , ) == __FALSE)

         {

              DHCP_Status = __TRUE;

              printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");          

         }

     }

     if(CacheARP_Status == __FALSE)  

     {

         if(arp_cache_ip (Rem_IP, ARP_FIXED_IP) == __FALSE)

         {

              CacheARP_Status = __TRUE;

              printf_debug("通过IP地址可以解析出MAC\r\n");                   

         }

     }   

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer())

     {

         bsp_LedToggle();

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

         DCHP_ARP_Check();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

          /* TCP轮询 */

         tcpnet_poll();

         /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程UDP设备发送8字节数据 */

                   case KEY_DOWN_K1:

                       /* 用于设置发送次数 */

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();  

                       }while(iCount > );

                       break;

                   /* K2键按下,给远程UDP设备发送10240字节数据 */

                   case KEY_DOWN_K2: 

                        /* 用于设置发送次数,每次1024字节 */                  

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */                         

                            bsp_DelayMS();

                       }while(iCount > );

                       break;

                   /* K3键按下,给远程UDP设备发送2MB数据 */

                   case KEY_DOWN_K3:

                       /* 用于设置发送次数,每次发送1024字节 */                   

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}

17.9.2 STM32F429开发板实验

配套例子:

V6-1020_RL-TCPnet实验_UDP通信(裸机)

实验目的:

  1. 学习RL-TCPnet的UDP通信创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):

    #define IP1            192

    #define IP2            168

    #define IP3            1

    #define IP4            2

    #define PORT_NUM       1001

  3. 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
  4. 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
  5. 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。

    (1)printf_debug("通过IP地址可以解析出MAC\r\n");

    (2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");

  6. 按键K1按下,发送8字节的数据给电脑端UDP端口。
  7. 按键K2按下,发送10240字节的数据给电脑端UDP端口。
  8. 按键K3按下,发送2MB字节的数据给电脑端UDP端口。

实验操作:

详见本章节17.8小节。

配置向导文件设置(Net_Config.c):

详见本章节17.4小节。

调试文件设置(Net_Debug.c):

详见本章节17.5小节。

程序设计:

主函数初始化

在main.c文件实现:

/*

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

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 进入RL-TCPnet测试函数 */

     TCPnetTest();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

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

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

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

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     bsp_InitUart();    /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitLed();     /* 初始LED指示灯端口 */

     bsp_InitTimer();   /* 初始化滴答定时器 */

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,这里是创建了一个UDP Socket。

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_NUM         1001

/* 这个是本地端口 */

#define LocalPort_NUM    1024

/*

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

*                                         变量

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

*/

extern LOCALM localm[];

uint8_t udp_soc;

uint8_t DHCP_Status = __FALSE;

uint8_t CacheARP_Status = __FALSE;

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

/*

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

*    函 数 名: DCHP_ARP_Check

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void DCHP_ARP_Check(void)

{

     if(DHCP_Status == __FALSE) 

     {

         if(mem_test(localm[NETIF_ETH].IpAdr, , ) == __FALSE)

         {

              DHCP_Status = __TRUE;

              printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");          

         }

     }

     if(CacheARP_Status == __FALSE)  

     {

         if(arp_cache_ip (Rem_IP, ARP_FIXED_IP) == __FALSE)

         {

              CacheARP_Status = __TRUE;

              printf_debug("通过IP地址可以解析出MAC\r\n");                   

         }

     }   

}

/*

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

*    函 数 名: tcpnet_poll

*    功能说明: 使用TCPnet必须要一直调用的函数

*    形    参: 无

*    返 回 值: 无

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

*/

void tcpnet_poll(void)

{

     if(bsp_CheckTimer())

     {

         bsp_LedToggle();

         /* 此函数坚决不可以放在中断里面跑 */

         timer_tick ();

         DCHP_ARP_Check();

     }

     main_TcpNet ();

}

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     uint8_t ucKeyCode;

     /* 初始化网络协议栈 */

     init_TcpNet ();

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     /* 创建一个周期是100ms的软定时器 */

    bsp_StartAutoTimer(, );

     while ()

     {

          /* TCP轮询 */

         tcpnet_poll();

         /* 按键消息的处理 */

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,给远程UDP设备发送8字节数据 */

                   case KEY_DOWN_K1:

                       /* 用于设置发送次数 */

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();  

                       }while(iCount > );

                       break;

                   /* K2键按下,给远程UDP设备发送10240字节数据 */

                   case KEY_DOWN_K2: 

                        /* 用于设置发送次数,每次1024字节 */                  

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */                         

                            bsp_DelayMS();

                       }while(iCount > );

                       break;

                   /* K3键按下,给远程UDP设备发送2MB数据 */

                   case KEY_DOWN_K3:

                       /* 用于设置发送次数,每次发送1024字节 */                   

                       iCount = ;

                       do

                       {

                            tcpnet_poll();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            bsp_DelayMS();

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                        break;

              }

         }

     }

}

17.10         实验例程说明(RTX)

17.10.1   STM32F407开发板实验

配套例子:

V5-1021_RL-TCPnet实验_UDP通信(RTX)

实验目的:

  1. 学习RL-TCPnet的UDP通信创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):

    #define IP1            192

    #define IP2            168

    #define IP3            1

    #define IP4            2

    #define PORT_NUM       1001

  3. 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
  4. 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
  5. 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。

    (1)printf_debug("通过IP地址可以解析出MAC\r\n");

    (2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");

  6. 按键K1按下,发送8字节的数据给电脑端UDP端口。
  7. 按键K2按下,发送10240字节的数据给电脑端UDP端口。
  8. 按键K3按下,发送2MB字节的数据给电脑端UDP端口。

实验操作:

详见本章节17.8小节。

配置向导文件设置(Net_Config.c):

详见本章节17.4小节。

调试文件设置(Net_Debug.c):

详见本章节17.5小节。

RTX配置:

RTX配置向导详情如下:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

Task Configuration

(1)Number of concurrent running tasks

允许创建6个任务,实际创建了如下5个任务:

AppTaskUserIF任务 :按键消息处理。

AppTaskLED任务    :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskTCPMain任务:RL-TCPnet测试任务。

AppTaskStart任务  :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

(2)Number of tasks with user-provided stack

创建的5个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

RTX初始化:

/*

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

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

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

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

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

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     bsp_InitDWT();     /* 初始化DWT */

     bsp_InitUart();    /* 初始化串口 */

     bsp_InitKey();    /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitLed();    /* 初始LED指示灯端口 */

}

RTX任务创建:

/*

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

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

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

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

五个RTX任务的实现:

/*

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

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

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

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */

                   case KEY_DOWN_K1:

                       printf("K1键按下,直接发送事件标志给任务AppTaskTCPMain,bit0被设置\r\n");

                       os_evt_set (KEY1_BIT0, HandleTaskTCPMain);                      

                       break;  

                   /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                   /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */

                   case KEY_DOWN_K3:

                       printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n");

                       os_evt_set (KEY3_BIT2, HandleTaskTCPMain);

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait();

     }

}

/*

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

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

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

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

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

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

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

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

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

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

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

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,这里是创建了一个UDP Socket。

#include "includes.h" 

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_NUM         1001

/* 这个是本地端口 */

#define LocalPort_NUM    1024

/*

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

*                                         变量

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

*/

extern LOCALM localm[];

uint8_t udp_soc;

uint8_t DHCP_Status = __FALSE;

uint8_t CacheARP_Status = __FALSE;

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

/*

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

*    函 数 名: DCHP_ARP_Check

*    功能说明: 检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。

*    形    参: 无

*    返 回 值: 无

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

*/

void DCHP_ARP_Check(void)

{

     if(DHCP_Status == __FALSE) 

     {

         if(mem_test(localm[NETIF_ETH].IpAdr, , ) == __FALSE)

         {

              DHCP_Status = __TRUE;

              printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");          

         }

     }

     if(CacheARP_Status == __FALSE)  

     {

         if(arp_cache_ip (Rem_IP, ARP_FIXED_IP) == __FALSE)

         {

              CacheARP_Status = __TRUE;

              printf_debug("通过IP地址可以解析出MAC\r\n");                   

         }

     }   

}

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     OS_RESULT xResult;

     const uint16_t usMaxBlockTime = ; /* 延迟周期 */

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     while ()

     {

          /* RL-TCPnet处理函数 */

         main_TcpNet();

          /* 检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC */

         DCHP_ARP_Check();

          /* 按键消息的处理 */

         if(os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)

         {

              xResult = os_evt_get ();

              switch (xResult)

              {

                   /* 接收到K1键按下消息,给远程UDP设备发送8字节数据 */

                   case KEY1_BIT0:

                       /* 用于设置发送次数 */

                       iCount = ;

                       do

                       {

                            main_TcpNet();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            os_dly_wait();  

                       }while(iCount > );

                       break;

                   /* 接收到K2键按下消息,给远程UDP设备发送10240字节数据 */

                   case KEY2_BIT1:   

                        /* 用于设置发送次数,每次1024字节 */                  

                       iCount = ;

                       do

                       {

                            main_TcpNet();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */                         

                            os_dly_wait();

                       }while(iCount > );

                       break;

                   /* 接收到K3键按下消息,给远程UDP设备发送2MB数据 */

                   case KEY3_BIT2:

                        /* 用于设置发送次数,每次发送1024字节 */                   

                       iCount = ;

                       do

                       {

                            main_TcpNet();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            os_dly_wait();

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}

17.10.2   STM32F429开发板实验

配套例子:

V6-1021_RL-TCPnet实验_UDP通信(RTX)

实验目的:

  1. 学习RL-TCPnet的UDP通信创建和数据收发。

实验内容:

  1. 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
  2. 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):

    #define IP1            192

    #define IP2            168

    #define IP3            1

    #define IP4            2

    #define PORT_NUM       1001

  3. 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
  4. 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
  5. 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。

    (1)printf_debug("通过IP地址可以解析出MAC\r\n");

    (2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");

  6. 按键K1按下,发送8字节的数据给电脑端UDP端口。
  7. 按键K2按下,发送10240字节的数据给电脑端UDP端口。
  8. 按键K3按下,发送2MB字节的数据给电脑端UDP端口。

实验操作:

详见本章节17.8小节。

配置向导文件设置(Net_Config.c):

详见本章节17.4小节。

调试文件设置(Net_Debug.c):

详见本章节17.5小节。

RTX配置:

RTX配置向导详情如下:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

Task Configuration

(1)Number of concurrent running tasks

允许创建6个任务,实际创建了如下5个任务:

AppTaskUserIF任务   :按键消息处理。

AppTaskLED任务     :LED闪烁。

AppTaskMsgPro任务 :按键检测。

AppTaskTCPMain任务:RL-TCPnet测试任务。

AppTaskStart任务  :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

(2)Number of tasks with user-provided stack

创建的5个任务都是采用自定义堆栈方式。

(3)Run in privileged mode

设置任务运行在非特权级模式。

RTX任务调试信息:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

程序设计:

任务栈大小分配:

static uint64_t AppTaskUserIFStk[1024/8];   /* 任务栈 */

static uint64_t AppTaskLEDStk[1024/8];      /* 任务栈 */

static uint64_t AppTaskMsgProStk[1024/8];  /* 任务栈 */

static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */

static uint64_t AppTaskStartStk[1024/8];     /* 任务栈 */

将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。

系统栈大小分配:

【RL-TCPnet网络教程】第17章 RL-TCPnet之UDP通信

RTX初始化:

/*

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

*    函 数 名: main

*    功能说明: 标准c程序入口。

*    形    参: 无

*    返 回 值: 无

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

*/

int main (void)

{   

     /* 初始化外设 */

     bsp_Init();

     /* 创建启动任务 */

     os_sys_init_user (AppTaskStart,              /* 任务函数 */

                       ,                         /* 任务优先级 */

                       &AppTaskStartStk,          /* 任务栈 */

                       sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */

     while();

}

硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

/*

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

*    函 数 名: bsp_Init

*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次

*    形    参:无

*    返 回 值: 无

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

*/

void bsp_Init(void)

{

     /*

         由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。

         启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。

         系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件

     */

     /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/

     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

     SystemCoreClockUpdate();    /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */

     bsp_InitDWT();      /* 初始化DWT */

     bsp_InitUart();     /* 初始化串口 */

     bsp_InitKey();     /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */

     bsp_InitExtIO();    /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */

     bsp_InitLed();      /* 初始LED指示灯端口 */

}

RTX任务创建:

/*

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

*    函 数 名: AppTaskCreate

*    功能说明: 创建应用任务

*    形    参: 无

*    返 回 值: 无

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

*/

static void AppTaskCreate (void)

{

     HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskUserIFStk,         /* 任务栈 */

                                           sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */

     HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */

                                        ,                       /* 任务优先级 */

                                        &AppTaskLEDStk,          /* 任务栈 */

                                        sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */

     HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskMsgProStk,         /* 任务栈 */

                                           sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */

    HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,             /* 任务函数 */

                                           ,                         /* 任务优先级 */

                                           &AppTaskTCPMainStk,         /* 任务栈 */

                                           sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */

}

五个RTX任务的实现:

/*

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

*    函 数 名: AppTaskUserIF

*    功能说明: 按键消息处理     

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 1  (数值越小优先级越低,这个跟uCOS相反)

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

*/

__task void AppTaskUserIF(void)

{

     uint8_t ucKeyCode;

    while()

    {

         ucKeyCode = bsp_GetKey();

         if (ucKeyCode != KEY_NONE)

         {

              switch (ucKeyCode)

              {

                   /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */

                   case KEY_DOWN_K1:

                       printf("K1键按下,直接发送事件标志给任务AppTaskTCPMain,bit0被设置\r\n");

                       os_evt_set (KEY1_BIT0, HandleTaskTCPMain);                      

                       break;  

                   /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */

                   case KEY_DOWN_K2:

                       printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n");

                       os_evt_set (KEY2_BIT1, HandleTaskTCPMain);

                       break;

                   /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */

                   case KEY_DOWN_K3:

                       printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n");

                       os_evt_set (KEY3_BIT2, HandleTaskTCPMain);

                       break;

                   /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

         os_dly_wait();

     }

}

/*

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

*    函 数 名: AppTaskLED

*    功能说明: LED闪烁。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 2 

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

*/

__task void AppTaskLED(void)

{

     const uint16_t usFrequency = ; /* 延迟周期 */

     /* 设置延迟周期 */

     os_itv_set(usFrequency);

    while()

    {

         bsp_LedToggle();

         /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/

         os_itv_wait();

    }

}

/*

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

*    函 数 名: AppTaskMsgPro

*    功能说明: 按键检测

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 3 

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

*/

__task void AppTaskMsgPro(void)

{

    while()

    {

         bsp_KeyScan();

         os_dly_wait();

    }

}

/*

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

*    函 数 名: AppTaskTCPMain

*    功能说明: RL-TCPnet测试任务

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 4 

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

*/

__task void AppTaskTCPMain(void)

{

     while ()

     {

         TCPnetTest();

     }

}

/*

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

*    函 数 名: AppTaskStart

*    功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。

*    形    参: 无

*    返 回 值: 无

*   优 先 级: 5 

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

*/

__task void AppTaskStart(void)

{

     /* 初始化RL-TCPnet */

     init_TcpNet ();

     /* 创建任务 */

     AppTaskCreate();

     os_itv_set ();

    while()

    {

         os_itv_wait ();

         /* RL-TCPnet时间基准更新函数 */

         timer_tick ();

    }

}

RL-TCPnet功能测试

这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,这里是创建了一个UDP Socket。

#include "includes.h" 

/*

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

*                                      用于本文件的调试

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

*/

#if 1

     #define printf_debug printf

#else

     #define printf_debug(...)

#endif

/*

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

*                              宏定义,远程服务器的IP和端口

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

*/

/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */

#define IP1            192

#define IP2            168

#define IP3            1

#define IP4            2

#define PORT_NUM         1001

/* 这个是本地端口 */

#define LocalPort_NUM    1024

/*

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

*                                         变量

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

*/

extern LOCALM localm[];

uint8_t udp_soc;

uint8_t DHCP_Status = __FALSE;

uint8_t CacheARP_Status = __FALSE;

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

/*

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

*    函 数 名: DCHP_ARP_Check

*    功能说明: 检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。

*    形    参: 无

*    返 回 值: 无

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

*/

void DCHP_ARP_Check(void)

{

     if(DHCP_Status == __FALSE) 

     {

         if(mem_test(localm[NETIF_ETH].IpAdr, , ) == __FALSE)

         {

              DHCP_Status = __TRUE;

              printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");          

         }

     }

     if(CacheARP_Status == __FALSE)  

     {

         if(arp_cache_ip (Rem_IP, ARP_FIXED_IP) == __FALSE)

         {

              CacheARP_Status = __TRUE;

              printf_debug("通过IP地址可以解析出MAC\r\n");                   

         }

     }   

}

/*

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

*    函 数 名: tcp_callback

*    功能说明: TCP Socket的回调函数

*    形    参: socket   UDP Socket类型

*             remip    远程设备的IP地址

*             remport  远程设备的端口号

*             buf      远程设备发来的数据地址

*             len      远程设备发来的数据长度,单位字节

*    返 回 值: 默认返回0即可,一般用不上

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

*/

U16 udp_callback(U8 socket, U8 *remip, U16 remport, U8 *buf, U16 len)

{

     char buffer[];

     U16 i;

     /* 确保是udp_soc的回调 */

     if (socket != udp_soc)

     {

         return ();

     }

     /* 发消息的远程IP,打印IP和端口号 */

     sprintf(buffer, "远程连接IP: %d.%d.%d.%d", remip[], remip[], remip[], remip[]);

     printf_debug("%s  port:%d\r\n", buffer, remport);

     /* 接收到的数据长度,单位字节 */

     printf_debug("Data length = %d\r\n", len);

     /* 打印接收到的消息 */

     for(i = ; i < len; i++)

     {

         printf_debug("buf[%d] = %d\r\n", i, buf[i]);

     }

     return ();

}

/*

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

*    函 数 名: TCPnetTest

*    功能说明: TCPnet应用

*    形    参: 无

*    返 回 值: 无

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

*/

void TCPnetTest(void)

{ 

     int32_t iCount;

     uint8_t *sendbuf;

     uint8_t res;

     OS_RESULT xResult;

     const uint16_t usMaxBlockTime = ; /* 延迟周期 */

     /* 获取一个UDP Socket  */

     udp_soc = udp_get_socket (, UDP_OPT_SEND_CS | UDP_OPT_CHK_CS, udp_callback);

     if (udp_soc != )

     {

         /* 打开UDP端口号 */

         udp_open (udp_soc, LocalPort_NUM);

     }

     while ()

     {

          /* RL-TCPnet处理函数 */

         main_TcpNet();

          /* 检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC */

         DCHP_ARP_Check();

          /* 按键消息的处理 */

         if(os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)

         {

              xResult = os_evt_get ();

              switch (xResult)

              {

                   /* 接收到K1键按下消息,给远程UDP设备发送8字节数据 */

                   case KEY1_BIT0:

                       /* 用于设置发送次数 */

                       iCount = ;

                       do

                       {

                            main_TcpNet();

                            /* 申请8字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 初始化8个字节变量 */

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                 sendbuf[] = '';

                                sendbuf[] = '';

                                 sendbuf[] = '';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            os_dly_wait();  

                       }while(iCount > );

                       break;

                   /* 接收到K2键按下消息,给远程UDP设备发送10240字节数据 */

                   case KEY2_BIT1:   

                        /* 用于设置发送次数,每次1024字节 */                  

                       iCount = ;

                       do

                       {

                            main_TcpNet();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */                         

                            os_dly_wait();

                        }while(iCount > );

                       break;

                   /* 接收到K3键按下消息,给远程UDP设备发送2MB数据 */

                   case KEY3_BIT2:

                        /* 用于设置发送次数,每次发送1024字节 */                   

                       iCount = ;

                       do

                       {

                            main_TcpNet();

                            /* 申请1024字节的空间 */

                            sendbuf = udp_get_buf ();

                            if(sendbuf != NULL)

                            {

                                 /* 将申请到的1024字节全部清零 */

                                 memset(sendbuf, , );

                                 /* 这里仅初始化了每次所发送数据包的前8个字节 */

                                 sendbuf[] = 'a';

                                 sendbuf[] = 'b';

                                 sendbuf[] = 'c';

                                 sendbuf[] = 'd';

                                 sendbuf[] = 'e';

                                 sendbuf[] = 'f';

                                 sendbuf[] = 'g';

                                 sendbuf[] = 'h';

                                 res = udp_send (udp_soc, Rem_IP, PORT_NUM, sendbuf, );  

                            /* 保证发送成功了发送次数才可以减一,这里发送成功并不保证远程设备接受成功 */

                                 if(res == __TRUE )

                                 {

                                     iCount--;

                                 }

                            }

                            /*

                               由于UDP没有重发,应答,流控制等机制,这里简单的做个延迟,

                               保证远程设备可以接受到数据

                            */

                            os_dly_wait();

                       }while(iCount > );

                       break;

                    /* 其他的键值不处理 */

                   default:                    

                       break;

              }

         }

     }

}

17.11         总结

本章节就为大家讲解这么多,希望大家多做测试,争取可以熟练掌握这些API函数的使用。