UCOSIII 软件定时器
- 软件定时器原理
- 软件定时器相关函数汇总
- 总结函数的参数和用法及注意事项
- 举个栗子
在不带操作系统的时候,如果想要定时一般会使用芯片上的硬件定时器,但是在UCOS中,我们可以使用软件定时器,而把硬件定时器留作他用,而且UCOS的软件定时器是没有数量限制的,当然前提是内存够大才行。虽然软件定时器不占用硬件定时器的资源,但是他也有个缺点,就是精度不高,因为软件定时器的频率来源于心跳频率的分频,所以频率不会大于心跳频率。所以如果精度要求特别高的话还是用硬件定时器吧,在一般时间精度要求不高的场合用软件定时器还是挺方便的。
软件定时器有个使能开关,如果要使用软降定时器的话得打开这个开关OS_CFG_TMR_EN。
软件定时器应该可以算是一个任务,所以他也有优先级,这个优先级是可以在更改的,在os_cfg_app.h文件中,更改OS_CFG_TMR_TASK_PRIO就可以重置软件定时器的优先级。
软件定时器的频率设置在os_cfg_app.h文件中,更改OS_CFG_TMR_TASK_RATE_HZ就可以设置软件定时器的频率。这个频率不能大于心跳频率,因为软件定时器时钟是又系统时钟分频而来,为了准确的分频,软件定时器的频率和心跳频率最好成倍数关系,这个频率最好不要设得太高,至于为什么我也不知道,可能会导致系统负担加重什么的吧。
软件定时器其实是一个到计数器OSTmrTickCtr,当软件定时器的脉冲到来时,OSTmrTickCtr就会自减,当OSTmrTickCtr减到0时就会触发回调函数,回调函数可以理解为中断处理函数吧。因为在执行回调函数时任务调度器是上锁的,所以回调函数应该简短,就像中断处理函数一样,千万不要在回调函数中调用会导致阻塞的函数,不然程序会卡在回调函数中很久。
软件定时器创建后要手动启动,不然是不会运行的。
软甲定时器有4中状态,可以说是一个简单的状态机吧。分别是
0;未使用,可以理解为未创建,又或者是被删除了
1;暂停,创建了未启动或者是中途暂停了
2;运行中
3;运行完毕后停止,这个应该是针对单次定时器的,周期定时器应该不存在停止的说法
软件定时器有两种模式单次模式和周期模式(包括有初始化延时周期和无初始化延时周期)。单次模式就是只运行一次就停止了,回调函数只会运行一次;而周期模式则是可以重复运行的定时器,回调函数也会周期性的执行。
单次定时器,在创建软件定时器的时候给定时器一个初始值dly,也就是OSTmrTickCtr的初值,当启动定时器的时候每来一个定时器时钟OSTmrTickCtr就会减1,当OSTmrTickCtr减到0的时候就会触发回调函数,回调函数执行完成后这个定时器就没什么用了。如果想要再次使用这个定时器的话直接启动就行,不需要重新创建一个定时器。但是如果在定时器运行过程中重新启动定时器,那么定时器会重新开始计数而不是继续往下计数。打个比方,我要定时100个定时器节拍,把OSTmrTickCtr初始化为100,在OSTmrTickCtr减到70的时候重新启动定时器,那么OSTmrTickCtr由会从100开始倒计数,而不会从70往下接着计数,当计数到0的时候实际上已经计数了130个定时器节拍。但是如果先暂停定时器然后再启动定时器的话,那么计数器会从原来的位置接着往下计数。在单次定时器模式下周期值period是没有用的,period组好设置为0吧。
周期定时器,周期定时器可以用硬件定时器的思维来理解,比如51单片机吧,在定时器初始化的时候会设置一个初始化计数变量,然后在中断中重装载计数值。UCOS中的软件定时器周期模式也是一样,在创建定时器的时候要设置一个初始值dly,这是第一次定时的时间,从第二次开始定时的时间就是周期值period定时的时间了。打个比方,dly=10,period=50,那么启动定时器的时候OSTmrTickCtr会从10开始倒计数,当OSTmrTickCtr倒计数到0的时候会第一次触发回调函数,然后给OSTmrTickCtr重置为50,从50计数到0的时候会第二次触发回调函数,然后OSTmrTickCtr又重置为50................也就是说只有第一次定时是10个定时器节拍的,从第二次开始之后的每一次都是定时器50个定时器节拍。这里有个需要注意的地方,在周期定时器模式下当dly初始化为0时会马上触发回调函数,因为第一次定时的时间为0.如果在定时器运行过程中启动定时器的话,定时器的计数值也是会重置的,当启动已经暂停了的定时器的话,定时器会接着原来位置继续运行,而不会重置计数值。
与软件定时器相关的函数不多,就那么几个
1、OSTmrCreate()
2、OSTmrDel()
3、OSTmrRemainGet()
4、OSTmrStart()
5、OSTmrStateGet()
6、OSTmrStop()
各个函数用法及参数解释
- OSTmrCreate(),定时器创建函数
函数原型 | void OSTmrCreate (OS_TMR *p_tmr, CPU_CHAR *p_name, OS_TICK dly, OS_TICK period, OS_OPT opt, OS_TMR_CALLBACK_PTR p_callback, void *p_callback_arg, OS_ERR *p_err) | |
使能开关 | OS_CFG_TMR_EN | |
各个入口参数说明 | OS_TMR *p_tmr, | 定时器控制块,用法功能有点像任务控制块 |
CPU_CHAR *p_name, | 可以给定时器一个名字,一般用不到,只有用上位机调试的时候才会用到 | |
OS_TICK dly, | 定时器的初始值,单次模式下就算定时的定时器节拍数,周期模式下是第一次定时的定时器节拍数 | |
OS_TICK period, | 定时器周期值,单次模式这个值没用,一般设置为0.周期模式下代表定时器的重装载值,也就是定时周期。 | |
OS_OPT opt, | 定时器选项,这两个选项的区别上面已经说了 OS_OPT_TMR_ONE_SHOT 单次模式 OS_OPT_TMR_PERIODIC 周期模式 | |
OS_TMR_CALLBACK_PTR p_callback, | 定时器的回到函数,回调函数是有格式要求的,统一格式如下 Void MyCallBack(OS_TMR * p_tmr, void * p_arg) 实测不给任何入口参数也是可以用的 | |
void *p_callback_arg, | 可以给回调函数一个入口参数, | |
OS_ERR *p_err | 函数返回的错误码 |
- OSTmrDel()
删除定时器,这个删除并不是删除定时器的代码,而是让这个定时器不受UCOS的管理,减少UCOS的负担,删除定时器后该定时器就不能使用了。
函数原型 | CPU_BOOLEAN OSTmrDel (OS_TMR *p_tmr, OS_ERR *p_err) | |
使能开关 | OS_CFG_TMR_EN | |
各个入口参数说明 | OS_TMR *p_tmr, | 软件定时器控制块 |
OS_ERR *p_err | 返回的错误码 |
3、OSTmrRemainGet()
获取剩余时间,这个时间并不是真正的时间,而是软件定时器剩余的节拍数,看看还有多少个定时器时钟节拍就会执行回调函数。这个函数不能在中断中调用。
函数原型 | OS_TICK OSTmrRemainGet (OS_TMR *p_tmr, OS_ERR *p_err) | |
使能开关 | OS_CFG_TMR_EN | |
各个入口参数说明 | OS_TMR *p_tmr, | 软件定时器控制块 |
OS_ERR *p_err | 返回的错误码 |
4、OSTmrStart()
启动或重启定时器,要启动的定时器必须是已经创建了的。
函数原型 | CPU_BOOLEAN OSTmrStart (OS_TMR *p_tmr, OS_ERR *p_err) | |
使能开关 | OS_CFG_TMR_EN | |
各个入口参数说明 | OS_TMR *p_tmr, | 软件定时器控制块 |
OS_ERR *p_err | 返回的错误码 |
5、OSTmrStateGet()
获取当前定时器状态,没事谁会去管定时器的状态啊,
6、OSTmrStop()
暂停定时器,只是暂停而已并不是停止,暂停后只要启动还是可以继续运行的,在暂停定时器时还可以执行一次回调函数,也可以不执行。如果是暂停已经停止的定时器的话,无论是任何选项都不会触发回调函数。
函数原型 | CPU_BOOLEAN OSTmrStop (OS_TMR *p_tmr, OS_OPT opt, void *p_callback_arg, OS_ERR *p_err) | |
使能开关 | OS_CFG_TMR_EN | |
各个入口参数说明 | OS_TMR *p_tmr, | 定时器的任务控制块 |
OS_OPT opt, | 选项 OS_OPT_TMR_NONE 无选项,不执行回调函数 OS_OPT_TMR_CALLBACK 执行回调函数,使用初始化的参数 OS_OPT_TMR_CALLBACK_ARG 执行回调函数,使用最新传递的参数 | |
void *p_callback_arg, | 给回调函数传递一个参数 | |
OS_ERR *p_err | 返回的错误码 |
举个栗子吧;本工程软件定时器频率为100hz,也就是10ms一个脉冲。
功能;在main函数中创建第一个任务star task,然后在这个任务中创建一个LED灯闪烁任务led task和创建两个定时器timer 1、timer 2,并启动定时器。定时器也可以理解为是任务吧,所以在创建的时候和创建任务并列。老规矩,创建完定时器和LED任何后就把第一个任务star tas删除了。其中LED任务提示系统正在运行,定时器1是一个单次定时器,只会在上电后运行一次,在第5秒执行回调函数,在串口发送自己运行的次数。而定时器2是一个周期定时器,每隔1秒向串口输出执行回调函数的次数。代码如下;
Main.c文件
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"
#include "task.h"
/************************************************
ALIENTEK战舰STM32开发板UCOS实验
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
//UCOSIII中以下优先级用户程序不能使用,ALIENTEK
//将这些优先级分配给了UCOSIII的5个系统内部任务
//优先级0:中断服务服务管理任务 OS_IntQTask()
//优先级1:时钟节拍任务 OS_TickTask()
//优先级2:定时任务 OS_TmrTask()
//优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
//优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com
//广州市星翼电子科技有限公司
//作者:正点原子 @ALIENTEK
//*****************************************************************
//开始任务
OS_TCB starTaskTCB; //任务控制块
#define STAR_TASK_PRIO 3 //任务优先级
#define STAR_TASK_STK_SIZE 128 //任务堆栈总大小
CPU_STK STAR_TASK_STK[ STAR_TASK_STK_SIZE ]; //任务堆栈数组
int main()
{
OS_ERR myErr; //os的错误码
CPU_SR_ALLOC(); //使用临街保护就得添加这玩意,不然会报错
delay_init(); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始化
OSInit(&myErr); //os初始化
//创建任务的时候进入临街保护比较好,但是这个函数非用户的API函数,用户使用是否合适以后再讨论
OS_CRITICAL_ENTER();
//创建开始任务,在开始任务里创建其他任务,为每个函数入口参数都强制转换类型吧,以防万一
OSTaskCreate ( (OS_TCB *)&starTaskTCB, //任务控制块,
(CPU_CHAR *)"star task", //任务名字
(OS_TASK_PTR )starTaskFunc, //任务函数
(void *)0, //任务函数入口参数
(OS_PRIO )STAR_TASK_PRIO, //任务优先级
(CPU_STK *)&STAR_TASK_STK[0], //堆栈数组基地址
(CPU_STK_SIZE ) STAR_TASK_STK_SIZE/10, //堆栈溢出限位
(CPU_STK_SIZE ) STAR_TASK_STK_SIZE, //堆栈大小
(OS_MSG_QTY ) 0, //本任务消息长度
(OS_TICK ) 0, //时间片个数
(void *) 0, //任务块补充参数
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //选项
(OS_ERR *)&myErr); //错误码
OS_CRITICAL_EXIT(); //任务创建完了就该退出临界保护了
OSStart(&myErr); //任务创建完成后就该运行了
while(1); //运行系统后就一直在系统里面跑了,应该运行不到这里
}
Task.c文件
#include "task.h"
#include "sys.h"
#include "led.h"
#include "includes.h" //想要使用UCOS系统,得包含这个头文件
//LED秒闪任务,提示系统正在运行
OS_TCB ledTaskTCB;
#define LED_TASK_PRIO 4
#define LED_TASK_STK_SIZE 128
CPU_STK LED_TASK_STK[LED_TASK_STK_SIZE];
//定时器1
OS_TMR timer1;
//定时器2
OS_TMR timer2;
void starTaskFunc()
{
OS_ERR myErr;
//创建LED闪烁任务
OSTaskCreate ( (OS_TCB *)&ledTaskTCB,
(CPU_CHAR *)"led task",
(OS_TASK_PTR )ledTaskFunc,
(void *)0,
(OS_PRIO )LED_TASK_PRIO,
(CPU_STK *)&LED_TASK_STK[0],
(CPU_STK_SIZE ) LED_TASK_STK_SIZE/10,
(CPU_STK_SIZE ) LED_TASK_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&myErr);
//创建定时器任务1
OSTmrCreate ( (OS_TMR *)&timer1, //定时器控制块
(CPU_CHAR *)"timer 1", //定时器名字
(OS_TICK )500, //初值 500*10ms=5000ms
(OS_TICK )0, //周期值,单次DJ讲恍枰芷谥?
(OS_OPT )OS_OPT_TMR_ONE_SHOT, //单次模式
(OS_TMR_CALLBACK_PTR )timer1_CallBackFunc, //回调函数
(void *)0, //回调函数入口参数
(OS_ERR *)&myErr); //返回的错误码
//创建定时器任务2
OSTmrCreate ( (OS_TMR *)&timer2,
(CPU_CHAR *)"timer 2",
(OS_TICK )0, //初值为0,启动后马上执行一次回调函数
(OS_TICK )100, //周期值 100*10ms=1000ms
(OS_OPT )OS_OPT_TMR_PERIODIC, //周期模式
(OS_TMR_CALLBACK_PTR )timer2_CallBackFunc,
(void *)0,
(OS_ERR *)&myErr);
//既然任务和定时器都创建完成了那就启动定时器吧
OSTmrStart((OS_TMR*)&timer1 ,&myErr);
OSTmrStart((OS_TMR*)&timer2 ,&myErr);
//这个任务只是用来创建其他任务的,其他任务创建完成了这个任务也就没用了,删除掉
OSTaskDel((OS_TCB*)0,&myErr);
}
//LED任务的任务函数
//LED每隔500ms翻转一次,提示系统正在运行
void ledTaskFunc()
{
OS_ERR myErr;
while(1) //注意,每个任务都是一个死循环
{
OSTimeDlyHMSM ( (CPU_INT16U ) 0, //时
(CPU_INT16U ) 0, //分
(CPU_INT16U ) 0, //秒
(CPU_INT32U ) 100, //毫秒
(OS_OPT ) OS_OPT_TIME_HMSM_STRICT, //选项
(OS_ERR *)&myErr); //错误码
LED = !LED; //LED状态翻转
}
}
//定时器1回调函数
//功能;通过串口向电脑发送一条信息
void timer1_CallBackFunc(void *p_tmr, void *p_arg)
{
static u16 callBackCount=0;
callBackCount++;
printf("定时器1 回调函数执行的次数= %d \r\n",callBackCount);
}
//定时器2回调函数
//功能;通过串口向电脑发送一条信息
void timer2_CallBackFunc()
{
static u16 callBackCount=0;
callBackCount++;
printf("定时器2 回调函数执行的次数= %d \r\n",callBackCount);
}
Task.h文件
#ifndef __TASK_H
#define __TASK_H
void starTaskFunc();
void ledTaskFunc();
void timer1_CallBackFunc();
void timer2_CallBackFunc();
#endif