creep 发表于 2017-4-8 21:12:46

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

本帖最后由 creep 于 2017-4-8 21:16 编辑

            
   在上个贴子里面我简单的移植测试了UCOSIII在Nucleo-L496上的效果,下面我们就简单测试下UCOSIII的软件定时器和信号量的使用,通过简单的例子来熟悉这2个功能的用法。


   1、软件定时器

UCOSIII上的软件定时器是一个向下计数的定时器,当计数器为0时为调用一个用户设置的回调函数,在这个回调函数中可以进行一些点灯,输出信息等操作,需要注意的是这个回调函数里面不能调用一些block或者pend性质的函数。理论上可以定义很多个软件定时器,这主要取决你MCU的内存了。
软件定时器的分辨率取决于系统的滴答计时器的分辨率和定时器本身的频率设置,   软件定时器在UCOSIII内存是作为一个任务存在的,在初始化OS时创建这个任务,定时器任务的优先级设置比一般的任务都高(默认为2),下面是UCOSIII的Tick和软件定时器的优先级、频率、堆栈设置

                                                            /* ------------------------ TICKS ----------------------- */
#defineOS_CFG_TICK_RATE_HZ            1000u               /* Tick rate in Hertz (10 to 1000 Hz)                     */
#defineOS_CFG_TICK_TASK_PRIO             1u               /* Priority                                             */
#defineOS_CFG_TICK_TASK_STK_SIZE       128u               /* Stack size (number of CPU_STK elements)                */


                                                            /* ----------------------- TIMERS ----------------------- */
#defineOS_CFG_TMR_TASK_PRIO             2u               /* Priority of 'Timer Task'                               */
#defineOS_CFG_TMR_TASK_RATE_HZ         100u               /* Rate for timers (100 Hz Typ.)                           */
#defineOS_CFG_TMR_TASK_STK_SIZE      128u               /* Stack size (number of CPU_STK elements)                */
定时器实现的原理是在过定时器任务中不停的判断设置的定时器的时间是否递减到0,如果递减到0就调用定时器的回调函数,然后根据定时器是周期定时器还是一次性的定时器重新装载定时器的计数值。定时器的任务函数如下:

voidOS_TmrTask (void*p_arg)
{
    OS_ERR               err;
    OS_TMR_CALLBACK_PTRp_fnct;
    OS_TMR            *p_tmr;
    OS_TMR            *p_tmr_next;
    CPU_TS               ts;
    CPU_TS               ts_start;
    CPU_TS               ts_delta;



    (void)&p_arg;                                                /* Not using 'p_arg', prevent compiler warning       */
    while (DEF_ON) {
      (void)OSTaskSemPend((OS_TICK )0,                         /* Wait for signal indicating time to update tmrs    */
                            (OS_OPT)OS_OPT_PEND_BLOCKING,
                            (CPU_TS *)&ts,
                            (OS_ERR *)&err);


      OS_TmrLock();
      ts_start = OS_TS_GET();
      OSTmrTickCtr++;                                          /* Increment the current time                        */
      p_tmr    = OSTmrListPtr;
      while (p_tmr != (OS_TMR *)0) {                           /* Update all the timers in the list               */
            OSSchedLock(&err);
            (void)&err;
            p_tmr_next = p_tmr->NextPtr;
            p_tmr->Remain--;
            if (p_tmr->Remain == 0) {
                if (p_tmr->Opt == OS_OPT_TMR_PERIODIC) {
                  p_tmr->Remain = p_tmr->Period;               /* Reload the time remaining                         */
                } else {
                  OS_TmrUnlink(p_tmr);                         /* Remove from list                                  */
                  p_tmr->State = OS_TMR_STATE_COMPLETED;       /* Indicate that the timer has completed             */
                }
                p_fnct = p_tmr->CallbackPtr;                     /* Execute callback function if available            */
                if (p_fnct != (OS_TMR_CALLBACK_PTR)0) {
                  (*p_fnct)((void *)p_tmr,
                              p_tmr->CallbackPtrArg);
                }
            }
            p_tmr = p_tmr_next;
            OSSchedUnlock(&err);
            (void)&err;
      }

      ts_delta = OS_TS_GET() - ts_start;                      /* Measure execution time of timer task            */

      if (OSTmrTaskTimeMax < ts_delta) {
            OSTmrTaskTimeMax = ts_delta;
      }

      OS_TmrUnlock();
    }
}
      软件定时器中使用了任务间的信号量,通过Pend这个信号量来判断是不是要进行一次定时器计数减1判断,这个任务间的信号量在系统的TimeTick 函数中被Post,这个函数在systick中断被调用,在这个函数中同样进行了任务调度的任务间信号量Post。TimeTick 函数如下:

