10. µCOS-Ⅲ的软件定时器

一、软件定时器的简介

  软件定时器指的是由软件实现的定时器,并不是由具体的硬件组成,µC/OS-Ⅲ 提供的软件定时器是一种向下计数的定时器,当设置好一个定时器超时时间后,软件定时器便从这个定时器时间对应的数值开始往下递减,每经过一个系统时钟节拍,软件定时器的计数值就往下减一,当软件定时器的计数值减到 0 的时候,软件定时器就会自动地去调用其对应的超时回调函数。

  µC/OS-Ⅲ 提供了两种软件定时器:

  • 单次定时器:单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数,不会自动重新开启定时,不过可以被手动重新开启。
  • 周期定时器:周期定时器的一旦启动以后就会在执行完回调函数以后自动的重新启动 ,从而周期地执行其软件定时器回调函数。

  软件定时器共有 4 种状态:

  • 未使用态:当软件定时器被定义但还未被创建或软件定时器被删除时, 软件定时器就处于未使用态。
  • 停止态:当软件定时器被创建但还未开启定时器或被停止时,软件定时器就处于停止态。
  • 运行态:运行态的定时器,当指定时间到达之后,它的超时回调函数会被调用。
  • 完成态:当单次定时器定时超时后,软件定时器处于完成态。

新创建的软件定时器处于停止状态 ,也就是未运行的。

如需使能软件定时器,需将 OS_CFG_TMR_EN 配置项配置成 1。

二、软件定时器定时频率

  软件定时器的频率由宏 OS_CFG_TMR_TASK_RATE_HZ 决定,要注意软件定时器的频率并不等于系统时钟节拍的频率,但软件定时器是依靠系统节拍实现定时的,所以需要进行换算:

\[OSTmrToTicksMult = \frac{OSCfg\_TickRate\_Hz}{OSCfg\_TmrTaskRate\_Hz} \]

  可以看到,在初始化软件定时器功能的时候,计算了一个系数(OSTmrToTicksMult),这个系数就是用于将软件定时器的时间转换为系统时钟节拍的节拍数,在计算这一系数的时候使用到了两个变量,分别为 OSCfg_TickRate_HzOSCfg_TmrTaskRate_Hz,这两个变量又分别由文件 os_cfg_app.h 中的两个宏定义定义,分别为宏定义 OS_CFG_TICK_RATE_HZ 和宏定义 OS_CFG_TMR_TASK_RATE_HZ。正是因为软件定时器的定时频率与系统时钟节拍的节拍频率不同,才导致如果设置一个软件定时器定时器的时间为 10,但在开启软件定时器后,软件定时器并不一定会在 10 个系统时钟节拍后超时,而是会在 10 * OSTmrToTicksMult 个系统时钟节拍后超时。

三、软件定时器时序图

3.1、单次定时器

单次定时器

重新触发单次定时器

3.2、周期定时器

周期定时器1

周期定时器2

四、软件定时器相关API函数

4.1、软件定时器的控制块结构体

  下面来看一下软件定时器的控制块结构体,软件定时器的控制块结构体定义在文件 os.h 中,具体的代码如下所示:

struct  os_tmr {
#if (OS_OBJ_TYPE_REQ > 0u)                          // 此宏用于使能检查内核对象的类型
    // 对象类型,在软件定时器初始化时,软件定时器的类型会被初始化为OS_OBJ_TYPE_TMR
    OS_OBJ_TYPE          Type;
#endif
#if (OS_CFG_DBG_EN > 0u)                            // 此宏用于使能代码调试功能
    // 如果使能了代码调试功能,那么就会为每一个软件定时器赋一个名称,方便调试
    CPU_CHAR            *NamePtr;
#endif
    OS_TMR_CALLBACK_PTR  CallbackPtr;               // 超时回调函数
    void                *CallbackPtrArg;            // 时回调函数参数
    OS_TMR              *NextPtr;                   // 指向软件定时器链表中下一个软件定时器的指针
    OS_TMR              *PrevPtr;                   // 指向软件定时器链表中上一个软件定时器的指针
    OS_TICK              Remain;                    // 剩余超时时间
    OS_TICK              Dly;                       // 开启延迟时间
    OS_TICK              Period;                    // 周期时间
    OS_OPT               Opt;                       // 操作选项,单次定时器或周期定时器
    OS_STATE             State;                     // 状态
  
#if (OS_CFG_DBG_EN > 0u)                            // 此宏用于使能代码调试功能
    OS_TMR              *DbgPrevPtr;
    OS_TMR              *DbgNextPtr;
#endif
};
  1. 成员变量 CallbackPtr

  该成员变量是一个 指向软件定时器超时回调函数的指针,当软件定时器定时超时的时候,就会调用该成员变量指向的函数。

  软件定时器的超时回调函数是完全由用户自定义的,并且会在软件定时器定时器超时的时候被自动地调用,且可以设置函数的传入参数。通过软件定时器的回调函数,可以实现点亮或关闭一个 LED 灯、启动电机等一系列的操作。但是要特别注意的是,绝对不能在软件定时器的超时回调函数中使用可能会导致任务被阻塞、被挂起甚至被删除的函数,例如调用函数 OSTimeDly()、函数 OSTimeDlyHMSM()、函数 OSxxxPend() 等任何可能导致软件定时器任务被阻塞、被挂起甚至被删除的函数。

