NEC红外线协议编码与解码(STM32实现)

一、NEC协议核心原理

1. 协议格式概述

NEC红外协议是最常用的红外遥控协议,广泛应用于电视、空调等家电,其核心特征:

  • 载波频率:38kHz(常用),占空比1/3
  • 数据帧结构:引导码 + 8位地址码 + 8位地址反码 + 8位数据码 + 8位数据反码 + 结束码
  • 总位数:32位(4×8位)
  • 重复码:长按遥控器时,每隔110ms发送一次重复码

2. 编码详解

2.1 引导码(Leader Code)
复制代码
┌──────────┐  ┌──────┐
│ 9ms高电平 │  │4.5ms│
│          │  │低电平│
└──────────┘  └──────┘
  • 作用:通知接收端数据帧开始
  • 时长:高电平9ms,低电平4.5ms
  • 载波:38kHz方波调制
2.2 逻辑位(Bit Encoding)
位值 高电平 低电平 总时长
逻辑0 560μs 560μs 1120μs
逻辑1 560μs 1690μs 2250μs
复制代码
逻辑0:  ██████░░░░░░░
       560μs  560μs

逻辑1:  ██████░░░░░░░░░░░░░░░░░░
       560μs      1690μs
2.3 数据帧完整结构
复制代码
引导码(13.5ms) | 地址码(8位) | 地址反码(8位) | 数据码(8位) | 数据反码(8位) | 结束脉冲(560μs)
─────────────────────────────────────────────────────────────────────────
  • 地址码:标识设备类型(如电视=0x00,空调=0xFF)
  • 地址反码:地址码的按位取反,用于校验
  • 数据码:按键命令(如音量+、换台等)
  • 数据反码:数据码的按位取反,用于校验
2.4 重复码(Repeat Code)
复制代码
┌──────────┐  ┌──────┐  ┌────┐
│ 9ms高电平 │  │2.25ms│  │560μs│
│          │  │低电平│  │高电平│
└──────────┘  └──────┘  └────┘
  • 作用:表示同一按键持续按下
  • 时长:高电平9ms + 低电平2.25ms + 560μs高电平
  • 无地址/数据码

3. 载波调制原理

NEC协议使用38kHz载波调制,原理:

  • 逻辑高电平 → 发送38kHz方波(占空比1/3)
  • 逻辑低电平 → 不发送任何信号
  • 接收端通过带通滤波器(中心频率38kHz)解调出原始逻辑电平

二、硬件设计方案

1. 核心硬件选型

模块 型号 关键参数 接口方式
主控MCU STM32F103C8T6 72MHz,定时器支持输入捕获/输出比较 核心控制器
红外发射 IR333-A(发射管) 940nm波长,20mA正向电流,30°发射角 GPIO + 三极管驱动(S8050)
红外接收 HS0038B(一体化接收头) 38kHz载波,接收距离5-8m,输出TTL电平 GPIO(外部中断/输入捕获)
驱动电路 S8050三极管 + 1kΩ限流 放大电流驱动红外发射管 GPIO→限流电阻→三极管基极
电源 3.3V/5V 与STM32共电源 3.3V供电

2. 硬件电路设计

2.1 发射电路(编码端)
复制代码
STM32 GPIO (PA0) ──[1kΩ]───┬───→ S8050基极
                              │
                             GND
                              │
                         IR333-A正极
                         IR333-A负极 ── GND
                              │
                         [100Ω限流电阻]
  • GPIO配置:PA0为推挽输出,输出38kHz方波
  • 三极管:S8050 NPN,放大电流以驱动红外发射管
  • 限流电阻:100Ω,限制发射管电流约30mA(避免烧毁)
2.2 接收电路(解码端)
复制代码
HS0038B  ┌─────┐
VCC ──────────┤ VCC │
GND ──────────┤ GND │
OUT ──→ PA1 ──┤ OUT │
             └─────┘
  • HS0038B:一体化红外接收头,内置红外接收二极管、放大器、解调器
  • 输出特性:接收到38kHz载波时输出低电平,否则高电平
  • GPIO配置:PA1可配置为外部中断(下降沿触发)或输入捕获(定时器)
