修复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
相关产品推荐
相关产品推荐

