You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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) {}
}

脉冲波形图

异常脉冲波形1
异常脉冲波形2


问题分析与修正方案

你的单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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 04:57:01