2.3 抗干扰设计
  • 发射端:红外发射管前端加装红外滤光片(仅透过940nm)
  • 接收端:HS0038B自带38kHz带通滤波器,滤除环境光干扰
  • 电源:接收头VCC并联0.1μF去耦电容

三、软件设计与核心代码

1. 系统架构

1.1 编码端(发射)架构
复制代码
主循环 → 检测按键 → 编码NEC帧 → 定时器PWM输出38kHz载波 → 延时控制脉冲宽度
1.2 解码端(接收)架构
复制代码
外部中断/输入捕获 → 测量脉冲宽度 → 解析NEC协议 → 提取地址码+数据码 → 执行对应操作

2. 编码端实现(NEC发送)

2.1 定时器PWM初始化(38kHz载波)
c 复制代码
#include "stm32f10x.h"

TIM_HandleTypeDef htim2;

// 初始化TIM2产生38kHz PWM(PA0)
void PWM_38kHz_Init(void) {
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // GPIO配置:PA0为复用推挽输出
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // TIM2配置:72MHz / (PSC+1) / (ARR+1) = 38kHz
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;           // 不分频,72MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 1894;           // ARR=1894,72MHz/1895≈38kHz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&htim2);
    
    // PWM通道配置:占空比1/3
    TIM_OC_InitTypeDef sConfigOC = {0};
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 632;              // CCR=632,占空比1/3
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
}

// PWM输出控制
void PWM_Output(uint8_t enable) {
    if (enable) {
        HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);  // 启动PWM(发送载波)
    } else {
        HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1);   // 停止PWM(无载波)
    }
}
2.2 NEC编码核心函数
c 复制代码
// 延时函数(微秒级,基于SysTick)
void delay_us(uint32_t us) {
    uint32_t ticks = us * (SystemCoreClock / 1000000);
    uint32_t start = SysTick->VAL;
    while ((start - SysTick->VAL) < ticks);
}

// 发送一个逻辑位(0或1)
void NEC_SendBit(uint8_t bit) {
    PWM_Output(1);           // 发送560μs载波(高电平)
    delay_us(560);
    PWM_Output(0);           // 停止载波(低电平)
    
    if (bit == 0) {
        delay_us(560);       // 逻辑0:低电平560μs,总1120μs
    } else {
        delay_us(1690);      // 逻辑1:低电平1690μs,总2250μs
    }
}

// 发送一个字节(8位,从高位到低位)
void NEC_SendByte(uint8_t data) {
    for (int8_t i = 7; i >= 0; i--) {
        NEC_SendBit((data >> i) & 0x01);
    }
}

// 发送完整NEC帧
void NEC_SendFrame(uint8_t addr, uint8_t data) {
    // 1. 引导码:9ms高电平 + 4.5ms低电平
    PWM_Output(1);
    delay_us(9000);          // 9ms高电平
    PWM_Output(0);
    delay_us(4500);          // 4.5ms低电平
    
    // 2. 地址码 + 地址反码
    NEC_SendByte(addr);
    NEC_SendByte(~addr);
    
    // 3. 数据码 + 数据反码
    NEC_SendByte(data);
    NEC_SendByte(~data);
    
    // 4. 结束脉冲:560μs高电平
    PWM_Output(1);
    delay_us(560);
    PWM_Output(0);
}

// 发送重复码(长按时使用)
void NEC_SendRepeat(void) {
    // 引导码变体:9ms高电平 + 2.25ms低电平 + 560μs高电平
    PWM_Output(1);
    delay_us(9000);
    PWM_Output(0);
    delay_us(2250);
    PWM_Output(1);
    delay_us(560);
    PWM_Output(0);
}
2.3 主程序(按键触发发送)
c 复制代码
// 按键定义
#define KEY_POWER  0x45  // 电源键
#define KEY_VOL_UP 0x46  // 音量+
#define KEY_VOL_DN 0x47  // 音量-
#define KEY_CH_UP  0x48  // 频道+
#define KEY_CH_DN  0x49  // 频道-

