第9章 RL-TCPnet网络协议栈移植(uCOS-III)
本章教程为大家讲解RL-TCPnet网络协议栈的uCOS-III操作系统移植方式,学习了第6章讲解的底层驱动接口函数之后,移植就比较容易了,主要是添加库文件、配置文件和驱动文件即可。另外,RL-TCPnet移植到uCOS-III要重新配置RL-TCPnet的接口函数,以此来支持RL-TCPent多任务运行。使用RTX无需重新配置,因为默认情况下就是采用RTX的API函数配置的。
本章教程含STM32F407开发板和STM32F429开发板的移植。
9.1 移植前准备工作说明
9.2 STM32F407移植RL-TCPnet协议栈
9.3 STM32F429移植RL-TCPnet协议栈
9.4 总结
9.1 移植前准备工作说明
1、学习本章节前,务必要优先学习第6章的底层驱动讲解。
2、RL-TCPnet只有库,没有源码。库分为两个版本,一个用于调试的版本TCPD_CM3.lib和一个正式版本TCP_CM3.lib,当前的例子统一使用调试版本。另外注意,虽然是CM3版本的,但可同时用于CM3和CM4内核的MCU,因为官方没有专门的CM4内核库。
3、测试时,请将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
而且使能了NetBIOS局域网域名,用户只需在电脑端ping armfly,就可以获得板子的IP地址。
4、如果要使用固定IP进行测试,请看第57章。
5、网口使用的是DM9161/9162(紧挨着9帧串口座的网口),而不是DM9000。
6、找一个简单的工程,最好是跑马灯之类的,越简单越好,我们就在这个简单的工程上面移植即可。
9.2 STM32F407移植RL-TCPnet协议栈
9.2.1 RL-TCPnet网络协议栈移植
首先准备好一个简单的uCOS-III工程模板,工程模板的制作就不做讲解了,这里的重点是教大家移植RL-TCPnet协议栈。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):
准备好工程模板后,就可以开始移植了。首先要做的就是将所有需要的文件放到工程模板里面。下面分4步跟大家进行说明,当然,不限制必须使用下面的方法添加源码到工程,只要将需要的文件添加到工程模板即可。
第1步:将我们uCOS-III模板中制作好的RL-ARM文件夹复制粘贴到大家准备好的工程模板中。
RL-ARM文件夹中有如下七个文件夹:
Config文件夹用于存放RTX及其中间件的配置文件。
Driver文件夹用于存放中间件的驱动文件,也就是底层移植文件。
RL-CAN文件夹用于存放CAN总线的源码文件。
RL-FlashFS文件夹用于存放文件系统RL-FlashFS的库文件。
RL-RTX文件夹用于存放RTX的源码文件。
RL-TCPnet文件夹用于存放网络协议栈RL-TCPnet的库文件。
RL-USB文件夹用于存放USB协议栈RL-USB的库文件。
也许有用户会问:我们不是仅仅需要移植RL-TCPnet的相关文件就行了吗,为什么把RTX及其所有中间件都添加进来了?这样做的目的是为了以后升级的方便,如果需要添加USB、文件系统、CAN等组件,直接添加到工程即可。
这些文件全部来自MDK4.74的安装目录,库文件位于路径:C:\Keil_v474\ARM\RV31下,而驱动和配置文件位于路径:C:\Keil_v474\ARM\RL下。
第2步:添加RL-TCPnet的库文件、配置文件和驱动文件到工程,添加完毕后的效果如下:
Net_lib.c,Net_Config.c和NET_Debug.c在RL-ARM文件夹的Config文件里面。
TCPD_CM3.lib在RL-ARM文件夹的RL-TCPnet文件里面。
ETH_STM32F4xx.c和ETH_STM32F4xx.h在RL-ARM文件夹的Driver文件里面。
第3步:添加相应的头文件路径,在原来工程模板的基础上新添加的几个路径:
第4步:也是最后一步,添加预定义宏,点击MDK的option -> c/c++选项,添加上__RTX(注意,字母RTX前面有两个下划线的),添加这个宏定义才可以使能RL-TCPnet的多任务支持。
至此,RL-TCPnet的移植工作就完成了,剩下就是系统配置和应用了。
9.2.2 RL-TCPnet配置说明(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数量。
范围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接收窗口大小。
9.2.3 RL-TCPnet调试说明(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章详细为大家讲解,移植阶段将其全部关闭即可。
9.2.4 RL-TCPnet的多任务驱动接口函数
要让RL-TCPnet支持多任务,就需要修改Net_lib.c文件。默认情况下,Net_lib.c文件是支持RTX操作系统的,现在要将其修改为支持uCOS-III,需要修改的几个地方如下:
添加uCOS-III的头文件。
#if (__RTX) #include "os.h" #endif
定义信号量和互斥信号量。
#if (BSD_ENABLE) static BSD_INFO bsd_scb[BSD_NUMSOCKS + BSD_SRVSOCKS]; #ifdef __RTX OS_MUTEX bsd_mutex; OS_SEM bsd_sem; #define BSD_INRTX __TRUE #else #define BSD_INRTX __FALSE #endif
创建互斥信号量和信号量。
/*--------------------------- init_system -----------------------------------*/ void init_system (void) { /* Initialize configured interfaces and applications. */ #if (ETH_ENABLE) eth_init_link (); #endif #if (PPP_ENABLE) ppp_init_link (); #endif #if (SLIP_ENABLE) slip_init_link (); #endif ip_init (); icmp_init (); #if (ETH_ENABLE && IGMP_ENABLE) igmp_init (); #endif #if (UDP_ENABLE) udp_init (); #endif #if (TCP_ENABLE) tcp_init (); #endif #if (BSD_ENABLE) bsd_init (); #if (BSD_GETHOSTEN) bsd_init_host (); #endif #endif #if (HTTP_ENABLE) http_init (); #endif #if (TNET_ENABLE) tnet_init (); #endif #if (TFTP_ENABLE) tftp_init (); #endif #if (TFTPC_ENABLE) tftpc_init (); #endif #if (FTP_ENABLE) ftp_init (); #endif #if (FTPC_ENABLE) ftpc_init (); #endif #if (ETH_ENABLE && NBNS_ENABLE) nbns_init (); #endif #if (ETH_ENABLE && DHCP_ENABLE) dhcp_init (); #elif (ETH_ENABLE) arp_notify (); #endif #if (DNS_ENABLE) dns_init (); #endif #if (SMTP_ENABLE) smtp_init (); #endif #if (SNMP_ENABLE) snmp_init (); #endif #if (SNTP_ENABLE) sntp_init (); #endif #if (BSD_ENABLE && __RTX) { OS_ERR err; OSMutexCreate(&bsd_mutex, "BSD Mutex", &err); OSSemCreate((OS_SEM *)&bsd_sem, (CPU_CHAR *)"BSD Sem", (OS_SEM_CTR )0, (OS_ERR *)&err); } #endif }
系统运行时,使用互斥信号量。
/*--------------------------- run_system ------------------------------------*/ void run_system (void) { /* Run configured interfaces and applications. */ #if (BSD_ENABLE && __RTX) OS_ERR err; OSMutexPend((OS_MUTEX *)&bsd_mutex, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); #endif #if (ETH_ENABLE) eth_run_link (); #endif #if (PPP_ENABLE) ppp_run_link (); #endif #if (SLIP_ENABLE) slip_run_link (); #endif ip_run_local (); icmp_run_engine (); #if (ETH_ENABLE && IGMP_ENABLE) igmp_run_host (); #endif #if (TCP_ENABLE) tcp_poll_sockets (); #endif #if (BSD_ENABLE) bsd_poll_sockets (); #endif #if (HTTP_ENABLE) http_run_server (); #endif #if (TNET_ENABLE) tnet_run_server (); #endif #if (TFTP_ENABLE) tftp_run_server (); #endif #if (TFTPC_ENABLE) tftpc_run_client (); #endif #if (FTP_ENABLE) ftp_run_server (); #endif #if (FTPC_ENABLE) ftpc_run_client (); #endif #if (ETH_ENABLE && DHCP_ENABLE) dhcp_run_client (); #endif #if (DNS_ENABLE) dns_run_client (); #endif #if (SMTP_ENABLE) smtp_run_client (); #endif #if (SNMP_ENABLE) snmp_run_agent (); #endif #if (SNTP_ENABLE) sntp_run_client (); #endif #if (BSD_ENABLE && __RTX) OSMutexPost((OS_MUTEX *)&bsd_mutex, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); #endif }
使能BSD Socket的话,Socket挂起和恢复的实现。
/*--------------------------- bsd_suspend/resume ----------------------------*/ #if (BSD_ENABLE && __RTX) __used void bsd_suspend (U8 *tsk_id) { OS_ERR err; /* Suspend a socket owner task. */ OSMutexPost((OS_MUTEX *)&bsd_mutex, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); OSSemPend((OS_SEM *)&bsd_sem, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); OSMutexPend((OS_MUTEX *)&bsd_mutex, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); } __used void bsd_resume (U8 tsk_id) { /* Resume a task waiting for a socket event. */ OS_ERR err; OSSemPost((OS_SEM *)&bsd_sem, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); } #endif
使能BSD Socket的话,锁机制的实现。
/*--------------------------- bsd_lock/unlock -------------------------------*/ #if (BSD_ENABLE && __RTX) __used void bsd_lock (void) { /* Acquire mutex - Lock TCPnet functions. */ OS_ERR err; OSMutexPend((OS_MUTEX *)&bsd_mutex, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); } __used void bsd_unlock (void) { /* Release mutex - Unlock TCPnet functions. */ OS_ERR err; OSMutexPost((OS_MUTEX *)&bsd_mutex, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); } #endif
9.2.5 RL-TCPnet应用实例
为了验证移植的RL-TCPnet是否可以使用,需要添加测试代码。下面是编写的测试代码,配套的测试例子完整版是:V5-1002_RL-TCPnet实验_工程移植模板(uCOS-III)。
uCOS-III操作系统创建的任务
经过上面的移植和配置之后,在 main.c 文件中添加如下代码,代码中创建了5个用户任务:
App Task Start任务 :启动任务,实现RL-TCPnet的时间基准更新。
App Task TCPnet任务 :RL-TCPnet测试任务。
App Task MspPro任务:消息处理,这里用作按键检测。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :LED闪烁。
具体代码如下:
#include "includes.h" /* ********************************************************************************************************* * 静态全局变量 ********************************************************************************************************* */ static OS_TCB AppTaskStartTCB; static CPU_STK AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE]; static OS_TCB AppTaskTCPnetTCB; static CPU_STK AppTaskTCPnetStk[APP_CFG_TASK_TCPnet_STK_SIZE]; static OS_TCB AppTaskMsgProTCB; static CPU_STK AppTaskMsgProStk[APP_CFG_TASK_MsgPro_STK_SIZE]; static OS_TCB AppTaskCOMTCB; static CPU_STK AppTaskCOMStk[APP_CFG_TASK_COM_STK_SIZE]; static OS_TCB AppTaskUserIFTCB; static CPU_STK AppTaskUserIFStk[APP_CFG_TASK_USER_IF_STK_SIZE]; /* ********************************************************************************************************* * 函数声明 ********************************************************************************************************* */ static void AppTaskStart (void *p_arg); static void AppTaskTCPnet (void *p_arg); static void AppTaskMsgPro (void *p_arg); static void AppTaskUserIF (void *p_arg); static void AppTaskCOM (void *p_arg); static void AppTaskCreate (void); static void DispTaskInfo (void); static void AppObjCreate (void); static void App_Printf (CPU_CHAR *format, ...); /* ******************************************************************************************************* * 变量 ******************************************************************************************************* */ static OS_SEM AppPrintfSemp; /* 用于printf互斥 */ OS_FLAG_GRP FLAG_TCPnet; /* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main(void) { OS_ERR err; /* 初始化uC/OS-III 内核 */ OSInit(&err); /* 创建一个启动任务(也就是主任务)。启动任务会创建所有的应用程序任务 */ OSTaskCreate((OS_TCB *)&AppTaskStartTCB, /* 任务控制块地址 */ (CPU_CHAR *)"App Task Start", /* 任务名 */ (OS_TASK_PTR )AppTaskStart, /* 启动任务函数地址 */ (void *)0, /* 传递给任务的参数 */ (OS_PRIO )APP_CFG_TASK_START_PRIO, /* 任务优先级 */ (CPU_STK *)&AppTaskStartStk[0], /* 堆栈基地址 */ (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE / 10, /* 堆栈监测区,这里表示后10%作为监测区 */ (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE, /* 堆栈空间大小 */ (OS_MSG_QTY )0, /* 本任务支持接受的最大消息数 */ (OS_TICK )0, /* 设置时间片 */ (void *)0, /* 堆栈空间大小 */ (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), /* 定义如下: OS_OPT_TASK_STK_CHK 使能检测任务栈,统计任务栈已用的和未用的 OS_OPT_TASK_STK_CLR 在创建任务时,清零任务栈 OS_OPT_TASK_SAVE_FP 如果CPU有浮点寄存器,则在任务切换时保存浮点寄存器的内容 */ (OS_ERR *)&err); /* 启动多任务系统,控制权交给uC/OS-III */ OSStart(&err); (void)&err; return (0); } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 这是一个启动任务,在多任务系统启动后,必须初始化滴答计数器。本任务主要实现RL-TCPnet的时间 * 基准更新。 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 2 ********************************************************************************************************* */ static void AppTaskStart (void *p_arg) { OS_ERR err; (void)p_arg; CPU_Init(); /* 此函数要优先调用,因为外设驱动中使用的us和ms延迟是基于此函数的 */ bsp_Init(); init_TcpNet ();/* 初始化RL-TCPnet */ BSP_Tick_Init(); #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN CPU_IntDisMeasMaxCurReset(); #endif /* 创建任务 */ AppTaskCreate(); /* 创建任务间通信机制 */ AppObjCreate(); while (1) { /* RL-TCPnet时间基准更新函数 */ timer_tick (); OSTimeDly(100, OS_OPT_TIME_PERIODIC, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskTCPnet * 功能说明: RL-TCPnet测试任务 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 3 ********************************************************************************************************* */ static void AppTaskTCPnet(void *p_arg) { (void)p_arg; while(1) { TCPnetTest(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 消息处理,这里用按键检测 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 3 ********************************************************************************************************* */ static void AppTaskMsgPro(void *p_arg) { OS_ERR err; (void)p_arg; while(1) { bsp_KeyScan(); OSTimeDly(10, OS_OPT_TIME_DLY, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 4 ********************************************************************************************************* */ static void AppTaskUserIF(void *p_arg) { OS_ERR err; uint8_t ucKeyCode; /* 按键代码 */ (void)p_arg; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */ case KEY_DOWN_K1: App_Printf("K1键按下,直接发送事件标志给任务AppTaskTCPMain,bit0被设置\r\n"); OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )KEY1_BIT0, /* 设置bit0 */ (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); break; /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */ case KEY_DOWN_K2: App_Printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n"); OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )KEY2_BIT1, /* 设置bit1 */ (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); break; /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */ case KEY_DOWN_K3: App_Printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n"); OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )KEY3_BIT2, /* 设置bit20 */ (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); break; /* 摇杆的OK键按下 打印任务执行情况 */ case JOY_DOWN_OK: DispTaskInfo(); break; default: /* 其他的键值不处理 */ break; } } OSTimeDly(20, OS_OPT_TIME_DLY, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskCom * 功能说明: LED闪烁 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 5 ********************************************************************************************************* */ static void AppTaskCOM(void *p_arg) { OS_ERR err; (void)p_arg; while(1) { bsp_LedToggle(2); OSTimeDly(500, OS_OPT_TIME_DLY, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskCreate * 功能说明: 创建应用任务 * 形 参:p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 ********************************************************************************************************* */ static void AppTaskCreate (void) { OS_ERR err; /**************创建MsgPro任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskTCPnetTCB, (CPU_CHAR *)"App Task TCPnet", (OS_TASK_PTR )AppTaskTCPnet, (void *)0, (OS_PRIO )APP_CFG_TASK_TCPnet_PRIO, (CPU_STK *)&AppTaskTCPnetStk[0], (CPU_STK_SIZE )APP_CFG_TASK_TCPnet_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_TCPnet_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建MsgPro任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskMsgProTCB, (CPU_CHAR *)"App Task MspPro", (OS_TASK_PTR )AppTaskMsgPro, (void *)0, (OS_PRIO )APP_CFG_TASK_MsgPro_PRIO, (CPU_STK *)&AppTaskMsgProStk[0], (CPU_STK_SIZE )APP_CFG_TASK_MsgPro_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_MsgPro_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建USER IF任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskUserIFTCB, (CPU_CHAR *)"App Task UserIF", (OS_TASK_PTR )AppTaskUserIF, (void *)0, (OS_PRIO )APP_CFG_TASK_USER_IF_PRIO, (CPU_STK *)&AppTaskUserIFStk[0], (CPU_STK_SIZE )APP_CFG_TASK_USER_IF_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_USER_IF_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建COM任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskCOMTCB, (CPU_CHAR *)"App Task COM", (OS_TASK_PTR )AppTaskCOM, (void *)0, (OS_PRIO )APP_CFG_TASK_COM_PRIO, (CPU_STK *)&AppTaskCOMStk[0], (CPU_STK_SIZE )APP_CFG_TASK_COM_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_COM_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); } /* ********************************************************************************************************* * 函 数 名: AppObjCreate * 功能说明: 创建任务通讯 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 ********************************************************************************************************* */ static void AppObjCreate (void) { OS_ERR err; /* 创建信号量数值为1的时候可以实现互斥功能,也就是只有一个资源可以使用 本例程是将串口1的打印函数作为保护的资源。防止串口打印的时候被其它任务抢占 造成串口打印错乱。 */ OSSemCreate((OS_SEM *)&AppPrintfSemp, (CPU_CHAR *)"AppPrintfSemp", (OS_SEM_CTR )1, (OS_ERR *)&err); /* 创建事件标志组 */ OSFlagCreate ((OS_FLAG_GRP *)&FLAG_TCPnet, (CPU_CHAR *)"FLAG TCPnet", (OS_FLAGS )0, (OS_ERR *)&err); } /* ********************************************************************************************************* * 函 数 名: App_Printf * 功能说明: 线程安全的printf方式 * 形 参: 同printf的参数。 * 在C中,当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 * 返 回 值: 无 ********************************************************************************************************* */ static void App_Printf(CPU_CHAR *format, ...) { CPU_CHAR buf_str[80 + 1]; va_list v_args; OS_ERR os_err; va_start(v_args, format); (void)vsnprintf((char *)&buf_str[0], (size_t ) sizeof(buf_str), (char const *) format, v_args); va_end(v_args); /* 互斥操作 */ OSSemPend((OS_SEM *)&AppPrintfSemp, (OS_TICK )0u, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)0, (OS_ERR *)&os_err); printf("%s", buf_str); (void)OSSemPost((OS_SEM *)&AppPrintfSemp, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&os_err); } /* ********************************************************************************************************* * 函 数 名: DispTaskInfo * 功能说明: 将uCOS-III任务信息通过串口打印出来 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ static void DispTaskInfo(void) { OS_TCB *p_tcb; /* 定义一个任务控制块指针, TCB = TASK CONTROL BLOCK */ float CPU = 0.0f; CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); p_tcb = OSTaskDbgListPtr; CPU_CRITICAL_EXIT(); /* 打印标题 */ App_Printf("===============================================================\r\n"); App_Printf(" 优先级 使用栈 剩余栈 百分比 利用率 任务名\r\n"); App_Printf(" Prio Used Free Per CPU Taskname\r\n"); /* 遍历任务控制块列表(TCB list),打印所有的任务的优先级和名称 */ while (p_tcb != (OS_TCB *)0) { CPU = (float)p_tcb->CPUUsage / 100; App_Printf(" %2d %5d %5d %02d%% %5.2f%% %s\r\n", p_tcb->Prio, p_tcb->StkUsed, p_tcb->StkFree, (p_tcb->StkUsed * 100) / (p_tcb->StkUsed + p_tcb->StkFree), CPU, p_tcb->NamePtr); CPU_CRITICAL_ENTER(); p_tcb = p_tcb->DbgNextPtr; CPU_CRITICAL_EXIT(); } }
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化硬件设备 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。 启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。 系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 设置NVIC优先级分组为Group4:可配置0-15级抢占式优先级,0级子优先级 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); bsp_InitUart(); /* 初始化串口 */ bsp_InitLed(); /* 初始LED指示灯端口 */ bsp_InitKey(); /* 按键初始化 */ }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[50]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { return (0); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]); printf_debug("IP:%s port:%d\r\n", buf, par); return (1); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = 0; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (0); } /* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; OS_ERR err; CPU_TS ts; OS_FLAGS xResult; const uint16_t usMaxBlockTime = 2; /* 延迟周期 */ /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback); if(socket_tcp != 0) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while (1) { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); xResult = OSFlagPend ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )0xFFFF, (OS_TICK )usMaxBlockTime, (OS_OPT )OS_OPT_PEND_FLAG_SET_ANY + OS_OPT_PEND_FLAG_CONSUME, (CPU_TS *)&ts, (OS_ERR *)&err); if((err == OS_ERR_NONE)&&(tcp_status == __TRUE)) { switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = 8; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < 0) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(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'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > 0); break; /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY2_BIT1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = 1024; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < 0) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[0] = 'a'; sendbuf[1] = 'b'; sendbuf[2] = 'c'; sendbuf[3] = 'd'; sendbuf[4] = 'e'; sendbuf[5] = 'f'; sendbuf[6] = 'g'; sendbuf[7] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > 0); break; /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */ case KEY3_BIT2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = 5*1024*1024; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < 0) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[0] = 'a'; sendbuf[1] = 'b'; sendbuf[2] = 'c'; sendbuf[3] = 'd'; sendbuf[4] = 'e'; sendbuf[5] = 'f'; sendbuf[6] = 'g'; sendbuf[7] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > 0); break; /* 其他的键值不处理 */ default: break; } } } }
至此,uCOS-III方式移植的RL-TCPnet就可以运行了。
9.2.6 RL-TCPnet实验测试和实验现象
测试前,先将开发板上面的DM9161/9162网口通过网线接到路由器或者交换机上面。
RJ45网络变压器插座上绿灯和黄灯现象
各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。
绿灯:长亮代表100M; 不亮代表10M。
黄灯:长亮代表无数据收发; 闪烁代表有数据收发。
也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看你的网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。
对于STM32F407开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。
底层驱动执行情况
为了验证RL-TCPnet底层驱动接口函数是否有问题,专门在ETH_STM32F4xx.c文件中配置了串口调试打印函数:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_eth printf #else #define printf_eth(...) #endif
如果底层驱动正常执行了,打印的效果如下:
ping是否正确
ping命令的主要作用是通过发送数据包并接收应答信息来检测两台设备之间的网络是否连通。ping命令成功说明当前主机与目的主机之间存在连通的路径。如果不成功,需要查看网线是否连通、网卡设置是否正确、IP地址是否可用等。测试方法如下:
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)弹出的命令窗口中,输入ping armfly,因为在前面的配置中我们使能了NetBIOS局域网域名,并将名字设置为armfly,而且使能了DHCP,通过ping命令还可以获得板子自动获取的IP地址。
(3)输入ping armfly后,回车。
收发相同,没有数据丢失,说明ping命令也是成功的。
电脑端创建一个TCP Client与板子上面的TCP Server通信
具体测试方法,查看第13章的13.6小节即可,因为配套例子实现的功能是一样的。
9.3 STM32F429移植RL-TCPnet协议栈
9.3.1 RL-TCPnet网络协议栈移植
首先准备好一个简单的uCOS-III工程模板,工程模板的制作就不做讲解了,这里的重点是教大家移植RL-TCPnet协议栈。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):
准备好工程模板后,就可以开始移植了。首先要做的就是将所有需要的文件放到工程模板里面。下面分4步跟大家进行说明,当然,不限制必须使用下面的方法添加源码到工程,只要将需要的文件添加到工程模板即可。
第1步:将我们uCOS-III模板中制作好的RL-ARM文件夹复制粘贴到大家准备好的工程模板中。
RL-ARM文件夹中有如下七个文件夹:
Config文件夹用于存放RTX及其中间件的配置文件。
Driver文件夹用于存放中间件的驱动文件,也就是底层移植文件。
RL-CAN文件夹用于存放CAN总线的源码文件。
RL-FlashFS文件夹用于存放文件系统RL-FlashFS的库文件。
RL-RTX文件夹用于存放RTX的源码文件。
RL-TCPnet文件夹用于存放网络协议栈RL-TCPnet的库文件。
RL-USB文件夹用于存放USB协议栈RL-USB的库文件。
也许有用户会问:我们不是仅仅需要移植RL-TCPnet的相关文件就行了吗,为什么把RTX及其所有中间件都添加进来了?这样做的目的是为了以后升级的方便,如果需要添加USB、文件系统、CAN等组件,直接添加到工程即可。
这些文件全部来自MDK4.74的安装目录,库文件位于路径:C:\Keil_v474\ARM\RV31下,而驱动和配置文件位于路径:C:\Keil_v474\ARM\RL下。
第2步:添加RL-TCPnet的库文件、配置文件和驱动文件到工程。
添加完毕后的效果如下:
Net_lib.c,Net_Config.c和NET_Debug.c在RL-ARM文件夹的Config文件里面。
TCPD_CM3.lib在RL-ARM文件夹的RL-TCPnet文件里面。
ETH_STM32F4xx.c和ETH_STM32F4xx.h在RL-ARM文件夹的Driver文件里面。
第3步:添加相应的头文件路径,在原来工程模板的基础上面新添加的几个路径:
第4步:也是最后一步,添加预定义宏,点击MDK的option -> c/c++选项,添加上__RTX(注意,字母RTX前面有两个下划线的),添加这个宏定义才可以使能RL-TCPnet的多任务支持。
至此,RL-TCPnet的移植工作就完成了,剩下就是系统配置和应用了。
9.3.2 RL-TCPnet配置说明(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数量。
范围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接收窗口大小。
9.3.3 RL-TCPnet调试说明(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章详细为大家讲解,移植阶段将其全部关闭即可。
9.3.4 RL-TCPnet的多任务驱动接口函数
要让RL-TCPnet支持多任务,就需要修改Net_lib.c文件。默认情况下,Net_lib.c文件是支持RTX操作系统的,现在要将其修改为支持uCOS-III,需要修改的几个地方如下:
添加uCOS-III的头文件。
#if (__RTX) #include "os.h" #endif
定义信号量和互斥信号量。
#if (BSD_ENABLE) static BSD_INFO bsd_scb[BSD_NUMSOCKS + BSD_SRVSOCKS]; #ifdef __RTX OS_MUTEX bsd_mutex; OS_SEM bsd_sem; #define BSD_INRTX __TRUE #else #define BSD_INRTX __FALSE #endif
创建互斥信号量和信号量。
/*--------------------------- init_system -----------------------------------*/ void init_system (void) { /* Initialize configured interfaces and applications. */ #if (ETH_ENABLE) eth_init_link (); #endif #if (PPP_ENABLE) ppp_init_link (); #endif #if (SLIP_ENABLE) slip_init_link (); #endif ip_init (); icmp_init (); #if (ETH_ENABLE && IGMP_ENABLE) igmp_init (); #endif #if (UDP_ENABLE) udp_init (); #endif #if (TCP_ENABLE) tcp_init (); #endif #if (BSD_ENABLE) bsd_init (); #if (BSD_GETHOSTEN) bsd_init_host (); #endif #endif #if (HTTP_ENABLE) http_init (); #endif #if (TNET_ENABLE) tnet_init (); #endif #if (TFTP_ENABLE) tftp_init (); #endif #if (TFTPC_ENABLE) tftpc_init (); #endif #if (FTP_ENABLE) ftp_init (); #endif #if (FTPC_ENABLE) ftpc_init (); #endif #if (ETH_ENABLE && NBNS_ENABLE) nbns_init (); #endif #if (ETH_ENABLE && DHCP_ENABLE) dhcp_init (); #elif (ETH_ENABLE) arp_notify (); #endif #if (DNS_ENABLE) dns_init (); #endif #if (SMTP_ENABLE) smtp_init (); #endif #if (SNMP_ENABLE) snmp_init (); #endif #if (SNTP_ENABLE) sntp_init (); #endif #if (BSD_ENABLE && __RTX) { OS_ERR err; OSMutexCreate(&bsd_mutex, "BSD Mutex", &err); OSSemCreate((OS_SEM *)&bsd_sem, (CPU_CHAR *)"BSD Sem", (OS_SEM_CTR )0, (OS_ERR *)&err); } #endif }
系统运行时,使用互斥信号量。
/*--------------------------- run_system ------------------------------------*/ void run_system (void) { /* Run configured interfaces and applications. */ #if (BSD_ENABLE && __RTX) OS_ERR err; OSMutexPend((OS_MUTEX *)&bsd_mutex, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); #endif #if (ETH_ENABLE) eth_run_link (); #endif #if (PPP_ENABLE) ppp_run_link (); #endif #if (SLIP_ENABLE) slip_run_link (); #endif ip_run_local (); icmp_run_engine (); #if (ETH_ENABLE && IGMP_ENABLE) igmp_run_host (); #endif #if (TCP_ENABLE) tcp_poll_sockets (); #endif #if (BSD_ENABLE) bsd_poll_sockets (); #endif #if (HTTP_ENABLE) http_run_server (); #endif #if (TNET_ENABLE) tnet_run_server (); #endif #if (TFTP_ENABLE) tftp_run_server (); #endif #if (TFTPC_ENABLE) tftpc_run_client (); #endif #if (FTP_ENABLE) ftp_run_server (); #endif #if (FTPC_ENABLE) ftpc_run_client (); #endif #if (ETH_ENABLE && DHCP_ENABLE) dhcp_run_client (); #endif #if (DNS_ENABLE) dns_run_client (); #endif #if (SMTP_ENABLE) smtp_run_client (); #endif #if (SNMP_ENABLE) snmp_run_agent (); #endif #if (SNTP_ENABLE) sntp_run_client (); #endif #if (BSD_ENABLE && __RTX) OSMutexPost((OS_MUTEX *)&bsd_mutex, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); #endif }
使能BSD Socket的话,Socket挂起和恢复的实现。
/*--------------------------- bsd_suspend/resume ----------------------------*/ #if (BSD_ENABLE && __RTX) __used void bsd_suspend (U8 *tsk_id) { OS_ERR err; /* Suspend a socket owner task. */ OSMutexPost((OS_MUTEX *)&bsd_mutex, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); OSSemPend((OS_SEM *)&bsd_sem, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); OSMutexPend((OS_MUTEX *)&bsd_mutex, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); } __used void bsd_resume (U8 tsk_id) { /* Resume a task waiting for a socket event. */ OS_ERR err; OSSemPost((OS_SEM *)&bsd_sem, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); } #endif
使能BSD Socket的话,锁机制的实现。
/*--------------------------- bsd_lock/unlock -------------------------------*/ #if (BSD_ENABLE && __RTX) __used void bsd_lock (void) { /* Acquire mutex - Lock TCPnet functions. */ OS_ERR err; OSMutexPend((OS_MUTEX *)&bsd_mutex, (OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS )0, (OS_ERR *)&err); } __used void bsd_unlock (void) { /* Release mutex - Unlock TCPnet functions. */ OS_ERR err; OSMutexPost((OS_MUTEX *)&bsd_mutex, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&err); } #endif
9.3.5 RL-TCPnet应用实例
为了验证移植的RL-TCPnet是否可以使用,需要添加测试代码。下面是编写的测试代码,配套的测试例子完整版是:V5-1002_RL-TCPnet实验_工程移植模板(uCOS-III)。
uCOS-III操作系统创建的任务
经过上面的移植和配置之后,在 main.c 文件中添加如下代码,代码中创建了5个用户任务:
App Task Start任务 :启动任务,实现RL-TCPnet的时间基准更新。
App Task TCPnet任务 :RL-TCPnet测试任务。
App Task MspPro任务:消息处理,这里用作按键检测。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :LED闪烁。
具体代码如下:
#include "includes.h" /* ********************************************************************************************************* * 静态全局变量 ********************************************************************************************************* */ static OS_TCB AppTaskStartTCB; static CPU_STK AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE]; static OS_TCB AppTaskTCPnetTCB; static CPU_STK AppTaskTCPnetStk[APP_CFG_TASK_TCPnet_STK_SIZE]; static OS_TCB AppTaskMsgProTCB; static CPU_STK AppTaskMsgProStk[APP_CFG_TASK_MsgPro_STK_SIZE]; static OS_TCB AppTaskCOMTCB; static CPU_STK AppTaskCOMStk[APP_CFG_TASK_COM_STK_SIZE]; static OS_TCB AppTaskUserIFTCB; static CPU_STK AppTaskUserIFStk[APP_CFG_TASK_USER_IF_STK_SIZE]; /* ********************************************************************************************************* * 函数声明 ********************************************************************************************************* */ static void AppTaskStart (void *p_arg); static void AppTaskTCPnet (void *p_arg); static void AppTaskMsgPro (void *p_arg); static void AppTaskUserIF (void *p_arg); static void AppTaskCOM (void *p_arg); static void AppTaskCreate (void); static void DispTaskInfo (void); static void AppObjCreate (void); static void App_Printf (CPU_CHAR *format, ...); /* ******************************************************************************************************* * 变量 ******************************************************************************************************* */ static OS_SEM AppPrintfSemp; /* 用于printf互斥 */ OS_FLAG_GRP FLAG_TCPnet; /* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main(void) { OS_ERR err; /* 初始化uC/OS-III 内核 */ OSInit(&err); /* 创建一个启动任务(也就是主任务)。启动任务会创建所有的应用程序任务 */ OSTaskCreate((OS_TCB *)&AppTaskStartTCB, /* 任务控制块地址 */ (CPU_CHAR *)"App Task Start", /* 任务名 */ (OS_TASK_PTR )AppTaskStart, /* 启动任务函数地址 */ (void *)0, /* 传递给任务的参数 */ (OS_PRIO )APP_CFG_TASK_START_PRIO, /* 任务优先级 */ (CPU_STK *)&AppTaskStartStk[0], /* 堆栈基地址 */ (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE / 10, /* 堆栈监测区,这里表示后10%作为监测区 */ (CPU_STK_SIZE )APP_CFG_TASK_START_STK_SIZE, /* 堆栈空间大小 */ (OS_MSG_QTY )0, /* 本任务支持接受的最大消息数 */ (OS_TICK )0, /* 设置时间片 */ (void *)0, /* 堆栈空间大小 */ (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), /* 定义如下: OS_OPT_TASK_STK_CHK 使能检测任务栈,统计任务栈已用的和未用的 OS_OPT_TASK_STK_CLR 在创建任务时,清零任务栈 OS_OPT_TASK_SAVE_FP 如果CPU有浮点寄存器,则在任务切换时保存浮点寄存器的内容 */ (OS_ERR *)&err); /* 启动多任务系统,控制权交给uC/OS-III */ OSStart(&err); (void)&err; return (0); } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 这是一个启动任务,在多任务系统启动后,必须初始化滴答计数器。本任务主要实现RL-TCPnet的时间 * 基准更新。 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 2 ********************************************************************************************************* */ static void AppTaskStart (void *p_arg) { OS_ERR err; (void)p_arg; CPU_Init(); /* 此函数要优先调用,因为外设驱动中使用的us和ms延迟是基于此函数的 */ bsp_Init(); init_TcpNet ();/* 初始化RL-TCPnet */ BSP_Tick_Init(); #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN CPU_IntDisMeasMaxCurReset(); #endif /* 创建任务 */ AppTaskCreate(); /* 创建任务间通信机制 */ AppObjCreate(); while (1) { /* RL-TCPnet时间基准更新函数 */ timer_tick (); OSTimeDly(100, OS_OPT_TIME_PERIODIC, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskTCPnet * 功能说明: RL-TCPnet测试任务 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 3 ********************************************************************************************************* */ static void AppTaskTCPnet(void *p_arg) { (void)p_arg; while(1) { TCPnetTest(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 消息处理,这里用按键检测 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 3 ********************************************************************************************************* */ static void AppTaskMsgPro(void *p_arg) { OS_ERR err; (void)p_arg; while(1) { bsp_KeyScan(); OSTimeDly(10, OS_OPT_TIME_DLY, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 4 ********************************************************************************************************* */ static void AppTaskUserIF(void *p_arg) { OS_ERR err; uint8_t ucKeyCode; /* 按键代码 */ (void)p_arg; while(1) { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */ case KEY_DOWN_K1: App_Printf("K1键按下,直接发送事件标志给任务AppTaskTCPMain,bit0被设置\r\n"); OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )KEY1_BIT0, /* 设置bit0 */ (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); break; /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */ case KEY_DOWN_K2: App_Printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n"); OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )KEY2_BIT1, /* 设置bit1 */ (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); break; /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */ case KEY_DOWN_K3: App_Printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n"); OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )KEY3_BIT2, /* 设置bit20 */ (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); break; /* 摇杆的OK键按下 打印任务执行情况 */ case JOY_DOWN_OK: DispTaskInfo(); break; default: /* 其他的键值不处理 */ break; } } OSTimeDly(20, OS_OPT_TIME_DLY, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskCom * 功能说明: LED闪烁 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 5 ********************************************************************************************************* */ static void AppTaskCOM(void *p_arg) { OS_ERR err; (void)p_arg; while(1) { bsp_LedToggle(2); OSTimeDly(500, OS_OPT_TIME_DLY, &err); } } /* ********************************************************************************************************* * 函 数 名: AppTaskCreate * 功能说明: 创建应用任务 * 形 参:p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 ********************************************************************************************************* */ static void AppTaskCreate (void) { OS_ERR err; /**************创建MsgPro任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskTCPnetTCB, (CPU_CHAR *)"App Task TCPnet", (OS_TASK_PTR )AppTaskTCPnet, (void *)0, (OS_PRIO )APP_CFG_TASK_TCPnet_PRIO, (CPU_STK *)&AppTaskTCPnetStk[0], (CPU_STK_SIZE )APP_CFG_TASK_TCPnet_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_TCPnet_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建MsgPro任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskMsgProTCB, (CPU_CHAR *)"App Task MspPro", (OS_TASK_PTR )AppTaskMsgPro, (void *)0, (OS_PRIO )APP_CFG_TASK_MsgPro_PRIO, (CPU_STK *)&AppTaskMsgProStk[0], (CPU_STK_SIZE )APP_CFG_TASK_MsgPro_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_MsgPro_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建USER IF任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskUserIFTCB, (CPU_CHAR *)"App Task UserIF", (OS_TASK_PTR )AppTaskUserIF, (void *)0, (OS_PRIO )APP_CFG_TASK_USER_IF_PRIO, (CPU_STK *)&AppTaskUserIFStk[0], (CPU_STK_SIZE )APP_CFG_TASK_USER_IF_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_USER_IF_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); /**************创建COM任务*********************/ OSTaskCreate((OS_TCB *)&AppTaskCOMTCB, (CPU_CHAR *)"App Task COM", (OS_TASK_PTR )AppTaskCOM, (void *)0, (OS_PRIO )APP_CFG_TASK_COM_PRIO, (CPU_STK *)&AppTaskCOMStk[0], (CPU_STK_SIZE )APP_CFG_TASK_COM_STK_SIZE / 10, (CPU_STK_SIZE )APP_CFG_TASK_COM_STK_SIZE, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), (OS_ERR *)&err); } /* ********************************************************************************************************* * 函 数 名: AppObjCreate * 功能说明: 创建任务通讯 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 ********************************************************************************************************* */ static void AppObjCreate (void) { OS_ERR err; /* 创建信号量数值为1的时候可以实现互斥功能,也就是只有一个资源可以使用 本例程是将串口1的打印函数作为保护的资源。防止串口打印的时候被其它任务抢占 造成串口打印错乱。 */ OSSemCreate((OS_SEM *)&AppPrintfSemp, (CPU_CHAR *)"AppPrintfSemp", (OS_SEM_CTR )1, (OS_ERR *)&err); /* 创建事件标志组 */ OSFlagCreate ((OS_FLAG_GRP *)&FLAG_TCPnet, (CPU_CHAR *)"FLAG TCPnet", (OS_FLAGS )0, (OS_ERR *)&err); } /* ********************************************************************************************************* * 函 数 名: App_Printf * 功能说明: 线程安全的printf方式 * 形 参: 同printf的参数。 * 在C中,当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 * 返 回 值: 无 ********************************************************************************************************* */ static void App_Printf(CPU_CHAR *format, ...) { CPU_CHAR buf_str[80 + 1]; va_list v_args; OS_ERR os_err; va_start(v_args, format); (void)vsnprintf((char *)&buf_str[0], (size_t ) sizeof(buf_str), (char const *) format, v_args); va_end(v_args); /* 互斥操作 */ OSSemPend((OS_SEM *)&AppPrintfSemp, (OS_TICK )0u, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)0, (OS_ERR *)&os_err); printf("%s", buf_str); (void)OSSemPost((OS_SEM *)&AppPrintfSemp, (OS_OPT )OS_OPT_POST_1, (OS_ERR *)&os_err); } /* ********************************************************************************************************* * 函 数 名: DispTaskInfo * 功能说明: 将uCOS-III任务信息通过串口打印出来 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ static void DispTaskInfo(void) { OS_TCB *p_tcb; /* 定义一个任务控制块指针, TCB = TASK CONTROL BLOCK */ float CPU = 0.0f; CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); p_tcb = OSTaskDbgListPtr; CPU_CRITICAL_EXIT(); /* 打印标题 */ App_Printf("===============================================================\r\n"); App_Printf(" 优先级 使用栈 剩余栈 百分比 利用率 任务名\r\n"); App_Printf(" Prio Used Free Per CPU Taskname\r\n"); /* 遍历任务控制块列表(TCB list),打印所有的任务的优先级和名称 */ while (p_tcb != (OS_TCB *)0) { CPU = (float)p_tcb->CPUUsage / 100; App_Printf(" %2d %5d %5d %02d%% %5.2f%% %s\r\n", p_tcb->Prio, p_tcb->StkUsed, p_tcb->StkFree, (p_tcb->StkUsed * 100) / (p_tcb->StkUsed + p_tcb->StkFree), CPU, p_tcb->NamePtr); CPU_CRITICAL_ENTER(); p_tcb = p_tcb->DbgNextPtr; CPU_CRITICAL_EXIT(); } }
硬件外设初始化
硬件外设的初始化是在 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_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */ bsp_InitExtIO(); /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */ bsp_InitLed(); /* 初始LED指示灯端口 */ }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[50]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { return (0); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]); printf_debug("IP:%s port:%d\r\n", buf, par); return (1); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = 0; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (0); } /* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; OS_ERR err; CPU_TS ts; OS_FLAGS xResult; const uint16_t usMaxBlockTime = 2; /* 延迟周期 */ /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback); if(socket_tcp != 0) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while (1) { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); xResult = OSFlagPend ((OS_FLAG_GRP *)&FLAG_TCPnet, (OS_FLAGS )0xFFFF, (OS_TICK )usMaxBlockTime, (OS_OPT )OS_OPT_PEND_FLAG_SET_ANY + OS_OPT_PEND_FLAG_CONSUME, (CPU_TS *)&ts, (OS_ERR *)&err); if((err == OS_ERR_NONE)&&(tcp_status == __TRUE)) { switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = 8; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < 0) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(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'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > 0); break; /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY2_BIT1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = 1024; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < 0) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[0] = 'a'; sendbuf[1] = 'b'; sendbuf[2] = 'c'; sendbuf[3] = 'd'; sendbuf[4] = 'e'; sendbuf[5] = 'f'; sendbuf[6] = 'g'; sendbuf[7] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > 0); break; /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */ case KEY3_BIT2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = 5*1024*1024; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < 0) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[0] = 'a'; sendbuf[1] = 'b'; sendbuf[2] = 'c'; sendbuf[3] = 'd'; sendbuf[4] = 'e'; sendbuf[5] = 'f'; sendbuf[6] = 'g'; sendbuf[7] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > 0); break; /* 其他的键值不处理 */ default: break; } } } }
至此,uCOS-III方式移植的RL-TCPnet就可以运行了。
9.3.6 RL-TCPnet实验测试和实验现象
测试前,先将开发板上面的DM9161/9162网口通过网线接到路由器或者交换机上面。
RJ45网络变压器插座上绿灯和黄灯现象
各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。
绿灯:长亮代表100M; 不亮代表10M。
黄灯:长亮代表无数据收发; 闪烁代表有数据收发。
也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看你的网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。
对于STM32F429开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。
底层驱动执行情况
为了验证RL-TCPnet底层驱动接口函数是否有问题,专门在ETH_STM32F4xx.c文件中配置了串口调试打印函数:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_eth printf #else #define printf_eth(...) #endif
如果底层驱动正常执行了,打印的效果如下:
ping是否正确
ping命令的主要作用是通过发送数据包并接收应答信息来检测两台设备之间的网络是否连通。ping命令成功说明当前主机与目的主机之间存在连通的路径。如果不成功,需要查看网线是否连通、网卡设置是否正确、IP地址是否可用等。测试方法如下:
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)弹出的命令窗口中,输入ping armfly,因为在前面的配置中我们使能了NetBIOS局域网域名,并将名字设置为armfly,而且使能了DHCP,通过ping命令还可以获得板子自动获取的IP地址。
(3)输入ping armfly后,回车。
收发相同,没有数据丢失,说明ping命令也是成功的。
电脑端创建一个TCP Client与板子上面的TCP Server通信
具体测试方法,查看第13章的13.6小节即可,因为配套例子实现的功能是一样的。
9.4 总结
本章节为大家讲解了RL-TCPnet网络协议栈的uCOS-III操作系统移植方法,移植相对比较简单。另一个重要内容是Net_Config.c配置向导文件的说明,这个比较重要,初学者要好好熟悉下。