参考教程:【正点原子】手把手教你学UCOS-III实时操作系统_哔哩哔哩_bilibili
一、软件定时器简介
1、定时器的概念与种类
(1)定时器的概念:从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可自定义定时器的周期。
(2)定时器的种类:
①硬件定时器:芯片本身自带的定时器模块,硬件定时器的精度一般很高,每次在定时时间到达之后就会自动触发一个中断,用户在中断服务函数中处理信息。
②软件定时器:具有定时功能的软件,可设置定时周期,当指定时间到达后要调用回调函数(也称超时函数),用户在回调函数中处理信息。
2、软件定时器的优缺点
(1)优点:硬件定时器数量有限,而软件定时器理论上只需有足够内存,就可以创建多个,且使用简单、成本低。
(2)缺点:软件定时器相对硬件定时器来说,精度没有那么高(因为它以系统时钟为基准,系统时钟中断优先级又是最低,容易被打断),对于需要高精度要求的场合,不建议使用软件定时器。
二、µC/OS-III软件定时器
1、µC/OS-III软件定时器的种类和状态
(1)µC/OS-III提供了两种软件定时器:
①单次定时器:单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数,不会自动重新开启定时,不过可以被手动重新开启。
②周期定时器:周期定时器的一旦启动以后就会在执行完回调函数以后自动的重新启动 ,从而周期地执行其软件定时器回调函数。
(2)软件定时器共有4种状态:
①未使用态:当软件定时器被定义但还未被创建或软件定时器被删除时,软件定时器就处于未使用态。
②停止态:当软件定时器被创建但还未开启定时器或被停止时,软件定时器就处于停止态。
③运行态:运行态的定时器,当指定时间到达之后,它的超时回调函数会被调用。
④完成态:当单次定时器定时超时后,软件定时器处于完成态。
2、µC/OS-III软件定时器特点
(1)软件定时器支持裁剪,如需使能软件定时器,需将OS_CFG_TMR_EN配置项配置成1。
(2)软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的API函数。
(3)软件定时器的频率由宏OS_CFG_TMR_TASK_RATE_HZ决定,要注意软件定时器的频率并不等于系统时钟节拍的频率,但软件定时器是依靠系统节拍实现定时的,所以需要进行换算:
软件定时器时间分辨率 = OS_CFG_TICK_RATE_HZ / OS_CFG_TMR_TASK_RATE_HZ
//例如:在例程中将宏OS_CFG_TMR_TASK_RATE_HZ设置为10,也就是将软件定时器时钟信号频率设置为10HZ(100ms),假设现在开启一个定时时长为10的软件定时器,那么他大约在10 * 100ms = 1000ms后超时
3、µC/OS-III软件定时器相关API函数
(1)µC/OS-III软件定时器相关API函数概览:
函数 | 描述 |
OSTmrCreate | 创建一个软件定时器 |
OSTmrDel | 删除一个软件定时器 |
OSTmrRemainGet | 获取软件定时器的剩余超时时间 |
OSTmrSet | 设置软件定时器的延时时间、周期、超时回调函数及其参数等 |
OSTmrStart | 开启软件定时器定时 |
OSTmrStateGet | 获取软件定时器的状态 |
OSTmrStop | 停止软件定时器定时 |
(2)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
)
形参 | 描述 |
p_tmr | 指向软件定时器结构体指针 |
p_name | 指向软件定时器名的字符串指针 |
dly | 软件定时器的开启延时时间,注意单次定时器必须大于0(也就是开启之后多久超时) |
period | 周期定时器的定时周期时间,注意周期定时器必须大于0 |
opt | OS_OPT_TMR_ONE_SHOT(单次定时器) OS_OPT_TMR_PERIODIC(周期定时器) |
p_callback | 指向超时回调函数的指针 |
p_callback_arg | 超时回调函数入口参数 |
p_err | 指向接收错误代码变量的指针 |
①周期软件定时器的时序图:
②单次软件定时器的时序图:
(3)OSTmrDel函数:
CPU_BOOLEAN OSTmrDel //返回OS_TRUE表示删除成功,OS_FALSE表示删除失败
(
OS_TMR* p_tmr,
OS_ERR* p_err
)
形参 | 描述 |
p_tmr | 指向软件定时器结构体指针 |
p_err | 指向接收错误代码变量的指针 |
(4)OSTmrStart函数:
CPU_BOOLEAN OSTmrStart //返回OS_TRUE表示开启成功,OS_FALSE表示开启失败
(
OS_TMR* p_tmr,
OS_ERR* p_err
)
形参 | 描述 |
p_tmr | 指向软件定时器结构体指针 |
p_err | 指向接收错误代码变量的指针 |
(5)OSTmrStop函数:
CPU_BOOLEAN OSTmrStop
(
OS_TMR* p_tmr,
OS_OPT opt,
void* p_callback_arg,
OS_ERR* p_err
)
形参 | 描述 |
p_tmr | 指向软件定时器结构体的指针 |
opt | 函数操作选项 |
p_callback_arg | 传给软件定时器超时回调函数的参数 |
p_err | 指向接收错误代码变量的指针 |
可选的函数操作选项如下:
Opt | 描述 |
OS_OPT_TMR_CALLBACK | 执行一次回调函数,代入原始参数 |
OS_OPT_TMR_CALLBACK_ARG | 执行一次回调函数,代入指定参数 |
OS_OPT_TMR_NONE | 仅停止软件定时器 |
三、µC/OS-III软件定时器实验
1、原理图与实验目标
(1)原理图:
(2)实验目标:
①设计2个任务——start_task、task1:
[1]start_task:用于创建task1任务,创建两个定时器并启动之(分别为单次和周期型,其回调函数中分别需要翻转LED1和LED2的状态)。
[2]task1:用于按键扫描,并对两个软件定时器进行开启、停止操作,按下按键2即开始两个软件定时器,按下按键1反之。
②预期实验现象:
[1]程序下载到板子上后,LED1灯状态仅变更一次,LED2灯持续闪烁。
[2]按下按键1,LED1和LED2状态冻结。
[3]按下按键2,LED1灯状态仅变更一次,LED2恢复持续闪烁。
2、实验步骤
(1)将“任务创建和删除实验”的工程文件夹复制一份,在拷贝版中进行实验。
(2)删除UCOS_experiment.c文件中关于task2和task3的内容,在start_task中创建一个单次定时器和周期定时器,并编写回调函数,同时更改task1函数的具体实现,如下所示。
#include "stm32f10x.h" // Device header
#include "os.h"
#include "cpu.h"
#include "LED.h"
#include "Key.h"
#include <stdio.h>
/* START_TASK 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 256
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
OS_TCB start_task_tcb;
void start_task(void);
/* TASK1 任务 配置
* 包括: 任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIO 4
#define TASK1_STACK_SIZE 256
CPU_STK task1_stack[TASK1_STACK_SIZE];
OS_TCB task1_tcb;
void task1(void);
OS_TMR timer1,timer2;
void timer_callback(OS_TMR *pxTimer, void *p_arg);
void timer_callback(OS_TMR *pxTimer, void *p_arg);
void UCOS_Test(void)
{
OS_ERR err;
OSInit(&err); //初始化μC/OS-III
//创建Start Task
OSTaskCreate (&start_task_tcb,
"start_task",
(OS_TASK_PTR)start_task,
NULL,
START_TASK_PRIO,
start_task_stack,
START_TASK_STACK_SIZE / 10,
START_TASK_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
/* 开始任务调度 */
OSStart(&err);
}
void start_task(void)
{
OS_ERR err; //接收错误代码使用,对错误代码进行分情况处理可增强程序鲁棒性
CPU_Init(); //初始化CPU库
CPU_SR_ALLOC();
OSTmrCreate(&timer1,"timer1", 10, 0,
OS_OPT_TMR_ONE_SHOT,
(OS_TMR_CALLBACK_PTR)timer_callback,
NULL, &err); //创建单次定时器
OSTmrCreate(&timer2,"timer2", 0, 10,
OS_OPT_TMR_PERIODIC,
(OS_TMR_CALLBACK_PTR)timer_callback,
NULL, &err); //创建周期定时器
//滴答定时器重装载值 = 系统主频 / 滴答定时器中断频率(滴答定时器是递减计数)
CPU_INT32U cnts = SystemCoreClock / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); //配置Systick中断及优先级
CPU_CRITICAL_ENTER(); //进入临界区(关中断)
//创建task1
OSTaskCreate (&task1_tcb,
"task1",
(OS_TASK_PTR)task1,
0,
TASK1_PRIO,
task1_stack,
TASK1_STACK_SIZE / 10,
TASK1_STACK_SIZE,
0,
0,
0,
(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
&err);
OSTmrStart(&timer1, &err); //启动定时器1
OSTmrStart(&timer2, &err); //启动定时器2
CPU_CRITICAL_EXIT(); //退出临界区(开中断)
OSTaskDel(NULL, &err); //删除任务自身
}
void task1(void)
{
OS_ERR err;
uint8_t key = 0;
while(1)
{
key = Key_GetNum(); //读取按键键值
if(key == 1)
{
OSTmrStop(&timer1, OS_OPT_TMR_NONE, NULL, &err); //停止定时器1
OSTmrStop(&timer2, OS_OPT_TMR_NONE, NULL, &err); //停止定时器1
}
if(key == 2)
{
OSTmrStart(&timer1, &err); //启动定时器1
OSTmrStart(&timer2, &err); //启动定时器2
}
OSTimeDly(10,OS_OPT_TIME_DLY,&err);
}
}
/* 软件定时器的超时回调函数 */
void timer_callback(OS_TMR *pxTimer, void *p_arg) //第一个参数是调用回调函数的定时器的指针,第二个是函数参数
{
if(pxTimer == &timer1) //根据传入的定时器指针判断执行什么操作
{
LED1_Turn();
}
else if(pxTimer == &timer2)
{
LED2_Turn();
}
}
(3)程序完善好后点击“编译”,然后将程序下载到开发板上。