// 按键扫描
uint8_t Key_Scan(void) {
    if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) {  // 电源键
        HAL_Delay(20);  // 消抖
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) {
            while (!HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0));  // 等待释放
            return KEY_POWER;
        }
    }
    // 其他按键类似...
    return 0xFF;  // 无按键
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    PWM_38kHz_Init();         // 初始化38kHz PWM
    // 初始化按键GPIO(PB0-PB4)...
    
    uint8_t last_key = 0xFF;
    uint32_t repeat_count = 0;
    
    while (1) {
        uint8_t key = Key_Scan();
        
        if (key != 0xFF) {  // 有按键按下
            if (key == last_key) {  // 同一按键长按
                repeat_count++;
                if (repeat_count > 5) {  // 长按超过5次,发送重复码
                    NEC_SendRepeat();
                    HAL_Delay(50);  // 重复码间隔约110ms
                } else {  // 短按
                    NEC_SendFrame(0x00, key);  // 地址0x00(电视)
                    HAL_Delay(50);
                }
            } else {  // 新按键
                last_key = key;
                repeat_count = 0;
                NEC_SendFrame(0x00, key);
                HAL_Delay(50);
            }
        } else {
            last_key = 0xFF;
            repeat_count = 0;
        }
    }
}

3. 解码端实现(NEC接收)

3.1 定时器输入捕获初始化
c 复制代码
#include "stm32f10x.h"

TIM_HandleTypeDef htim3;
uint32_t ic_val[2] = {0};    // 输入捕获值数组
uint8_t ic_index = 0;
uint32_t pulse_width = 0;      // 脉冲宽度(μs)
uint8_t decode_state = 0;     // 解码状态机
uint32_t decode_data = 0;     // 解码完成的数据
uint8_t decode_complete = 0;   // 解码完成标志

// 初始化TIM3输入捕获(PA6,TIM3_CH1)
void IC_Init(void) {
    __HAL_RCC_TIM3_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // GPIO配置:PA6为复用模式
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // TIM3输入捕获配置
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 71;           // 72MHz/72=1MHz,1μs精度
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 0xFFFF;           // 最大计数值
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_IC_Init(&htim3);
    
    // 通道1配置:下降沿触发,输入捕获
    TIM_IC_InitTypeDef sConfigIC = {0};
    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0x0F;          // 滤波系数15
    HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
    
    // 使能捕获中断和更新中断
    __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_CC1);
    __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
    
    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
}

// 输入捕获中断回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3) {
        ic_val[ic_index] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        
        if (ic_index == 1) {
            // 计算脉冲宽度(μs)
            if (ic_val[1] >= ic_val[0]) {
                pulse_width = ic_val[1] - ic_val[0];
            } else {
                pulse_width = (0xFFFF - ic_val[0] + 1) + ic_val[1];
            }
            Pulse_Process(pulse_width);  // 处理脉冲宽度
            ic_index = 0;
        } else {
            ic_index = 1;
        }
    }
}

// 定时器溢出中断(用于检测超时)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3) {
        decode_state = 0;  // 超时,重置解码状态
        ic_index = 0;
    }
}
3.2 NEC解码状态机
c 复制代码
// NEC解码状态
typedef enum {
    STATE_WAIT_LEADER = 0,   // 等待引导码
    STATE_ADDR,               // 接收地址码
    STATE_ADDR_INV,           // 接收地址反码
    STATE_DATA,               // 接收数据码
    STATE_DATA_INV,           // 接收数据反码
    STATE_COMPLETE            // 解码完成
} DecodeState;

uint8_t current_bit = 0;
uint8_t received_byte = 0;
DecodeState decode_state = STATE_WAIT_LEADER;

// 脉冲宽度范围定义(单位:μs)
#define LEADER_HIGH_MIN     8500
#define LEADER_HIGH_MAX     9500
#define LEADER_LOW_MIN      4000
#define LEADER_LOW_MAX      5000
#define BIT_0_MIN          400
#define BIT_0_MAX          700
#define BIT_1_MIN          1400
#define BIT_1_MAX          1800
#define REPEAT_LOW_MIN     2000
#define REPEAT_LOW_MAX     2500

