UCOSIII 时间管理(延时函数)
- 系统频率设置方法及延时函数原理
- 各个函数参数用法介绍,及注意事项
- 栗子功能+代码
延时函数会涉及到系统滴答时钟,所以有必要介绍下系统滴答时钟的原理。系统里有一个计数变量OSTickCtr。系统滴答时钟是由滴答定时器产生的,其实也就是通过一个定时器来产生特定时间间隔的中断,每产生一个系统时钟中断这个变量就自加1。而延时函数就是用这个OSTickCtr来产生延时的,比如OSTimeDly()函数在相对延时模式下延时20个时钟节拍,当前OSTickCtr=100,那么当OSTickCtr==120的时候延时结束。当然,通过与OSTickCtr挂钩来延时是不准确的,比如滴答定时器每5ms产生一个中断,然后OSTickCtr++。比如当前OSTickCtr==100开始延时,因为不知道过1毫秒还是4毫秒OSTickCtr会加到101。所以这个延时精度取决于系统心跳时钟的频率。
系统时钟是在os_cfg_app.h文件里面设置的,OS_CFG_TICK_RATE_HZ代表系统心跳的频率,注意这个是频率,不是周期。本工程用的系统心跳频率为200HZ,也就是5ms一个心跳。
当程序调用OSTimeDly()或者OSTimeDlyHMSM()这两个延时函数时,系统会把当前任务挂起来,然后去运行别的任务,等到延时结束后程序才会回到当前任务运行。这个和不带系统的软件延时有点区别,不带系统的软件延时程序会卡在当前位置一直等待延时结束,但是带系统的延时函数在延时期间系统会去运行别的任务,而不会让系统傻傻地等啥都不干。
与时间管理相关的函数不多,也就这么几个而已
1、OSTimeDly()
2、OSTimeDlyHMSM()
3、OSTimeDlyResume()
4、OSTimeGet()
5、OSTimeSet()
6、OSTimeTick()
7、OSTimeTickHook()
下面总结下各个函数的用法吧
1、OSTimeDly()函数,运行这个函数该任务会被挂起,原型如下
void OSTimeDly (OS_TICK dly,
OS_OPT opt,
OS_ERR *p_err)
参数解释
OS_TICK dly | 这个就是要延时的心跳节拍数量,心跳节拍的频率设置方法上面已经写了 |
OS_OPT opt | 这个函数有三个模式,这个选项就是用来选择模式的,三个模式分别是
|
OS_OPT_TIME_DLY 相对延时模式, 这个模式很好理解,比如当前系统心跳OSTickCtr=100,dly=20,也就是要延时20个系统节拍,那么OSTickCtr=120的时候程序就会继续往下执行,如果系统任务比较中的话,这个模式时间误差会比较大。
| |
OS_OPT_TIME_PERIODIC 周期模式 还是举例子吧,比如当前系统心跳OSTickCtr=100,dly=20,也就是说周期是20个时钟节拍,当系统时钟计数变量OSTickCtr是dly的整数倍时,120、140、160、180、200........程序就会从延时函数处继续往下执行,这个模式在长时间延时时误差比相对延时模式要小,所以这个模式比较常用
| |
OS_OPT_TIME_MATCH 绝对模式 这个模式有点意思,就是设定一个值dly,当OSTickCtr >= dly时就会执行延时函数后面的代码。比如心跳频率为200hz,也就是5ms,现在我要在上电5秒后打开一个灯,那么dly 初始值为dly = 5000ms / 5ms =1000,这样当OSTickCtr 到达1000时就会运行延时函数后面的代码从而开灯。但是这个模式有个坑,当OSTickCtr ==1000时会开灯,但是当OSTickCtr 等于1001、1002、1003时都会执行延时函数后面的代码,也就是说从上电第五秒起会频繁的开灯(虽然灯没关闭过)。所以这个绝对模式适用于只执行一次的任务,任务执行后就删除该任务,这样就不会一直执行这个任务了。如果这个任务本来就是要一直执行的话另当别论,就算是这个任务要一直执行那也要另外再加一个延时函数,不然这个任务会一直处于就绪太,会占用非常高的CPU资源
| |
OS_ERR *p_err | 函数返回的错误码 |
- OSTimeDlyHMSM()函数,运行这个函数该任务会被挂起,原型如下
void OSTimeDlyHMSM (CPU_INT16U hours,
CPU_INT16U minutes,
CPU_INT16U seconds,
CPU_INT32U milli,
OS_OPT opt,
OS_ERR *p_err)
使用这个函数有个要注意的地方,也就是打开使能开关OS_CFG_TIME_DLY_HMSM_EN才能使用,这个使能开关在os_cfg.h文件里。
这个函数使用起来挺方便的,只要设置好延时的时间的时、分、秒、毫秒就行,而不需要去管系统心跳频率是多少。虽然不需要关心系统心跳频率,但是这个函数也是根据系统心跳来延时的,所以延时精度也由心跳频率决定。
参数解释
| OS_OPT_TIME_HMSM_STRICT | OS_OPT_TIME_HMSM_NON_STRICT |
CPU_INT16U hours, | 延时的时间,小时。参数范围 0-99 | 范围 0-999 |
CPU_INT16U minutes, | 延时的时间,分钟。参数范围 0-59 | 范围 0-9999 |
CPU_INT16U seconds, | 延时的时间,秒。参数范围 0-59 | 范围 0-65535 |
CPU_INT32U milli, | 延时的时间,毫秒。参数范围 0-999 | 范围 0-4294967295 |
OS_OPT opt, | 选项,这里有两个选项,严格模式和非严格模式,这个名字是我根据单词直译的,至于这两个模式的区别在另外几个参数里有介绍,其实就是不同的模式下延时函数入口参数的可取范围不一样而已 OS_OPT_TIME_HMSM_STRICT 严格模式 OS_OPT_TIME_HMSM_NON_STRICT 非严格模式 | |
OS_ERR *p_err | 老规矩,返回的错误码 |
这些参数看着挺蛋疼的,来两个栗子吧。都是延时70分钟。
严格模式
70分钟=1小时+10分钟;OSTimeDlyHMSM (1,10,0,0,OS_OPT_TIME_HMSM_STRICT,&myErr)
非严格模式,这个模式可以有不同的表示方法
70分钟表示为70分钟;OSTimeDlyHMSM (0,70,0,0,OS_OPT_TIME_HMSM_NON_STRICT,&myErr)
70分钟表示为70*60秒;OSTimeDlyHMSM (0,0,70*60,0,OS_OPT_TIME_HMSM_NON_STRICT,&myErr)
70分钟表示为70*60*1000毫秒;OSTimeDlyHMSM (0,0,0, 70*60*1000,OS_OPT_TIME_HMSM_NON_STRICT,&myErr)
至于用什么模式,还是看个人习惯吧。
3、OSTimeDlyResume()
这个函数是让被OSTimeDly()或者OSTimeDlyHMSM()挂起任务解挂,从而进入就绪太。但是这样解挂有个缺点,任务并不知道是延时时间到了还是被提前解挂了。比如任务要延时10分钟,但是在延时6分钟的时候OSTimeDlyResume() 把延时取消了,然后任务进入就绪太,这时任务会认为延时10分钟已经到了,并不知道实际上10分钟延时还没到。有个要注意的地方,不能在在本任务中给自己解挂,因为已经被挂起了,所以本任务根本不会运行,又何来解挂的说法,所以要在别的任务中给本任务解挂。
函数原型如下;
void OSTimeDlyResume (OS_TCB *p_tcb,
OS_ERR *p_err)
这里只有连个参数,第一个参数是要解挂的任务的TCB块,第二个参数是返回的错误码。
4、OSTimeGet()
这个函数是获取系统心跳计数变量OSTickCtr 的。
5、OSTimeSet()
这个函数是设置系统心跳计数变量OSTickCtr 的,因为延时函数是根据这个OSTickCtr 来延时的,所以这个OSTickCtr 没事不要更改。比如当前OSTickCtr =100,我要延时20个心跳节拍,当OSTickCtr 到达110的时候我把OSTickCtr 重置为100,当OSTickCtr 到达120的时候实际上已经过了30个心跳节拍,延时的时间就不准了。除了影响延时函数还会影响软件定时器,所以最好不要调用这个函数。
6、OSTimeTick()
这个可以认为是系统内部函数,就是每次心跳节拍来的时候都会调用一次,在这个函数里更新OSTickCtr 和各个延时函数的变量等。用户一般用不到吧
- OSTimeTickHook()
钩子函数,以后会用专门的章节来总结钩子函数的。
最后一部分,来个栗子吧。创建几个任务,就是把上面的两个延时函数的各个不同选项都用一遍,具体在代码里有注释。
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_TCB uartTask_1_TCB;
#define UART_TASK_1_PRIO 5
#define UART_TASK_1_STK_SIZE 128
CPU_STK UART_TASK_1_STK[ UART_TASK_1_STK_SIZE ];
//串口输出任务2
OS_TCB uartTask_2_TCB;
#define UART_TASK_2_PRIO 6
#define UART_TASK_2_STK_SIZE 128
CPU_STK UART_TASK_2_STK[ UART_TASK_2_STK_SIZE ];
//串口输出任务3
OS_TCB uartTask_3_TCB;
#define UART_TASK_3_PRIO 7
#define UART_TASK_3_STK_SIZE 128
CPU_STK UART_TASK_3_STK[ UART_TASK_3_STK_SIZE ];
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
OSTaskCreate ( (OS_TCB *)&uartTask_1_TCB,
(CPU_CHAR *)"uart task 1 ",
(OS_TASK_PTR )uartTask_1_Func,
(void *)0,
(OS_PRIO )UART_TASK_1_PRIO,
(CPU_STK *)&UART_TASK_1_STK[0],
(CPU_STK_SIZE ) UART_TASK_1_STK_SIZE/10,
(CPU_STK_SIZE ) UART_TASK_1_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);
//创建串口输出任务2
OSTaskCreate ( (OS_TCB *)&uartTask_2_TCB,
(CPU_CHAR *)"uart task 2 ",
(OS_TASK_PTR )uartTask_2_Func,
(void *)0,
(OS_PRIO )UART_TASK_2_PRIO,
(CPU_STK *)&UART_TASK_2_STK[0],
(CPU_STK_SIZE ) UART_TASK_2_STK_SIZE/10,
(CPU_STK_SIZE ) UART_TASK_2_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);
//创建串口输出任务3
OSTaskCreate ( (OS_TCB *)&uartTask_3_TCB,
(CPU_CHAR *)"uart task 3 ",
(OS_TASK_PTR )uartTask_3_Func,
(void *)0,
(OS_PRIO )UART_TASK_3_PRIO,
(CPU_STK *)&UART_TASK_3_STK[0],
(CPU_STK_SIZE ) UART_TASK_3_STK_SIZE/10,
(CPU_STK_SIZE ) UART_TASK_3_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);
//这个任务只是用来创建其他任务的,其他任务创建完成了这个任务也就没用了,删除掉
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的任务函数
//功能;每隔0.5秒向电脑串口助手发送一次数据
void uartTask_1_Func()
{
OS_ERR myErr;
while(1)
{
OSTimeDly(100, OS_OPT_TIME_DLY, &myErr );// 相对延时模式,延时100*5ms=500ms
printf("Im uart task 1 \r\n");
}
}
//串口任务2的任务函数
//功能;每隔1秒向电脑串口助手发送一次数据
void uartTask_2_Func()
{
OS_ERR myErr;
while(1)
{
OSTimeDly(200, OS_OPT_TIME_PERIODIC, &myErr );// 周期模式,200*5ms=1000ms
printf("Im uart task 2 \r\n");
}
}
//串口任务3的任务函数
//功能;上电5秒后通过串口向电脑发送数据,只发送一次
void uartTask_3_Func()
{
OS_ERR myErr;
while(1)
{
OSTimeDly(1000, OS_OPT_TIME_MATCH, &myErr );// 绝对模式,1000*5ms=5000ms
printf("Im uart task 3 ,上电时间到达5秒\r\n");
OSTaskDel((OS_TCB*)0,&myErr);//这个任务只在上电第5秒运行一次,然后就把自己给删除掉
}
}
Task.h文件
#ifndef __TASK_H
#define __TASK_H
void starTaskFunc();
void ledTaskFunc();
void uartTask_1_Func();
void uartTask_2_Func();
void uartTask_3_Func();
#endif