znlgis 博客

GIS开发与技术分享

第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 本章小结

本章介绍了红外遥控编解码:

  1. 红外通信原理
    • 38kHz 载波调制
    • 一体化接收头解调
  2. NEC 编码协议
    • 引导码 (9ms + 4.5ms)
    • 逻辑 0/1 时序
    • 32 位数据 + 反码校验
  3. 实际应用
    • 按键解码映射
    • 遥控 LED/舵机

下一章将学习 WS2812 RGB 灯珠驱动。