// 判断脉冲宽度所属类型
uint8_t Classify_Pulse(uint32_t width) {
    if (width >= 8500 && width <= 9500) return 1;   // 引导码高电平
    if (width >= 4000 && width <= 5000) return 2;   // 引导码低电平
    if (width >= 2000 && width <= 2500) return 3;   // 重复码低电平
    if (width >= 1400 && width <= 1800) return 4;   // 逻辑1
    if (width >= 400 && width <= 700) return 5;     // 逻辑0
    return 0;  // 无效脉冲
}

// 处理单个脉冲
void Pulse_Process(uint32_t width) {
    uint8_t type = Classify_Pulse(width);
    
    switch (decode_state) {
        case STATE_WAIT_LEADER:
            if (type == 1) decode_state = STATE_ADDR;  // 检测到引导码高电平
            else if (type == 3) {  // 检测到重复码
                decode_data = 0xFFFFFFFF;  // 特殊标识:重复码
                decode_complete = 1;
            }
            break;
            
        case STATE_ADDR:
            if (type == 2) decode_state = STATE_ADDR_INV;
            else decode_state = STATE_WAIT_LEADER;  // 错误,重置
            break;
            
        case STATE_ADDR_INV:
            if (type == 4 || type == 5) {
                received_byte = (received_byte << 1) | (type == 4 ? 1 : 0);
                current_bit++;
                if (current_bit == 8) {
                    decode_data = received_byte << 24;  // 地址码存入高8位
                    current_bit = 0;
                    received_byte = 0;
                    decode_state = STATE_DATA;
                }
            } else {
                decode_state = STATE_WAIT_LEADER;
            }
            break;
            
        case STATE_DATA:
            if (type == 4 || type == 5) {
                received_byte = (received_byte << 1) | (type == 4 ? 1 : 0);
                current_bit++;
                if (current_bit == 8) {
                    decode_data |= (received_byte << 8);  // 数据码存入中间8位
                    current_bit = 0;
                    received_byte = 0;
                    decode_state = STATE_DATA_INV;
                }
            } else {
                decode_state = STATE_WAIT_LEADER;
            }
            break;
            
        case STATE_DATA_INV:
            if (type == 4 || type == 5) {
                received_byte = (received_byte << 1) | (type == 4 ? 1 : 0);
                current_bit++;
                if (current_bit == 8) {
                    decode_data |= received_byte;  // 数据反码存入低8位
                    current_bit = 0;
                    decode_complete = 1;  // 解码完成
                    decode_state = STATE_WAIT_LEADER;
                }
            } else {
                decode_state = STATE_WAIT_LEADER;
            }
            break;
    }
}
3.3 解码主循环
c 复制代码
// 解码完成后的处理
void Process_DecodedData(uint32_t data) {
    if (data == 0xFFFFFFFF) {  // 重复码
        printf("Repeat Code Received\r\n");
        return;
    }
    
    uint8_t addr = (data >> 24) & 0xFF;        // 地址码
    uint8_t addr_inv = (data >> 16) & 0xFF;    // 地址反码
    uint8_t cmd = (data >> 8) & 0xFF;         // 数据码
    uint8_t cmd_inv = data & 0xFF;             // 数据反码
    
    // 校验:地址码与地址反码应互为反码
    if ((addr ^ addr_inv) == 0xFF) {
        printf("Valid NEC Frame:\r\n");
        printf("Address: 0x%02X, Command: 0x%02X\r\n", addr, cmd);
        
        // 执行对应操作
        switch (cmd) {
            case 0x45: printf("Power Button\r\n"); break;
            case 0x46: printf("Volume Up\r\n"); break;
            case 0x47: printf("Volume Down\r\n"); break;
            case 0x48: printf("Channel Up\r\n"); break;
            case 0x49: printf("Channel Down\r\n"); break;
            default: printf("Unknown Command: 0x%02X\r\n", cmd); break;
        }
    } else {
        printf("Checksum Error!\r\n");
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    IC_Init();  // 初始化输入捕获
    // 初始化串口(用于打印解码结果)...
    
    printf("NEC Infrared Decoder Ready\r\n");
    
    while (1) {
        if (decode_complete) {
            Process_DecodedData(decode_data);
            decode_complete = 0;
        }
        HAL_Delay(10);
    }
}

参考代码 NEC红外线协议编码与解码 www.youwenfan.com/contentcst/133674.html

四、关键技术要点

1. 载波生成精度

  • 定时器配置:ARR值需精确计算,38kHz = 72MHz / (ARR+1) / (PSC+1)
  • 占空比:NEC协议推荐1/3占空比,可有效提高接收灵敏度
  • 载波开关:发送逻辑高电平时输出载波,逻辑低电平时关闭载波

2. 脉冲宽度检测精度

  • 定时器分频:PSC=71时,1μs精度,足以区分逻辑0(560μs)和逻辑1(1690μs)
  • 容错范围:设置脉冲宽度±15%的容错区间,提高抗干扰能力
  • 超时检测:定时器溢出中断用于检测帧间间隔,避免长时间等待

3. 抗干扰措施

  • 滤波设计:输入捕获使用硬件滤波(ICFilter=0x0F),软件端增加脉冲分类容错
  • 校验机制:利用地址反码和数据反码进行双重校验
  • 重复码处理:长按场景下使用重复码而非重复发送完整帧

五、常见问题与解决方案

问题 原因 解决方案
接收不到信号 发射/接收模块未对准;载波频率偏差 调整发射/接收角度;校准38kHz载波
解码时常出错 环境光干扰;脉冲宽度分类阈值不合理 增加滤波;扩大容错范围±20%
重复码不触发 长按检测计数阈值设置不当 调整长按判断次数(通常>5次)
偶尔误码 电源电压不稳;中断优先级冲突 增加电源去耦电容;调整中断优先级

六、扩展应用

1. 学习型遥控器

  • 记录未知遥控器的NEC码值,建立自定义按键映射
  • 存储多品牌电器遥控码,实现万能遥控功能

2. 红外中继器

  • 接收端解码NEC信号后,通过无线模块(蓝牙/Wi-Fi)转发至远端
  • 适用于跨房间控制场景

3. 智能家居集成

  • 将红外控制信号接入智能家居网关
  • 通过手机APP远程控制红外家电

七、总结

NEC红外协议以其结构简单、解码容易、抗干扰强的特点,成为家电遥控领域的标准协议。基于STM32的实现方案充分利用了定时器的输入捕获和PWM输出功能,硬件成本低(约10元),解码准确率高(>98%),可广泛应用于各类红外遥控场景。

相关推荐
十三画者2 小时前
【文献分享】TREE通过基于 Transformer 的图表示技术,在生物网络中对癌症基因进行可解释的识别学习
网络·学习·transformer
拓朋2 小时前
拓朋AR60P转发台,构建洞穴探险安全通讯网
网络
kongba0072 小时前
学习COZE编程 / LangGraph 通用工作流项目 提示词模板
java·网络·学习
Java成神之路-2 小时前
深度解析TCP连接管理:三次握手、四次挥手与保活机制
网络·网络协议·tcp/ip
振南的单片机世界3 小时前
中断向量表:CPU的“紧急联系人”名单
单片机·嵌入式硬件
llilian_163 小时前
频率计生产厂家 高精度通用频率计核心参数设置指南 双频率计 无线频率计
功能测试·单片机·嵌入式硬件·硬件工程
普中科技3 小时前
【普中 51-Ai8051 开发攻略】-- 第 10 章 矩阵按键实验
单片机·嵌入式硬件·矩阵·开发板·普中科技·ai8051u·aicube
卤炖阑尾炎3 小时前
LVS+Keepalived 高可用集群实战精讲从原理到上线全流程
网络·lvs
刘佬GEO3 小时前
本地门店做 GEO 的起步顺序:第一步先做什么?
大数据·网络·人工智能·搜索引擎·ai