软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的 API 函数。

  1. 成员变量 CallbackPtrArg

  该成员变量是一个 指向软件定时器超时回调函数参数的指针,当软件定时器超时的时候,就会将该成员变量指向的参数参入软件定时器的超时回调函数,通过这一成员变量,可令多个软件定时器使用同一个超时回调函数,只要让软件定时器的超时回调函数传入指向该软件定时器控制块结构体的指针,接着在软件定时器的超时回调函数中判断入参是指向哪一个软件定时器控制块结构体的指针,即可进行不同的处理。

  1. 成员变量 Opt

  该成员变量在软件定时器被创建的时候被初始化,并且只有两种可能的值,分别为 OS_OPT_TMR_ONE_SHOTOS_OPT_TMR_PERIODIC,其中 OS_OPT_TMR_ONE_SHOT 代表该软件定时器是 单次定时器,单次定时器在开启定时后,只会超时一次,便停止定时了,若要单次定时器再次定时,需要再次调用软件定时器的开启 API 函数开启单次定时器;OS_OPT_TMR_PERIODIC 代表该软件定时器是 周期定时器,周期定时器在超时时,会自动地根据设置好的定时周期进行重新定时,若不调用相关 API 函数停止周期定时器,那么周期定时器会自动地开启定时和超时。

  1. 成员变量 Remain

  该成员变量就是 软件定时器的定时器计数器,当软件定时器开启定时器时,会为该成员变量赋一个初值,开启定时器后,软件定时器任务会在每经过一个系统时钟节拍就将该成员变量递减 1,当该成员变量第一次从初值减到 0 时,说明该软件定时器超时了。

  1. 成员变量 Dly

  该成员变量定义了 软件定时器的延时开启时间,当调用相关 API 函数开启软件定时器定时器后,软件定时器会延时该成员变量定义的系统时钟节拍数后,才会真正地开始定时。

  1. 成员变量 Period

  该成员变量定义了 周期定时器的定时周期,该成员变量仅对周期定时器有效,当周期定时器超时后,会将成员变量 Remain 重新赋值为该成员变量定义的值,然后重新开启该软件定时器的定时器。

4.2、创建一个软件定时器

void  OSTmrCreate (OS_TMR               *p_tmr,             // 指向软件定时器结构体的指针
                   CPU_CHAR             *p_name,            // 指向作为软件定时器名的 ASCⅡ字符串的指针
                   OS_TICK               dly,               // 软件定时器的开启延时时间
                   OS_TICK               period,            // 周期定时器的定时周期时间
                   OS_OPT                opt,               // 函数操作选项(创建的是单次定时器还是周期定时器)
                   OS_TMR_CALLBACK_PTR   p_callback,        // 指向软件定时器超时回调函数的指针
                   void                 *p_callback_arg,    // 指向软件定时器超时回调函数参数的指针
                   OS_ERR               *p_err);            // 指向接收错误代码变量的指针

  函数 OSTmrCreate() 的错误代码描述,如下所示:

