【STM32H7】第12章 RL-TCPnet V7.X之TCP客户端
最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=95243
第12章 RL-TCPnet之TCP客户端
本章节为大家讲解RL-TCPnet的TCP客户端实现,学习本章节前,务必要优先学习第10章TCP传输控制协议基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
12.1 初学者重要提示
12.2 TCP客户端API函数
12.3 系统配置说明(Net_Config.c)
12.4 TCP配置说明(Net_Config_TCP.h)
12.5 以太网配置说明(Net_Config_ETH.h)
12.6 网络调试说明(Net_Debug.c)
12.7 TCP客户端的实现方法
12.8 网络调试助手和板子的调试操作步骤
12.9 实验例程说明(RTX5)
12.10 实验例程说明(FreeRTOS)
12.11 总结
12.1 初学者重要提示
- 学习本章节前,务必保证已经学习了第10章的基础知识。
- 相比上一个章节的TCP服务器,TCP客户端的测试要稍麻烦些,例子中默认访问的TCP服务器端IP地址是192.168.1.2,端口号1001。大家测试时要根据自己电脑的实际IP地址设置app_tcpnet_lib.c文件中远程IP和端口。具体测试方法详看本章节的12.8小节。
- 本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取能够熟练使用。
12.2 TCP客户端API函数
使用如下几个函数可以实现RL-TCPnet的TCP通信:
- netTCP_Abort
- netTCP_Close
- netTCP_Connect
- netTCP_GetBuffer
- netTCP_GetLocalPort
- netTCP_GetMaxSegmentSize
- netTCP_GetPeer
- netTCP_GetSocket
- netTCP_GetState
- netTCP_GetTimer
- netTCP_Listen
- netTCP_ReleaseSocket
- netTCP_ResetReceiveWindow
- netTCP_Send
- netTCP_SendReady
- netTCP_SetOption
关于这几个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料文件:
关于这些函数注意以下两点:
- 这些函数都支持多任务调用。
- TCP接口函数通过TCP Socket做数据传输,主要用于将数据安全作为首选的场合。TCP Socket发送完数据后会等待应答,任何数据包失败都会重传。
12.2.1 函数netTCP_cb_t
函数原型:
uint32_t(* netTCP_cb_t)(int32_t socket, /* socket句柄 */
netTCP_Event event, /* 事件类型 */
const NET_ADDR *addr,/* NET_ADDR类型变量,记录IP地址,端口号*/
const uint8_t *buf, /* 接收到的数据 */
uint32_t len) /* 接收到的字节数 */
函数描述:
供TCP Socket使用的回调函数,每个TCP Socket都可以定制自己的回调函数。
函数参数:
- 第1个参数是TCP socket句柄。
- 第2个参数是事件类型,支持的事件类型如下:
- 第3个参数NET_ADDR格式的结构体变量。
typedef struct net_addr {
int16_t addr_type; /* IP地址,NET_ADDR_IP4或者 NET_ADDR_IP6 */
uint16_t port; /* 端口号 */
uint8_t addr[NET_ADDR_IP6_LEN]; /* IPv4或者IPv6 */
} NET_ADDR;
- 第4个参数是接收数据的缓冲地址。
- 第5个参数是接收到的数据个数,单位字节。
- 返回值,此函数的返回值仅适用于接收到事件netTCP_EventConnect时,当返回1时,表示接收远程客户端的连接请求,返回0时,表示拒绝远程客户端的连接请求。
使用举例:
/*
*********************************************************************************************************
* 函 数 名: tcp_cb_server
* 功能说明: TCP Socket的回调函数
* 形 参: socket 句柄
* event 事件类型
* addr NET_ADDR类型变量,记录IP地址,端口号。
* buf ptr指向的缓冲区记录着接收到的TCP数据。
* len 记录接收到的数据个数。
* 返 回 值:
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
const NET_ADDR *addr, const uint8_t *buf, uint32_t len)
{
switch (event)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case netTCP_EventConnect:
if (addr->addr_type == NET_ADDR_IP4)
{
printf_debug("远程客户端请求连接IP: %d.%d.%d.%d 端口号:%drn",
addr->addr[0],
addr->addr[1],
addr->addr[2],
addr->addr[3],
addr->port);
return (1);
}
else if (addr->addr_type == NET_ADDR_IP6)
{
return (1);
}
return(0);
/* Socket远程连接已经建立 */
case netTCP_EventEstablished:
printf_debug("Socket is connected to remote peerrn");
break;
/* 连接断开 */
case netTCP_EventClosed:
printf_debug("Connection has been closedrn");
break;
/* 连接终止 */
case netTCP_EventAborted:
break;
/* 发送的数据收到远程设备应答 */
case netTCP_EventACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case netTCP_EventData:
printf_debug("Data length = %drn", len);
printf ("%.*srn",len, buf);
break;
}
return (0);
}
12.2.2 函数netTCP_GetSocket
函数原型:
int32_t netTCP_GetSocket(netTCP_cb_t cb_func)
函数描述:
函数netTCP_GetSocket用于获取一个TCP Socket,并设置相关状态变量到默认状态。
- 第1个参数是netTCP_cb_t类型的回调函数。
- 返回值
- 如果大于等于0,表示成功获得TCP Socket。
- 如果小于0,表示申请失败。申请失败又分两种情况,netInvalidParameter表示回调函数无效,netError表示已经没有可用的Socket。
注意事项:
- 调用TCP Socket任何其它函数前,务必要优先调用此函数。
- 如果用于服务器模式,要调用监听函数netTCP_Listen进行设置。
使用举例:
/*
*********************************************************************************************************
* 函 数 名: tcp_cb_server
* 功能说明: TCP Socket的回调函数
* 形 参: socket 句柄
* event 事件类型
* addr NET_ADDR类型变量,记录IP地址,端口号。
* buf ptr指向的缓冲区记录着接收到的TCP数据。
* len 记录接收到的数据个数。
* 返 回 值:
*********************************************************************************************************
*/
uint32_t tcp_cb_server (int32_t socket, netTCP_Event event,
const NET_ADDR *addr, const uint8_t *buf, uint32_t len)
{
switch (event)
{
case netTCP_EventConnect:
return(0);
/* Socket远程连接已经建立 */
case netTCP_EventEstablished:
break;
/* 连接断开 */
case netTCP_EventClosed:
break;
/* 连接终止 */
case netTCP_EventAborted:
break;
/* 发送的数据收到远程设备应答 */
case netTCP_EventACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case netTCP_EventData:
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: TCPnetTest
* 功能说明: TCPnet应用
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void TCPnetTest(void)
{
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
/* 创建成功 */
}
}
12.2.3 函数netTCP_Listen
函数原型:
netStatus netTCP_Listen ( int32_t socket, /* TCP socket 句柄 */
uint16_t port ) /* 监听的端口号 */
函数描述:
函数netTCP_Listen用于设置TCP服务器的监听端口。
函数参数:
- 第1个参数是监听的TCP Socket句柄。
- 第2个参数是监听端口号。
- 返回值,返回netOK表示监听成功,netInvalidParameter表示参数无效,netWrongState表示状态错误(Socket not closed)。
注意事项:
- 端口号不允许设置为0,因为这个是系统保留的。
- RL-TCPnet服务器类应用,比如Telnet Server,HTTP Server,务必要打开一个TCP Socket用于监听。
使用举例:
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
netTCP_Listen (tcp_sock, 2000);
}
12.2.4 函数netTCP_SendReady
函数原型:
bool netTCP_SendReady(int32_t socket)
函数描述:
函数netTCP_SendReady用于检测是否可以发送数据。此函数通过检测TCP连接是否建立以及上次发送的数据是否接收到远程机器的应答来判断是否可以发送数据。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,可以发送数据,返回true;不可以发送数据,返回false。
使用举例:
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
if(netTCP_SendReady(tcp_sock) == true )
{
}
12.2.5 函数netTCP_GetMaxSegmentSize
函数原型:
uint32_t netTCP_GetMaxSegmentSize(int32_t socket)
函数描述:
函数netTCP_GetMaxSegmentSize用于获得当前可以发送的最大报文长度(MSS,Maximum Segment Size)。在配置向导中,默认配置的MSS是1440字节,然而在实际建立连接后,此值会被动态调整,但一定是小于等于1440字节的。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回本次可以发送的最大报文长度,单位字节。
注意事项:
- 这个函数只能在调用了函数netTCP_Listen或netTCP_Connect后,才可以使用。
使用举例:
int32_t tcp_sock;
uint32_t maxlen;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
}
12.2.6 函数netTCP_GetBuffer
函数原型:
uint8_t * netTCP_GetBuffer(uint32_t size )
函数描述:
函数netTCP_GetBuffer用于获取TCP发送缓冲区,用户将要发送的数据存到这个缓冲区中,然后通过函数netTCP_Send发送。发送完毕后要等待远程主机的应答,收到应答后,会在函数netTCP_Send中释放申请的发送缓冲区。
函数参数:
- 第1个参数是要申请的缓冲区大小。
- 返回值,返回获取的缓冲区地址。
注意事项:
- 每次发送都需要调用此函数获取发送缓冲区地址。
- 申请的发送缓冲区大小不可超过最大报文长度(MSS,Maximum Segment Size),即1440字节。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小。
使用举例:
int32_t tcp_sock;
uint32_t maxlen;
uint8_t *sendbuf;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
sendbuf = netTCP_GetBuffer (maxlen);
}
12.2.7 函数netTCP_Send
函数原型:
netStatus netTCP_Send ( int32_t socket, /* TCP socket 句柄 */
uint8_t * buf, /* 数据缓冲区地址 */
uint32_t len ) /* 要发送的数据个数,单位字节 */
函数描述:
函数netTCP_Send用于数据包发送。
函数参数:
- 第1个参数是TCP Socket句柄。
- 第2个参数是函数netTCP_GetBuffer获取的缓冲区地址。
- 第3个参数是发送数据个数,单位字节。
- 返回值:
- netOK: 数据发送成功。
- netInvalidParameter: 参数无效或者参数不支持。
- netWrongState: 状态错误,Socket未连接或者关闭中。
- netBusy: 前面发送的数据还没有收到应答。
- netError: 数据发送失败。
注意事项:
- 不管函数netTCP_Send发送成功还是失败,都会释放通过函数netTCP_GetBuffer获取的缓冲区。
- 以下两种情况不可使用函数netTCP_Send发送数据包: (1) TCP连接还未建立。 (2) 发送给远程机器的数据包还未收到应答。
- 调用函数netTCP_Send前务必要调用函数netTCP_GetBuffer获得缓冲区。
- 申请的发送缓冲区大小不可超过最大报文长度(MSS,Maximum Segment Size),即1440字节。
- netTCP_Send不会发送长度为0的数据包,如果用户设置为0,可以用来释放缓冲区。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小。
- 不可以在TCP Socket的回调函数里面调用netTCP_Send。
使用举例:
int32_t tcp_sock;
uint32_t maxlen;
uint8_t *sendbuf;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
sendbuf = netTCP_GetBuffer (maxlen);
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
12.2.8 函数netTCP_GetState
函数原型:
netTCP_State netTCP_GetState(int32_t socket)
函数描述:
函数netTCP_GetState用于获取TCP Socket的当前状态。用户应用程序可以通过此函数监控TCP Socket的连接、断开等状态。最有用的状态值是netTCP_StateCLOSED, netTCP_StateLISTEN和netTCP_StateESTABLISHED。
- 第1个参数是TCP Socket句柄。
- 返回值,返回以下几种状态值:
使用举例:
int32_t tcp_sock;
uint32_t maxlen;
uint8_t *sendbuf;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
sendbuf = netTCP_GetBuffer (maxlen);
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}
12.2.9 函数netTCP_Abort
函数原型:
netStatus netTCP_Abort(int32_t socket)
函数描述:
用于立即终止TCP通信。此函数通过发送带RESET标志的TCP帧给远程设备来关闭连接。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回以下几种状态值:
- netOK: 终止成功
- netInvalidParameter: 参数错误
- netWrongState: 状态错误。
注意事项:
- 当远程客户端终止了连接,TCP Socket才会调用监听回调函数。如果是自己调用的终止连接,那么不会调用回调函数。
使用举例:
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
/* 终止socket */
netTCP_Abort(tcp_sock);
12.2.10 函数netTCP_Close
函数原型:
netStatus netTCP_Close( int32_t socket)
函数描述:
用于关闭TCP通信,完成关闭需要一点时间。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回以下几种状态值:
- netOK: 关闭成功
- netInvalidParameter: 参数错误
- netWrongState: 状态错误。
注意事项:
- 当远程客户端关闭了连接,TCP Socket才会调用监听回调函数。如果是自己调用的关闭连接,那么不会调用回调函数。
- 如果调用了监听函数netTCP_Listen,那么首次调用函数netTCP_Close并不会关闭连接,只会关闭当前处于连接状态的TCP,关闭后依然可以监听新的连接。要真正关闭,需要用户再次调用netTCP_Close。
- 调用了函数netTCP_Close后,还没有释放TCP Socket占用的空间,需要大家调用函数netTCP_ReleaseSocket来释放。
使用举例:
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_server);
if (tcp_sock > 0)
{
res = netTCP_Listen (tcp_sock, PORT_NUM);
}
/* 关闭socket */
netTCP_Close(tcp_sock);
12.2.11 函数netTCP_GetPeer
函数原型:
netStatus netTCP_GetPeer(int32_t socket,
NET_ADDR * addr,
uint32_t addr_len )
函数描述:
用于获取远程客户端的IP和端口号。
函数参数:
- 第1个参数是TCP Socket句柄。
- 第2个参数用于存储获取的IP和端口号。
- 第3个参数是用于填写第2个参数的结构体大小,单位字节。
- 返回值,返回以下几种状态值:
- netOK: 获取成功
- netInvalidParameter: 参数错误
- netWrongState: 状态错误。
使用举例:
NET_ADDR peer;
char ip_ascii[40];
netTCP_GetPeer (tcp_sock, &peer, sizeof(peer));
netIP_ntoa (peer.addr_type, peer.addr, ip_ascii, sizeof (ip_ascii));
printf ("Peer address: %sn", ip_ascii);
printf ("Peer port: %dn", peer.port);
12.2.12 函数netTCP_GetTimer
函数原型:
uint32_t netTCP_GetTimer(int32_t socket)
函数描述:
用于获取TCP连接的溢出时间或者当前的保活值(keep alive),如果溢出时间到了,协议栈会关闭连接或者发送一个keep-alive值。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回溢出时间或者keep-alive值,单位秒,如果返回0表示参数错了或者无效状态。
使用举例:
int32_t tcp_sock;
printf ("Socket %d will close in %d secondsn", socket, netTCP_GetTimer (socket));
12.2.13 函数netTCP_ReleaseSocket
函数原型:
netStatus netTCP_ReleaseSocket(int32_t socket)
函数描述:
用于释放创建TCP Socket时申请的内容空间。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回溢出时间或者keep-alive值,单位秒,如果返回0表示参数错了或者无效状态。
- 返回值,返回以下几种状态值:
- netOK: 是否TCP Socket成功。
- netInvalidParameter: 参数错误。
- netWrongState: 状态错误。
注意事项:
- 如果不再使用TCP Socket了,务必记得调用此函数释放TCP Socket占用的空间。
- 释放后,还在再次申请使用。
使用举例:
int32_t tcp_sock;
/*关闭Socket */
netTCP_Close (tcp_sock);
/*释放 Socket */
netTCP_ReleaseSocket (tcp_sock);
12.2.14 函数netTCP_ResetReceiveWindow
函数原型:
netStatus netTCP_ResetReceiveWindow(int32_t socket)
函数描述:
用于复位TCP接收窗口大小到默认值。默认值是由Net_Config_TCP.h文件里面TCP_RECEIVE_WIN_SIZE定义的。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回溢出时间或者keep-alive值,单位秒,如果返回0表示参数错了或者无效状态。
- 返回值,返回以下几种状态值:
- netOK: 复位接收窗口成功。
- netInvalidParameter: 参数错误或者TCP流控制没有使能。
- netWrongState: 状态错误,Socket连接还没有建立。
注意事项:
- 此函数只能用在使能了TCP流控制的Socket上。
- 通过函数netTCP_SetOption的netTCP_OptionFlowControl参数来使能流控制,这样以来,接收的时候就可以使用滑动窗口协议了。
- 在流控制模式下,每个接收到一次数据包,都将调整接收窗口大小,即减去接收到的数据包字节数, 直到窗口大小变得很小或为0,此时远程主机停止发送数据,并等待窗口更新。处理完接收到的数据后,我们可以调用netTCP_ResetReceiveWindow函数来重新打开接收窗口,继续接收数据。
- 根据用户调用此函数的上下文,调用此函数有如下几种效果
- 如果TCP Socket没有处于netTCP_StateESTABLISHED状态或者没有使能参数netTCP_OptionFlowControl,调用此函数没有任何效果。
- 如果程序其它地方调用了此函数,会复位窗口大小,并发送一个Window Update包。
- 如果在TCP Socket的回调函数里面调用此函数,当回调函数返回时,窗口大小会在TCP生成的确认数据包中更改。
使用举例:
uint8_t uart_buf[TCP_RECEIVE_WIN_SIZE];
uint32_t tcp_cb_func (int32_t socket, netTCP_Event event,
const NET_ADDR *addr, const uint8_t *buf, uint32_t len) {
switch (event) {
case netTCP_EventConnect:
return (1);
case netTCP_EventData:
/* 接收到的数据放到缓冲里面 */
memcpy (&uart_buf[head], buf, len);
head += len;
break;
}
return (0);
}
int main (void) {
int32_t tcp_sock;
uint32_t head, tail;
/* 内核初始化 */
netInitialize ();
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
/* 开始监听 */
netTCP_Listen (tcp_sock, 8080);
}
/* 以太网转串口 */
head = 0;
tail = 0;
while (1) {
if (uart_busy () || head == tail) {
/* 串口忙,或者缓冲空 */
continue;
}
/* 向串口发送数据 */
send_uart (uart_buf[tail++]);
if (tail == head) {
/* 缓冲空 */
tail = 0;
head = 0;
netTCP_ResetReceiveWindow (tcp_sock);
}
}
}
12.2.15 函数netTCP_GetLocalPort
函数原型:
uint16_t netTCP_GetLocalPort(int32_t socket)
函数描述:
用于获取TCP Socket的端口号。如果用户在使用netTCP_Connect时,未指定端口,将使用系统自动分配的,可以使用此函数获取。
函数参数:
- 第1个参数是TCP Socket句柄。
- 返回值,返回0表示无效状态或者无效参数,返回其它表示成功获取的端口号
使用举例:
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
netTCP_Connect (tcp_sock, addr, 0);
printf ("Local port is %dn", netTCP_GetLocalPort (tcp_sock));
}
12.2.16 函数netTCP_SetOption
函数原型:
netStatus netTCP_SetOption(int32_t socket,
netTCP_Option option,
uint32_t val )
函数描述:
用于TCP Socket的一些选项配置。
函数参数:
- 第1个参数是TCP Socket句柄。
- 第2个参数是配置选项,当前支持的选项如下:
- 第3个参数对于上面列表的第3列,前两个选择默认取0即可,一般不用。
- 返回值,返回以下几种状态值:
- netOK: 设置TCP Socket成功。
- netInvalidParameter: 参数错误。
- netWrongState: 状态错误。
使用举例:
int32_t tcp_sock;
tcp_sock = netTCP_GetSocket (tcp_cb_func);
if (tcp_sock >= 0) {
/* 设置30秒内,如果没有数据通信,将断开连接 */
netTCP_SetOption (tcp_sock, netTCP_OptionTimeout, 30);
/* 使能Keep Alive */
netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
}
12.2.17 函数netTCP_Connect
函数原型:
netStatus netTCP_Connect(int32_t socket,
const NET_ADDR * addr,
uint16_t local_port )
函数描述:
用于连接远程服务器。
函数参数:
- 第1个参数是TCP Socket句柄。
- 第2个参数NET_ADDR类型结构体变量,用于设置要连接的远程服务器IP地址和端口号。
- 第3个参数用于设置本地端口号,如果设置为0的话,系统将自动分配一个端口号。自动分配的端口号,可以通过函数netTCP_GetLocalPort获取。
- 返回值,返回以下几种状态值:
- netOK: 设置TCP Socket成功。
- netInvalidParameter: 参数错误。
- netWrongState: 状态错误。
使用举例:
NET_ADDR4 addr = { NET_ADDR_IP4, 2000, 192, 168, 0, 1 };
netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, 0);
for (;;) {
switch (netTCP_GetState (tcp_sock)) {
case netTCP_StateUNUSED:
case netTCP_StateCLOSED:
/* 连接失败 */
return;
case netTCP_StateESTABLISHED:
/* 连接成功 */
break;
}
osDelay (10);
}
12.3 系统配置说明(Net_Config.c)
RL-TCPnet的系统配置工作是通过文件Net_Config.c实现。在MDK工程中打开文件Net_Config.c,可以看到下图所示的工程配置向导:
Network System Settings
- Local Host Name
局域网域名。
这里起名为armfly,使用局域网域名限制为15个字符。
- Memory Pool size
参数范围1536-262144字节。
内存池大小配置,单位字节。
- Start System Services
开启系统服务。如果使能了此选项(打上对勾表示使能),系统将自动开启系统服务,比如HTTP, FTP, TFTP server等。如果没有使能,需要用户调用专门的API使能。
- OS Resource Settings
- Core Thread Stack Size
RL-TCPnet内核任务需要的栈大小,单位字节,范围512到65535。
- NET_THREAD_PRIORITY
RL-TCPnet内核任务的优先级。
这个选择在配置向导里面没有展示,需要大家点击上图左下角的Text Editor按钮查看宏定义修改。
12.4 TCP配置说明(Net_Config_TCP.h)
TCP配置文件:
TCP Sockets
- Number of TCP Sockets
范围1-20。
用于配置可创建的TCP Sockets数量。
- Number of Retries
范围0-20。
用于配置重试次数,TCP数据传输时,如果在设置的重试时间内得不到应答,算一次重试失败,这里就是配置的最大重试次数。
- Retry Timeout in seconds
范围1-10,单位秒。
重试时间。如果发送的数据在时间内得不到应答,将重新发送数据。
- Default Connect Timeout in seconds
范围1-600,单位秒。
用于配置默认的保持连接时间,即我们常说的Keep Alive时间,如果时间到了将断开连接。常用于HTTP Server,Telnet Server等。
- Maximum Segment Size
范围536-1440,单位字节。
MSS定义了TCP数据包能够传输的最大数据分段。
- Receive Window Size
范围536-65535,单位字节。
TCP接收窗口大小。
12.5 以太网配置说明(Net_Config_ETH_0.h)
以太网涉及到的配置选项比较多:
Ethernet Network Interface 0
Connect to hardware via Driver_ETH#
用于指定驱动号,这个一般不需要用户去设置,比如RTE创建的文件名是Net_Config_ETH_0.h,就会自动将此参数设置为0。
VLAN
虚拟局域网。
- ETH0_VLAN_ID
VLAN的ID号,12bit数值,范围1到4093。
MAC Address
局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。
注意,MAC地址的第1个字节的最后一个bit一定要是0。
IP Address
IP地址。
Subnet mask
子网掩码。
Default Gateway
默认网关。
Primary DNS Server
首选DNS服务器地址。
Secondary DNS Server
备选DNS服务器地址。
IP Fragmentation
使用发送IP报文的分片处理和接收IP报文的重组。
- MTU Size
范围576-1500字节。
最大的传输单元。
ARP Address Resolution
地址解析协议
- Cache Table size
ARP Cache表大小。
- Cache Timeout in seconds
Cache表超时时间。
- Number of Retries
尝试解析IP地址的次数
- Resend Timeout in seconds
每次解析请求的时间间隔。
- Send Notification on Address changes
启用此选项后,嵌入式主机将在启动时或设备IP地址已更改时发送ARP通知。
IGMP Group Management
IGMP分组管理。
- Membership Table size
此主机可以加入的分组数。
NetBIOS Name Service
NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过Net_Config.c文件配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。
Dynaminc Host Configuration
即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。
- Vendor Class Identifier
厂商ID,如果设置了的话,会将其加到DHCP的请求消息中,用于识别网络设备的不同厂商。
- Bootfile Name
从DHCP 服务器获取的引导文件名。
- NTP Servers
从DCHP服务器获得NTP服务器列表。
12.6 网络调试说明(Net_Debug.c)
RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到如下图所示的工程配置向导:
Print Time Stamp
勾选了此选项的话,打印消息时,前面会附带时间信息。
其它所有的选项
默认情况下,所有的调试选项都是关闭的,每个选项有三个调试级别可选择,这里我们以Memory Management为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。
Off:表示关闭此选项的调试功能。
Errors only:表示仅在此选项出错时,将其错误打印出来。
Full debug:表示此选项的全功能调试。
具体测试,我们这里就不做了,大家可以按照第9章讲解的调试方法进行测试。
12.7 TCP客户端的实现方法
有了本章前面小节的配置后,剩下的问题就是TCP客户端的创建和TCP客户端数据收发的实现。
12.7.1 创建TCP客户端
TCP客户端的创建比较简单,调用函数netTCP_GetSocket即可,此函数的使用和注意事项在本章的12.2.2小节有讲解:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 0
#define IP4 100
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
NET_ADDR4 addr = { NET_ADDR_IP4, PORT_NUM, IP1,IP2,IP3,IP4};
int32_t tcp_sock;
/*
*********************************************************************************************************
* 函 数 名: TCPnetTest
* 功能说明: TCPnet应用
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void TCPnetTest(void)
{
int32_t iCount;
uint8_t *sendbuf;
uint32_t maxlen;
netStatus res;
const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
uint32_t EvtFlag;
tcp_sock = netTCP_GetSocket (tcp_cb_client);
if (tcp_sock > 0)
{
/* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
}
while (1)
{
EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
/* 按键消息的处理 */
switch (EvtFlag)
{
/* 省略 */
/* 接收到摇杆OK键按下,连接远程服务器 */
case KEY4_BIT3:
if (tcp_sock > 0)
{
if(netTCP_GetState(tcp_sock) != netTCP_StateESTABLISHED)
{
res = netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, LocalPort_NUM);
printf_debug("%srn", ReVal_Table[res]);
}
}
break;
/* 其他的键值不处理 */
default:
break;
}
}
}
12.7.2 TCP数据发送
TCP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在本章节的12.2小节有讲解。下面的代码中对数据发送专门做了处理,支持任意字节大小的数据发送,仅需修改计数变量iCount的初始值即可,初始值是多少,就是发送多少字节。下面的代码是测试发送8字节,1024字节和5MB:
1. /*
2. ******************************************************************************************************
3. * 函 数 名: TCPnetTest
4. * 功能说明: TCPnet应用
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
9. void TCPnetTest(void)
10. {
11. int32_t iCount;
12. uint8_t *sendbuf;
13. uint32_t maxlen;
14. netStatus res;
15. const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
16. uint32_t EvtFlag;
17.
18. tcp_sock = netTCP_GetSocket (tcp_cb_client);
19.
20. if (tcp_sock > 0)
21. {
22. /* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
23. netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
24. }
25.
26. while (1)
27. {
28.
29. EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
30.
31. /* 按键消息的处理 */
32. switch (EvtFlag)
33. {
34. /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */
35. case KEY1_BIT0:
36. iCount = 8;
37. if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
38. {
39. do
40. {
41. if(netTCP_SendReady(tcp_sock) == true )
42. {
43. maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
44.
45. iCount -= maxlen;
46.
47. if(iCount < 0)
48. {
49. /* 这么计算没问题的 */
50. maxlen = iCount + maxlen;
51. }
52.
53. sendbuf = netTCP_GetBuffer (maxlen);
54. sendbuf[0] = '1';
55. sendbuf[1] = '2';
56. sendbuf[2] = '3';
57. sendbuf[3] = '4';
58. sendbuf[4] = '5';
59. sendbuf[5] = '6';
60. sendbuf[6] = '7';
61. sendbuf[7] = '8';
62.
63. /* 必须使用申请的内存空间 */
64. netTCP_Send (tcp_sock, sendbuf, maxlen);
65. }
66.
67. }while(iCount > 0);
68. }
69. break;
70.
71. /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */
72. case KEY2_BIT1:
73. iCount = 1024;
74. if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
75. {
76. do
77. {
78. if(netTCP_SendReady(tcp_sock) == true )
79. {
80. maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
81.
82. iCount -= maxlen;
83.
84. if(iCount < 0)
85. {
86. /* 这么计算没问题的 */
87. maxlen = iCount + maxlen;
88. }
89.
90. sendbuf = netTCP_GetBuffer (maxlen);
91. sendbuf[0] = '1';
92. sendbuf[1] = '2';
93. sendbuf[2] = '3';
94. sendbuf[3] = '4';
95. sendbuf[4] = '5';
96. sendbuf[5] = '6';
97. sendbuf[6] = '7';
98. sendbuf[7] = '8';
99.
100. /* 必须使用申请的内存空间 */
101. netTCP_Send (tcp_sock, sendbuf, maxlen);
102. }
103.
104. }while(iCount > 0);
105. }
106. break;
107.
108.
109. /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */
110. case KEY3_BIT2:
111. iCount = 5*1024*1024;
112. if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
113. {
114. do
115. {
116. if(netTCP_SendReady(tcp_sock) == true )
117. {
118. maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
119.
120. iCount -= maxlen;
121.
122. if(iCount < 0)
123. {
124. /* 这么计算没问题的 */
125. maxlen = iCount + maxlen;
126. }
127.
128. sendbuf = netTCP_GetBuffer (maxlen);
129. sendbuf[0] = '1';
130. sendbuf[1] = '2';
131. sendbuf[2] = '3';
132. sendbuf[3] = '4';
133. sendbuf[4] = '5';
134. sendbuf[5] = '6';
135. sendbuf[6] = '7';
136. sendbuf[7] = '8';
137.
138. /* 必须使用申请的内存空间 */
139. netTCP_Send (tcp_sock, sendbuf, maxlen);
140. }
141.
142. }while(iCount > 0);
143. }
144. break;
145.
146. /* 接收到摇杆OK键按下,连接远程服务器 */
147. case KEY4_BIT3:
148. if (tcp_sock > 0)
149. {
150. if(netTCP_GetState(tcp_sock) != netTCP_StateESTABLISHED)
151. {
152. res = netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, LocalPort_NUM);
153. printf_debug("%srn", ReVal_Table[res]);
154. }
155. }
156. break;
157.
158. /* 其他的键值不处理 */
159. default:
160. break;
161. }
162. }
163. }
- 第36行,通过变量iCount设置要发送的字节数,这里是发送8字节数据。
- 第39到67行,do while语句中的流程很重要:
- 发送前务必要调用函数netTCP_SendReady查看发送是否就绪。
- 函数netTCP_GetMaxSegmentSize,netTCP_GetBuffer和netTCP_Send务必要依次调用,一个都不能少。
- 第73行,通过变量iCount设置要发送的字节数,这里是发送1024字节数据。
- 第111行,通过变量iCount设置要发送的字节数,这里是发送5MB数据。
12.7.3 TCP数据接收
TCP数据接收主要是通过函数netTCP_GetSocket的回调函数实现(RTX5和FreeRTOS):
/*
*********************************************************************************************************
* 函 数 名: tcp_cb_client
* 功能说明: TCP Socket的回调函数
* 形 参: socket 句柄
* event 事件类型
* addr NET_ADDR类型变量,记录IP地址,端口号。
* buf ptr指向的缓冲区记录着接收到的TCP数据。
* len 记录接收到的数据个数。
* 返 回 值:
*********************************************************************************************************
*/
uint32_t tcp_cb_client (int32_t socket, netTCP_Event event,
const NET_ADDR *addr, const uint8_t *buf, uint32_t len)
{
switch (event)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case netTCP_EventConnect:
return (1);
/* Socket远程连接已经建立 */
case netTCP_EventEstablished:
printf_debug("Socket is connected to remote peerrn");
break;
/* 连接断开 */
case netTCP_EventClosed:
printf_debug("Connection has been closedrn");
break;
/* 连接终止 */
case netTCP_EventAborted:
break;
/* 发送的数据收到远程设备应答 */
case netTCP_EventACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case netTCP_EventData:
printf_debug("Data length = %drn", len);
printf ("%.*srn",len, buf);
break;
}
return (0);
}
- TCP服务器的数据接收主要是通过回调函数的TCP_EVT_DATA消息实现,进入消息后,指针变量buf是接收数据缓冲区首地址,变量len记录接收到的数据长度,单位字节。
12.8 网络调试助手和板子的调试操作步骤
我们这里使用下面这款调试助手,任何其它网络调试助手均可,不限制:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=1568 。
12.8.1 获取板子IP地址
(说明,对于TCP客户端实验,这一步已经不需要了,不过大家还可以进行测试)
首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。测试方法如下:
- WIN+R组合键打开“运行”窗口,输入cmd。
- 弹出的命令窗口中,输入ping armfly。
- 输入ping armfly后,回车。
获得IP地址是192.168.1.6。也许有读者会问,这里的响应时间为什么不是小于1ms的?这是因为我们还没有让板子跟电脑端的服务器建立连接,客户端一直在发SYN包尝试建立连接,所以占用了不少时间,从而导致响应时间不是小于1ms的,建立了连接后就好了。
12.8.2 获取电脑的IP地址
获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取,方法跟上面14.6.1小节中的方式一样。
- WIN+R组合键打开“运行”窗口,输入cmd。
- 弹出的命令窗口中,输入ipconfig。
- 输入ipconfig后,回车。
获得电脑的IP地址是192.168.1.2.
12.8.3 在程序中配置要访问的远程IP地址和端口
根据前面本章12.8.2小节获取的电脑端IP地址,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义,其中IP地址填前面获取的192.168.1.2,大家要根据电脑实际的IP地址填写。而端口号,我们这里随意配置一个即可,配置为1001,后面电脑端使用网络调试助手创建TCP服务器时,务必要跟这个端口号统一:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
12.8.4 网络调试助手创建TCP服务器
- 打开调试助手,点击左上角创建服务器:
- 弹出如下界面,指定IP设置为192.168.1.2,一定要跟12.8.2小节中获得的电脑IP地址一致,端口号1001,最后点击确定:
- 点击确定后的界面效果如下:
- 然后点击启动服务器:
12.8.5 创建TCP客户端连接TCP服务器
如果开发板下载了TCP客户端的程序,并且开发板已经上电,按下摇杆的OK键,可以看到客户端连接已经加入:
跟我们在程序中设置的端口号,即app_tcpnet_lib.c文件开头的宏定义:
#define LocalPort_NUM 1024是一致的。IP地址也跟本章12.8.1小节中获取的IP地址也是一致的。
连接上后,串口软件也会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):
12.8.6 TCP客户端发送数据
板子和网络调试助手建立连接后就可以互相收发数据了。对于发送数据,程序中创建了三种大小的数据发送测试。
- K1按键按下,发送了8个字符,从1到8。
- K2按键按下,发送1024字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
- K3按键按下,发送5*1024*1024 = 5242880字节,即5MB。每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
12.8.7 TCP客户端接收数据
TCP服务器接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送0到9,共10个字符:
点击发送后,可以看到串口软件打印出接收到的10个字符:
测试也是没问题的。
12.9 实验例程说明(RTX5)
配套例子:
V7-1008_RL-TCPnet V7.X实验_TCP客户端(RTX5)
实验目的:
- 学习RL-TCPnet的TCP客户端创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了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
- 创建了一个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
- 用户可以在电脑端用网络调试软件创建TCP Server连接此客户端。
- 按键K1按下,发送8字节的数据给TCP Server。
- 按键K2按下,发送1024字节的数据给TCP Server。
- 按键K3按下,发送5MB字节的数据给TCP Server。
- 摇杆OK键按下,连接远程服务器。
实验操作:
详见本章节12.8小节。
系统配置说明(Net_Config.c):
详见本章节12.3小节。
TCP配置说明(Net_Config_TCP.h):
详见本章节12.4小节。
以太网配置说明(Net_Config_ETH_0.h):
详见本章节12.5小节。
网络调试说明(Net_Debug.c):
详见本章节12.6小节。
RTX5配置:
RTX5配置向导详情如下:
System Configuration
系统配置
- Global Dynamic Memory size
全局动态内存大小,单位字节。
当前配置为20480字节。
- Kernel Tick Frequency
内核滴答时钟频率。
当前配置为1KHz
- Round-Robin Thread switching
使能时间片调度,并把时间片设置为5个,即5ms。
- OS_ISR_FIFO_QUEUE
中断服务程序里面调用RTX5的API,需要用到这个FIFO队列,当前FIFO大小设置为16个。
- OS_ISR_FIFO_QUEUE
中断服务程序里面调用RTX
Thread Configuration
任务配置。
- Default Thread Stack size
默认的任务栈大小,单位字节。
- Idle Thread Stack size
空闲任务栈大小,单位字节。
- Idle Thread Stack size
空闲任务栈大小,单位字节。
- Stack overrun checking
使能栈溢出检测。
- Stack usage watermark
栈使用率。
- Privileged mode
使能特权模式。
RTX5任务调试信息:
RL-TCPnet协议栈调试信息:
程序设计:
任务分配:
AppTaskUserIF任务 : 按键消息处理。
AppTaskLED任务 : LED闪烁。
AppTaskMsgPro任务 : TCPnet应用任务。
AppTaskEthCheck : 网线插拔状态检测。
AppTaskStart任务 : 启动任务,也是最高优先级任务,这里用作BSP驱动包处理。
netCore_Thread任务 : TCPnet内核任务。
netEth0_Thread任务 : TCPnet以太网接口任务。
osRtxTimerThread任务: 定时器任务,TCPnet时间基准。
系统栈大小分配:
RTX5初始化
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: 标准c程序入口。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int main (void)
{
/* HAL库,MPU,Cache,时钟等系统初始化 */
System_Init();
/* 内核开启前关闭HAL的时间基准 */
HAL_SuspendTick();
/* 内核初始化 */
osKernelInitialize();
/* 创建启动任务 */
ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);
/* 开启多任务 */
osKernelStart();
while(1);
}
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/*
*********************************************************************************************************
* 函 数 名: System_Init
* 功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void System_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init();
/*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
}
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */
bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
bsp_InitTimer(); /* 初始化滴答定时器 */
}
RTX任务创建:
/*
*********************************************************************************************************
* 函 数 名: AppTaskCreate
* 功能说明: 创建应用任务
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
ThreadIdTaskEthCheck = osThreadNew(AppTaskEthCheck, NULL, &ThreadEthCheck_Attr);
ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);
ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);
}
几个RTX任务的实现:
/*
*********************************************************************************************************
* 函 数 名: AppTaskUserIF
* 功能说明: 按键消息处理
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
void AppTaskUserIF(void *argument)
{
uint8_t ucKeyCode;
while(1)
{
ucKeyCode = bsp_GetKey();
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
/* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */
case KEY_DOWN_K1:
printf("K1键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit0被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY1_BIT0);
break;
/* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */
case KEY_DOWN_K2:
printf("K2键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit1被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY2_BIT1);
break;
/* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */
case KEY_DOWN_K3:
printf("K3键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit2被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY3_BIT2);
break;
/* 摇杆OK键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3 */
case JOY_DOWN_OK:
printf("OK键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit3被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY4_BIT3);
break;
/* 其他的键值不处理 */
default:
break;
}
}
osDelay(20);
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskLED
* 功能说明: LED闪烁。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal1
*********************************************************************************************************
*/
void AppTaskLED(void *argument)
{
const uint16_t usFrequency = 200; /* 延迟周期 */
uint32_t tick;
/* 获取当前时间 */
tick = osKernelGetTickCount();
while(1)
{
bsp_LedToggle(2);
/* 相对延迟 */
tick += usFrequency;
osDelayUntil(tick);
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskMsgPro
* 功能说明: TCPnet应用任务
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal2
*********************************************************************************************************
*/
void AppTaskMsgPro(void *argument)
{
while(1)
{
TCPnetTest();
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskEthCheck
* 功能说明: 检查网线插拔状态。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal3
*********************************************************************************************************
*/
void AppTaskEthCheck(void *argument)
{
/* 初始化变量 */
ThreadIdTaskMsgPro = NULL;
g_ucEthLinkStatus = 0;
/* 初始化网络 */
netInitialize();
while(1)
{
/* 网线插拔处理,方便移植,大家也可以根据需要发送任务事件标志做处理 */
switch (g_ucEthLinkStatus)
{
/* 插拔临时状态,无需处理 */
case 0:
case 1:
break;
/* 网线插入,创应用任务 */
case 2:
if(ThreadIdTaskMsgPro == NULL)
{
printf_taskdbg("网线插入,创建应用任务rn");
ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
}
break;
/* 网线拔掉,复位网络,删除应用任务 */
case 3:
printf_taskdbg("网线拔掉,复位网络,删除应用任务rn");
/* 释放所有网络资源,含TCPnet内核任务和ETH接口任务 */
netUninitialize();
printf_taskdbg("netUninitializern");
/* 删除TCPnet应用任务 */
osThreadTerminate(ThreadIdTaskMsgPro);
ThreadIdTaskMsgPro = NULL;
g_ucEthLinkStatus = 0;
/* 重新初始化 */
netInitialize();
printf_taskdbg("netInitializern");
/* 其他的键值不处理 */
default:
break;
}
osDelay(10);
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskStart
* 功能说明: 启动任务,这里用作BSP驱动包处理。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal4
*********************************************************************************************************
*/
void AppTaskStart(void *argument)
{
const uint16_t usFrequency = 1; /* 延迟周期 */
uint32_t tick;
/* 初始化外设 */
HAL_ResumeTick();
bsp_Init();
/* 创建任务 */
AppTaskCreate();
/* 获取当前时间 */
tick = osKernelGetTickCount();
while(1)
{
/* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
bsp_ProPer1ms();
/* 相对延迟 */
tick += usFrequency;
osDelayUntil(tick);
}
}
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP 客户端。
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 0
#define IP4 103
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
NET_ADDR4 addr = { NET_ADDR_IP4, PORT_NUM, IP1,IP2,IP3,IP4};
int32_t tcp_sock;
/* TCPnet API的返回值 */
static const char * ReVal_Table[]=
{
"netOK: Operation succeeded",
"netBusy: Process is busy",
"netError: Unspecified error",
"netInvalidParameter: Invalid parameter specified",
"netWrongState: Wrong state error",
"netDriverError: Driver error",
"netServerError: Server error",
"netAuthenticationFailed: User authentication failed",
"netDnsResolverError: DNS host resolver failed",
"netFileError: File not found or file r/w error",
"netTimeout: Operation timeout",
};
/*
*********************************************************************************************************
* 函 数 名: tcp_cb_client
* 功能说明: TCP Socket的回调函数
* 形 参: socket 句柄
* event 事件类型
* addr NET_ADDR类型变量,记录IP地址,端口号。
* buf ptr指向的缓冲区记录着接收到的TCP数据。
* len 记录接收到的数据个数。
* 返 回 值:
*********************************************************************************************************
*/
uint32_t tcp_cb_client (int32_t socket, netTCP_Event event,
const NET_ADDR *addr, const uint8_t *buf, uint32_t len)
{
switch (event)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case netTCP_EventConnect:
return (1);
/* Socket远程连接已经建立 */
case netTCP_EventEstablished:
printf_debug("Socket is connected to remote peerrn");
break;
/* 连接断开 */
case netTCP_EventClosed:
printf_debug("Connection has been closedrn");
break;
/* 连接终止 */
case netTCP_EventAborted:
break;
/* 发送的数据收到远程设备应答 */
case netTCP_EventACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case netTCP_EventData:
printf_debug("Data length = %drn", len);
printf ("%.*srn",len, buf);
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: TCPnetTest
* 功能说明: TCPnet应用
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void TCPnetTest(void)
{
int32_t iCount;
uint8_t *sendbuf;
uint32_t maxlen;
netStatus res;
const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
uint32_t EvtFlag;
tcp_sock = netTCP_GetSocket (tcp_cb_client);
if (tcp_sock > 0)
{
/* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
}
while (1)
{
EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
/* 按键消息的处理 */
switch (EvtFlag)
{
/* 接收到K1键按下,给远程TCP客户端发送8字节数据 */
case KEY1_BIT0:
iCount = 8;
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
do
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = netTCP_GetBuffer (maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}while(iCount > 0);
}
break;
/* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */
case KEY2_BIT1:
iCount = 1024;
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
do
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = netTCP_GetBuffer (maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}while(iCount > 0);
}
break;
/* 接收到K3键按下,给远程TCP客户端发送5MB数据 */
case KEY3_BIT2:
iCount = 5*1024*1024;
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
do
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = netTCP_GetBuffer (maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}while(iCount > 0);
}
break;
/* 接收到摇杆OK键按下,连接远程服务器 */
case KEY4_BIT3:
if (tcp_sock > 0)
{
if(netTCP_GetState(tcp_sock) != netTCP_StateESTABLISHED)
{
res = netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, LocalPort_NUM);
printf_debug("%srn", ReVal_Table[res]);
}
}
break;
/* 其他的键值不处理 */
default:
break;
}
}
}
12.10 实验例程说明(FreeRTOS)
配套例子:
V7-1009_RL-TCPnet V7.X实验_TCP客户端(FreeRTOS)
实验目的:
- 学习RL-TCPnet的TCP客户端创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了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
- 创建了一个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,本地端口被设置为1024。
- 用户可以在电脑端用网络调试软件创建TCP Server连接此客户端。
- 按键K1按下,发送8字节的数据给TCP Server。
- 按键K2按下,发送1024字节的数据给TCP Server。
- 按键K3按下,发送5MB字节的数据给TCP Server。
- 摇杆OK键按下,连接远程服务器。
实验操作:
详见本章节12.8小节。
系统配置说明(Net_Config.c):
详见本章节12.3小节。
TCP配置说明(Net_Config_TCP.h):
详见本章节12.4小节。
以太网配置说明(Net_Config_ETH_0.h):
详见本章节12.5小节。
网络调试说明(Net_Debug.c):
详见本章节12.6小节。
FreeRTOS配置:
FreeRTOS配置向导详情如下:
- Minimal stack size
最小任务栈大小,主要是空闲任务,单位字(4个字节)。
当前设置的是512字节。
- Total heap size
FreeRTOS总的堆大小,单位字节。
当前设置的30960字节。
- Kernel tick frequency
FreeRTOS的系统时钟节拍。
当前设置的是1KHz。
- Timer task stack depth
定时器任务栈大小,单位字(4字节)。
当前设置的2048字节。
- Timer task priority
定时器任务优先级。
当前设置的48。
- Timer queue length
定时器消息队列大小。
- Use time slicing
使能时间片调度,这个选项非常重要,RL-TCPnet V7.X用于FreeRTOS版要用到。
FreeRTOS任务调试信息:
RL-TCPnet协议栈调试信息:
程序设计:
任务分配:
AppTaskUserIF任务 : 按键消息处理。
AppTaskLED任务 : LED闪烁。
AppTaskMsgPro任务 : TCPnet应用任务。
AppTaskEthCheck : 网线插拔状态检测。
AppTaskStart任务 : 启动任务,也是最高优先级任务,这里用作BSP驱动包处理。
netCore_Thread任务 : TCPnet内核任务。
netEth0_Thread任务 : TCPnet以太网接口任务。
osRtxTimerThread任务: 定时器任务,TCPnet时间基准。
系统栈大小分配:
FreeRTOS初始化
/*
*********************************************************************************************************
* 函 数 名: main
* 功能说明: 标准c程序入口。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
int main (void)
{
/* HAL库,MPU,Cache,时钟等系统初始化 */
System_Init();
/* 内核开启前关闭HAL的时间基准 */
HAL_SuspendTick();
/* 内核初始化 */
osKernelInitialize();
/* 创建启动任务 */
ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);
/* 开启多任务 */
osKernelStart();
while(1);
}
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/*
*********************************************************************************************************
* 函 数 名: System_Init
* 功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
* 形 参:无
* 返 回 值: 无
*********************************************************************************************************
*/
void System_Init(void)
{
/* 配置MPU */
MPU_Config();
/* 使能L1 Cache */
CPU_CACHE_Enable();
/*
STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
- 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
- 设置NVIV优先级分组为4。
*/
HAL_Init();
/*
配置系统时钟到400MHz
- 切换使用HSE。
- 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
*/
SystemClock_Config();
/*
Event Recorder:
- 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
- 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
*/
#if Enable_EventRecorder == 1
/* 初始化EventRecorder并开启 */
EventRecorderInitialize(EventRecordAll, 1U);
EventRecorderStart();
#endif
}
/*
*********************************************************************************************************
* 函 数 名: bsp_Init
* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */
bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
bsp_InitUart(); /* 初始化串口 */
bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
bsp_InitLed(); /* 初始化LED */
bsp_InitTimer(); /* 初始化滴答定时器 */
}
FreeRTOS任务创建:
/*
*********************************************************************************************************
* 函 数 名: AppTaskCreate
* 功能说明: 创建应用任务
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
ThreadIdTaskEthCheck = osThreadNew(AppTaskEthCheck, NULL, &ThreadEthCheck_Attr);
ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);
ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);
}
几个FreeRTOS任务的实现:
/*
*********************************************************************************************************
* 函 数 名: AppTaskUserIF
* 功能说明: 按键消息处理
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
void AppTaskUserIF(void *argument)
{
uint8_t ucKeyCode;
while(1)
{
ucKeyCode = bsp_GetKey();
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
/* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */
case KEY_DOWN_K1:
printf("K1键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit0被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY1_BIT0);
break;
/* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */
case KEY_DOWN_K2:
printf("K2键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit1被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY2_BIT1);
break;
/* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */
case KEY_DOWN_K3:
printf("K3键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit2被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY3_BIT2);
break;
/* 摇杆OK键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3 */
case JOY_DOWN_OK:
printf("OK键按下,直接发送事件标志给任务ThreadIdTaskMsgPro,bit3被设置rn");
osThreadFlagsSet(ThreadIdTaskMsgPro, KEY4_BIT3);
break;
/* 其他的键值不处理 */
default:
break;
}
}
osDelay(20);
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskLED
* 功能说明: LED闪烁。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal1
*********************************************************************************************************
*/
void AppTaskLED(void *argument)
{
const uint16_t usFrequency = 200; /* 延迟周期 */
uint32_t tick;
/* 获取当前时间 */
tick = osKernelGetTickCount();
while(1)
{
bsp_LedToggle(2);
/* 相对延迟 */
tick += usFrequency;
osDelayUntil(tick);
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskMsgPro
* 功能说明: TCPnet应用任务
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal2
*********************************************************************************************************
*/
void AppTaskMsgPro(void *argument)
{
while(1)
{
TCPnetTest();
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskEthCheck
* 功能说明: 检查网线插拔状态。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal3
*********************************************************************************************************
*/
void AppTaskEthCheck(void *argument)
{
/* 初始化变量 */
ThreadIdTaskMsgPro = NULL;
g_ucEthLinkStatus = 0;
/* 初始化网络 */
netInitialize();
while(1)
{
/* 网线插拔处理,方便移植,大家也可以根据需要发送任务事件标志做处理 */
switch (g_ucEthLinkStatus)
{
/* 插拔临时状态,无需处理 */
case 0:
case 1:
break;
/* 网线插入,创应用任务 */
case 2:
if(ThreadIdTaskMsgPro == NULL)
{
printf_taskdbg("网线插入,创建应用任务rn");
ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);
}
break;
/* 网线拔掉,复位网络,删除应用任务 */
case 3:
printf_taskdbg("网线拔掉,复位网络,删除应用任务rn");
/* 释放所有网络资源,含TCPnet内核任务和ETH接口任务 */
netUninitialize();
printf_taskdbg("netUninitializern");
/* 删除TCPnet应用任务 */
osThreadTerminate(ThreadIdTaskMsgPro);
ThreadIdTaskMsgPro = NULL;
g_ucEthLinkStatus = 0;
/* 重新初始化 */
netInitialize();
printf_taskdbg("netInitializern");
/* 其他的键值不处理 */
default:
break;
}
osDelay(10);
}
}
/*
*********************************************************************************************************
* 函 数 名: AppTaskStart
* 功能说明: 启动任务,这里用作BSP驱动包处理。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: osPriorityNormal4
*********************************************************************************************************
*/
void AppTaskStart(void *argument)
{
const uint16_t usFrequency = 1; /* 延迟周期 */
uint32_t tick;
/* 初始化外设 */
HAL_ResumeTick();
bsp_Init();
/* 创建任务 */
AppTaskCreate();
/* 获取当前时间 */
tick = osKernelGetTickCount();
while(1)
{
/* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
bsp_ProPer1ms();
/* 相对延迟 */
tick += usFrequency;
osDelayUntil(tick);
}
}
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP 客户端。
/*
*********************************************************************************************************
* 函 数 名: TCPnetTest
* 功能说明: TCPnet应用
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void TCPnetTest(void)
{
int32_t iCount;
uint8_t *sendbuf;
uint32_t maxlen;
netStatus res;
const uint16_t usMaxBlockTime = 2; /* 延迟周期 */
uint32_t EvtFlag;
tcp_sock = netTCP_GetSocket (tcp_cb_client);
if (tcp_sock > 0)
{
/* 使能TCP_TYPE_KEEP_ALIVE,会一直保持连接 */
netTCP_SetOption (tcp_sock, netTCP_OptionKeepAlive, 1);
}
while (1)
{
EvtFlag = osThreadFlagsWait(0x0000000FU, osFlagsWaitAny, usMaxBlockTime);
/* 按键消息的处理 */
switch (EvtFlag)
{
/* 接收到K1键按下,给远程TCP客户端发送8字节数据 */
case KEY1_BIT0:
iCount = 8;
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
do
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = netTCP_GetBuffer (maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}while(iCount > 0);
}
break;
/* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */
case KEY2_BIT1:
iCount = 1024;
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
do
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = netTCP_GetBuffer (maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}while(iCount > 0);
}
break;
/* 接收到K3键按下,给远程TCP客户端发送5MB数据 */
case KEY3_BIT2:
iCount = 5*1024*1024;
if(netTCP_GetState(tcp_sock) == netTCP_StateESTABLISHED)
{
do
{
if(netTCP_SendReady(tcp_sock) == true )
{
maxlen = netTCP_GetMaxSegmentSize (tcp_sock);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = netTCP_GetBuffer (maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 必须使用申请的内存空间 */
netTCP_Send (tcp_sock, sendbuf, maxlen);
}
}while(iCount > 0);
}
break;
/* 接收到摇杆OK键按下,连接远程服务器 */
case KEY4_BIT3:
if (tcp_sock > 0)
{
if(netTCP_GetState(tcp_sock) != netTCP_StateESTABLISHED)
{
res = netTCP_Connect (tcp_sock, (NET_ADDR *)&addr, LocalPort_NUM);
printf_debug("%srn", ReVal_Table[res]);
}
}
break;
/* 其他的键值不处理 */
default:
break;
}
}
}
12.11 总结
本章节就为大家讲解这么多,希望大家多做测试,争取可以熟练掌握这些API函数的使用。
- 用python抓取淘宝评论
- 使用Dropwizard(2)-配置分类ConfiguredBundle
- Upgrade with the Gradle Wrapper, gradlew升级
- 使用swagger作为restful api的doc文档生成
- 面试机器学习、大数据岗位时遇到的各种问题
- 使用nginx代理跨域,使用nginx代理bing的每日一图
- Java Web基础入门
- Java中Optional使用注意事项
- 使用awk来解析dump文件 (73天)
- IntelIj IDEA运行JUnit Test OutOfMemoryError
- git pull fails “unable to resolve reference” “unable to update local ref”
- 使用dropwizard(5)--加入swagger
- 使用hint来调优sql语句(72天)
- 用R语言对城管事件数据分析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php实现多站点共用session实现单点登录的方法详解
- php实例化一个类的具体方法
- PHP MVC框架中类的自动加载机制实例分析
- smarty模板的使用方法实例分析
- 关于Yii中模型场景的一些简单介绍
- php文件包含的几种方式总结
- 一个基于Laravel5的个人博客系统:Lablog搭建教程
- php快速导入大量数据的实例方法
- Laravel 模型关联基础教程详解
- yunBT:一个基于TP3.1的多用户BT离线下载程序,支持在线播放
- 使用Chihaya搭建一个可以屏蔽迅雷的Tracker
- [jio本]Debian9一键安装各种下载工具
- php实现QQ小程序发送模板消息功能
- 微软自家沙盒 Sandbox公布
- php DES加密算法实例分析