读取Arduino红外信号时时序偏差问题排查
问题分析与解决方案
核心问题:轮询方式导致的时序延迟
你的代码采用轮询检测引脚电平的方式,这是时序偏差过大的主要原因:
- 循环中每次读取引脚的操作存在固定开销(即使是简单位运算和判断,也会占用数个CPU周期),当红外信号的电平变化发生在两次读取之间时,程序无法立即感知,只能等到下一次循环才记录变化,直接导致测量的时间差比实际值偏小(比如你看到的430us vs 预期562us)。
- 红外信号的关键时序(如NEC协议的562us脉冲)本身很短,轮询带来的几十微秒延迟,会严重干扰测量精度,远超过
micros()的4us误差。
代码中的显性bug
变量类型不匹配:
initial被定义为long int,但micros()返回的是unsigned long(32位无符号整数)。当micros()溢出(从4294967295回到0)时,你的手动溢出处理会因long int的有符号特性导致计算错误(4294967295超过32位有符号长整型最大值2147483647,会触发溢出变为负数)。- 正确做法是将
initial改为unsigned long,直接用micros() - initial计算时间差——无符号整数减法会自动处理溢出情况,无需额外判断。
无数组边界检查:
bitPosition没有上限判断,若100ms内电平变化次数超过arr和bitVal的数组长度,会触发数组越界,导致程序崩溃或数据混乱。
正确实现思路:改用外部中断
要精确捕获红外信号时序,必须用外部中断替代轮询:
- 将红外接收引脚配置为外部中断触发(选择电平变化触发,适配红外信号的调制特性)。
- 在中断服务函数(ISR)中记录每次电平变化的时间戳,保证电平变化被立即捕获,延迟仅为中断响应的几微秒,远小于轮询延迟。
- 使用
unsigned long存储时间戳,彻底避免溢出问题。
示例修正代码:
const int IR_PIN = 7; // 对应PIND的第7位(B10000000) unsigned long arr[64]; // 预设最多存储64个时序值 int bitVal[64]; int bitPosition = 0; unsigned long lastTime = 0; void setup() { pinMode(IR_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(IR_PIN), irInterrupt, CHANGE); // 电平变化触发中断 Serial.begin(9600); } void irInterrupt() { if (bitPosition >= 64) return; // 防止数组越界 unsigned long currentTime = micros(); arr[bitPosition] = currentTime - lastTime; bitVal[bitPosition] = digitalRead(IR_PIN); bitPosition++; lastTime = currentTime; } void loop() { // 在此处处理捕获到的时序数据,比如解析红外协议 }
其他潜在影响因素
- 红外接收模块延迟:部分廉价红外接收头本身存在几十微秒的响应延迟,可更换品质较好的模块测试。
- 电源噪声:不稳定的电源可能干扰红外接收模块输出,导致电平变化检测不准,建议给模块添加100nF滤波电容。
内容的提问来源于stack exchange,提问作者Abdrlrahman
相关产品推荐
相关产品推荐