OS_ERR_NONE                             // 成功创建一个软件定时器
OS_ERR_ILLEGAL_CREATE_RUN_TIME          // 在系统运行过程中非法创建内核对象
OS_ERR_OBJ_PTR_NULL                     // 指向软件定时器结构体的指针为空
OS_ERR_OPT_INVALID                      // 无效的函数操作选项
OS_ERR_TMR_INVALID_CALLBACK             // 无效的软件定时器超时处理函数
OS_ERR_TMR_INVALID_DLY                  // 无效的软件定时器开启延时时间
OS_ERR_TMR_INVALID_PERIOD               // 无效的周期定时器定时周期时间
OS_ERR_TMR_ISR                          // 在中断中非法调用该函数

4.3、删除一个软件定时器

// 返回值:软件定时器删除是否成功,OS_TRUE:软件定时器删除成功;OS_FALSE:软件定时器删除失败
CPU_BOOLEAN  OSTmrDel (OS_TMR  *p_tmr,      // 指向软件定时器结构体的指针
                       OS_ERR  *p_err);     // 指向接收错误代码变量的指针

  函数 OSTmrDel() 的错误代码描述,如下所示:

OS_ERR_NONE                     // 成功创删除一个软件定时器
OS_ERR_ILLEGAL_DEL_RUN_TIME     // 在系统运行过程中非法删除内核对象
OS_ERR_OBJ_TYPE                 // 操作的内核对象的类型不是软件定时器
OS_ERR_OS_NOT_RUNING            // μC/OS-Ⅲ 内核还未运行
OS_ERR_TMR_INACTIVE             // 该软件定时器已被删除
OS_ERR_TMR_INVALID              // 指向软件定时器结构体的指针为空
OS_ERR_TMR_INVALID_STATE        // 软件定时器处于无效状态
OS_ERR_TMR_ISR                  // 在中断中非法调用该函数

4.4、获取软件定时器的剩余超时时间

// 返回值:软件定时器的剩余超时时钟节拍数
OS_TICK  OSTmrRemainGet (OS_TMR  *p_tmr,        // 指向软件定时器结构体的指针
                         OS_ERR  *p_err);       // 指向接收错误代码变量的指针

  函数 OSTmrRemainGet() 的错误代码描述,如下所示:

OS_ERR_NONE                     // 成功获取软件定时器的剩余超时时间
OS_ERR_OBJ_TYPE                 // 操作的内核对象的类型不是软件定时器
OS_ERR_OS_NOT_RUNING            // µC/OS-Ⅲ 内核还未运行
OS_ERR_TMR_INACTIVE             // 该软件定时器已被删除
OS_ERR_TMR_INVALID              // 指向软件定时器结构体的指针为空
OS_ERR_TMR_INVALID_STATE        // 无效的软件定时器状态
OS_ERR_TMR_ISR                  // 在中断中非法调用该函数

4.5、设置软件定时器参数

void  OSTmrSet (OS_TMR               *p_tmr,                // 指向软件定时器结构体的指针
                OS_TICK               dly,                  // 软件定时器的延时启动时间
                OS_TICK               period,               // 周期定时器的定时周期时间
                OS_TMR_CALLBACK_PTR   p_callback,           // 指向软件定时器超时回调函数的指针
                void                 *p_callback_arg,       // 指向软件定时器超时回调函数参数的指针
                OS_ERR               *p_err);               // 指向接收错误代码变量的指针

  函数 OSTmrSet() 的错误代码描述,如下所示:

OS_ERR_NONE                         // 设置软件定时器参数成功
OS_ERR_OBJ_TYPE                     // 操作的内核对象的类型不是软件定时器
OS_ERR_OS_NOT_RUNING                // μC/OS-Ⅲ 内核还未运行
OS_ERR_TMR_INVALID                  // 指向软件定时器结构体的指针为空
OS_ERR_TMR_INVALID_CALLBACK         // 无效的软件定时器超时回调函数指针
OS_ERR_TMR_INVALID_DLY              // 无效的软件定时器延时启动时间
OS_ERR_TMR_INVALID_PERIOD           // 无效的周期定时器定时周期时间
OS_ERR_TMR_ISR                      // 在中断中非法调用该函数

4.6、开启软件定时器定时

// 返回值:软件定时器开启是否成功,OS_TRUE:软件定时器开启成功;OS_FALSE:软件定时器开启失败
CPU_BOOLEAN  OSTmrStart (OS_TMR  *p_tmr,        // 指向软件定时器结构体的指针
                         OS_ERR  *p_err);       // 指向接收错误代码变量的指针

  函数 OSTmrStart() 的错误代码描述,如表所示:

