引言
在Arduino平台上,delayMicroseconds()虽能提供微秒级延时,但其基于软件循环,极易被中断打断,无法保证脉冲序列的精确性。硬件定时器则通过独立计数与比较匹配,在硬件层面生成精确波形,是高频脉冲输出的首选方案。然而,要真正发挥其威力,必须深入理解其边界条件。
硬件定时器基础
定时器与预分频器
Arduino Uno(ATmega328P)拥有三个定时器:Timer0(8位)、Timer1(16位)、Timer2(8位)。其中Timer0已被系统用于millis(),不建议占用。Timer1和Timer2均可用于脉冲生成,但分辨率不同。
- Timer1:16位计数器,最大计数值65535,适合较长周期。
- Timer2:8位计数器,最大255,但支持异步时钟(外部32.768kHz晶振),适合低功耗场景。
预分频器(Prescaler)决定计数时钟频率。例如,16MHz系统时钟下,预分频为8时,计数频率为2MHz,即每0.5μs计数一次,分辨率0.5μs。
比较匹配与波形生成
定时器通过比较寄存器(OCRnx)与计数器值匹配,自动翻转输出引脚(CTC模式)或产生中断。利用CTC模式(Clear Timer on Compare Match),可生成精确方波,无需CPU干预。
边界条件分析
1. 时钟源与预分频的取舍
预分频越小,分辨率越高,但可生成的最大周期越短。以Timer1为例,16MHz无预分频时,分辨率62.5ns,但16位计数器最大周期仅4.096ms。若需更长周期,必须增大预分频,但分辨率随之下降。
关键点:选择预分频时,需同时满足最小脉冲宽度和最大周期需求。例如,生成1μs至10ms的脉冲,预分频8(分辨率0.5μs)可覆盖,但若需0.1μs分辨率,则必须无预分频,且周期上限受限。
2. 定时器分辨率与溢出
8位定时器(Timer2)在预分频1时,计数到255仅需16μs(16MHz),若需更长周期,需在中断中软件扩展计数,但中断延迟会引入抖动。16位定时器则更灵活,但需注意溢出中断(TOV)与比较匹配中断(OCF)的优先级。
3. 中断延迟的不可预测性
若使用中断服务程序(ISR)来翻转引脚,则脉冲间隔受中断响应时间影响。AVR中断响应典型为10-20个时钟周期(约0.6-1.25μs@16MHz),但若其他中断正在执行,延迟可能更长。因此,高精度脉冲序列应使用硬件输出比较(OCnx引脚自动翻转),而非ISR软件翻转。
4. 代码执行时间与原子性
在修改定时器寄存器时,若被中断打断,可能导致中间状态。例如,写入16位寄存器TCNT1时,需先写高字节再写低字节,期间若发生比较匹配,可能产生错误波形。因此,需使用原子操作(如关闭中断)或利用临时寄存器。
5. 引脚复用与输出极性
OC1A(Pin9)和OC1B(Pin10)可自动翻转,但需配置为输出模式。同时,注意引脚是否被其他外设占用(如SPI、I2C)。
实战:使用Timer1生成精确脉冲序列
需求
生成频率为10kHz(周期100μs)的方波,占空比50%,要求抖动小于±0.5μs。
配置步骤
- 设置引脚:将Pin9设为输出。
-
配置Timer1为CTC模式:
- 设置TCCR1A的WGM12位(CTC模式)。
- 选择预分频:系统时钟16MHz,预分频8,计数频率2MHz,周期0.5μs。
- 计算比较值:周期100μs / 0.5μs = 200,故OCR1A = 199(计数从0到199,共200个时钟)。
- 使能比较匹配中断(可选,用于同步其他操作,但波形输出不依赖中断)。
- 启动定时器:设置CS10位(预分频8)。
完整代码示例
// 使用Timer1 CTC模式生成10kHz方波于Pin9
// 无需中断,硬件自动翻转
void setup() {
// 设置Pin9为输出(OC1A)
DDRB |= (1 << PB1); // Pin9是PB1
// 配置Timer1
TCCR1A = 0; // 清零
TCCR1B = 0;
TCNT1 = 0; // 初始计数
// CTC模式:WGM12=1,WGM13=0,WGM11:10=0
TCCR1B |= (1 << WGM12);
// 比较值:周期100μs,预分频8,计数频率2MHz,OCR1A = 100μs * 2MHz - 1 = 199
OCR1A = 199;
// 设置OC1A在比较匹配时翻转(COM1A0=1,COM1A1=0)
TCCR1A |= (1 << COM1A0);
// 启动定时器,预分频8(CS11=1)
TCCR1B |= (1 << CS11);
}
void loop() {
// 主循环可执行其他任务,脉冲生成由硬件完成
}
代码说明
- 通过
COM1A0位设置OC1A在比较匹配时自动翻转,无需软件干预。 - 预分频8,分辨率0.5μs,满足±0.5μs抖动要求。
- 注意:若需改变频率,需重新计算OCR1A,并注意写入时的原子性(此处为16位寄存器,但OCR1A的写入在CTC模式下是缓冲的,比较匹配时更新,故安全)。
边界条件测试与验证
使用逻辑分析仪或示波器测量实际波形。测试不同预分频和比较值,观察抖动。若发现抖动,可能原因:
- 电源噪声或引脚负载过大。
- 其他中断干扰(但硬件输出不受影响)。
- 定时器寄存器被意外修改(如库函数)。
注意事项
- 避免使用delay()或millis():它们依赖Timer0,可能干扰系统时钟。
-
关闭全局中断:在修改关键寄存器时,使用
cli()和sei()保护。 - 考虑时钟源精度:Arduino板载晶振精度通常为±20ppm,对微秒级影响不大,但若需更高精度,需外部晶振或温补晶振。
- 多任务冲突:若同时使用多个定时器,需规划好资源。
总结
利用硬件定时器生成微秒级脉冲序列,关键在于理解预分频、比较匹配和中断的边界条件。通过合理选择预分频和利用硬件输出比较,可避免中断延迟,实现高精度波形。本文提供的配置方法可扩展至任意频率,只需调整OCR值。嵌入式开发中,细节决定成败,掌握这些边界条件,你将能自信应对更复杂的时序需求。