用STM32F103RCT6和0.96寸OLED,我DIY了一个能控制空调风扇的万能遥控器(附完整代码)
从零打造智能红外遥控中枢:STM32F103与OLED的完美组合
去年夏天,我家里堆积了七个不同品牌的遥控器——电视、空调、风扇、机顶盒、音响……每次找遥控器都像在玩寻宝游戏。作为一名嵌入式开发者,我决定用STM32F103RCT6和0.96寸OLED打造一个万能遥控中枢,彻底解决这个生活痛点。这个项目不仅实现了标准NEC协议设备的控制,还能学习非标准协议(如格力空调的特殊编码),下面将完整分享我的开发历程。
1. 硬件架构设计与选型思考
1.1 核心控制器选择
STM32F103RCT6成为我的首选,原因有三:
- 性价比突出:Cortex-M3内核搭配72MHz主频,256KB Flash完全满足红外数据存储需求
- 生态完善:丰富的中文资料和标准外设库降低开发门槛
- 引脚资源:足够驱动OLED并同时处理红外收发
注意:务必选择LQFP64封装版本,QFN封装手工焊接难度较大
1.2 红外模块的"暗坑"
市面常见红外模块存在三大陷阱:
- 接收头供电电压不匹配(部分需5V)
- 发射管驱动电流不足(建议加三极管放大)
- 38kHz载波精度差(影响信号识别)
我的解决方案:
// 红外发射驱动电路配置 #define IR_SEND_PIN PC2 // 控制三极管基极 #define IR_LED_PIN PA0 // 载波输出1.3 OLED显示优化技巧
0.96寸OLED(SSD1306)在使用中要注意:
- 刷新策略:局部刷新比全屏刷新快3倍
- 显示缓存:自定义128x64位图数组提升渲染效率
- 接口选择:I2C模式节省引脚但SPI刷新更快
实测刷新率对比:
| 刷新方式 | 引脚占用 | 帧率(fps) |
|---|---|---|
| I2C | 2 | 25 |
| SPI | 4 | 58 |
2. 红外协议逆向工程实战
2.1 NEC标准协议解析
典型NEC协议帧结构:
[引导码]9ms高+4.5ms低 [用户码]16位 [数据码]16位(含反码)解码关键代码:
uint32_t decode_NEC(uint16_t* buffer) { if(buffer[0] < 8000 || buffer[0] > 10000) return 0; // 验证引导码 uint32_t result = 0; for(int i=2; i<34; i++) { if(buffer[i] > 1000 && buffer[i] < 1500) { // 逻辑1 result |= (1UL << (33-i)); } } return result; }2.2 非标协议攻克方案
格力空调采用的特有编码特点:
- 引导码长达4.5ms
- 数据位采用PWM宽度编码
- 帧长度不固定(最长可达68字节)
我的采集方法:
- 使用中断捕获所有边沿跳变
- 记录高/低电平持续时间
- 动态分配存储数组(最大350个时间点)
void EXTI9_5_IRQHandler() { static uint32_t last_time = 0; uint32_t current = micros(); uint16_t duration = current - last_time; if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9)) { // 上升沿触发 ir_buffer[ir_index++] = duration | 0x8000; // 标记为高电平 } else { // 下降沿触发 ir_buffer[ir_index++] = duration & 0x7FFF; } last_time = current; EXTI_ClearITPendingBit(EXTI_Line9); }3. 存储系统设计与优化
3.1 Flash分区管理策略
为解决按键数量限制问题,我设计了九宫格存储方案:
- 每个分区占用独立Flash扇区
- 相同按键在不同分区执行不同功能
- 采用地址偏移量实现快速切换
存储结构设计:
| 地址范围 | 内容说明 |
|---|---|
| 0x08010000 | 分区1数据(按键1-9) |
| 0x08011000 | 分区2数据(按键1-9) |
| ... | ... |
| 0x08018000 | 分区9数据(按键1-9) |
3.2 数据压缩算法
针对空调长码采用行程编码(RLE)压缩:
- 连续相同状态合并记录
- 压缩率可达60%以上
- 添加校验位保证数据完整
压缩示例:
原始数据:500,500,500,1000,1000,500 压缩后:3×500,2×1000,1×5004. 用户交互系统开发
4.1 菜单导航设计
采用状态机实现多级菜单:
graph TD A[主界面] -->|按键1| B[分区选择] A -->|按键2| C[学习/发射] A -->|按键3| D[数据管理] A -->|按键4| E[信号分析] C -->|模式键| F[数字学习] C -->|音量+键| G[模拟学习]实际代码实现:
typedef enum { STATE_HOME, STATE_LEARN, STATE_SEND, STATE_ANALYZE } SystemState; SystemState current_state = STATE_HOME; void handle_keypress(uint8_t key) { switch(current_state) { case STATE_HOME: if(key == 1) current_state = STATE_PARTITION; // 其他状态转换... break; case STATE_LEARN: // 学习状态处理... break; } }4.2 波形可视化技巧
在OLED上实现红外波形显示:
- 时间轴压缩算法
- 电平状态用不同高度条状图表示
- 添加游标查看具体时间值
显示效果对比:
| 显示模式 | 优点 | 缺点 |
|---|---|---|
| 二进制 | 直观显示NEC码 | 无法看波形 |
| 波形图 | 分析非标信号 | 占用空间大 |
5. 项目优化与性能提升
5.1 低功耗设计
通过以下措施使待机电流降至3.8mA:
- 动态关闭红外接收电路
- OLED进入睡眠模式
- STM32切换为Stop模式
电源管理代码片段:
void enter_low_power() { OLED_DisplayOff(); IR_Receiver_Disable(); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新初始化外设 SystemInit(); OLED_Init(); }5.2 抗干扰措施
实际测试发现的干扰问题及解决方案:
日光灯干扰:
- 增加38kHz带通滤波电路
- 软件端添加信号校验
信号反射问题:
- 设置最小重复间隔200ms
- 添加防连发机制
Flash写入干扰:
- 关键操作禁用中断
- 采用ECC校验
6. 开发中的经验教训
在调试红外发射时,曾连续三天无法控制客厅的格力空调,最终发现三个关键点:
- 空调需要精确的38kHz载波(误差<±200Hz)
- 引导码后需要添加2.5ms的静默期
- 数据帧结束需要额外的40ms低电平
修正后的发射时序:
void send_gree_ac(uint16_t* data) { PWM_SetFrequency(37900); // 精确调整载波 send_pulse(9000, 4500); // 引导码 delay_us(2500); // 关键静默期 for(int i=0; i<data[0]; i++) { send_pulse(data[i+1] & 0x7FFF, data[i+1] >> 15); } IR_LED_OFF(40000); // 结束信号 }这个项目从构思到最终完成历时两个月,最耗时的部分是非标准协议的逆向工程。建议初学者先从NEC协议入手,再逐步挑战复杂协议。所有源码已托管在GitHub,包含详细注释和示波器抓取的信号图,希望能帮助更多开发者少走弯路。
