creep 发表于 2017-4-16 11:55:31

【NUCLEO-L496ZG评测】+ UCOSIII:事件标志组

         
      在写裸机程序中断经常会遇到的一种情况是某个状态的处理要和多个事件进行同步处理。比如操作SD卡写数据,我们可能要根据写卡的过程中出现的状态做出某种提示或者处理:例如写卡的过程SD卡出错、SD卡空间不足、SD卡被取出,当我们遇到这3种情况发生任何一个时都要提示用户SD卡出错并及时检测SD卡,针对这种情况我们在程序中常用的是设置一个或者多个变量进行置位然后去周期检测这个状态及时做出错误处理。
   在前面的软件定时器和信号量帖子中我们使用信号量和单一的事件(按键按下)进行同步,对于多事件的同步问题,在UCOSIII中我们可以使用事件标志组进行处理,若多个事件中其中一个事件发生时处理任务就要进行就绪叫做逻辑或(OR),若是需要所有的事件都发生时处理任务才就绪叫做逻辑与(AND).
事件标志组的使用框图总结如下:
                                    


1、事件标志组
    事件标志组的使用非常灵活,可以设置某些标志为0或者为1,部分有效(逻辑或)或者全部有效(逻辑与),在事件标志组的pend函数中可以设置下面的选项:
OS_OPT_PEND_FLAG_CLR_ALL   You will wait for ALL bits in 'flags' to be clear (0)
OS_OPT_PEND_FLAG_CLR_ANY   You will wait for ANY bitin 'flags' to be clear (0)
OS_OPT_PEND_FLAG_SET_ALL   You will wait for ALL bits in 'flags' to be set   (1)
OS_OPT_PEND_FLAG_SET_ANY   You will wait for ANY bitin 'flags' to be set   (1)   在任务取得有效的事件标志组之后,任务就要做出某种处理,大部分时候这个处理之后就要清除置位的事件标志组,比如上面的写SD卡出错,提示用户一次就可以了,但是有的时候这个事件可能比较严重需要重复提示直到设备关机或者彻底修复,这个时候一旦事件标志组置位后就不会被清除,对应的任务就会一直有效做出某种提示(比如蜂鸣器长响),这种配置取决要不要设置下面的这个选项,设置后事件标志被任务取得后仅有效一次,不设置这个选项任务取得事件标志有效后会一直有效进行某种处理。
OS_OPT_PEND_FLAG_CONSUME   除了上面的选项,pend事件标志组的任务还可以设置要不要block的选项,如果设置no_block,既是获取不到事件标志有效标志任务也不会被block,这几个选项可以根据需要进行组合配置。

   事件标志组的使用和裸机程序很相似,首先定义几个状态的宏定义然后定义一个事件标志组变量:
//事件标志组宏定义
#define LPUART_RVE       (OS_FLAGS)0x0001    //串口收到数据事件标志
#define USRKEY_PRE       (OS_FLAGS)0x0002    //用户按键按下事件标志
//定义事件标志组
OS_FLAG_GRP MyEventFlagGrp;

和其他内核对象一样,定义之后使用之前要创建事件标志组,其实就是初始化一下。注意第3个参数设置为0表示事件标志组初始化为0
//创建事件标志组
OSFlagCreate(&MyEventFlagGrp,
            "My Event Flag Group",
            (OS_FLAGS)0,
            &err);       
2、逻辑与(AND)模式

为了测试事件标志组,我使用串口中断收到数据和用户按键按下这2个事件进行模拟。测试中在一个任务中pend串口接收数据和用户按下事件标志是不是同时置位(设置为1)。代码如下,设置的是事件标志单次有效(OS_OPT_PEND_FLAG_CONSUME)。
void task1_task(void *p_arg)
{
        OS_ERR err;
        CPU_TSts;
        while(1)
        {
    CPU_SR_ALLOC();
    while(1)
    {
                        OSFlagPend(&MyEventFlagGrp,
                                                                LPUART_RVE + USRKEY_PRE,
                                                                (OS_TICK )0,
                                                                (OS_OPT)OS_OPT_PEND_FLAG_SET_ALL | (OS_OPT)OS_OPT_PEND_FLAG_CONSUME,
                                                                &ts,
                                                                &err);
      OS_CRITICAL_ENTER();
      printf("LPUART_RVE && USRKEY_PRE SET \r\n");
      OS_CRITICAL_EXIT();
      OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TMR_PERIODIC, &err);
    }               
        }
}在串口中断接收完成回调函数中post串口事件标志位,设置串口标志位为1,并打印信息标志串口事件完成。