OS_ERR_NONE                         // 软件定时器开启成功
OS_ERR_OBJ_TYPE                     // 操作的内核对象的类型不是软件定时器
OS_ERR_OS_NOT_RUNING                // μC/OS-Ⅲ 内核还未运行
OS_ERR_TMR_INACTIVE                 // 该软件定时器已被删除
OS_ERR_TMR_INVALID                  // 指向软件定时器结构体的指针为空
OS_ERR_TMR_INVALID_STATE            // 无效的软件定时器状态
OS_ERR_TMR_ISR                      // 在中断中非法调用该函数

4.7、获取软件定时器的状态

// 返回值:软件定时器的状态
OS_STATE  OSTmrStateGet (OS_TMR  *p_tmr,        // 指向软件定时器结构体的指针
                         OS_ERR  *p_err);       // 指向接收错误代码变量的指针

  函数 OSTmrStateGet() 的错误代码描述,如下所示:

OS_ERR_NONE                         // 获取软件定时器的状态成功
OS_ERR_OBJ_TYPE                     // 操作的内核对象的类型不是软件定时器
OS_ERR_OS_NOT_RUNING                // μC/OS-Ⅲ 内核还未运行
OS_ERR_TMR_INVALID                  // 指向软件定时器结构体的指针为空
OS_ERR_TMR_INVALID_STATE            // 无效的软件定时器状态
OS_ERR_TMR_ISR                      // 在中断中非法调用该函数

4.8、停止软件定时器定时

// 返回值:停止软件定时器是否成功,OS_TRUE:软件定时器停止成功;OS_FALSE:软件定时器停止失败
CPU_BOOLEAN  OSTmrStop (OS_TMR  *p_tmr,                 // 指向软件定时器结构体的指针
                        OS_OPT   opt,                   // 函数操作选项
                        void    *p_callback_arg,        // 指向传给软件定时器超时回调函数的参数的指针
                        OS_ERR  *p_err);                // 指向接收错误代码变量的指针

  函数 OSTmrStop() 的函数操作选项描述,如下所示:

OS_OPT_TMR_CALLBACK                 // 执行一次回调函数,代入原始参数
OS_OPT_TMR_CALLBACK_ARG             // 执行一次回调函数,代入指定参数
OS_OPT_TMR_NONE                     // 仅停止软件定时器

  函数 OSTmrStop() 的错误代码描述,如下所示:

OS_ERR_NONE                         // 停止软件定时器成功
OS_ERR_OBJ_TYPE                     // 操作的内核对象的类型不是软件定时器
OS_ERR_OPT_INVALID                  // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING                // μC/OS-Ⅲ 内核还未运行
OS_ERR_TMR_INVALID                  // 指向软件定时器结构体的指针为空
OS_ERR_TMR_INVALID_STATE            // 无效的软件定时器状态
OS_ERR_TMR_ISR                      // 在中断中非法调用该函数
OS_ERR_TMR_NO_CALLBACK              // 软件定时器没有超时回调函数
OS_ERR_TMR_STOPED                   // 软件定时器当前已经停止

五、实验例程

  main() 函数内容如下:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    LED_Init();
    Key_Init();
  
    UC_OS3_Demo();

    return 0;
}

  µC/OS-Ⅲ 例程入口函数:

/**
 * @brief µC/OS-Ⅲ例程入口函数
 * 
 */
