1. 项目背景与核心功能
红外遥控器是我们日常生活中最常见的电子设备控制方式之一。从电视机到空调,几乎所有的家电都配备了红外遥控功能。但每次使用不同设备都需要切换遥控器,确实有些麻烦。这就是为什么我想到了开发一个基于STM32的万能红外遥控器。
这个项目的核心功能其实很简单:学习、存储和发射红外信号。具体来说,它可以做到以下几点:
- 学习标准NEC格式的红外信号(大多数机顶盒和部分电视使用这种格式)
- 学习非标准格式的红外信号(比如空调、风扇等设备使用的特殊编码)
- 将学习到的信号存储在STM32的Flash中
- 根据需要重新发射这些信号来控制设备
我选择STM32F103RCT6作为主控芯片有几个原因:首先它价格亲民,其次它有足够的Flash空间(256KB)来存储大量红外编码数据。实测下来,这个容量可以存储上百个不同的遥控指令。
2. 硬件选型与电路设计
2.1 核心组件清单
做这个项目,你需要准备以下硬件组件:
- 主控芯片:STM32F103RCT6最小系统板(其他F103系列也可以,但Flash最好≥256KB)
- 显示模块:0.96寸OLED屏(IIC接口,128×64分辨率)
- 红外接收头:VS1838B或类似通用红外接收模块
- 红外发射管:940nm波长的红外LED
- 控制输入:可以用现成的红外遥控器作为输入设备
2.2 关键电路设计要点
红外接收电路很简单,只需要将接收头的输出端接到STM32的任意GPIO(我用的PB9)。但红外发射部分有几个需要注意的地方:
- 驱动能力:单个GPIO的输出电流有限(通常8mA左右),直接驱动红外LED亮度不够。我建议使用一个NPN三极管(如8050)来放大电流。
- 载波频率:大多数红外遥控使用38kHz载波,所以我们需要用PWM来生成这个频率。
- 发射距离:为了提高发射距离,可以使用2-3个红外LED串联,配合适当的限流电阻。
具体接线方式如下:
OLED屏:
- SCL → PB6
- SDA → PB7
- VCC → 3.3V
- GND → GND
红外接收头:
- OUT → PB9
- VCC → 3.3V
- GND → GND
红外发射电路:
- LED阳极 → 3.3V通过限流电阻
- LED阴极 → 三极管集电极
- 三极管基极 → PA0通过基极电阻
- 三极管发射极 → GND
- 控制端 → PC2(用于整体开关控制)
3. 软件架构与核心代码
3.1 工程目录结构
整个项目的代码结构是这样的:
Template ├── USER │ └── main.c ├── SYSTEM │ └── delay.c └── HARDWARE ├── oled.c ├── remote.c ├── pwm.c ├── irsend.c ├── stmflash.c └── remote_save.c3.2 主程序逻辑
主程序的流程很直观:
- 初始化所有外设(OLED、红外接收、PWM等)
- 显示欢迎界面
- 进入主菜单循环
主菜单有四个功能选项:
- 设置分区(实现按键复用)
- 学习/发射模式
- 删除存储的数据
- 显示红外数据信息
while (1) { OLED_Fill_picture(0x00); OLED_ShowStr(0, 0, "1.SetPart( )"); OLED_ShowNum(10, 0, GetPart(), 1); OLED_ShowStr(0, 1, "2.Send/Learn"); OLED_ShowStr(0, 2, "3.Delete"); OLED_ShowStr(0, 3, "4.ShowData"); switch (Remote_Num()) { case 1: SetPart(); break; case 2: SendLearn(); break; case 3: Delete(); break; case 4: ShowData(); break; } }3.3 红外信号处理
红外信号的处理是这个项目的核心难点,主要分为接收解码和发射编码两部分。
接收解码部分:
对于标准NEC协议,一个完整的信号包含:
- 9ms的起始高电平
- 4.5ms的起始低电平
- 32位数据(地址码+命令码)
- 560μs的脉冲间隔
我们通过外部中断捕获这些时间间隔,然后解码出具体的键值。
u32 Remote_GetData(void) { u32 data = 0; // 捕获起始信号 while(!RDATA); // 等待高电平 delay_us(9000); // 检测9ms高电平 // 捕获4.5ms低电平 // ...省略具体解码过程... return data; }发射编码部分:
发射时需要重新生成这些时间序列,同时还要调制38kHz载波。这里我们使用TIM2的PWM功能来生成载波。
void TIM2_PWM_Init(u16 arr, u16 psc) { // 初始化TIM2 PWM输出 // 频率计算:72MHz/(1895+1) ≈ 38kHz TIM_TimeBaseInitStructure.TIM_Period = arr; TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // ...其他初始化代码... }4. 数据存储方案设计
4.1 Flash存储结构
为了高效利用有限的Flash空间,我设计了一个专门的数据存储格式。每个红外指令占用351个16位空间,结构如下:
| 地址范围 | 用途 |
|---|---|
| 0-9 | 数据大小(1-350) |
| 10-11 | 保留 |
| 12-14 | 数据类型(0=NEC,1=模拟编码) |
| 15 | 数据有效标志 |
| 16-366 | 实际数据存储空间 |
这种设计有几个优点:
- 统一了标准和非标准格式的存储
- 通过分区管理实现了按键复用
- 可以快速查询存储状态
4.2 关键存储函数
int SaveData(int i, u8 type, u16 *pBuffer, u16 lenth) { // 检查参数有效性 if(i >= DATA_NUMBER || lenth > 350) return 0; // 准备状态字 u16 sta = 1 << 15; // 有效标志 sta |= (type & 0x07) << 12; // 类型 sta |= lenth & 0x03FF; // 长度 // 写入Flash STMFLASH_Write(FLASH_SAVE_ADDR + i*MAX_LENTH, &sta, 1); STMFLASH_Write(FLASH_SAVE_ADDR + i*MAX_LENTH + 1, pBuffer, lenth); return 1; }读取函数也很简单,先读取状态字判断数据是否有效,然后再读取实际数据。
5. 实际应用与调试技巧
5.1 常见家电的红外编码
不同品牌的设备使用不同的红外编码方案:
- NEC编码:最常用,占空比1:3,38kHz载波
- 格力空调:使用长码格式,脉冲间隔编码
- 索尼设备:通常使用SIRC协议
- 风扇类设备:很多使用简单的脉冲宽度编码
在调试时,建议先用逻辑分析仪或示波器观察原始遥控器的信号特征,然后再针对性地实现解码算法。
5.2 提高发射距离的技巧
如果发现红外信号发射距离不够,可以尝试以下方法:
- 增加红外LED的数量(2-3个串联)
- 调整限流电阻值,适当增大电流
- 确保LED指向正确方向
- 使用反射面增强信号
5.3 调试中的常见问题
接收不灵敏:
- 检查供电电压(3.3V最佳)
- 确保接收头没有被强光直射
- 尝试不同品牌的接收头
发射信号不稳定:
- 检查PWM频率是否准确(38kHz)
- 测量LED两端电压是否正常
- 确保三极管工作在饱和区
Flash写入失败:
- 检查写入地址是否擦除过
- 确保写入地址对齐
- 注意Flash的写入寿命(约1万次)
6. 项目优化与扩展方向
这个基础版本已经实现了核心功能,但还有很大的优化空间:
增加学习模式:
- 自动识别编码格式
- 支持更多协议(RC5、SIRC等)
改进用户界面:
- 增加图形化菜单
- 支持设备命名和图标
添加无线功能:
- 通过蓝牙或WiFi远程控制
- 手机APP控制界面
云端同步:
- 将学习到的编码上传到云端
- 从云端下载常见设备的编码库
低功耗优化:
- 使用STM32的低功耗模式
- 增加��动唤醒功能
在实际使用中,我发现分区管理确实大大提高了按键的利用率。通过将不同设备的遥控指令存储在不同分区,可以用相同的按键控制多个设备。比如按键"1"在分区1中是电视的开关,在分区2中就变成了空调的温度+。
红外信号的发射距离经过优化后,在5米范围内都能稳定工作,这对于家庭使用已经足够了。存储方面,256KB的Flash可以存储大约180条不同的红外指令,覆盖家中所有红外设备绰绰有余。