//串口接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *_UartHandle)
{
        OS_ERR err;
        //post串口接收数据完成事件
        OSFlagPost(&MyEventFlagGrp,
                                                LPUART_RVE,
                                                (OS_OPT)OS_OPT_POST_FLAG_SET,
                                                &err);
printf("LPUART_RVE SET \r\n");
        //设置接收下一个数据包
        if(HAL_UART_Receive_IT(&UartHandle, (uint8_t *)aRxBuffer, RXBUFFERSIZE) != HAL_OK)
{
    while(1);
}
}同样在按键事件中post对应的按键事件并置位1然后打印用户按键事件完成:
//按键定时器回调函数
void KeyTmrCallback(void *p_tmr,void *p_arg)
{
        OS_ERR err;
//定义USER键按下的时间计数,每次为10ms
static uint8_t KeyUser_PressCnt = 0;
static uint8_t KeyUser_Release= ENABLE;
if(KEY_USER)
{
    //按键按下
    KeyUser_PressCnt++;
    if((KeyUser_PressCnt > 10) && (KeyUser_Release == ENABLE))
    {
                        OSFlagPost(&MyEventFlagGrp,
                                        USRKEY_PRE,
                                        (OS_OPT)OS_OPT_POST_FLAG_SET,
                                        &err);
               printf("USRKEY_PRE SET \r\n");
   KeyUser_PressCnt = 0;
   KeyUser_Release= DISABLE;
    }
}
else
{
    //按键松开
    KeyUser_PressCnt = 0;
    KeyUser_Release= ENABLE;
}
}       测试结果如下。首先是我按下用户按键打印相应的信息,然后用串口发送了10个字节的数据并打印串口接收到的信息,紧接着就打印了
任务事件标志为置位的信息,我这里设置的是事件标志位单次有效所以置位输出一次打印信息。



如果去掉事件标志为单次有效,对应的测试如下,同样的操作之后可以看到取到事件标志位的任务会一直打印信息。




3、逻辑或模式

逻辑或模式要设置任务的pend函数op选项为任何一个事件标志置位即可。对应的代码如下:根据pend返回值中可以判断是那个任务被置位
然后打印对应的事件标志。
void task1_task(void *p_arg)
{
        OS_ERR err;
        CPU_TSts;
OS_FLAGS ret;
        while(1)
        {
    CPU_SR_ALLOC();
    while(1)
    {
                        ret = OSFlagPend(&MyEventFlagGrp,
                                                                LPUART_RVE + USRKEY_PRE,
                                                                (OS_TICK )0,
                                                                (OS_OPT)OS_OPT_PEND_FLAG_SET_ANY | (OS_OPT)OS_OPT_PEND_FLAG_CONSUME,
                                                                &ts,
                                                                &err);
      OS_CRITICAL_ENTER();   
   
      if(ret & LPUART_RVE)
      {      
      printf("LPUART_RVE || USRKEY_PRE SET by LPUART_RVE \r\n");
      }
      else
      {
         printf("LPUART_RVE || USRKEY_PRE SET by USRKEY_PRE \r\n");
      }
      OS_CRITICAL_EXIT();
      OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TMR_PERIODIC, &err);
    }               
        }
}


测试如下,可以看到无论是按键按下还是串口接收到数据都会在pend事件标志组的任务打印信息,并会提示是那个事件触发的任务。




通过上面简单的外设操作我们可以看到事件标志组的使用非常方便灵活,和裸机上使用全局变量置位进行判断的方法相比,UCOSIII的事件标志组模式能通过任务的调度更快的响应事件的发生,这在某些场合是非常重要的。需要注意的是上面测试代码串口接收中断设置的是10个字节,测试的时候可以根据具体的需要进行修改。

测试代码:




推荐阅读:

【NUCLEO-L496ZG评测】+ UCOSIII:内存管理和消息队列

【NUCLEO-L496ZG评测】+ UCOSIII:软件定时器和信号量

【NUCLEO-L496ZG评测】+ UCOSIII简单移植









哈佛祖安智 发表于 2017-4-16 12:18:23

前来顶帖

creep 发表于 2017-4-16 14:01:55

哈佛祖安智 发表于 2017-4-16 12:18
前来顶帖

感谢支持:handshake。
页: [1]
查看完整版本: 【NUCLEO-L496ZG评测】+ UCOSIII:事件标志组