8. 定时器
8.1 定时器介绍
定时器是单片机内部集成,可以通过编程控制。单片机的定时功能是通过计数来实现的,当单片机每一个机器周期产生一个脉冲时,计数器就加一。定时器的主要功能是用来计时,时间到达之后可以产生中断,提醒计时时间到,然后可以在中断函数中去执行功能。比如我们想让一个 led 灯 1 秒钟翻转一次,就可以使用定时器配置为 1 秒钟触发中断,然后在中断函数中执行 led 翻转的程序。
8.2 硬件定时器和软件定时器
定时器可以基于硬件也可以基于软件实现,两者有各自的特点和适用场景:
- 硬件定时器是由微控制器硬件提供的定时功能,由专门的计时/计数器电路实现。硬件定时器的最大优势在于精确度高和可靠性强,因为它们不受软件任务和操作系统调度的影响。当需要非常精确的定时功能,如产生PWM信号或者获取精确的时间测量时,硬件定时器是首选。由于定时操作由硬件直接完成,即使主CPU忙于其他任务,定时器仍然可以在预定时间到达时准确地执行回调操作。
- 软件定时器是由操作系统或者软件库实现的定时器,它们利用操作系统提供的机制来模拟定时器功能。软件定时器的实现受到当前系统负载和任务调度策略的影响,因此相对来说不如硬件定时器精确。但是软件定时器通常更灵活,可以创建大量的定时器,适用于不需要精确时间控制的场合。
- 在某些情况下,软件定时器可能会引起定时精度问题,例如在高负载条件下,或者当系统中有许多其他高优先级任务时。对于不需要高精度的简单延时,软件定时器通常足够使用。
8.3 定时器基本参数
ESP32S3芯片具有多个硬件定时器,例如 Timer0、Timer1、Timer2 等,每个定时器都包含多个通道。可以通过指定定时器号和通道号来选择具体使用的定时器和通道。基本的定时器参数包括:定时器号、通道号、预分频器、自动重新加载值、定时器中断使能等。
以下是一些基本概念和定时器的共有属性:
- 计时器(Counter): 定时器的核心组件,负责持续计数。
- 定时器溢出(Overflow): 当计数器达到其最大值然后归零时发生。
- 预置值(Preset Value): 计数器达到该值时会产生中断或其它事件。
- 分频器(Prescaler): 用于减小计数器接收的时钟信号频率,以延长定时器的最大计时范围。
- 中断(Interrupt): 当定时器达到预置值时,可以配置它来产生一个中断,中断处理程序将执行一些任务。
8.4 使用定时器流程
8.4.1 创建定时器对象
在程序顶部声明一个 hw_timer_t
类型的全局变量,用于表示定时器对象。例如:
hw_timer_t *timer = NULL;
8.4.2 初始化定时器
在 setup()
函数中初始化定时器。在初始化过程中,设置定时器的周期、以及绑定中断函数。例如:
void setup()
{
// 初始化定时器
timer = timerBegin(1000000);
// 使能定时器中断
timerAttachInterrupt(timer, onTimerISR);
//设置定时器触发报警的的目标计数值,并且开启无限重复计数
timerAlarm(timer,1000000,true, 0);
}
2
3
4
5
6
7
8
9
10
11
在上面的示例中,使用 timerBegin()
函数初始化定时器,并设置定时器的频率为 1000000Hz,换算成为时间就是每秒计数1000000次,即 1 微秒计一次数。接下来,使用 timerAttachInterrupt()
函数绑定定时器中断服务函数 onTimerISR
,并使能定时器中断。
以下为定时器修改函数的说明:
hw_timer_t * timerBegin(uint32_t frequency);
:初始化硬件定时器,参数说明:
frequency
: 设置定时器频率,在该函数的代码中已经为大家配置好了定时器的时钟源,所以我们只需要设置定时器的频率即可。定时器的频率设置会影响到定时器的计时周期。计时周期的确定取决于定时器频率,由于定时器频率在以上代码案例中设置为(1000000Hz)1MHz,因此定时器每计一个数的时间为:1 / 1000000 = 0.000001 秒 = 1微秒
。
void timerAttachInterrupt(hw_timer_t * timer, voidFuncPtr userFunc)
:用于将中断处理函数与特定的定时器关联起来,参数含义如下:
timer
;定时器指针;userFunc
: 中断处理函数。
void timerAlarm(hw_timer_t * timer, uint64_t alarm_value, bool autoreload, uint64_t reload_count)
:配置定时器的报警或中断值。
timer
: 定时器指针;alarm_value
: 报警值,当定时器从0计数到该值时,会触发中断;autoreload
: 是否开启自动重新装载计数值,即当计数到报警值后会变为reload_count
然后开始计数。true为开启,false为关闭;reload_count
:要重新装载的计数值;
8.4.3 定时器中断服务函数
引用上面的案例,在 onTimerISR()
函数中编写定时器中断处理逻辑,例如读取传感器数据、控制外设等。请根据实际需求编写相应的代码。中断服务函数的名称没有固定要求,只需要按照C格式命名即可;
8.5 硬件连接与准备
本案例通过LED测试定时器的定时效果,将LED状态变化放在定时器中断处理函数中,设置定时器的定时时间为1秒,当定时器的定时时间到时,就会触发中断,将LED的状态改变;
8.6 定时器验证
以下代码基于 arduino V2.3.3版本,esp32 3.1.0-RC1
#define LED 48
//定义一个定时器对象
hw_timer_t *timer = NULL;
// 定时器中断处理函数
void timer_interrupt()
{
//修改LED的状态,如果亮则修改为灭;如果灭则修改为亮
digitalWrite(LED, !digitalRead(LED));
}
void setup()
{
//设置LED引脚(48) 为输出模式
pinMode(LED, OUTPUT);
// 初始化定时器频率为1MHz
timer = timerBegin(1000000);
// 配置定时器中断服务函数
//定时器0的中断回调函数为timer_interrupt()
timerAttachInterrupt(timer,timer_interrupt);
// 设置定时器的计数值
// 定时器的计数值为1000,000,true为允许自动重载计数值
// 计数值单位为us,1000,000 us = 1000ms = 1s
timerAlarm(timer,1000000,true, 0);
}
void loop()
{
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
8.7 定时器效果
每隔一秒亮灭一次。