🌉RTOS原理与实现11:软件定时器
2023-7-21
| 2023-7-21
0  |  阅读时长 0 分钟
type
status
password
date
slug
summary
category
URL
tags
icon
notion image

1. 软件定时器的原理与创建

1.1 问题概述

1.1.1 需求场景

在实际应用中,常常需要周期性或者在指定时间做一件事情,具体可分为如下2种情况,
  • 周期性:在指定的延时开始做某件事情,然后周期性重复执行
  • 一次性:在指定的延时开始做某件事情,执行一次后立即结束
notion image

1.1.2 硬件方案

由于硬件定时器有限,而定时任务需求可能有很多个,所以单纯依赖硬件定时器肯定不能满足需求

1.1.3 软件方案

使用任务 + tTaskDelay实现定时任务,框架如下
💡
虽然上述方案可行,但是如果为每个定时需求都启动一个任务的话,会导致任务数量过多,资源开销太大
因此方案的总体思路就是使用一个任务满足所有的定时需求

1.2 设计原理

1.2.1 定时器任务方案

notion image
💡
优点:
  1. 将每种定时需求的信息保存到软定时器结构中
  1. 将不同的软定时器结构组织为定时器列表
  1. 定时器任务依靠系统时钟节拍工作(e.g. SysTick向定时器任务发送事件),定时器任务扫描定时器列表并执行已经到期的定时函数
💡
缺点:
  1. 定时器任务可能被更高优先级的任务抢占CPU运行,导致对定时器列表的刷新滞后
  1. 由一个定时器任务执行所有的定时器函数,在定时精度上也会有偏差(e.g. 某个定时器函数运行时间较长)

1.2.2 定时器中断方案

notion image
优点:为确保定时时间确定,在SysTick ISR中扫描定时器列表,并执行已经到期的定时函数
缺点:由于是在SysTick ISR中调用定时函数,所以要执行的函数必须尽可能短小简单

1.2.3 最终总体方案

notion image
💡
要点:设置2个定时器列表,一个由SysTick ISR处理,另一个由定时器任务处理
  • SysTick ISR处理的定时器任务必须尽可能简单,但是可以保持相对确定的计时
  • 定时器任务处理的定时器虽然计时相对不确定,但是对定时函数的要求更低,因此需要根据使用场景选择合适的方式
补充:这种关系类似Linux中的中断顶半部和底半部

1.3 设计实现

1.3.1 定义定时器类型

1.3.2 添加tTimerInit函数

💡
说明:如果将周期计数值(durationTicks)设置为0,则标识一次性定时任务

2. 软件定时器的启动与停止

2.1 设计原理

2.1.1 定时器的启动

notion image
💡
说明:启动定时器就是将软定时器结构根据配置插入指定的定时器列表

2.1.2 定时器的停止

notion image
💡
说明:停止定时器就是将软定时器结构从对应的定时器列表中移除

2.2 设计实现

2.2.1 添加定时器相关资源

在定义完上述定时器相关资源后,添加tTimerModuleInit函数初始化这些资源
说明:此处将定时器任务优先级设置为1,在设置时不能和空闲任务(idle task)处于同一优先级,因为空闲任务是没有其他任务可执行时的特殊任务
如果有任务和空闲任务同优先级,会影响后续空闲任务中的功能开发(e.g. 性能测量)

2.2.2 添加tTimerCallFuncList函数

说明:tTimerCallFuncList函数为通用的定时器列表处理函数,将分别被SysTick ISR和定时器任务调用,分别处理硬定时器列表和软定时器列表
讨论:在使用中如何确保定时器任务的时间精度
从tTimerCallFuncList函数的实现可见,无论是硬定时器还是软定时器,在处理过程中都会遍历各自的定时器列表,并在时间到期后调用定时器回调函数
所以如果定时器回调函数运行时间过长,就会影响定时精度,因此定时器任务应该尽可能简单短小。如果确实需要定时完成复杂的工作,应该启动单独的任务,通过定时器回调函数与任务的交互,将耗时的操作推迟给任务执行(在Linux内核编程中也应该做类似的处理)

2.2.3 添加tTimerStart函数

说明1:对硬定时器列表的保护
硬定时器列表被SysTick ISR和任务访问,所以使用临界区保护
说明2:对软定时器列表的保护
软定时器列表被定时器任务和其他任务访问,所以使用计数信号量保护。此处未使用互斥信号量,是因为临界区代码非常简单,无需互斥信号量的众多特性
说明3:目前假定tTimerStart & tTimerStop函数均只能在任务中调用,如果希望这2个函数也能在ISR中被调用,则需要添加进出临界区的保护(仅使用信号量就不够了[更准确的说,使用信号量是不行])
此处要注意,在ISR中是没有办法用信号量实现互斥的,因为ISR不是一个任务,触发任务调度也不能起到阻塞ISR的作用
说明4:启动定时器时需要再次设置timer->delayTicks
因为可能启动一个已经运行过的定时器,此时该定时器的delayTicks字段为0

2.2.4 添加tTimerStop函数

2.2.5 对硬定时器队列的处理

硬定时器队列由SysTick ISR处理,此处添加tTimerModuleTickNotify函数实现该功能,同时向定时器任务发送信号
说明1:向定时器任务发送信号后,定时器任务才会运行并处理软定时器列表
说明2:tTimerModuleTickNotify函数由SysTick ISR调用

2.2.6 对软定时器队列的处理

软定时器列表由定时器任务处理,而定时器任务的运行需要依赖SysTick ISR向其发送信号

3. 软件定时器的删除与状态查询

3.1 设计原理

3.1.1. 定时器删除

notion image
说明:删除定时器就是将定时器结构从对应的定时器列表中删除,然后将定时器结构的状态设置为tTimerDestroy

3.2 设计实现

3.2.1 添加tTimerDestroy函数

说明:销毁定时器就是先将定时器停止,然后修改其状态为tTimerDestroy

3.2.2 添加tTimerGetInfo函数

为获取定时器的状态,首先定义用于保存定时器信息的结构
说明:关于互斥的讨论
目前对定时器的操作包含启动、停止、销毁、遍历定时器列表和查询状态,目前均通过进入临界区或信号量的方式进行了互斥
而此处的共享资源就是定时器列表及其中每个定时器结构的状态
 
  • RTOS
  • RTOS原理与实现10:互斥信号量实现RTOS原理与实现12:性能测量
    Loading...
    目录