第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 初学者重要提示
- 学习本章节前,务必保证已经学习了第16章的基础知识。
- 本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取达到熟练运用。
- 对于UDP通讯时的丢包问题在本章节的17.6小节有特别说明。
- 本章节使用“野人网络调试助手”,前面几个章节使用的网络调试助手在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 文件:
这里我们重点的说以下 4个函数,因为本章节配套的例子使用的是这4个函数:
- udp_get_socket
- udp_open
- udp_get_buf
- udp_send
关于这些函数注意以下两点:
- 这些函数都不支持重入,也就是不支持多任务调用。
- 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个参数是校验和选项,有如下两种可选。
用户可以通过或操作将发送和接收校验和都选上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。
使用这个函数要注意以下问题:
- 调用UDP Socket任何其它函数前,务必要调用此函数udp_get_socket。
- 以太网数据包受到以太网CRC的保护。
- 传输的数据包通过路由器、代理服务器、网关等,数据包是可以被修改的。
- 使用函数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。
使用这个函数要注意以下问题:
- 如果第二个参数填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的调用。
使用这个函数要注意以下问题:
- 每次发送都需要调用此函数获取发送缓冲区地址。
- 申请的发送缓冲区大小不可超过最大数据包大小UDP Maximum Packet Size,即1472字节。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小,否则会造成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。
使用这个函数要注意以下问题:
- 调用函数udp_send前务必要调用函数udp_get_buf获得缓冲区。
- 数据通信前,务必要通过函数udp_open打开。
- 同一个端口号,同一个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要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。
System Definitions
(1)Local Host Name
局域网域名。
这里起名为armfly,使用局域网域名限制为15个字符。
(2)Memory Pool size
参数范围1536-262144字节。
内存池大小配置,单位字节。另外注意一点,配置向导这里显示的单位是字节,如果看原始定义,MDK会做一个自动的4字节倍数转换,比如我们这里配置的是8192字节,那么原始定义是#define MEM_SIZE 2048,也就是8192/4 = 2048。
(3)Tick Timer interval
可取10,20,25,40,50,100,200,单位ms。
系统滴答时钟间隔,也就是网络协议栈的系统时间基准,默认情况下,取值100ms。
Ethernet Network Interface
以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP。
(1)MAC Address
局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。
(2)IP Address
IP地址。
(3)Subnet mask
子网掩码。
(4)Default Gateway
默认网关。
Ethernet Network Interface
以太网接口配置,这个配置里面还有如下两项比较重要的配置需要说明。
(1)NetBIOS Name Service
NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。
(2)Dynaminc Host Configuration
即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。
UDP Sockets
UDP Sockets配置,打上对勾就使能了此项功能
(1)Number of UDP Sockets
用于配置可创建的UDP Sockets数量,这里配置了5个,特别注意本章节前面17.3小节的说明。
范围1 – 20。
TCP Sockets
TCP Sockets配置,打上对勾就使能了此项功能
(1)Number of TCP Sockets
用于配置可创建的TCP Sockets数量。
(2)Number of Retries
范围0-20。
用于配置重试次数,TCP数据传输时,如果在设置的重试时间内得不到应答,算一次重试失败,这里就是配置的最大重试次数。
(3)Retry Timeout in seconds
范围1-10,单位秒。
重试时间。如果发送的数据在重试时间内得不到应答,将重新发送数据。
(4)Default Connect Timeout in seconds
范围1-600,单位秒。
用于配置默认的保持连接时间,即我们常说的Keep Alive时间,如果时间到了将断开连接。常用于HTTP Server,Telnet Server等。
(5)Maximum Segment Size
范围536-1460,单位字节。
MSS定义了TCP数据包能够传输的最大数据分段。
(6)Receive Window Size
范围536-65535,单位字节。
TCP接收窗口大小。
17.5 UDP调试说明(Net_Debug.c)
(重要说明,RL-TCPnet的调试是通过串口打印出来的)
RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:
Print Time Stamp
勾选了此选项的话,打印消息时,前面会附带时间信息。
其它所有的选项
默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以Memory Management Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。
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; } } } }
- 函数timer_tick用于实现网络时间基准,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms。
- 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
- 通过变量iCount设置要发送的次数,这里是发送1次。
- do while语句中的流程很重要:
(1) 函数tcpnet_poll一定要实时调用着。
(2) 函数udp_get_buf和udp_send务必要依次调用,一个都不能少。
- 一定要保证发送成功了,发送次数才可以减一,但这里发送成功并不保证远程设备接收成功。
- 由于UDP没有重发、应答、流控制等机制,这里简单的做个延迟,保证远程设备可以接收到数据。
- 通过变量iCount设置要发送的次数,这里是发送10次,每次发送1024字节。
- 将申请到的1024字节数据全部清零,因为后面的代码仅初始化了前8个字节,RL-TCPnet不负责对申请的空间清零,申请的空间依然保存着上次数据包或者其它应用时的数值。
- 通过变量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; } } } }
- 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
- 这里使用了事件标志组,溢出时间设置为了2毫秒。这样一方面保证了函数main_TcpNet的周期性执行,另一方面用来等待按键发送事件标志消息。
- 通过变量iCount设置要发送的字节数,这里是发送8字节数据。
- do while语句中的流程很重要:
(1) 函数main_TcpNet一定要实时调用着。
(2) 发送前务必要调用函数tcp_check_send查看发送是否就绪。
(3) 函数tcp_max_dsize,tcp_get_buf和tcp_send务必要依次调用,一个都不能少。
- 通过变量iCount设置要发送的字节数,这里是发送1024字节数据。
- 通过变量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。
(2)弹出的命令窗口中,输入ping armfly。
(3)输入ping armfly后,回车。
获得IP地址是192.168.1.5。
17.8.2 获取电脑的IP地址
获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取,方法跟上面17.8.1小节中的方式一样:
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)弹出的命令窗口中,输入ipconfig。
(3)输入ipconfig后,回车。
获得电脑的IP地址是192.168.1.2。
17.8.3 网络调试助手打开UDP端口
1、打开调试助手,可以自动识别出电脑的IP地址,我们这里仅需配置成UDP模式,端口号1001并且勾选“显示接收时间”功能:
2、点击“打开”按钮后,就可以使用了。“打开”按钮会变成如下状态:
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):
17.8.6 UDP发送数据
将板子上电,并且网络调试助手的UDP通信也打开后就可以相互收发数据了。对于发送数据,程序中创建了三种数据大小的数据发送测试。
(1)K1按键按下,发送了8个字符,从1到8。
(2)K2按键按下,发送了10次数据包,每次发1024字节,每个数据包的前8个字节设置了字符a到字符h,后面都未做设置。
(3)K3按键按下,发送了2048次,每次1024字节,共计发送2048*1024 = 2097152字节,即2MB。这里仅设置了每个数据包的前8个字节为字符a到字符h,后面都未做设置。
17.8.7 UDP接收数据
UDP接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送0到9,共10个字符:
点击发送后,可以看到串口软件打印出发送此消息的远程设备IP地址和端口号以及接收到的10个字符:
字符0对应的ASCII值就是48,其它字符数值依次增加。测试也是没问题的。
17.9 实验例程说明(裸机)
17.9.1 STM32F407开发板实验
配套例子:
V5-1020_RL-TCPnet实验_UDP通信(裸机)
实验目的:
- 学习RL-TCPnet的UDP通信创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
- 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
- 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。
(1)printf_debug("通过IP地址可以解析出MAC\r\n");
(2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");
- 按键K1按下,发送8字节的数据给电脑端UDP端口。
- 按键K2按下,发送10240字节的数据给电脑端UDP端口。
- 按键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通信(裸机)
实验目的:
- 学习RL-TCPnet的UDP通信创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
- 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
- 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。
(1)printf_debug("通过IP地址可以解析出MAC\r\n");
(2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");
- 按键K1按下,发送8字节的数据给电脑端UDP端口。
- 按键K2按下,发送10240字节的数据给电脑端UDP端口。
- 按键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)
实验目的:
- 学习RL-TCPnet的UDP通信创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
- 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
- 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。
(1)printf_debug("通过IP地址可以解析出MAC\r\n");
(2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");
- 按键K1按下,发送8字节的数据给电脑端UDP端口。
- 按键K2按下,发送10240字节的数据给电脑端UDP端口。
- 按键K3按下,发送2MB字节的数据给电脑端UDP端口。
实验操作:
详见本章节17.8小节。
配置向导文件设置(Net_Config.c):
详见本章节17.4小节。
调试文件设置(Net_Debug.c):
详见本章节17.5小节。
RTX配置:
RTX配置向导详情如下:
Task Configuration
(1)Number of concurrent running tasks
允许创建6个任务,实际创建了如下5个任务:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :按键检测。
AppTaskTCPMain任务:RL-TCPnet测试任务。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。
(2)Number of tasks with user-provided stack
创建的5个任务都是采用自定义堆栈方式。
(3)Run in privileged mode
设置任务运行在非特权级模式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
static uint64_t AppTaskUserIFStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskLEDStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskMsgProStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */
static uint64_t AppTaskStartStk[1024/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 创建启动任务 */ os_sys_init_user (AppTaskStart, /* 任务函数 */ , /* 任务优先级 */ &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)
实验目的:
- 学习RL-TCPnet的UDP通信创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 测试此例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要跟实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 创建了一个UDP通信,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
- 对于UDP通信,UDP Socket是不区分客户端和服务器端的,板子和电脑端的网络助手都开启UDP后,可以直接互发数据。
- 由于UDP不需要建立连接就可以收发数据,而且也没有重复、应答、流控制等保证数据可靠发送的机制,程序在创建了UDP Socket后就检测DHCP是否获取了IP地址以及UDP通信要访问的远程IP地址是否可以解析出对应的MAC。这样就保证板子已经获得了IP地址并且要访问的远程设备也存在。此时用户就可以做UDP通信了。正常情况下,板子上电后,程序会打印出对应的成功消息。
(1)printf_debug("通过IP地址可以解析出MAC\r\n");
(2)printf_debug("DHCP已经初始化成功,注意未使能DHCP也是返回成功的\r\n");
- 按键K1按下,发送8字节的数据给电脑端UDP端口。
- 按键K2按下,发送10240字节的数据给电脑端UDP端口。
- 按键K3按下,发送2MB字节的数据给电脑端UDP端口。
实验操作:
详见本章节17.8小节。
配置向导文件设置(Net_Config.c):
详见本章节17.4小节。
调试文件设置(Net_Debug.c):
详见本章节17.5小节。
RTX配置:
RTX配置向导详情如下:
Task Configuration
(1)Number of concurrent running tasks
允许创建6个任务,实际创建了如下5个任务:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :按键检测。
AppTaskTCPMain任务:RL-TCPnet测试任务。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。
(2)Number of tasks with user-provided stack
创建的5个任务都是采用自定义堆栈方式。
(3)Run in privileged mode
设置任务运行在非特权级模式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
static uint64_t AppTaskUserIFStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskLEDStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskMsgProStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */
static uint64_t AppTaskStartStk[1024/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 创建启动任务 */ os_sys_init_user (AppTaskStart, /* 任务函数 */ , /* 任务优先级 */ &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函数的使用。