在学习STM32的时候会使用定时器来做很多定时任务,这个定时器是单片机自带的,也就是硬件定时器,在UCOSIII中提供了软件定时器,我们可以使用这些软件定时器完成一些功能,本文我们就讲解一下UCOSIII软件定时器。
定时器其实就是一个递减计数器,当计数器递减到0的时候就会触发一个动作,这个动作就是回调函数,当定时器计时完成时就会自动的调用这个回调函数。因此我们可以使用这个回调函数来完成一些设计。比如,定时10秒后打开某个外设等等,在回调函数中应避免任何可以阻塞或者删除定时任务的函数。
如果要使用定时器的话需要将宏OS_CFG_TMR_DEL_EN定义为1。
定时器的分辨率由我们定义的系统节拍频率OS_CFG_TICK_RATE_HZ决定,比如我们定义为200,系统时钟周期就是5ms,定时器的最小分辨率肯定就是5ms。但是定时器的实际分 辨 率 是 通 过 宏OS_CFG_TMR_TASK_RATE_HZ定 义 的 , 这 个 值 绝 对 不 能 大 于OS_CFG_TICK_RATE_HZ。比如我们定义OS_CFG_TMR_TASK_RATE_HZ为100,则定时器的时间分辨率为10ms。有关UCOSIII定时器的函数都在os_tmr.c文件中。
什么是回调函数呢?
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
函数 | 含义 |
OSTmrCreate() | 创建定时器并制定运行模式 |
OSTmrDel() | 删除定时器 |
OSTmrRemainGet() | 获取定时器的剩余时间 |
OSTmrStart() | 启动定时器计数 |
OSTmrStateGet() | 获取当前定时器状态 |
OSTmrStop() | 停止计数器倒计时 |
如果我们要使用定时器,肯定需要先创建一个定时器,使用OSTmrCreate()函数来创建一个定时器,这个函数也用来确定定时器的运行模式,OSTmrCreate()函数原型如下:
void OSTmrCreate (OS_TMR *p_tmr, //指向定时器的指针,宏OS_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) //调用此函数以后返回的错误码
{
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
p_tmr->State = (OS_STATE )OS_TMR_STATE_STOPPED; /* Initialize the timer fields */
p_tmr->Type = (OS_OBJ_TYPE )OS_OBJ_TYPE_TMR;
p_tmr->NamePtr = (CPU_CHAR *)p_name;
p_tmr->Dly = (OS_TICK )dly;
p_tmr->Match = (OS_TICK )0;
p_tmr->Remain = (OS_TICK )0;
p_tmr->Period = (OS_TICK )period;
p_tmr->Opt = (OS_OPT )opt;
p_tmr->CallbackPtr = (OS_TMR_CALLBACK_PTR)p_callback;
p_tmr->CallbackPtrArg = (void *)p_callback_arg;
p_tmr->NextPtr = (OS_TMR *)0;
p_tmr->PrevPtr = (OS_TMR *)0;
OSTmrQty++; /* Keep track of the number of timers created */
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}
opt参数:定时器运行选项,这里有两个模式可以选择。OS_OPT_TMR_ONE_SHOT单次定时器,OS_OPT_TMR_PERIODIC周期定时器。
单次定时器
使用OSTmrCreate()函数创建定时器时把参数opt设置为OS_OPT_TMR_ONE_SHOT,就是创建的单次定时器。创建一个单次定时器以后,我们一旦调用OSTmrStart()函数定时器就会从创建时定义的dly开始倒计数,直到减为0调用回调函数并停止。单次定时器的定时器只执行一次。
上图展示了单次定时器在调用OSTmrStart()函数后开始倒计数,将dly减为0后调用回调函数的过程,到这里定时器就停止运行,不再做任何事情了,我们可以调用OSTmrDel()函数来删除这个运行完成的定时器。其实我们也可以重新调用OSTmrStart()函数来开启一个已经运行完成的定时器,通过调用OSTmrStart()函数来重新触发单次定时器,如下图所示。
周期模式(无初始延迟)
使用OSTmrCreate()函数创建定时器时把参数opt设置为OS_OPT_TMR_PERIODIC,就是创建的周期定时器。当定时器倒计数完成后,定时器就会调用回调函数,并且重置计数器开始下一轮的定时,就这样一直循环下去。如果使用OSTmrCreate()函数创建定时器的时候,参数dly为0的话,那么定时器在每个周期开始时计数器的初值就为period,如下图所示。
周期定时器(有初始化延迟)
在创建定时器的时候也可以创建带有初始化延时的,初始化延时就是OSTmrCreate()函数中的参数dly就是初始化延迟,定时器的第一个周期就是dly。当第一个周期完成后就是用参数period作为周期值,调用OSTmrStart()函数开启有初始化延时的定时器,如下图所示。
例程要求:本例程新建两个任务:任务A和任务B,任务A用于创建两个定时器:定时器1和定时器2,任务A还创建了另外一个任务B。其中定时器1为周期定时器,初始延时为200ms,以后的定时器周期为1000ms,定时器2为单次定时器,延时为2000ms。
任务B作为按键检测任务,当KEY_UP键按下的时候,打开定时器1;当KEY0按下的时候打开定时器2;当KEY1按下的时候,同时关闭定时器1和2;任务B还用来控制LED0,使其闪烁,提示系统正在运行。
定时器1定时完成以后调用回调函数刷新其工作区域的背景,并且在LCD上显示定时器1运行的次数。定时器2定时完成后也调用其回调函数来刷新其工作区域的背景,并且显示运行次数,由于定时器2是单次定时器,我们通过串口打印来观察单次定时器的运行情况。
例子:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "includes.h"
//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()
//任务优先级
#define START_TASK_PRIO 3
//任务堆栈大小
#define START_STK_SIZE 128
//任务控制块
OS_TCB StartTaskTCB;
//任务堆栈
CPU_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *p_arg);
//任务优先级
#define TASK1_TASK_PRIO 4
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务控制块
OS_TCB Task1_TaskTCB;
//任务堆栈
CPU_STK TASK1_TASK_STK[TASK1_STK_SIZE];
void task1_task(void *p_arg);
OS_TMR tmr1; //定时器1
OS_TMR tmr2; //定时器2
void tmr1_callback(void *p_tmr, void *p_arg); //定时器1回调函数
void tmr2_callback(void *p_tmr, void *p_arg); //定时器2回调函数
int lcd_discolor[14]={ WHITE, RED, BLUE, BRED, //LCD刷屏时使用的颜色
GRED, GBLUE, BLACK, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
int main(void) //主函数
{
OS_ERR err;
CPU_SR_ALLOC();
delay_init(); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始化
LCD_Init(); //LCD初始化
KEY_Init(); //按键初始化
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ALIENTEK STM32F1");
LCD_ShowString(30,30,200,16,16,"UCOSIII Examp 9-1");
LCD_ShowString(30,50,200,16,16,"KEY_UP:Start Tmr1");
LCD_ShowString(30,70,200,16,16,"KEY0:Start Tmr2");
LCD_ShowString(30,90,200,16,16,"KEY1:Stop Tmr1 and Tmr2");
LCD_DrawLine(0,108,239,108); //画线
LCD_DrawLine(119,108,119,319); //画线
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16, "TIMER1:000");
LCD_ShowString(126,111,110,16,16,"TIMER2:000");
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块
(CPU_CHAR * )"start task", //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void * )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK * )&START_TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度,
(void * )0, //用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
}
void start_task(void *p_arg) //开始任务函数
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //统计任务
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
//创建定时器1
OSTmrCreate((OS_TMR *)&tmr1, //定时器1
(CPU_CHAR *)"tmr1", //定时器名字
(OS_TICK )20, //20*10=200ms
(OS_TICK )100, //100*10=1000ms
(OS_OPT )OS_OPT_TMR_PERIODIC, //周期模式
(OS_TMR_CALLBACK_PTR)tmr1_callback,//定时器1回调函数
(void *)0, //参数为0
(OS_ERR *)&err); //返回的错误码
//创建定时器2
OSTmrCreate((OS_TMR *)&tmr2,
(CPU_CHAR *)"tmr2",
(OS_TICK )200, //200*10=2000ms
(OS_TICK )0,
(OS_OPT )OS_OPT_TMR_ONE_SHOT, //单次定时器
(OS_TMR_CALLBACK_PTR)tmr2_callback, //定时器2回调函数
(void *)0,
(OS_ERR *)&err);
OS_CRITICAL_ENTER(); //进入临界区
//创建TASK1任务
OSTaskCreate((OS_TCB * )&Task1_TaskTCB,
(CPU_CHAR * )"Task1 task",
(OS_TASK_PTR )task1_task,
(void * )0,
(OS_PRIO )TASK1_TASK_PRIO,
(CPU_STK * )&TASK1_TASK_STK[0],
(CPU_STK_SIZE)TASK1_STK_SIZE/10,
(CPU_STK_SIZE)TASK1_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 * )&err);
OS_CRITICAL_EXIT(); //退出临界区
OSTaskDel((OS_TCB*)0,&err); //删除start_task任务自身
}
void task1_task(void *p_arg) //任务1的任务函数
{
u8 key,num;
OS_ERR err;
while(1)
{
key = KEY_Scan(0);
switch(key)
{
case WKUP_PRES: //当key_up按下的话打开定时器1
OSTmrStart(&tmr1,&err); //开启定时器1
printf("开启定时器1\r\n");
break;
case KEY0_PRES: //当key0按下的话打开定时器2
OSTmrStart(&tmr2,&err); //开启定时器2
printf("开启定时器2\r\n");
break;
case KEY1_PRES: //当key1按下话就关闭定时器
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err); //关闭定时器1
OSTmrStop(&tmr2,OS_OPT_TMR_NONE,0,&err); //关闭定时器2
printf("关闭定时器1和2\r\n");
break;
}
num++;
if(num==50) //每500msLED0闪烁一次
{
num = 0;
LED0 = ~LED0;
}
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err); //延时10ms
}
}
void tmr1_callback(void *p_tmr, void *p_arg) //定时器1的回调函数
{
static u8 tmr1_num=0;
LCD_ShowxNum(62,111,tmr1_num,3,16,0x80); //显示定时器1的执行次数
LCD_Fill(6,131,114,313,lcd_discolor[tmr1_num%14]);//填充区域
tmr1_num++; //定时器1执行次数加1
}
void tmr2_callback(void *p_tmr,void *p_arg) //定时器2的回调函数
{
static u8 tmr2_num = 0;
tmr2_num++; //定时器2执行次数加1
LCD_ShowxNum(182,111,tmr2_num,3,16,0x80); //显示定时器1执行次数
LCD_Fill(126,131,233,313,lcd_discolor[tmr2_num%14]); //填充区域
LED1 = ~LED1;
printf("定时器2运行结束\r\n");
}