【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架
第21章 RL-TCPnet之高效的事件触发框架
本章节为大家讲解高效的事件触发框架实现方法,BSD Socket编程和后面章节要讲解到的FTP、TFTP和HTTP等都非常适合使用这种方式。实际项目中也推荐大家采用这种方式,不过仅适用于RTOS环境,比如RTX、FreeRTOS或者uCOS-III均可,裸机方式不支持。
另外,前面章节讲解的TCP和UDP的原始socket使用这种方式不太方便,因为应用程序的编写会变的稍麻烦,不像BSD Socket那么省事。
21.1 初学者重要提示
21.2 高效的事件触发框架说明
21.3 RTX系统实例修改方法
21.4 uCOS-III系统实例修改方法
21.5 FreeRTOS系统实例修改方法
21.6 实验操作和实验例程说明
21.7 总结
21.1 初学者重要提示
- 实际项目中强烈推荐大家采用这种方式,不过仅适用于RTOS环境,比如RTX、FreeRTOS或者uCOS-III均可。后面章节配套的例子,基本也都采用这种方式。
- 前面章节讲解的TCP和UDP的原始socket使用这种方式不太方便,因为应用程序的编写会变的稍麻烦,不像BSD Socket这么省事。
21.2 高效的事件触发框架说明
讲解高效的事件触发框架之前,先看下没有使用事件触发方式时,ping的响应速度,以例程:V6-1024_RL-TCPnet实验_BSD Socket服务器之TCP(RTX)为例进行说明:

下面是使用了事件触发方式时,ping的响应速度,以例程:V6-1030_RL-TCPnet实验_高效的事件触发框架(RTX)为例进行说明:

