一、软件定时器简介
1 、基本概念
定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户 可以自定义定时器的周期与频率。类似生活中的闹钟,我们可以设置闹钟每天什么时候响, 还能设置响的次数,是响一次还是每天都响。
定时器有硬件定时器和软件定时器之分:
硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯 片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产 生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基 础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定 时器也是类似的。
使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中 处理信息;而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的 函数(也称超时函数/回调函数,为了统一,下文均用回调函数描述),在回调函数中处理 信息。
注意:软件定时器回调函数的上下文是任务,下文所说的定时器均为软件定时器。
软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。 定时精度与系统时钟的周期有关。一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况(软件定时器回调函数的上下文环境是任务),比如 vTaskDelay() 以及其它能阻塞任务运行的函数,两次触发回调函数的时间间隔 xTimerPeriodInTicks 叫定时器的定时周期。
FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码。
- 单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器删除,不再重新执行。
- 周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。
FreeRTOS 通过一个 prvTimerTask 任务(也叫守护任务 Daemon)管理软定时器,它是在启动调度器时自动创建的,为了满足用户定时需求。prvTimerTask 任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h 中的宏定义configUSE_TIMERS 设置为 1 ,将相关代码编译进来,才能正常使用软件定时器相关功能。
2 、时间精度
在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为 configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中有定义,默认是 1000。那么系统的时钟节拍周期就为 1ms(1s 跳动 1000 下,每一下就为 1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,例如节拍周期是 10ms,那么上层软件定时器定时数值只能是10ms,20ms,100ms 等,而不能取值为 15ms。由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。
3、 注意要点
- 软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定时器起任务挂起或者阻塞的 API 接口,在回调函数中也绝对不允许出现死循环。
- 软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默
- 认为 configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任务中最高的优先级。
- 创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件定时器,并回收资源。
- 定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。
4、软件定时器应用场景
在很多应用中,我们需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以 满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件 定时器代替硬件定时器任务。但需要注意的是软件定时器的精度是无法和硬件定时器相比 的,而且在软件定时器的定时过程中是极有可能被其它中断所打断,因为软件定时器的执 行上下文环境是任务。所以,软件定时器更适用于对时间精度要求不高的任务,一些辅助 型的任务。
二、STM32CubeMX设置
1、配置RCC、USART1、时钟72M
2、配置SYS,将Timebase Source修改为除滴答定时器外的其他定时器。
3、初始化LED的两个引脚
4、开启FreeRTOS,v1与v2版本不同,一般选用v1即可
5、创建两个线程:任务LED1用作发送,LED2用作接收。
以上步骤可参考:STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客
6、创建软件定时器Timer
要想使用软件定时器必须在 Config parameters
中把 USE_TIMERS
选择 Enabled
来使能。
在 Timers and Semaphores
中创建两个定时器,一个周期定时器,一个单次定时器。
- Timer Name: 定时器名称
- Callback: 回调函数名称
- Type: 定时器类型,osTimerPeriodic周期定时器,osTimerOnce单次定时器
- Code Generation Option: 代码生成选项
- Parameter: 回调函数形参,不用的时候配置为0或NULL即可
- Allocation: 分配方式:Dynamic 动态内存创建
- Conrol Block Name: 控制块名称
7、生成代码
三、相关API函数说明
1、创建软件定时器:osTimerCreate
创建一个软件定时器,并返回一个定时器ID。
例:
osTimerId myTimer01Handle;
osTimerId myTimer02Handle;
//创建周期定时器
osTimerDef(myTimer01, Callback01);
myTimer01Handle = osTimerCreate(osTimer(myTimer01), osTimerPeriodic, NULL);
//创建单次定时器
osTimerDef(myTimer02, Callback02);
myTimer02Handle = osTimerCreate(osTimer(myTimer02), osTimerOnce, NULL);
2、启动定时器:osTimerStart,可以在中断中使用,单位为ms
例:
osTimerStart(myTimer01Handle,1000);
3、停止定时器:osTimerStop
停止一个软件定时器,让其进入休眠态。该函数可以在中断中使用。
函数 | osStatus osTimerStop (osTimerId timer_id) |
---|---|
参数 | timer_id: 定时器ID |
返回值 | 错误码 |
例:
osTimerStop(myTimer01Handle);
4、删除定时器
用于删除一个已经被创建成功的软件定时器,删除之后就无法使用该定时器,并且定时器相应的资源也会被系统回收释放。
函数 | osStatus osTimerDelete (osTimerId timer_id) |
---|---|
参数 | timer_id: 定时器ID |
返回值 | 错误码 |
例:
osTimerDelete(myTimer01Handle)
四、程序编程
在cubemx中设置了两个定时器,会自动生成两个定时回调函数,定时器需要执行的程序可以在回调函数中编写,但在使用定时器之前需要开启一下定时器并设定定时时间。
1、在空闲任务中开启定时器,将循环定时器定时1000ms,单次定时器定时3000ms
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN StartDefaultTask */
//开始软件定时器
osTimerStart(myTimer01Handle, 1000);
osTimerStart(myTimer02Handle, 3000);
/* Infinite loop */
for(;;)
{
osDelay(1);
}
/* USER CODE END StartDefaultTask */
}
2、在定时器回调函数中编写程序。
void Callback01(void const * argument)
{
/* USER CODE BEGIN Callback01 */
static uint32_t TmrCb_Count1 = 0; /* 记录软件定时器 1 回调函数执行次数 */
TickType_t tick_num1;
TmrCb_Count1++; /* 每回调一次加一 */
tick_num1 = osKernelSysTick(); /* 获取滴答定时器的计数值 */
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("定时器1 %d,tick_num=%d\n", TmrCb_Count1,tick_num1);
/* USER CODE END Callback01 */
}
/* Callback02 function */
void Callback02(void const * argument)
{
/* USER CODE BEGIN Callback02 */
static uint32_t TmrCb_Count2 = 0; /* 记录软件定时器 2 回调函数执行次数 */
TickType_t tick_num2;
TmrCb_Count2++; /* 每回调一次加一 */
tick_num2 = osKernelSysTick(); /* 获取滴答定时器的计数值 */
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("定时器2 %d,tick_num=%d\n", TmrCb_Count2,tick_num2);
/* USER CODE END Callback02 */
}
下载验证:
程序编译无误后下载到板子上查看串口输出,可以看到定时器1每隔1000ms运行一次,定时器2定时3000ms运行一次后不再运行了。
五 、参考文献
韦东山freeRTOS系列教程之【第十章】软件定时器(software timer)_xtimerstart-CSDN博客
STM32CubeMX学习笔记(33)——FreeRTOS实时操作系统使用(软件定时器)_freertos cubemx 会占用定时器几-CSDN博客
FreeRTOS软件定时器 基于STM32_xtimerstart-CSDN博客