1. ESP32红外遥控信号接收全解析
红外遥控技术在家电控制领域应用广泛,不同厂商采用不同的编码协议。本文将详细介绍如何使用ESP32开发板接收并解码NEC、Sony和RC5三种常见红外协议信号。
1.1 硬件准备与连接
我们需要准备以下硬件组件:
- ESP32开发板(任何型号均可)
- 红外接收模块(如VS1838B)
- 杜邦线若干
- 面包板(可选)
红外接收模块有三个引脚:
- VCC(接3.3V)
- GND(接地)
- OUT(信号输出)
连接方式:
- 将红外接收模块的VCC引脚连接到ESP32的3.3V引脚
- GND引脚连接到ESP32的GND引脚
- OUT引脚连接到ESP32的GPIO14(或其他可用GPIO)
注意:红外接收模块的工作电压通常是3.3V-5V,ESP32的GPIO耐压为3.3V,因此建议使用3.3V供电以避免损坏ESP32。
1.2 红外信号接收原理
红外遥控信号采用脉冲宽度调制(PWM)方式传输数据。不同协议的区别主要体现在:
- 载波频率(通常为38kHz)
- 引导脉冲的宽度和格式
- 数据位的编码方式
- 数据帧的结构
ESP32通过GPIO引脚的中断功能来捕获红外信号的高低电平变化,记录每个脉冲的持续时间,然后根据协议规则解码出原始数据。
2. 代码实现详解
2.1 初始化设置
python复制import time
from machine import Pin
class UniversalIRReceiver:
def __init__(self, gpio_pin=14):
"""初始化通用红外接收器"""
self.ir_pin = Pin(gpio_pin, Pin.IN, Pin.PULL_UP)
self.ir_pin.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=self._ir_handler)
# 红外解码相关变量
self.ir_step = 0 # 解码状态
self.ir_count = 0 # 已接收脉冲计数
self.buf64 = [0] * 64 # 脉冲时间缓冲区
self.recived_ok = False # 接收完成标志
self.cmd = None # 当前命令
self.cmd_last = None # 上一个命令
self.repeat = 0 # 重复次数
self.start = 0 # 开始时间
self.t_ok = None # 接收完成时间
self.changed = False # 数据变化标志
self.protocol = "Unknown" # 协议类型
# 设备特定协议参数
self.device_protocols = {
"projector": {"name": "投影仪", "protocol": "NEC", "tolerance": 200},
"lamp": {"name": "台灯", "protocol": "Sony", "tolerance": 150},
"teapot": {"name": "茶吧机", "protocol": "RC5", "tolerance": 300}
}
2.2 中断处理函数
中断处理函数是红外解码的核心,它负责记录每个脉冲的持续时间并识别协议特征:
python复制def _ir_handler(self, source):
"""红外信号中断处理函数"""
this_time = time.ticks_us()
cur_time = time.ticks_diff(this_time, self.start)
self.start = this_time
# 检测引导码 (9ms NEC协议 - 投影仪)
if 8000 <= cur_time <= 10000:
self.ir_step = 1
return
# 检测Sony协议引导码 (2.4ms - 台灯)
if 2000 <= cur_time <= 3000 and self.ir_step == 0:
self.ir_step = 1
self.ir_count = 0
self.recived_ok = False
self.buf64[self.ir_count] = cur_time
self.ir_count += 1
return
# 检测RC5协议引导码 (889μs - 茶吧机)
if 700 <= cur_time <= 1200 and self.ir_step == 0:
self.ir_step = 1
self.ir_count = 0
self.recived_ok = False
self.buf64[self.ir_count] = cur_time
self.ir_count += 1
return
2.3 协议解码实现
2.3.1 NEC协议解码
NEC协议特点:
- 载波频率:38kHz
- 引导脉冲:9ms高电平+4.5ms低电平
- 数据表示:560μs脉冲+560μs/1.68ms间隔表示0/1
- 32位数据格式:16位地址+16位命令(取反)
python复制def _decode_nec(self):
"""解码NEC协议数据 (投影仪)"""
try:
data = 0
for i in range(32):
x = i * 2
t = self.buf64[x] + self.buf64[x+1]
data <<= 1
if 1800 <= t <= 2800: # 逻辑1: 1.68ms
data += 1
elif 1000 <= t <= 1800: # 逻辑0: 0.56ms
pass
else:
return None # 无效数据
return data
except:
return None
2.3.2 Sony协议解码
Sony协议特点:
- 载波频率:40kHz
- 引导脉冲:2.4ms高电平+600μs低电平
- 数据表示:600μs/1.2ms脉冲表示0/1
- 数据长度:12/15/20位
python复制def _decode_sony(self):
"""解码Sony协议数据 (台灯)"""
try:
data = 0
bits = min(self.ir_count, 20)
for i in range(bits):
t = self.buf64[i]
data >>= 1
if 1000 <= t <= 1800: # 逻辑1: 1.2ms
data |= 0x80000000
elif 300 <= t <= 800: # 逻辑0: 0.6ms
pass
else:
return None
return data >> (32 - bits)
except:
return None
2.3.3 RC5协议解码
RC5协议特点:
- 载波频率:36kHz
- 双相位编码(Manchester编码)
- 每位持续时间:1.778ms(64个周期)
- 14位数据格式:2位起始位+5位系统码+6位命令码
python复制def _decode_rc5(self):
"""解码RC5协议数据 (茶吧机)"""
try:
data = 0
bits = min(self.ir_count, 14)
for i in range(bits):
t = self.buf64[i]
data <<= 1
if 700 <= t <= 1200: # 位周期: 889μs
data |= 1
elif 300 <= t <= 600: # 半周期: 444μs
pass
else:
return None
return data
except:
return None
3. 实际应用与调试
3.1 主程序流程
python复制def run(self):
"""运行红外接收器"""
print("=" * 70)
print("通用红外遥控器数据接收器")
print("=" * 70)
print("支持多种设备遥控器协议:")
print(" NEC协议 - 投影仪")
print(" Sony协议 - 台灯")
print(" RC5协议 - 茶吧机")
print("-" * 70)
last_cmd_time = 0
debounce_time = 200 # 200ms防抖
while True:
changed, cmd, repeat, protocol = self.scan()
current_time = time.ticks_ms()
if changed and cmd is not None:
# 防抖处理
if time.ticks_diff(current_time, last_cmd_time) > debounce_time or repeat > 0:
device_type = self.get_device_type(protocol)
button_info = self.get_button_info(cmd, protocol)
hex_cmd = f"{cmd:08X}" if cmd < 0x100000000 else f"{cmd:X}"
print(f"【{time.localtime()[3]:02d}:{time.localtime()[4]:02d}:{time.localtime()[5]:02d}】")
print(f" 设备类型: {device_type}")
print(f" 协议类型: {protocol}")
print(f" 按键信息: {button_info}")
print(f" 十六进制: 0x{hex_cmd}")
print(f" 十进制: {cmd}")
if repeat > 0:
print(f" 重复次数: {repeat}")
print("-" * 50)
last_cmd_time = current_time
time.sleep(0.05)
3.2 常见问题排查
-
接收不到信号
- 检查红外接收模块连接是否正确
- 确保遥控器电池有电
- 尝试调整遥控器与接收模块的距离(通常10-30cm最佳)
-
解码错误
- 确认使用的协议与遥控器实际协议匹配
- 调整协议检测的时间容差(tolerance参数)
- 检查环境是否有强光干扰(日光、LED灯等)
-
信号不稳定
- 增加防抖时间(debounce_time)
- 尝试使用不同的GPIO引脚
- 在红外接收模块的VCC和GND之间添加100μF电容
调试技巧:可以在中断处理函数中添加打印语句,输出每个脉冲的持续时间,帮助分析协议特征。
4. 进阶应用与扩展
4.1 支持更多协议
除了NEC、Sony和RC5协议,还可以扩展支持其他常见协议:
-
Philips RC6协议
- 类似RC5但扩展了功能
- 36kHz载波频率
- 使用前导码区分模式
-
Sharp协议
- 15位或31位数据
- 使用脉冲位置调制
- 载波频率38kHz
-
Panasonic协议
- 48位数据格式
- 引导脉冲+地址+命令结构
- 载波频率38kHz
4.2 红外信号转发
解码后的红外信号可以通过红外LED重新发送,实现信号转发功能:
python复制def send_nec(self, data, gpio_pin=12):
"""发送NEC协议信号"""
ir_led = Pin(gpio_pin, Pin.OUT)
# 发送引导脉冲
ir_led.on()
time.sleep_us(9000)
ir_led.off()
time.sleep_us(4500)
# 发送数据
for i in range(32):
ir_led.on()
time.sleep_us(560)
ir_led.off()
if data & (1 << (31 - i)):
time.sleep_us(1680)
else:
time.sleep_us(560)
# 发送结束脉冲
ir_led.on()
time.sleep_us(560)
ir_led.off()
4.3 与智能家居系统集成
解码后的红外信号可以通过Wi-Fi或蓝牙发送到智能家居中枢,实现远程控制:
-
MQTT集成
- 将解码结果发布到MQTT主题
- 家庭助理等系统可以订阅这些主题
-
HTTP API
- 创建简单的Web服务器接收红外命令
- 与其他智能设备联动
-
蓝牙控制
- 通过BLE服务暴露红外控制接口
- 手机APP可以直接发送控制命令
5. 性能优化建议
-
中断处理优化
- 尽量减少中断处理函数中的计算量
- 使用缓冲区存储原始数据,在主循环中解码
-
内存管理
- 对于ESP32,注意MicroPython的内存限制
- 避免在中断处理函数中分配新对象
-
多协议并行检测
- 改进协议检测算法,减少误识别
- 添加协议优先级机制
-
低功耗设计
- 在不使用时进入低功耗模式
- 通过外部中断唤醒
在实际项目中,我发现红外接收对时间精度要求很高,ESP32的硬件定时器可以提供更精确的时间测量。另外,不同品牌的遥控器即使使用同一协议,脉冲时间也可能有细微差异,需要适当调整时间容差参数。