从上面的两个响应速度的对比中,可以看出,使用了时间触发方式的例子,响应速度都在1ms以下,效果还是非常明显的。
前面章节配套的例子里面,响应速度慢,是因为我们都是周期性的调用RL-TCPnet的主处理函数main_TcpNet(),比如前面BSD Socket服务器章节配套的例子中:
/*
*********************************************************************************************************
* 函 数 名: AppTaskTCPMain
* 功能说明: RL-TCPnet网络主任务
* 形 参: 无
* 返 回 值: 无
* 优 先 级: 5
*********************************************************************************************************
*/
__task void AppTaskTCPMain(void)
{
while ()
{
/* RL-TCPnet处理函数 */
main_TcpNet();
os_dly_wait();
}
}
这种方式有如下两个缺点:
- 没有网络通信时也要周期性的执行。
- 实时响应差,因为在延迟的这段时间内有网络数据包的话,数据包得不到及时的处理。
另外特别注意一点,一些不理解的读者会问,我们的底层函数里面不是有以太网中断吗,为什么还会不能实时性响应呢?根本的原因就在,虽然有以太网中断,但是中断后,RL-TCPent的主处理函数main_TcpNet()不能得到及时的执行,所以我们要解决的就是让主处理函数得到实时执行。
用户通过修改以下几个地方就可以实现:
- 修改ETH_STM32F4xx.c文件中的函数send_frame。
- 修改ETH_STM32F4xx.c文件中的以太网中断函数。
- 修改RL-TCPnet的时间基准更新任务。
- 修改RL-TCPnet的网络主任务,函数main_TcpNet的调用不再采用轮询方式,改成事件标志等待方式。
下面针对RTX、uCOS-III和FreeRTOS操作系统分别做讲解:
21.3 RTX系统实例修改方法
下面针对RTX系统要做的具体修改做个说明,我们以例程:V6-1024_RL-TCPnet实验_BSD Socket服务器之TCP(RTX)为例。通过修改函数send_frame,以太网中断和时间基准更新任务都给网络主任务发事件标志,让其得到实时执行,从而实现高效的事件触发框架。
21.3.1 修改函数send_frame
修改ETH_STM32F4xx.c文件中的函数send_frame,此函数的末尾添加事件标志函数os_evt_set(0x0001, HandleTaskTCPMain);
/*
*********************************************************************************************************
* 函 数 名: send_frame
* 功能说明: 传递数据帧给MAC DMA发送描述符,并使能发送。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
extern OS_TID HandleTaskTCPMain;
void send_frame (OS_FRAME *frame)
{
U32 *sp,*dp;
U32 i,j;
j = TxBufIndex;
/* 等待上一帧数据发送完成 */
while (Tx_Desc[j].CtrlStat & DMA_TX_OWN);
sp = (U32 *)&frame->data[];
dp = (U32 *)(Tx_Desc[j].Addr & ~);
/* 复制要发送的数据到DMA发送描述符中 */
for (i = (frame->length + ) >> ; i; i--)
{
*dp++ = *sp++;
}
/* 设置数据帧大小 */
Tx_Desc[j].Size = frame->length;
/* 发送描述符由DMA控制发送 */
Tx_Desc[j].CtrlStat |= DMA_TX_OWN;
if (++j == NUM_TX_BUF) j = ;
TxBufIndex = j;
/* 开始帧传输 */
/*
DMASR 以太网 DMA 状态寄存器
向ETH_DMASR寄存器[16:0]中的(未保留)位写入1会将其清零,写入 0 则不起作用。
位1 TPSS:发送过程停止状态 (Transmit process stopped status)
当发送停止时,此位置 1。
*/
ETH->DMASR = DSR_TPSS;
/*
DMATPDR 以太网DMA发送轮询请求寄存器
应用程序使用此寄存器来指示DMA轮询发送描述符列表。
位 31:0 TPD:发送轮询请求(Transmit poll demand)
向这些位写入任何值时,DMA都会读取ETH_DMACHTDR寄存器指向的当前描述符。如果
该描述符不可用(由CPU所有),则发送会返回到挂起状态,并将ETH_DMASR寄存器位2
进行置位。如果该描述符可用,则发送会继续进行。
*/
ETH->DMATPDR = ;
os_evt_set(0x0001, HandleTaskTCPMain);
}
21.3.2 修改以太网中断函数
修改ETH_STM32F4xx.c文件中的以太网中断函数,此函数的末尾添加事件标志函数:isr_evt_set(0x0001, HandleTaskTCPMain);
/*
*********************************************************************************************************
* 函 数 名: ETH_IRQHandler
* 功能说明: 以太网中断,主要处理从MAC DMA接收描述符接收到的数据帧以及错误标志的处理。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void ETH_IRQHandler (void)
{
OS_FRAME *frame;
U32 i, RxLen;
U32 *sp,*dp;
i = RxBufIndex;
/* 循环所有接受描述符列表,遇到未接收到数据的退出循环 */
do
{
/*
#define DMA_RX_ERROR_MASK (DMA_RX_ES | DMA_RX_LE | DMA_RX_RWT | \
DMA_RX_RE | DMA_RX_CE)
有错误,放弃此帧数据,错误类型包含如下:
位15 DMA_RX_ES:错误汇总(Error summary),即CRC错误,接收错误,看门狗超时,延迟冲突等。
位12 DMA_RX_LE:长度错误(Length error)
该位置1时,指示接收帧的实际长度与长度/类型字段的值不符。该字段仅在帧类
型位(RDES0[5])复位后有效。
位4 DMA_RX_RWT:接收看门狗超时 (Receive watchdog timeout)
该位置1时,表示接收看门狗计时器在接收当前帧时超时,且当前帧在看门狗超
时后被截断了
位3 DMA_RX_RE: 接收错误 (Receive error)
该位置1时,表示在帧接收期间,当发出RX_DV信号时,会发出RX_ERR信号。
位1 DMA_RX_CE: CRC 错误(CRC error)
该位置1时,表示接收的帧发生循环冗余校验(CRC)错误。只有最后一个描述符
(RDES0[8])置1时,该字段才有效
*/
if (Rx_Desc[i].Stat & DMA_RX_ERROR_MASK)
{
goto rel;
}
/*
#define DMA_RX_SEG_MASK (DMA_RX_FS | DMA_RX_LS)
位9 FS:第一个描述符 (First descriptor)
该位置1时,指示此描述符包含帧的第一个缓冲区。如果第一个缓冲区的大小为0,则第二
个缓冲区将包含帧的帧头。如果第二个缓冲区的大小为0,则下一个描述符将包含帧的帧头。
位8 LS:最后一个描述符 (Last descriptor)
该位置1时,指示此描述符指向的缓冲区为帧的最后一个缓冲区。
下面的函数用于判断此帧数据是否只有一个缓冲,初始化接收描述符列表的时候,每个描述符仅设置了
一个缓冲。
*/
if ((Rx_Desc[i].Stat & DMA_RX_SEG_MASK) != DMA_RX_SEG_MASK)
{
goto rel;
}
RxLen = ((Rx_Desc[i].Stat >> ) & 0x3FFF) - ;
if (RxLen > ETH_MTU)
{
/* 数据包太大,直接放弃 */
goto rel;
}
/* 申请动态内存,RxLen或上0x80000000表示动态内存不足了不会调用函数sys_error() */
frame = alloc_mem (RxLen | 0x80000000);
/* 如果动态内存申请失败了,放弃此帧数据;成功了,通过函数put_in_queue存入队列中 */
if (frame != NULL)
{
sp = (U32 *)(Rx_Desc[i].Addr & ~);
dp = (U32 *)&frame->data[];
for (RxLen = (RxLen + ) >> ; RxLen; RxLen--)
{
*dp++ = *sp++;
}
put_in_queue (frame);
}
/* 设置此接收描述符继续接收新的数据 */
rel: Rx_Desc[i].Stat = DMA_RX_OWN;
if (++i == NUM_RX_BUF) i = ;
}
while (!(Rx_Desc[i].Stat & DMA_RX_OWN));
RxBufIndex = i;
/*
DMASR DMA的状态寄存器(DMA status register)
位7 RBUS:接收缓冲区不可用状态 (Receive buffer unavailable status)
此位指示接收列表中的下一个描述符由CPU所拥有,DMA无法获取。接收过程进入挂起状态。
要恢复处理接收描述符,CPU应更改描述符的拥有关系,然后发出接收轮询请求命令。如果
未发出接收轮询请求命令,则当接收到下一个识别的传入帧时,接收过程会恢复。仅当上一
接收描述符由DMA所拥有时,才能将ETH_DMASR[7]置1。
DMAIER的接收缓冲区不可用中断RBUIE是bit7,对于的接收缓冲区不可用状态在DMA状态寄存器中也是bit7。
*/
if (ETH->DMASR & INT_RBUIE)
{
/* 接收缓冲区不可用,重新恢复DMA传输 */
ETH->DMASR = ETH_DMASR_RBUS;
ETH->DMARPDR = ;
}
/*
DMASR DMA的状态寄存器(DMA status register)
这里实现清除中断挂起标志
位16 ETH_DMASR_NIS:所有正常中断 (Normal interrupt summary)
位15 ETH_DMASR_AIS:所有异常中断 (Abnormal interrupt summary)
位6 ETH_DMASR_RS :接收状态 (Receive status)
此位指示帧接收已完成,具体的帧状态信息已经包含在描述符中,接收仍保持运行状态。
*/
ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_AIS | ETH_DMASR_RS;
isr_evt_set(0x0001, HandleTaskTCPMain);
}
21.3.3 修改RL-TCPnet的时间基准更新任务
修改RL-TCPnet的时间基准更新任务,添加事件标志函数os_evt_set(0x0001, HandleTaskTCPMain);
/*
*********************************************************************************************************
* 函 数 名: AppTaskStart
* 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。
* 形 参: 无
* 返 回 值: 无
* 优 先 级: 6
*********************************************************************************************************
*/
__task void AppTaskStart(void)
{
/* 初始化RL-TCPnet */
init_TcpNet ();
/* 创建任务 */
AppTaskCreate();
os_itv_set ();
while()
{
os_itv_wait ();
/* RL-TCPnet时间基准更新函数 */
timer_tick ();
os_evt_set(0x0001, HandleTaskTCPMain);
}
}
21.3.4 修改RL-TCPnet的网络主任务
修改RL-TCPnet的网络主任务,函数main_TcpNet的调用不再采用轮询方式,改成事件标志等待方式,即修改为如下形式:
/*
*********************************************************************************************************
* 函 数 名: AppTaskTCPMain
* 功能说明: RL-TCPnet网络主任务
* 形 参: 无
* 返 回 值: 无
* 优 先 级: 5
*********************************************************************************************************
*/
__task void AppTaskTCPMain(void)
{
while ()
{
/* RL-TCPnet处理函数 */
os_evt_wait_and(0x0001, 0xFFFF);
while (main_TcpNet() == __TRUE);
}
}
21.3.5 最后特别注意优先级安排
最后,用户要特别注意几个任务的优先级安排,非常重要。
- RL-TCPnet的时间基准更新任务一定要是最高优先级任务。
- RL-TCPnet的网络主任务,即调用函数main_TcpNet的任务是次高优先级任务。
- 应用层的任务要比前面两个任务的优先级都低。
21.4 uCOS-III系统实例修改方法
下面针对uCOS-III系统要做的具体修改做个说明,我们以例程:V6-1025_RL-TCPnet实验_BSD Socket服务器之TCP(uCOS-III)为例。通过修改函数send_frame,以太网中断和时间基准更新任务都给网络主任务发事件标志,让其得到实时执行,从而实现高效的事件触发框架。
21.4.1 创建事件标志组
创建uCOS-III的事件标志组:
OS_FLAG_GRP FLAG_TCPnet;
/*
*********************************************************************************************************
* 函 数 名: AppObjCreate
* 功能说明: 创建任务通讯
* 形 参: p_arg 是在创建该任务时传递的形参
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
OS_ERR err;
/* 创建事件标志组 */
OSFlagCreate ((OS_FLAG_GRP *)&FLAG_TCPnet,
(CPU_CHAR *)"FLAG TCPnet",
(OS_FLAGS ),
(OS_ERR *)&err);
}
21.4.2 修改函数send_frame
修改ETH_STM32F4xx.c文件中的函数send_frame,此函数的末尾添加事件标志函数OSFlagPost(宏定义uCOS_EN在bsp.h文件里面使能,针对教程配套例子做的定义,方便管理。大家自己搞时,不必受此限制)。
/*
*********************************************************************************************************
* 函 数 名: send_frame
* 功能说明: 传递数据帧给MAC DMA发送描述符,并使能发送。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void send_frame (OS_FRAME *frame)
{
U32 *sp,*dp;
U32 i,j;
#if uCOS_EN == 1
OS_ERR err;
#endif
j = TxBufIndex;
/* 等待上一帧数据发送完成 */
while (Tx_Desc[j].CtrlStat & DMA_TX_OWN);
sp = (U32 *)&frame->data[];
dp = (U32 *)(Tx_Desc[j].Addr & ~);
/* 复制要发送的数据到DMA发送描述符中 */
for (i = (frame->length + ) >> ; i; i--)
{
*dp++ = *sp++;
}
/* 设置数据帧大小 */
Tx_Desc[j].Size = frame->length;
/* 发送描述符由DMA控制发送 */
Tx_Desc[j].CtrlStat |= DMA_TX_OWN;
if (++j == NUM_TX_BUF) j = ;
TxBufIndex = j;
/* 开始帧传输 */
/*
DMASR 以太网 DMA 状态寄存器
向ETH_DMASR寄存器[16:0]中的(未保留)位写入1会将其清零,写入 0 则不起作用。
位1 TPSS:发送过程停止状态 (Transmit process stopped status)
当发送停止时,此位置 1。
*/
ETH->DMASR = DSR_TPSS;
/*
DMATPDR 以太网DMA发送轮询请求寄存器
应用程序使用此寄存器来指示DMA轮询发送描述符列表。
位 31:0 TPD:发送轮询请求(Transmit poll demand)
向这些位写入任何值时,DMA都会读取ETH_DMACHTDR寄存器指向的当前描述符。如果
该描述符不可用(由CPU所有),则发送会返回到挂起状态,并将ETH_DMASR寄存器位2
进行置位。如果该描述符可用,则发送会继续进行。
*/
ETH->DMATPDR = ;
#if uCOS_EN == 1
OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet,
(OS_FLAGS )0x0001,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR *)&err);
#endif
}
21.4.3 修改以太网中断函数
修改ETH_STM32F4xx.c文件中的以太网中断函数,此函数的末尾添加事件标志函数:OSFlagPost(宏定义uCOS_EN在bsp.h文件里面使能,针对教程配套例子做的定义,方便管理。大家自己搞时,不必受此限制)。
/*
*********************************************************************************************************
* 函 数 名: ETH_IRQHandler
* 功能说明: 以太网中断,主要处理从MAC DMA接收描述符接收到的数据帧以及错误标志的处理。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void ETH_IRQHandler (void)
{
OS_FRAME *frame;
U32 i, RxLen;
U32 *sp,*dp;
#if uCOS_EN == 1
OS_ERR err;
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
OSIntEnter();
CPU_CRITICAL_EXIT();
#endif
i = RxBufIndex;
/* 循环所有接受描述符列表,遇到未接收到数据的退出循环 */
do
{
/*
#define DMA_RX_ERROR_MASK (DMA_RX_ES | DMA_RX_LE | DMA_RX_RWT | \
DMA_RX_RE | DMA_RX_CE)
有错误,放弃此帧数据,错误类型包含如下:
位15 DMA_RX_ES:错误汇总(Error summary),即CRC错误,接收错误,看门狗超时,延迟冲突等。
位12 DMA_RX_LE:长度错误(Length error)
该位置1时,指示接收帧的实际长度与长度/类型字段的值不符。该字段仅在帧类
型位(RDES0[5])复位后有效。
位4 DMA_RX_RWT:接收看门狗超时 (Receive watchdog timeout)
该位置1时,表示接收看门狗计时器在接收当前帧时超时,且当前帧在看门狗超
时后被截断了
位3 DMA_RX_RE: 接收错误 (Receive error)
该位置1时,表示在帧接收期间,当发出RX_DV信号时,会发出RX_ERR信号。
位1 DMA_RX_CE: CRC 错误(CRC error)
该位置1时,表示接收的帧发生循环冗余校验(CRC)错误。只有最后一个描述符
(RDES0[8])置1时,该字段才有效
*/
if (Rx_Desc[i].Stat & DMA_RX_ERROR_MASK)
{
goto rel;
}
/*
#define DMA_RX_SEG_MASK (DMA_RX_FS | DMA_RX_LS)
位9 FS:第一个描述符 (First descriptor)
该位置1时,指示此描述符包含帧的第一个缓冲区。如果第一个缓冲区的大小为0,则第二
个缓冲区将包含帧的帧头。如果第二个缓冲区的大小为0,则下一个描述符将包含帧的帧头。
位8 LS:最后一个描述符 (Last descriptor)
该位置1时,指示此描述符指向的缓冲区为帧的最后一个缓冲区。
下面的函数用于判断此帧数据是否只有一个缓冲,初始化接收描述符列表的时候,每个描述符仅设置了
一个缓冲。
*/
if ((Rx_Desc[i].Stat & DMA_RX_SEG_MASK) != DMA_RX_SEG_MASK)
{
goto rel;
}
RxLen = ((Rx_Desc[i].Stat >> ) & 0x3FFF) - ;
if (RxLen > ETH_MTU)
{
/* 数据包太大,直接放弃 */
goto rel;
}
/* 申请动态内存,RxLen或上0x80000000表示动态内存不足了不会调用函数sys_error() */
frame = alloc_mem (RxLen | 0x80000000);
/* 如果动态内存申请失败了,放弃此帧数据;成功了,通过函数put_in_queue存入队列中 */
if (frame != NULL)
{
sp = (U32 *)(Rx_Desc[i].Addr & ~);
dp = (U32 *)&frame->data[];
for (RxLen = (RxLen + ) >> ; RxLen; RxLen--)
{
*dp++ = *sp++;
}
put_in_queue (frame);
}
/* 设置此接收描述符继续接收新的数据 */
rel: Rx_Desc[i].Stat = DMA_RX_OWN;
if (++i == NUM_RX_BUF) i = ;
}
while (!(Rx_Desc[i].Stat & DMA_RX_OWN));
RxBufIndex = i;
/*
DMASR DMA的状态寄存器(DMA status register)
位7 RBUS:接收缓冲区不可用状态 (Receive buffer unavailable status)
此位指示接收列表中的下一个描述符由CPU所拥有,DMA无法获取。接收过程进入挂起状态。
要恢复处理接收描述符,CPU应更改描述符的拥有关系,然后发出接收轮询请求命令。如果
未发出接收轮询请求命令,则当接收到下一个识别的传入帧时,接收过程会恢复。仅当上一
接收描述符由DMA所拥有时,才能将ETH_DMASR[7]置1。
DMAIER的接收缓冲区不可用中断RBUIE是bit7,对于的接收缓冲区不可用状态在DMA状态寄存器中也是bit7。
*/
if (ETH->DMASR & INT_RBUIE)
{
/* 接收缓冲区不可用,重新恢复DMA传输 */
ETH->DMASR = ETH_DMASR_RBUS;
ETH->DMARPDR = ;
}
/*
DMASR DMA的状态寄存器(DMA status register)
这里实现清除中断挂起标志
位16 ETH_DMASR_NIS:所有正常中断 (Normal interrupt summary)
位15 ETH_DMASR_AIS:所有异常中断 (Abnormal interrupt summary)
位6 ETH_DMASR_RS :接收状态 (Receive status)
此位指示帧接收已完成,具体的帧状态信息已经包含在描述符中,接收仍保持运行状态。
*/
ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_AIS | ETH_DMASR_RS;
#if uCOS_EN == 1
OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet,
(OS_FLAGS )0x0001,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR *)&err);
OSIntExit();
#endif
}
21.4.4 修改RL-TCPnet的时间基准更新任务
修改RL-TCPnet的时间基准更新任务,添加事件标志函数:OSFlagPost。
/*
*********************************************************************************************************
* 函 数 名: 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 ()
{
/* RL-TCPnet时间基准更新函数 */
timer_tick ();
OSFlagPost ((OS_FLAG_GRP *)&FLAG_TCPnet,
(OS_FLAGS )0x0001,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR *)&err);
OSTimeDly(, OS_OPT_TIME_PERIODIC, &err);
}
}
21.4.5 修改RL-TCPnet的网络主任务
修改RL-TCPnet的网络主任务,函数main_TcpNet的调用不再采用轮询方式,改成事件标志等待方式,即修改为如下形式:
/*
*********************************************************************************************************
* 函 数 名: AppTaskTCPnet
* 功能说明: RL-TCPnet网络主任务
* 形 参: p_arg 是在创建该任务时传递的形参
* 返 回 值: 无
优 先 级: 3
*********************************************************************************************************
*/
static void AppTaskTCPnet(void *p_arg)
{
OS_ERR err;
CPU_TS ts;
(void)p_arg;
while()
{
/* RL-TCPnet处理函数 */
OSFlagPend ((OS_FLAG_GRP *)&FLAG_TCPnet,
(OS_FLAGS )0x0001,
(OS_TICK ),
(OS_OPT )OS_OPT_PEND_FLAG_SET_ANY + OS_OPT_PEND_FLAG_CONSUME,
(CPU_TS *)&ts,
(OS_ERR *)&err);
while (main_TcpNet() == __TRUE);
}
}
21.4.6 最后特别注意优先级安排
最后,用户要特别注意几个任务的优先级安排,非常重要。
- RL-TCPnet的时间基准更新任务一定要是最高优先级任务。
- RL-TCPnet的网络主任务,即调用函数main_TcpNet的任务是次高优先级任务。
- 应用层的任务要比前面两个任务的优先级都低。
21.5 FreeRTOS系统实例修改方法
下面针对FreeRTOS系统要做的具体修改做个说明,我们以例程:V6-1026_RL-TCPnet实验_BSD Socket服务器之TCP(FreeRTOS)为例。通过修改函数send_frame,以太网中断和时间基准更新任务都给网络主任务发事件标志,让其得到实时执行,从而实现高效的事件触发框架。
21.5.1 创建事件标志组
创建FreeRTOS的事件标志组:
EventGroupHandle_t xCreatedTCPnetGroup = NULL;
/*
*********************************************************************************************************
* 函 数 名: AppObjCreate
* 功能说明: 创建任务通信机制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
/* 创建事件标志组 */
xCreatedTCPnetGroup = xEventGroupCreate();
if(xCreatedTCPnetGroup == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
}
}
21.5.2 修改函数send_frame
修改ETH_STM32F4xx.c文件中的函数send_frame,此函数的末尾添加事件标志函数xEventGroupSetBits(宏定义FreeRTOS_EN在bsp.h文件里面使能,针对教程配套例子做的定义,方便管理。大家自己搞时,不必受此限制)。
/*
*********************************************************************************************************
* 函 数 名: send_frame
* 功能说明: 传递数据帧给MAC DMA发送描述符,并使能发送。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void send_frame (OS_FRAME *frame)
{
U32 *sp,*dp;
U32 i,j;
j = TxBufIndex;
/* 等待上一帧数据发送完成 */
while (Tx_Desc[j].CtrlStat & DMA_TX_OWN);
sp = (U32 *)&frame->data[];
dp = (U32 *)(Tx_Desc[j].Addr & ~);
/* 复制要发送的数据到DMA发送描述符中 */
for (i = (frame->length + ) >> ; i; i--)
{
*dp++ = *sp++;
}
/* 设置数据帧大小 */
Tx_Desc[j].Size = frame->length;
/* 发送描述符由DMA控制发送 */
Tx_Desc[j].CtrlStat |= DMA_TX_OWN;
if (++j == NUM_TX_BUF) j = ;
TxBufIndex = j;
/* 开始帧传输 */
/*
DMASR 以太网 DMA 状态寄存器
向ETH_DMASR寄存器[16:0]中的(未保留)位写入1会将其清零,写入 0 则不起作用。
位1 TPSS:发送过程停止状态 (Transmit process stopped status)
当发送停止时,此位置 1。
*/
ETH->DMASR = DSR_TPSS;
/*
DMATPDR 以太网DMA发送轮询请求寄存器
应用程序使用此寄存器来指示DMA轮询发送描述符列表。
位 31:0 TPD:发送轮询请求(Transmit poll demand)
向这些位写入任何值时,DMA都会读取ETH_DMACHTDR寄存器指向的当前描述符。如果
该描述符不可用(由CPU所有),则发送会返回到挂起状态,并将ETH_DMASR寄存器位2
进行置位。如果该描述符可用,则发送会继续进行。
*/
ETH->DMATPDR = ;
#if USE_FreeRTOS == 1
xEventGroupSetBits(xCreatedTCPnetGroup, 0x0001);
#endif
}
21.5.3 修改以太网中断函数
修改ETH_STM32F4xx.c文件中的以太网中断函数,此函数的末尾添加事件标志函数:xEventGroupSetBitsFromISR(宏定义FreeRTOS_EN在bsp.h文件里面使能,针对教程配套例子做的定义,方便管理。大家自己搞时,不必受此限制)。
/*
*********************************************************************************************************
* 函 数 名: ETH_IRQHandler
* 功能说明: 以太网中断,主要处理从MAC DMA接收描述符接收到的数据帧以及错误标志的处理。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void ETH_IRQHandler (void)
{
OS_FRAME *frame;
U32 i, RxLen;
U32 *sp,*dp;
i = RxBufIndex;
#if USE_FreeRTOS == 1
BaseType_t xResult;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
#endif
/* 循环所有接受描述符列表,遇到未接收到数据的退出循环 */
do
{
/*
#define DMA_RX_ERROR_MASK (DMA_RX_ES | DMA_RX_LE | DMA_RX_RWT | \
DMA_RX_RE | DMA_RX_CE)
有错误,放弃此帧数据,错误类型包含如下:
位15 DMA_RX_ES:错误汇总(Error summary),即CRC错误,接收错误,看门狗超时,延迟冲突等。
位12 DMA_RX_LE:长度错误(Length error)
该位置1时,指示接收帧的实际长度与长度/类型字段的值不符。该字段仅在帧类
型位(RDES0[5])复位后有效。
位4 DMA_RX_RWT:接收看门狗超时 (Receive watchdog timeout)
该位置1时,表示接收看门狗计时器在接收当前帧时超时,且当前帧在看门狗超
时后被截断了
位3 DMA_RX_RE: 接收错误 (Receive error)
该位置1时,表示在帧接收期间,当发出RX_DV信号时,会发出RX_ERR信号。
位1 DMA_RX_CE: CRC 错误(CRC error)
该位置1时,表示接收的帧发生循环冗余校验(CRC)错误。只有最后一个描述符
(RDES0[8])置1时,该字段才有效
*/
if (Rx_Desc[i].Stat & DMA_RX_ERROR_MASK)
{
goto rel;
}
/*
#define DMA_RX_SEG_MASK (DMA_RX_FS | DMA_RX_LS)
位9 FS:第一个描述符 (First descriptor)
该位置1时,指示此描述符包含帧的第一个缓冲区。如果第一个缓冲区的大小为0,则第二
个缓冲区将包含帧的帧头。如果第二个缓冲区的大小为0,则下一个描述符将包含帧的帧头。
位8 LS:最后一个描述符 (Last descriptor)
该位置1时,指示此描述符指向的缓冲区为帧的最后一个缓冲区。
下面的函数用于判断此帧数据是否只有一个缓冲,初始化接收描述符列表的时候,每个描述符仅设置了
一个缓冲。
*/
if ((Rx_Desc[i].Stat & DMA_RX_SEG_MASK) != DMA_RX_SEG_MASK)
{
goto rel;
}
RxLen = ((Rx_Desc[i].Stat >> ) & 0x3FFF) - ;
if (RxLen > ETH_MTU)
{
/* 数据包太大,直接放弃 */
goto rel;
}
/* 申请动态内存,RxLen或上0x80000000表示动态内存不足了不会调用函数sys_error() */
frame = alloc_mem (RxLen | 0x80000000);
/* 如果动态内存申请失败了,放弃此帧数据;成功了,通过函数put_in_queue存入队列中 */
if (frame != NULL)
{
sp = (U32 *)(Rx_Desc[i].Addr & ~);
dp = (U32 *)&frame->data[];
for (RxLen = (RxLen + ) >> ; RxLen; RxLen--)
{
*dp++ = *sp++;
}
put_in_queue (frame);
}
/* 设置此接收描述符继续接收新的数据 */
rel: Rx_Desc[i].Stat = DMA_RX_OWN;
if (++i == NUM_RX_BUF) i = ;
}
while (!(Rx_Desc[i].Stat & DMA_RX_OWN));
RxBufIndex = i;
/*
DMASR DMA的状态寄存器(DMA status register)
位7 RBUS:接收缓冲区不可用状态 (Receive buffer unavailable status)
此位指示接收列表中的下一个描述符由CPU所拥有,DMA无法获取。接收过程进入挂起状态。
要恢复处理接收描述符,CPU应更改描述符的拥有关系,然后发出接收轮询请求命令。如果
未发出接收轮询请求命令,则当接收到下一个识别的传入帧时,接收过程会恢复。仅当上一
接收描述符由DMA所拥有时,才能将ETH_DMASR[7]置1。
DMAIER的接收缓冲区不可用中断RBUIE是bit7,对于的接收缓冲区不可用状态在DMA状态寄存器中也是bit7。
*/
if (ETH->DMASR & INT_RBUIE)
{
/* 接收缓冲区不可用,重新恢复DMA传输 */
ETH->DMASR = ETH_DMASR_RBUS;
ETH->DMARPDR = ;
}
/*
DMASR DMA的状态寄存器(DMA status register)
这里实现清除中断挂起标志
位16 ETH_DMASR_NIS:所有正常中断 (Normal interrupt summary)
位15 ETH_DMASR_AIS:所有异常中断 (Abnormal interrupt summary)
位6 ETH_DMASR_RS :接收状态 (Receive status)
此位指示帧接收已完成,具体的帧状态信息已经包含在描述符中,接收仍保持运行状态。
*/
ETH->DMASR = ETH_DMASR_NIS | ETH_DMASR_AIS | ETH_DMASR_RS;
#if USE_FreeRTOS == 1
xResult = xEventGroupSetBitsFromISR(xCreatedTCPnetGroup, /* 事件标志组句柄 */
0x0001, /* 设置bit0 */
&xHigherPriorityTaskWoken );
/* 消息被成功发出 */
if( xResult != pdFAIL )
{
/* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
#endif
}
21.5.4 修改RL-TCPnet的时间基准更新任务
修改RL-TCPnet的时间基准更新任务,添加事件标志函数:xEventGroupSetBits。
/*
*********************************************************************************************************
* 函 数 名: vTaskStart
* 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新
* 形 参: pvParameters 是在创建该任务时传递的形参
* 返 回 值: 无
* 优 先 级: 6
*********************************************************************************************************
*/
static void vTaskStart(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = ;
/* 初始化RL-TCPnet */
init_TcpNet ();
/* 获取当前的系统时间 */
xLastWakeTime = xTaskGetTickCount();
while()
{
/* RL-TCPnet时间基准更新函数 */
timer_tick ();
xEventGroupSetBits(xCreatedTCPnetGroup, 0x0001);
/* vTaskDelayUntil是绝对延迟,vTaskDelay是相对延迟。*/
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
21.5.5 修改RL-TCPnet的网络主任务
修改RL-TCPnet的网络主任务,函数main_TcpNet的调用不再采用轮询方式,改成事件标志等待方式,即修改为如下形式:
/*
*********************************************************************************************************
* 函 数 名: vTaskTCPnet
* 功能说明: RL-TCPnet网络主任务
* 形 参: pvParameters 是在创建该任务时传递的形参
* 返 回 值: 无
* 优 先 级: 5
*********************************************************************************************************
*/
static void vTaskTCPnet(void *pvParameters)
{
while()
{
/* RL-TCPnet处理函数 */
xEventGroupWaitBits(xCreatedTCPnetGroup, /* 事件标志组句柄 */
0x0001, /* 等待被设置 */
pdTRUE, /* 退出前bit0被清除 */
pdFALSE, /* 设置为pdFALSE表示仅等待bit0被设置*/
portMAX_DELAY); /* 永久等待 */
while (main_TcpNet() == __TRUE);
}
}
21.5.6 最后特别注意优先级安排
最后,用户要特别注意几个任务的优先级安排,非常重要。
- RL-TCPnet的时间基准更新任务一定要是最高优先级任务。
- RL-TCPnet的网络主任务,即调用函数main_TcpNet的任务是次高优先级任务。
- 应用层的任务要比前面两个任务的优先级都低。
21.6 实验操作和实验例程说明
21.6.1 STM32F407开发板实验
由于本章节配套的例子是由第19章的例子简单修改而来的,所以操作说明和例程说明,直接看第19章即可。不同的地方仅仅是使能了本章节讲解的事件触发方式,本章节配套了如下三个例子:

21.6.2 STM32F429开发板实验
由于本章节配套的例子是由第19章的例子简单修改而来的,所以操作说明和例程说明,直接看第19章即可。不同的地方仅仅是使能了本章节讲解的事件触发方式,本章节配套了如下三个例子:

21.7 总结
本章节的项目实战性很高,望初学者务必掌握,在实际项目中也推荐采用事件触发方式。
【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架的更多相关文章
- 【安富莱TCPnet网络教程】HTTP通信实例
第41章 HTTP超文本传输协议基础知识 本章节为大家讲解HTTP(HyperText Transfer Protocol,超文本传输协议),从本章节开始,正式进入嵌入式Web的设计和学习. ...
- 【RL-TCPnet网络教程】第37章 RL-TCPnet之FTP客户端
第37章 RL-TCPnet之FTP客户端 本章节为大家讲解RL-TCPnet的FTP客户端应用,学习本章节前,务必要优先学习第35章的FTP基础知识.有了这些基础知识之后,再搞本章节会有事 ...
- 【RL-TCPnet网络教程】第36章 RL-TCPnet之FTP服务器
第36章 RL-TCPnet之FTP服务器 本章节为大家讲解RL-TCPnet的FTP服务器应用,学习本章节前,务必要优先学习第35章的FTP基础知识.有了这些基础知识之后,再搞本章节会有事 ...
- 【RL-TCPnet网络教程】第32章 RL-TCPnet之Telnet服务器
第32章 RL-TCPnet之Telnet服务器 本章节为大家讲解RL-TCPnet的Telnet应用,学习本章节前,务必要优先学习第31章的Telnet基础知识.有了这些基础知识之后,再搞 ...
- 【RL-TCPnet网络教程】第30章 RL-TCPnet之SNTP网络时间获取
第30章 RL-TCPnet之SNTP网络时间获取 本章节为大家讲解RL-TCPnet的SNTP应用,学习本章节前,务必要优先学习第29章的NTP基础知识.有了这些基础知识之后,再搞本章节会 ...
- 【RL-TCPnet网络教程】第28章 RL-TCPnet之DNS应用
第28章 RL-TCPnet之DNS应用 本章节为大家讲解RL-TCPnet的DNS应用,学习本章节前,务必要优先学习第27章的DNS基础知识.有了这些基础知识之后,再搞本章节会有事半功倍的 ...
- 【RL-TCPnet网络教程】第26章 RL-TCPnet之DHCP应用
第26章 RL-TCPnet之DHCP应用 本章节为大家讲解RL-TCPnet的DHCP应用,学习本章节前,务必要优先学习第25章的DHCP基础知识.有了这些基础知识之后,再搞本章节会有事半功 ...
- 【RL-TCPnet网络教程】第20章 RL-TCPnet之BSD Socket客户端
第20章 RL-TCPnet之BSD Socket客户端 本章节为大家讲解RL-TCPnet的BSD Socket,学习本章节前,务必要优先学习第18章的Socket基础知识.有了这些基础知 ...
- 【RL-TCPnet网络教程】第19章 RL-TCPnet之BSD Socket服务器
第19章 RL-TCPnet之BSD Socket服务器 本章节为大家讲解RL-TCPnet的BSD Socket,学习本章节前,务必要优先学习第18章的Socket基础知识.有了这些基础知 ...
随机推荐
- java内部类:成员内部类,静态内部类方法内部类,匿名内部类(A)
package cn.kecheng; /** * 在java中,一个文件可以定义多个类,文件名必须和public 类型的类的类名保持一致.这两个类是平行关系. * 在java中,一个类也可以定义在一 ...
- 【spark】dataframe常见操作
spark dataframe派生于RDD类,但是提供了非常强大的数据操作功能.当然主要对类SQL的支持. 在实际工作中会遇到这样的情况,主要是会进行两个数据集的筛选.合并,重新入库. 首先加载数据集 ...
- 初识C语言(二)
C语言标识符的命名规则 变量或者函数起的名字就是标识符,而且C语言的标识符有它自己的命名规则: 标识符的长度最好不要超过8位,因为在一些版本的C语言中标示符的前八位是有效的,所以当两个标识符的前八位相 ...
- OpenStack—neutron组件介绍与安装
neutron介绍 Neutron 概述:传统的网络管理方式很大程度上依赖于管理员手工配置和维护各种网络硬件设备:而云环境下的网络已经变得非常复杂,特别是在多租户场景里,用户随时都可能需要创建.修改和 ...
- 认识uWSGI、uwsgi、wsgi
WSGI协议 首先弄清下面几个概念: WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web ...
- md
> 引用# 一级标题## 二级标题,总共六级标题 - ul + li + li 1. 1232. 456 [链接](http://www.baidu.com) 函数返回值:Python 实现
对于经常调用的函数,特别是递归函数或计算密集的函数,记忆(缓存)返回值可以显着提高性能.而在 Python 里,可以使用字典来完成. 例子:斐波那契数列 下面这个计算斐波那契数列的函数 fib() 具 ...
- Java重头学
前言:这两周来上了腾讯课堂-软媒-JAVA架构师黄埔班的课程后,发现自己真心是一个菜鸟,Java基础知识和日常开发所需的知识还有很多很多.就此,我准备跟着这个课程重头学习Java,成为一名合格的Jav ...
- Docker 学习8 Dockerfile详解2
一.继续上章节Docker学习7 CMD命令后. 11.ENTRYPOINT a.容器启动后相当于会启动ENTRYPOINT + CMD 命令,CMD相当于参数传给entrypoint的 [root@ ...
- 20165319 Exp6 信息收集与漏洞扫描
实验过程 whois 在虚拟机Kali的终端输入命令:whois gitee.com,查询码云的域名注册信息. dig或nslookup域名查询 在kali终端输入dig 网址或nslookup 网址 ...