基于Atmega328P的HC-SR04超声波测距仪单Timer1实现问题
HC-SR04单Timer1测距实现问题解决
问题背景
现有针对Atmega328P的C语言代码,通过Timer0(配置为PWM模式生成10µs初始脉冲)和Timer1(捕获模式,同时每秒重启Timer0)实现HC-SR04超声波测距功能。现需改用单个Timer1实现相同功能,但自行编写的代码无法生成1秒间隔的10µs脉冲,相关代码及PORTB1引脚输出的脉冲波形图如下:
原有双定时器实现代码
#include <avr/io.h> #include <avr/interrupt.h> void timer0Init() { TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00); TCCR0B = (1 << CS00); OCR0A = 159; TIMSK0 = (1 << OCIE0A); } void timer1Init() { TCCR1B = (1 << ICES1) | (1 << WGM12) | (1 << CS12) | (1 << CS10); OCR1A = 15625; TIMSK1 = (1 << ICIE1) | (1 << OCIE1A); } void usartInit() { UCSR0B = (1 << TXEN0); UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); UBRR0 = 103; } ISR(TIMER0_COMPA_vect) { TCCR0B ^= (1 << CS00); } ISR(TIMER1_COMPA_vect) { TCCR0B ^= (1 << CS00); } volatile uint16_t old, temp; ISR(TIMER1_CAPT_vect) { temp = ICR1; TCCR1B ^= (1 << ICES1); if ((TCCR1B & (1 << ICES1)) != 0) { uint16_t delta = 0; if (temp > old) { delta = temp - old; } else { delta = 65535 - old + temp; } if ((UCSR0A & (1 << UDRE0)) != 0) { UDR0 = delta * 10; } } else { old = temp; } } int main(void) { DDRD = 0x40; timer0Init(); timer1Init(); usartInit(); sei(); while (1) {} }
自行尝试的单Timer1实现代码
#include <avr/io.h> #include <avr/interrupt.h> void timer1Init() // 配置Timer1用于捕获ICP1引脚信号变化 { TCCR1A = (1 << COM1A1) | (1 << WGM10); // Fast PWM模式 TCCR1B = (1 << CS10) | (1 << WGM12); // 无预分频,使用CPU主频 OCR1A = 159; // 占空比62% TIMSK1 = (1 << OCIE1A); // 启用比较匹配A中断 } void usartInit() // 配置UART { UCSR0B = (1 << TXEN0); // 启用发送功能 UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); // 1停止位,8位数据帧 UBRR0 = 103; // 波特率9600bit/s } ISR(TIMER1_COMPA_vect) { if (OCR1A == 159) { // 切换为CTC模式,上升沿捕获,预分频clk/1024 TCCR1B = (1 << ICES1) | (1 << WGM12) | (1 << CS12) | (1 << CS10); OCR1A = 15625; // 测量间隔1秒 TIMSK1 = (1 << ICIE1) | (1 << OCIE1A); // 启用捕获中断 } else if (OCR1A == 15625) { TCCR1A = (1 << COM1A1) | (1 << WGM10); // 切回Fast PWM模式 TCCR1B = (1 << CS10) | (1 << WGM12); // 无预分频 OCR1A = 159; // 占空比62% TIMSK1 = (1 << OCIE1A); // 启用比较匹配A中断 } } volatile uint16_t old, temp; // 存储捕获值的全局变量 ISR(TIMER1_CAPT_vect) // Timer1捕获中断 { temp = ICR1; // 读取当前捕获值 TCCR1B ^= (1 << ICES1); // 切换捕获边沿类型 if ((TCCR1B & (1 << ICES1)) != 0) // 捕获到接收端信号 { uint16_t delta = 0; // 存储距离(单位mm) if (temp > old) // 当前值大于上次捕获值 { delta = temp - old; // 计算时间差 } else // 定时器溢出 { delta = 65535 - old + temp; // 处理溢出后的时间差 } if ((UCSR0A & (1 << UDRE0)) != 0) // UART缓冲区空闲 { UDR0 = delta * 10; // 通过UART发送数据 } } else // 捕获到发送端信号 { old = temp; // 保存当前捕获值 } } int main(void) { DDRB = (1 << PORTB1); timer1Init(); usartInit(); sei(); while (1) {} }
脉冲波形图


