前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架

【RL-TCPnet网络教程】第21章 RL-TCPnet之高效的事件触发框架

作者头像
Simon223
发布2018-09-04 09:42:01
2.5K0
发布2018-09-04 09:42:01
举报

第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 (1)

     {

         /* RL-TCPnet处理函数 */

         main_TcpNet();

         os_dly_wait(2);

     }

}

这种方式有如下两个缺点:

  1. 没有网络通信时也要周期性的执行。
  2. 实时响应差,因为在延迟的这段时间内有网络数据包的话,数据包得不到及时的处理。

另外特别注意一点,一些不理解的读者会问,我们的底层函数里面不是有以太网中断吗,为什么还会不能实时性响应呢?根本的原因就在,虽然有以太网中断,但是中断后,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[0];

     dp = (U32 *)(Tx_Desc[j].Addr & ~3);

 

     /* 复制要发送的数据到DMA发送描述符中 */

     for (i = (frame->length + 3) >> 2; i; i--)

     {

         *dp++ = *sp++;

     }

    

     /* 设置数据帧大小 */

     Tx_Desc[j].Size      = frame->length;

    

     /* 发送描述符由DMA控制发送 */

     Tx_Desc[j].CtrlStat |= DMA_TX_OWN;

    

     if (++j == NUM_TX_BUF) j = 0;

     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 = 0;

    

     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 >> 16) & 0x3FFF) - 4;

         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 & ~3);

              dp = (U32 *)&frame->data[0];

              for (RxLen = (RxLen + 3) >> 2; RxLen; RxLen--)

              {

                   *dp++ = *sp++;

              }

              put_in_queue (frame);

         }

        

          /* 设置此接收描述符继续接收新的数据 */

         rel: Rx_Desc[i].Stat = DMA_RX_OWN;

 

         if (++i == NUM_RX_BUF) i = 0;

     }

     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 = 0;

     }

    

     /*

        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 (100);

    

    while(1)

    {

         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 (1)

     {

          /* 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      )0,

                  (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[0];

     dp = (U32 *)(Tx_Desc[j].Addr & ~3);

 

     /* 复制要发送的数据到DMA发送描述符中 */

     for (i = (frame->length + 3) >> 2; i; i--)

     {

         *dp++ = *sp++;

     }

    

     /* 设置数据帧大小 */

     Tx_Desc[j].Size      = frame->length;

    

     /* 发送描述符由DMA控制发送 */

     Tx_Desc[j].CtrlStat |= DMA_TX_OWN;

    

     if (++j == NUM_TX_BUF) j = 0;

     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 = 0;

    

#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 >> 16) & 0x3FFF) - 4;

         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 & ~3);

              dp = (U32 *)&frame->data[0];

              for (RxLen = (RxLen + 3) >> 2; RxLen; RxLen--)

              {

                   *dp++ = *sp++;

              }

              put_in_queue (frame);

         }

        

          /* 设置此接收描述符继续接收新的数据 */

         rel: Rx_Desc[i].Stat = DMA_RX_OWN;

 

         if (++i == NUM_RX_BUF) i = 0;

     }

     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 = 0;

     }

    

     /*

        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 (1)

     { 

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

         timer_tick ();

        

         OSFlagPost ((OS_FLAG_GRP  *)&FLAG_TCPnet,

                       (OS_FLAGS      )0x0001,

                       (OS_OPT        )OS_OPT_POST_FLAG_SET,

                       (OS_ERR       *)&err); 

        

         OSTimeDly(100, 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(1)

     {

         /* RL-TCPnet处理函数 */

         OSFlagPend ((OS_FLAG_GRP  *)&FLAG_TCPnet,

                       (OS_FLAGS      )0x0001,

                       (OS_TICK       )0,

                       (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[0];

     dp = (U32 *)(Tx_Desc[j].Addr & ~3);

 

     /* 复制要发送的数据到DMA发送描述符中 */

     for (i = (frame->length + 3) >> 2; i; i--)

     {

         *dp++ = *sp++;

     }

    

     /* 设置数据帧大小 */

     Tx_Desc[j].Size      = frame->length;

    

     /* 发送描述符由DMA控制发送 */

     Tx_Desc[j].CtrlStat |= DMA_TX_OWN;

    

     if (++j == NUM_TX_BUF) j = 0;

     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 = 0;

    

#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 >> 16) & 0x3FFF) - 4;

         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 & ~3);

              dp = (U32 *)&frame->data[0];

              for (RxLen = (RxLen + 3) >> 2; RxLen; RxLen--)

              {

                   *dp++ = *sp++;

              }

              put_in_queue (frame);

         }

        

          /* 设置此接收描述符继续接收新的数据 */

         rel: Rx_Desc[i].Stat = DMA_RX_OWN;

 

         if (++i == NUM_RX_BUF) i = 0;

     }

     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 = 0;

     }

    

     /*

        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 = 100;

    

     /* 初始化RL-TCPnet */

     init_TcpNet ();

    

     /* 获取当前的系统时间 */

    xLastWakeTime = xTaskGetTickCount();

    

    while(1)

    {   

         /* 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(1)

    {

         /* 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 总结

本章节的项目实战性很高,望初学者务必掌握,在实际项目中也推荐采用事件触发方式。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-08-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第21章       RL-TCPnet之高效的事件触发框架
    • 21.1  初学者重要提示
      • 21.2  高效的事件触发框架说明
        • 21.3  RTX系统实例修改方法
          • 21.3.1 修改函数send_frame
          • 21.3.2 修改以太网中断函数
          • 21.3.3 修改RL-TCPnet的时间基准更新任务
          • 21.3.4 修改RL-TCPnet的网络主任务
          • 21.3.5 最后特别注意优先级安排
        • 21.4 uCOS-III系统实例修改方法
          • 21.4.1 创建事件标志组
          • 21.4.2 修改函数send_frame
          • 21.4.3 修改以太网中断函数
          • 21.4.4 修改RL-TCPnet的时间基准更新任务
          • 21.4.5 修改RL-TCPnet的网络主任务
          • 21.4.6 最后特别注意优先级安排
        • 21.5 FreeRTOS系统实例修改方法
          • 21.5.1 创建事件标志组
          • 21.5.2 修改函数send_frame
          • 21.5.3 修改以太网中断函数
          • 21.5.4 修改RL-TCPnet的时间基准更新任务
          • 21.5.5 修改RL-TCPnet的网络主任务
          • 21.5.6 最后特别注意优先级安排
        • 21.6 实验操作和实验例程说明
          • 21.6.1 STM32F407开发板实验
          • 21.6.2 STM32F429开发板实验
        • 21.7 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档