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

修复Arduino Uno HC-SR04超声波模块测距异常的C代码

HC-SR04测距值跳变问题的修复方案

针对你遇到的HC-SR04超声波模块测距值频繁在3000cm到5cm之间跳变的问题,以下是代码中的核心问题分析及修复方案:

核心问题分析

  • 输入捕获边沿未切换:原代码中始终设置为上升沿触发,导致中断服务程序(ISR)重复捕获上升沿,无法正确捕获ECHO信号的下降沿,进而计算出错误的时长。
  • 未处理定时器溢出:测量远距离时,16位Timer1会溢出,导致endTime < startTime,计算出的时长变成超大值(对应3000cm的错误结果)。
  • 无测量状态管理:每次触发传感器后未重置相关变量,旧测量值会干扰新结果。
  • 浮点数运算误差:距离计算使用浮点数,容易引入精度偏差。

修复后的完整代码

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>

// 位操作宏
#define bitSet(reg, n) (reg |= 1 << n)
#define bitRead(reg, n) (reg & (1 << n))
#define bitClear(reg, n) (reg &= ~(1 << n))

// 时钟与波特率定义
#define FOSC 16000000
#define BAUD 9600
#define MYUBRR FOSC / 16 / BAUD - 1

// 引脚定义
#define TRIG_PIN PB1
#define ECHO_PIN PB2

// 全局变量:管理测量状态与计时数据
volatile uint8_t measuring = 0;
volatile uint16_t overflowCount = 0;
volatile uint16_t startTime = 0;
volatile uint16_t endTime = 0;
volatile uint16_t duration = 0;
volatile uint16_t distance = 0;

// USART函数声明
void USART_Init(unsigned int ubrr);
void USART_Transmit(unsigned char data);
void txString(const char *pStr);

void USART_Init(unsigned int ubrr)
{
  UBRR0H = (unsigned char)(ubrr >> 8);
  UBRR0L = (unsigned char)ubrr;
  UCSR0B = (1 << RXEN0) | (1 << TXEN0);
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

void USART_Transmit(unsigned char data)
{
  while (!(UCSR0A & (1 << UDRE0)));
  UDR0 = data;
}

void txString(const char *pStr)
{
  while (*pStr != '\0')
  {
    USART_Transmit(*pStr);
    pStr++;
  }
}

// Timer1溢出中断:处理超时与计数溢出
ISR(TIMER1_OVF_vect)
{
  if (measuring) {
    overflowCount++;
    // 超时判定:超过100次溢出则认为无回波
    if (overflowCount > 100) {
      measuring = 0;
      overflowCount = 0;
      distance = 0;
    }
  }
}

// 输入捕获中断:处理ECHO的上升/下降沿
ISR(TIMER1_CAPT_vect)
{
  if (measuring) {
    if (bitRead(TCCR1B, ICES1)) {
      // 捕获上升沿:记录起始时间,切换为下降沿捕获
      startTime = ICR1;
      overflowCount = 0;
      bitClear(TCCR1B, ICES1);
    } else {
      // 捕获下降沿:计算总时长与距离
      endTime = ICR1;
      duration = (uint32_t)overflowCount * 65536UL + endTime - startTime;
      // 整数运算计算距离:等价于 duration*0.017/2,避免浮点数误差
      distance = (duration * 17UL) / 2000UL;
      // 重置状态,切换回上升沿捕获
      measuring = 0;
      bitSet(TCCR1B, ICES1);
    }
  }
}

int main(void)
{
  USART_Init(MYUBRR);

  // 配置引脚:TRIG为输出,ECHO为输入并启用上拉电阻
  DDRB |= (1 << TRIG_PIN);
  DDRB &= ~(1 << ECHO_PIN);
  PORTB |= (1 << ECHO_PIN);

  // 配置Timer1:输入捕获模式+噪声消除+预分频8
  TCCR1B |= (1 << ICNC1) | (1 << ICES1) | (1 << CS11);
  // 启用Timer1溢出中断与输入捕获中断
  TIMSK1 |= (1 << TOIE1) | (1 << ICIE1);

  sei(); // 开启全局中断

  while (1)
  {
    // 仅当未在测量时触发新的测距
    if (!measuring) {
      measuring = 1;
      distance = 0;
      bitSet(PORTB, TRIG_PIN);
      _delay_us(10);
      bitClear(PORTB, TRIG_PIN);
    }

    // 输出测量状态与结果
    char formattedDistance[30];
    if (distance == 0 && measuring) {
      sprintf(formattedDistance, "Measuring...\n");
    } else if (distance == 0) {
      sprintf(formattedDistance, "No echo detected\n");
    } else {
      sprintf(formattedDistance, "Distance: %d cm\n", distance);
    }
    txString(formattedDistance);

    _delay_ms(500);
  }
}

关键修改说明

  • 新增定时器溢出处理:通过TIMER1_OVF_vect跟踪溢出次数,解决长距离测量时的计时溢出问题,同时添加超时逻辑避免无限等待。
  • 动态切换捕获边沿:捕获ECHO上升沿后自动切换为下降沿捕获,测量完成后切回上升沿,确保每次测量的触发逻辑正确。
  • 测量状态管理:用measuring变量标记当前测量状态,避免重复触发和错误捕获。
  • 整数运算优化:将距离计算公式改为整数运算,消除浮点数精度误差。
  • ECHO引脚上拉:启用上拉电阻减少浮空状态下的噪声干扰。
  • 状态区分输出:在主循环中区分测量中、无回波、有效距离三种状态,输出信息更直观。

内容的提问来源于stack exchange,提问作者ANONYMOUSE

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:14:52