问题分析与修正方案
你的单Timer1代码存在以下核心问题:
- 切换定时器模式时未重置
TCNT1计数器,导致定时偏移,无法保证1秒间隔和10µs脉冲的准确性。 - 错误使用持续PWM模式生成触发脉冲,原逻辑是单次10µs脉冲而非持续PWM,这会导致引脚输出异常波形。
- 模式切换时的寄存器配置顺序错误,可能引发定时器状态混乱。
修正后的单Timer1实现代码
#include <avr/io.h> #include <avr/interrupt.h> #define TRIG_PIN PB1 #define F_CPU 16000000UL void timer1Init() { // 初始配置:CTC模式,准备生成10µs触发脉冲 DDRB |= (1 << TRIG_PIN); PORTB &= ~(1 << TRIG_PIN); // 触发脚初始置低 // CTC模式,无预分频(16MHz),OCR1A对应10µs计时 TCCR1A = 0; TCCR1B = (1 << WGM12) | (1 << CS10); OCR1A = 159; // 16MHz下,10µs对应160个时钟周期(计数范围0~159) TIMSK1 = (1 << OCIE1A); // 启用比较匹配A中断 } void usartInit() { UCSR0B = (1 << TXEN0); UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); UBRR0 = 103; // 9600波特率@16MHz } volatile uint16_t capture_old = 0; volatile uint8_t measure_state = 0; // 0: 等待触发, 1: 生成脉冲中, 2: 等待回波捕获 ISR(TIMER1_COMPA_vect) { switch(measure_state) { case 0: // 触发开始:置高触发脚,重置计数器保证10µs精度 PORTB |= (1 << TRIG_PIN); TCNT1 = 0; measure_state = 1; break; case 1: // 10µs脉冲结束:置低触发脚,切换为捕获模式(上升沿触发) PORTB &= ~(1 << TRIG_PIN); TCCR1B = (1 << ICES1) | (1 << CS12) | (1 << CS10); // 上升沿捕获,预分频1024 TIMSK1 = (1 << ICIE1); // 启用捕获中断,关闭比较中断 measure_state = 2; break; case 2: // 测距完成,切换回CTC模式设置1秒间隔 TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10); // CTC模式,预分频1024 OCR1A = 15624; // 16MHz/1024=15625Hz,1秒对应计数0~15624 TIMSK1 = (1 << OCIE1A); // 启用比较中断 TCNT1 = 0; // 重置计数器保证1秒间隔准确 measure_state = 0; break; } } ISR(TIMER1_CAPT_vect) { uint16_t capture_new = ICR1; if((TCCR1B & (1 << ICES1)) != 0) { // 捕获到回波上升沿:记录起始时间,切换为下降沿捕获 capture_old = capture_new; TCCR1B &= ~(1 << ICES1); } else { // 捕获到回波下降沿:计算时间差并转换为距离 uint16_t delta = capture_new - capture_old; // 处理定时器溢出情况 if(capture_new < capture_old) { delta = 0xFFFF - capture_old + capture_new + 1; } // 等待UART缓冲区空闲后发送数据 while(!(UCSR0A & (1 << UDRE0))); UDR0 = delta * 10; // 切换回CTC模式,启动1秒后下一次测量的定时 TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10); OCR1A = 15624; TIMSK1 = (1 << OCIE1A); TCNT1 = 0; measure_state = 0; } } int main(void) { timer1Init(); usartInit(); sei(); while(1); }
关键修正点说明
- 状态机管理:引入
measure_state变量清晰划分触发脉冲生成、回波捕获、间隔等待三个阶段,避免模式切换逻辑混乱。 - 计数器重置:每次切换定时器模式或配置新定时值时,重置
TCNT1,确保定时精度。 - 触发脉冲生成:放弃持续PWM,改用CTC模式生成单次10µs脉冲,完全匹配HC-SR04的触发要求。
- 中断配置优化:在不同阶段仅启用必要的中断(比较中断/捕获中断),减少不必要的中断触发,提升稳定性。
内容的提问来源于stack exchange,提问作者Elijah Sokol
相关产品推荐
相关产品推荐