void UC_OS3_Demo(void)
{
    OS_ERR error = {0};

    OSInit(&error);                                                             // 初始化µC/OS-Ⅲ

    // 创建开始任务
    OSTaskCreate((OS_TCB *   )  &start_task_tcb,                                // 任务控制块
                (CPU_CHAR *  )  "start_task",                                   // 任务名
                (OS_TASK_PTR )  Start_Task,                                     // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  START_TASK_PRIORITY,                            // 任务优先级
                (CPU_STK *   )  start_task_stack,                               // 任务堆栈
                (CPU_STK_SIZE)  START_TASK_STACK_SIZE / 10,                     // 任务栈的使用警戒线
                (CPU_STK_SIZE)  START_TASK_STACK_SIZE,                          // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    OSStart(&error);                                                            // 开始任务调度
}

  START_TASK 任务配置:

OS_TMR tim1;
OS_TMR tim2;

/**
 * START_TASK 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define START_TASK_PRIORITY     1
#define START_TASK_STACK_SIZE   256

OS_TCB  start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];

void Start_Task(void *p_arg);

/**
 * @brief 开始任务的任务函数
 * 
 * @param p_arg 任务参数
 */
void Start_Task(void *p_arg)
{
    OS_ERR error = {0};
    CPU_INT32U cnts = 0;

    CPU_Init();                                                                 // 初始化CPU库

    CPU_SR_ALLOC();                                                             // 临界区保护

    cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
    OS_CPU_SysTickInit(cnts);                                                   // 根据配置的节拍频率配置SysTick中断及优先级

    // 创建单次软件定时器
    OSTmrCreate(&tim1, "tim1", 10, 0, OS_OPT_TMR_ONE_SHOT, (OS_TMR_CALLBACK_PTR)TIM_Callback, 0, &error);

    // 创建周期软件定时器
    OSTmrCreate(&tim2, "tim2", 0, 10, OS_OPT_TMR_PERIODIC, (OS_TMR_CALLBACK_PTR)TIM_Callback, 0, &error);

    CPU_CRITICAL_ENTER();                                                       // 进入临界区

    // 创建任务1
    OSTaskCreate((OS_TCB *   )  &task1_tcb,                                     // 任务控制块
                (CPU_CHAR *  )  "task1",                                        // 任务名
                (OS_TASK_PTR )  Task1,                                          // 任务函数
                (void *      )  0,                                              // 任务参数
                (OS_PRIO     )  TASK1_PRIORITY,                                 // 任务优先级
                (CPU_STK *   )  task1_stack,                                    // 任务堆栈
                (CPU_STK_SIZE)  TASK1_STACK_SIZE / 10,                          // 任务栈的使用警戒线
                (CPU_STK_SIZE)  TASK1_STACK_SIZE,                               // 任务栈大小
                (OS_MSG_QTY  )  0,                                              // 消息队列长度
                (OS_TICK     )  0,                                              // 时间片长度,设置为0,则默认时间片长度
                (void *      )  0,                                              // 扩展内存
                (OS_OPT      )  (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),    // 任务选项
                (OS_ERR *    )  &error);                                        // 错误码

    CPU_CRITICAL_EXIT();                                                        // 退出临界区

    OSTaskDel(NULL, &error);                                                    // 删除任务
}

  TASK1 任务配置:

#define FLAG_BIT1           (1 << 1)
#define FLAG_BIT2           (1 << 2)
#define FLAG_BIT3           (1 << 3)

/**
 * TASK1 任务配置
 * 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
 */
#define TASK1_PRIORITY          2
#define TASK1_STACK_SIZE        256

OS_TCB  task1_tcb;
CPU_STK task1_stack[TASK1_STACK_SIZE];

void Task1(void *p_arg);

/**
 * @brief 任务1的任务函数
 * 
 * @param p_arg 任务参数
 */
void Task1(void *p_arg)
{
    OS_ERR error = {0};

    while (1)
    {
        switch (Key_Scan(0))
        {
        case WKUP_PRESS:
            OSTmrStart(&tim1, &error);                                          // 开启软件定时器
            break;

        case KEY1_PRESS:
            OSTmrStart(&tim2, &error);
            break;
  
        case KEY2_PRESS:
            OSTmrStop(&tim1, OS_OPT_TMR_NONE, 0, &error);
            break;

        case KEY3_PRESS:
            OSTmrStop(&tim2, OS_OPT_TMR_NONE, 0, &error);
            break;

        default:
            break;
        }

        OSTimeDly(10, OS_OPT_TIME_DLY, &error);
    }
}

  软件定时器超时回调函数:

/**
 * @brief 软件定时器超时回调函数
 * 
 * @param p_tmr 指向软件定时器的指针
 * @param p_arg 指向参数的指针
 */
void TIM_Callback(OS_TMR *p_tmr, void *p_arg)
{
    if (p_tmr == &tim1)
    {
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
    }
    else if (p_tmr == &tim2)
    {
        HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_10);
    }
}
posted @ 2024-02-24 20:47  星光映梦  阅读(251)  评论(0)    收藏  举报