voidOSTimeTick (void)
{
            
    OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK,             /* Post to ISR queue*/
                (void      *)&OSRdyList,
                (void      *) 0,
                (OS_MSG_SIZE) 0u,
                (OS_FLAGS   ) 0u,
                (OS_OPT   ) 0u,
                (CPU_TS   ) ts,
                (OS_ERR    *)&err);

#else

   (void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB,            /* Signal tick task*/
                     (OS_OPT) OS_OPT_POST_NONE,
                     (OS_ERR *)&err);

   (void)err;

#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
    OS_SchedRoundRobin(&OSRdyList);
#endif

#if OS_CFG_TMR_EN > 0u
    OSTmrUpdateCtr--;
    if (OSTmrUpdateCtr == (OS_CTR)0u) {
      OSTmrUpdateCtr = OSTmrUpdateCnt;
      OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB,            /* Signal timer task*/
                      (OS_OPT) OS_OPT_POST_NONE,
                      (OS_ERR *)&err);
    }
#endif

#endif
}定时器的使用是先声明然后定时器再启动即可,定义的时候要写回调函数,主要代码如下:
//定义软件定时器
OS_TMR led_Timer;
OS_TMR uart_Timer;

//创建定时器
OSTmrCreate((OS_TMR*)&led_Timer,
            (CPU_CHAR*)"led_timer",
            (OS_TICK) 0,
            (OS_TICK)10,                        //100ms 闪烁一次
            (OS_OPT)OS_OPT_TMR_PERIODIC,
            (OS_TMR_CALLBACK_PTR)LedTmrCallback,
            (void *) "This is led_tmr",
            (OS_ERR *)&err);

OSTmrCreate((OS_TMR*)&uart_Timer,
            (CPU_CHAR*)"uart_timer",
            (OS_TICK) 0,
            (OS_TICK)100,
            (OS_OPT)OS_OPT_TMR_PERIODIC,
            (OS_TMR_CALLBACK_PTR)UartTmrCallback,
            (void *) "This is uart_tmr",
            (OS_ERR *)&err);   
                        

//启动定时器
OSTmrStart((OS_TMR*)&led_Timer,(OS_ERR*)&err);   
OSTmrStart((OS_TMR*)&uart_Timer,(OS_ERR*)&err);   定时器的回调函数就是闪烁led灯和串口输出信息:
//软件定时器回调函数
void LedTmrCallback(void *p_tmr,void *p_arg)
{
    staticuint8_t sta = ON;
    LED1(sta);
    LED2(sta);
    LED3(sta);
                sta = !sta;
}

void UartTmrCallback(void *p_tmr,void *p_arg)
{
    CPU_SR_ALLOC();
    OS_CRITICAL_ENTER();
    printf("Please do not respond,the message from Uart_Tmr!!\r\n");
          OS_CRITICAL_EXIT();
}

串口输出的信息如下,可以看到定时器优先于任务2和任务3输出,因为定时器的优先级高于这2个任务




2 、信号量

在上面的定时器提到了任务间的信号量,除此之外还有系统的信号量,这主要用于用于任务间或者事件的同步。UCOSIII主要有信号量和事件标志2种同步的机制。下面我用信号量同步按键事件的发生的处理。Nucleo-L496上面有个user按键,我用软件定时器扫描按键按下的状态,当检测到一个按键按下后就Post一个信号量给另一个串口输出任务,该任务打印信号量的个数表示按键按下的次数。

信号的也是先声明后定义,定义的时候可以设置初始化信号量的个数,为了演示要默认设置信号量个数为5,主要代码如下:

//定义软件定时器
OS_TMR led_Timer;
OS_TMR key_Timer;
//定义信号量
OS_SEM Key_Sem;

