第19章:红外遥控编解码
本章讲解红外遥控的工作原理、NEC 编码协议与红外信号的发送接收方法。
19.1 红外遥控原理
19.1.1 系统组成
┌─────────┐ 红外光 ┌─────────┐
│ 红外 LED │ ──────────────→ │ 红外接收│
│ (发射器) │ 38kHz 调制 │ (接收器) │
└─────────┘ └─────────┘
19.1.2 38kHz 载波调制
红外信号使用 38kHz 载波调制,目的是:
- 区分环境红外光干扰
- 提高接收灵敏度
- 增加通信距离
原始信号: ──────┐ ┌──────
└──────┘
38kHz调制后: ~~~~~~~~ ~~~~~~~~
│ │ │ │
└────┘ └────┘
19.1.3 一体化接收头
VS1838B 等一体化接收头内置:
- 光电检测器
- 前置放大器
- 带通滤波器 (38kHz)
- 解调器
- 输出驱动
输出:解调后的数字信号(低电平有效)
19.2 NEC 编码协议
19.2.1 协议概述
NEC 协议是最常用的红外遥控协议:
- 载波频率:38kHz
- 数据格式:8 位地址 + 8 位地址反码 + 8 位命令 + 8 位命令反码
- 总计:32 位数据
19.2.2 帧结构
┌─────────┬─────────┬──────┬──────┬──────┬──────┐
│ 引导码 │ 地址码 │ 地址反码│ 命令码 │ 命令反码│ 结束 │
│ 9ms+4.5ms│ 8位 │ 8位 │ 8位 │ 8位 │ 560μs│
└─────────┴─────────┴──────┴──────┴──────┴──────┘
19.2.3 逻辑编码
逻辑 "0":
┌───┐
│ │ 560μs 高 + 560μs 低
└───┘
总计约 1.12ms
逻辑 "1":
┌───┐
│ │ 560μs 高 + 1690μs 低
└───┘
总计约 2.25ms
19.2.4 引导码
引导码 (9ms + 4.5ms):
┌─────────────────┐
│ │
│ 9ms 高 │ 4.5ms 低
│ │
└─────────────────┘
19.2.5 重复码
持续按住按键时发送重复码:
重复码 (9ms + 2.25ms + 560μs):
┌─────────────┐ ┌──┐
│ │ │ │
│ 9ms 高 │ │ │ 560μs
│ │ │ │
└─────────────┘ └──┘
2.25ms 低
19.3 红外接收
19.3.1 硬件连接
VS1838B:
┌───────┐
│ 1 │ → S (信号输出到 GPIO)
│ 2 │ → GND
│ 3 │ → VCC (3.3V)
└───────┘
19.3.2 基础接收代码
'''
实验:红外接收
接线:信号端连接 GP11
'''
from machine import Pin
import time
ir_pin = Pin(11, Pin.IN, Pin.PULL_UP)
def read_ir_signal(timeout_ms=100):
"""读取红外信号脉冲"""
pulses = []
timeout = time.ticks_ms() + timeout_ms
# 等待引导码
while ir_pin.value() == 1:
if time.ticks_ms() > timeout:
return None
# 记录脉冲时间
while time.ticks_ms() < timeout:
start = time.ticks_us()
# 记录低电平持续时间
while ir_pin.value() == 0:
pass
low_time = time.ticks_diff(time.ticks_us(), start)
start = time.ticks_us()
# 记录高电平持续时间
while ir_pin.value() == 1:
if time.ticks_diff(time.ticks_us(), start) > 10000:
break
high_time = time.ticks_diff(time.ticks_us(), start)
if high_time > 10000:
break
pulses.append((low_time, high_time))
return pulses
while True:
pulses = read_ir_signal()
if pulses:
print(f"接收到 {len(pulses)} 个脉冲")
time.sleep(0.1)
19.3.3 NEC 解码
'''
实验:NEC 协议解码
'''
from machine import Pin
import time
ir_pin = Pin(11, Pin.IN, Pin.PULL_UP)
def decode_nec(pulses):
"""解码 NEC 协议"""
if len(pulses) < 33:
return None
# 验证引导码 (约 9ms 低 + 4.5ms 高)
if not (8000 < pulses[0][0] < 10000 and 4000 < pulses[0][1] < 5000):
return None
# 解码数据位
bits = []
for low, high in pulses[1:33]:
if 1500 < high < 2000:
bits.append(1)
elif 400 < high < 700:
bits.append(0)
else:
return None
# 组装字节
def bits_to_byte(bit_list):
value = 0
for i, bit in enumerate(bit_list):
value |= bit << i
return value
addr = bits_to_byte(bits[0:8])
addr_inv = bits_to_byte(bits[8:16])
cmd = bits_to_byte(bits[16:24])
cmd_inv = bits_to_byte(bits[24:32])
# 验证反码
if addr ^ addr_inv != 0xFF or cmd ^ cmd_inv != 0xFF:
print("校验失败")
return None
return addr, cmd
def receive_ir():
"""接收并解码红外信号"""
pulses = []
# 等待引导码
while ir_pin.value() == 1:
pass
# 测量引导码
start = time.ticks_us()
while ir_pin.value() == 0:
pass
lead_low = time.ticks_diff(time.ticks_us(), start)
start = time.ticks_us()
while ir_pin.value() == 1:
if time.ticks_diff(time.ticks_us(), start) > 6000:
break
lead_high = time.ticks_diff(time.ticks_us(), start)
pulses.append((lead_low, lead_high))
# 接收 32 位数据
for _ in range(32):
start = time.ticks_us()
while ir_pin.value() == 0:
pass
low = time.ticks_diff(time.ticks_us(), start)
start = time.ticks_us()
while ir_pin.value() == 1:
if time.ticks_diff(time.ticks_us(), start) > 3000:
break
high = time.ticks_diff(time.ticks_us(), start)
pulses.append((low, high))
return decode_nec(pulses)
print("等待红外信号...")
while True:
if ir_pin.value() == 0: # 检测到信号
result = receive_ir()
if result:
addr, cmd = result
print(f"地址: 0x{addr:02X} 命令: 0x{cmd:02X}")
time.sleep(0.2)
19.4 红外键值映射
19.4.1 遥控器按键表
套件中的红外遥控器键值:
| 按键 | 命令码 | 按键 | 命令码 |
|---|---|---|---|
| 电源 | 0x45 | 模式 | 0x46 |
| 静音 | 0x47 | 播放 | 0x44 |
| 后退 | 0x40 | 前进 | 0x43 |
| EQ | 0x07 | - | 0x15 |
| + | 0x09 | 0 | 0x16 |
| 重复 | 0x19 | U/SD | 0x0D |
| 1 | 0x0C | 2 | 0x18 |
| 3 | 0x5E | 4 | 0x08 |
| 5 | 0x1C | 6 | 0x5A |
| 7 | 0x42 | 8 | 0x52 |
| 9 | 0x4A |
19.4.2 按键解码应用
'''
实验:红外遥控器解码
'''
KEY_MAP = {
0x45: '电源',
0x46: '模式',
0x47: '静音',
0x44: '播放',
0x40: '后退',
0x43: '前进',
0x07: 'EQ',
0x15: '-',
0x09: '+',
0x16: '0',
0x19: '重复',
0x0D: 'U/SD',
0x0C: '1',
0x18: '2',
0x5E: '3',
0x08: '4',
0x1C: '5',
0x5A: '6',
0x42: '7',
0x52: '8',
0x4A: '9',
}
while True:
if ir_pin.value() == 0:
result = receive_ir()
if result:
addr, cmd = result
key_name = KEY_MAP.get(cmd, f'未知({cmd:02X})')
print(f"按键: {key_name}")
time.sleep(0.15)
19.5 红外遥控应用
19.5.1 遥控 LED
'''
实验:红外遥控 LED
'''
from machine import Pin, PWM
import time
led = PWM(Pin(0))
led.freq(1000)
brightness = 50 # 初始亮度 50%
def control_led(cmd):
global brightness
if cmd == 0x45: # 电源
brightness = 0 if brightness > 0 else 50
elif cmd == 0x09: # +
brightness = min(100, brightness + 10)
elif cmd == 0x15: # -
brightness = max(0, brightness - 10)
elif cmd == 0x0C: # 1
brightness = 25
elif cmd == 0x18: # 2
brightness = 50
elif cmd == 0x5E: # 3
brightness = 75
elif cmd == 0x08: # 4
brightness = 100
led.duty_u16(int(brightness / 100 * 65535))
print(f"亮度: {brightness}%")
while True:
if ir_pin.value() == 0:
result = receive_ir()
if result:
addr, cmd = result
control_led(cmd)
time.sleep(0.15)
19.5.2 遥控舵机
'''
实验:红外遥控舵机
'''
from machine import Pin, PWM
import time
servo = PWM(Pin(16))
servo.freq(50)
angle = 90
def set_servo_angle(a):
duty = int(1638 + (8192 - 1638) * a / 180)
servo.duty_u16(duty)
set_servo_angle(angle)
while True:
if ir_pin.value() == 0:
result = receive_ir()
if result:
addr, cmd = result
if cmd == 0x40: # 后退 - 左转
angle = max(0, angle - 10)
elif cmd == 0x43: # 前进 - 右转
angle = min(180, angle + 10)
elif cmd == 0x16: # 0 - 归中
angle = 90
set_servo_angle(angle)
print(f"舵机角度: {angle}°")
time.sleep(0.15)
19.6 红外发射
19.6.1 发射原理
使用 PWM 产生 38kHz 载波,通过调制实现数据发送。
19.6.2 发射代码
'''
实验:红外发射
接线:红外 LED 连接 GP17
'''
from machine import Pin, PWM
import time
ir_led = Pin(17, Pin.OUT)
def ir_pulse(high_us):
"""发送调制脉冲"""
# 38kHz 周期约 26μs
cycles = high_us // 26
for _ in range(cycles):
ir_led.value(1)
time.sleep_us(13)
ir_led.value(0)
time.sleep_us(13)
def send_nec(addr, cmd):
"""发送 NEC 编码"""
# 引导码
ir_pulse(9000)
time.sleep_us(4500)
# 发送 32 位数据
data = [addr, addr ^ 0xFF, cmd, cmd ^ 0xFF]
for byte in data:
for i in range(8):
ir_pulse(560)
if byte & (1 << i):
time.sleep_us(1690) # 逻辑 1
else:
time.sleep_us(560) # 逻辑 0
# 结束脉冲
ir_pulse(560)
# 发送命令
send_nec(0x00, 0x45) # 发送电源键
19.7 本章小结
本章介绍了红外遥控编解码:
- 红外通信原理:
- 38kHz 载波调制
- 一体化接收头解调
- NEC 编码协议:
- 引导码 (9ms + 4.5ms)
- 逻辑 0/1 时序
- 32 位数据 + 反码校验
- 实际应用:
- 按键解码映射
- 遥控 LED/舵机
下一章将学习 WS2812 RGB 灯珠驱动。