//创建LED软件定时器
OSTmrCreate((OS_TMR*)&led_Timer,
            (CPU_CHAR*)"led_timer",
            (OS_TICK) 0,
            (OS_TICK)10,                        //100ms 闪烁一次
            (OS_OPT)OS_OPT_TMR_PERIODIC,
            (OS_TMR_CALLBACK_PTR)LedTmrCallback,
            (void *) "This is led_tmr",
            (OS_ERR *)&err);
//创建KEY软件定时器
OSTmrCreate((OS_TMR*)&key_Timer,
            (CPU_CHAR*)"key_timer",
            (OS_TICK) 0,
            (OS_TICK)1,                        //10ms 中断一次
            (OS_OPT)OS_OPT_TMR_PERIODIC,
            (OS_TMR_CALLBACK_PTR)KeyTmrCallback,
            (void *) "This is key_tmr",
            (OS_ERR *)&err);            
      
//创建信号量
OSSemCreate((OS_SEM * )&Key_Sem,
             (CPU_CHAR *) "KeyScanSem",
             (OS_SEM_CTR) 5,                           //创建值为5的信号量
             (OS_ERR *) &err);
                         按键的软定时器的回调函数如下:主要用于post信号量
//按键定时器回调函数
void KeyTmrCallback(void *p_tmr,void *p_arg)
{
//定义USER键按下的时间计数,每次为10ms
static uint8_t KeyUser_PressCnt = 0;
static uint8_t KeyUser_Release= ENABLE;
OS_ERR err;
if(KEY_USER)
{
    //按键按下
    KeyUser_PressCnt++;
    if((KeyUser_PressCnt > 10) && (KeyUser_Release == ENABLE))
    {
   OSSemPost((OS_SEM *) &Key_Sem, (OS_OPT) OS_OPT_PEND_ABORT_ALL, (OS_ERR *) &err);
   KeyUser_PressCnt = 0;
   KeyUser_Release= DISABLE;
    }
}
else
{
    //按键松开
    KeyUser_PressCnt = 0;
    KeyUser_Release= ENABLE;
}
}

串口任务如下,用于pend按键信号量,当检测到信号时先取得一个信号量,然后在串口中输出剩余的信号量个数,如果输出为0表示有一个信号量,代码如下:
void task1_task(void *p_arg)
{
      OS_ERR err;
      while(1)
      {
    CPU_SR_ALLOC();
    uint8_t buff;
    while(1)
    {
      OSSemPend((OS_SEM *) &Key_Sem, (OS_TICK) 0, (OS_OPT) OS_OPT_PEND_BLOCKING, (CPU_TS *) 0, (OS_ERR *) &err);
      OS_CRITICAL_ENTER();
      sprintf((char*)buff,"KeySemCnt:%d\r\n",Key_Sem.Ctr);
      printf((char*)buff);
      OS_CRITICAL_EXIT();
      OSTimeDlyHMSM(0, 0, 2, 0, OS_OPT_TMR_PERIODIC, &err);
    }               
      }
}
下载程序后串口先输出初始化时候的设置为5的信号量的状态如下,串口的任务2s执行一次。可以看到信号量的值在递减到0。



然后缓慢的按下user按键可以看到每次都可以获得一个信号量然后递减为0,如果频繁快速的按下user按键可以看到信号量在增加,停止按uesr信号量低递减直到为0,这样我们就可以在空闲的时候处理按键的事件了,所有有效的按键都不会丢失。



同时LED在软定时器中闪烁如下,闪烁频率为100ms,貌似闪烁太快了,转下码就看不出了。(┬_┬)




上面简单的测试了软件定时器和信号量,测试过程和测试代码都比较简陋,实际使用要多测试可靠性。


测试代码:





推荐阅读:
【NUCLEO-L496ZG评测】+ UCOSIII简单移植












Paderboy 发表于 2017-4-8 22:28:46

:loveliness::loveliness:沙发,学习了。。

QianFan 发表于 2017-4-15 12:40:57

:lol:lol:lol:lol,橙子哥,这个gif使用什么工具做的?

creep 发表于 2017-4-15 13:14:53

QianFan 发表于 2017-4-15 12:40
,橙子哥,这个gif使用什么工具做的?




上面2个都可以。

QianFan 发表于 2017-4-17 12:37:18

creep 发表于 2017-4-15 13:14
上面2个都可以。

:lol好东西
页: [1]
查看完整版本: 【NUCLEO-L496ZG评测】+ UCOSIII:软件定时器和信号量