恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于STM32的红外空调控制方案:从编码协议到状态机实现
首页
资讯中心
/
基于STM32的红外空调控制方案:从编码协议到状态机实现
基于STM32的红外空调控制方案:从编码协议到状态机实现
发布时间:2026/10/21 14:18:56
简介这份资源面向嵌入式开发初学者与智能家居DIY爱好者提供基于STM32微控制器驱动红外模块控制格力空调的完整工程实践。内容围绕STM32的GPIO推挽输出、定时器PWM波形生成、NEC红外编码与解码、格力空调协议适配等核心知识点展开帮助读者理解从红外信号发射到空调响应的完整链路。压缩包共226个文件约8.35MB以32个c源文件、34个h头文件、42个crf中间文件及44个d依赖文件为主另含uvprojx工程文件、sct分散加载文件、hex烧录文件与map映射文件构成可直接编译调试的Keil工程。目前已有890人学习下载。读者可从中获取完整的固件工程结构、定时器与GPIO初始化代码、红外编码实现逻辑及调试排错思路适合作为物联网与嵌入式课程设计、智能家居控制项目的参考范例。1. 从遥控器堆里解放出来STM32 红外控制空调的方案定位宿舍或者实验室里最常见的场景是桌上同时躺着三四个红外遥控器空调的、风扇的、投影仪的想调个温度得先翻半天。更麻烦的是很多老空调本身不带联网功能想做个定时开关或者远程控制基本无从下手。这个项目的切入点就在这里用 STM32 驱动红外发射模块把空调遥控器的编码逻辑复现出来让单片机替你去按那几个键。它适合正在做嵌入式课程设计的学生也适合想给旧家电加一层智能控制、又不想拆机改电路的开发者。整套方案的核心难点不在硬件接线而在红外编码协议的还原和时序控制这两点决定了你能不能真的把空调“按”响。2. 需求拆解与技术选型为什么是红外而不是拆机改线2.1 功能需求与非功能需求先把需求写清楚不然后面选型和验收都没有依据。这个项目的功能需求通常包括四条第一能够发射与目标空调遥控器一致的红外信号完成开关机、温度加减、模式切换这几类基本操作第二支持至少一组完整的空调状态编码也就是把制冷、26 度、风速自动这一整套参数打包成一条指令发出去第三具备本地触发方式比如按键或者串口指令第四预留扩展接口方便后面接温湿度传感器做自动控制。非功能需求往往被忽略但它直接决定方案能不能落地。红外发射要求载波频率稳定在 38kHz 附近误差太大会导致空调收不到指令发送必须一次成功不能靠反复重发来碰运气整个系统要能长时间运行不跑飞这对看门狗和电源稳定性都有要求。把这些写进需求文档答辩的时候才有东西可讲。2.2 主控与红外发射方案对比主控选 STM32 是合理的原因不是它“高级”而是它自带定时器和 PWM 输出能比较轻松地产生 38kHz 载波同时用另一个定时器控制编码时序。常见的型号像 STM32F103 系列主频和定时器资源都够用资料也多踩坑成本低。红外发射部分有两种常见做法。一种是直接用红外 LED 加限流电阻由 GPIO 配合定时器 PWM 驱动另一种是用现成的红外发射模块模块内部已经处理好驱动电路你只需要给它一个调制信号。前者成本低但需要自己算电流和载波后者接线简单但要注意模块的输入极性。我一般会建议课程设计用模块方案把精力留给编码逻辑而不是耗在模拟电路上。方案优点缺点适用场景红外 LED 直驱成本低、电路透明需要自己设计驱动、载波调试麻烦想深入理解红外调制的场景红外发射模块接线简单、稳定性好多一个模块、灵活性略低课程设计、快速验证2.3 空调红外协议的特殊性空调遥控器和电视遥控器最大的区别在于电视遥控器按一下发一个短码空调遥控器按一下往往要发一整帧状态码。也就是说你按“温度加”的时候遥控器并不是只发“加一度”这个动作而是把当前模式、温度、风速、摆风等所有状态重新编码后整帧发出去。这意味着你的 STM32 程序里必须维护一份空调状态表每次操作先更新状态再根据状态生成完整编码。这一点如果没想清楚后面写代码会非常别扭。3. 总体设计状态机加编码层的双模块结构3.1 系统架构与模块划分整套方案我倾向于分成三层应用层负责接收按键或串口指令维护空调状态编码层负责把状态翻译成红外时序驱动层负责用定时器输出 38kHz 载波和调制信号。三层之间通过一个状态结构体传递数据耦合度低调试的时候可以单独测某一层。具体到 STM32 上应用层跑在主循环里编码层用一个定时器中断配合状态机实现驱动层用另一个定时器输出 PWM。这样分工的好处是编码时序不会因为主循环里其他任务而抖动。下面是一个简化的状态结构体定义实际项目里可以根据空调支持的功能增减字段。// 空调状态结构体编码层根据这个结构生成红外帧 typedef struct { uint8_t power; // 0 关机1 开机 uint8_t mode; // 0 制冷1 制热2 送风3 除湿 uint8_t temperature;// 16-30 度 uint8_t fan_speed; // 0 自动1 低2 中3 高 uint8_t swing; // 0 关闭1 开启 } AC_State_t; // 全局状态应用层修改编码层读取 volatile AC_State_t g_ac_state {0, 0, 26, 0, 0};这段代码的关键点在于volatile修饰因为状态会被中断里的编码层读取不加这个关键字编译器可能优化掉更新。温度字段用整数表示实际编码时再映射到协议里的具体码值。模式字段的取值顺序要和后面编码表保持一致否则会出现“按了制冷出制热”的玄学问题。3.2 红外编码的数据组织方式空调红外协议通常用“引导码 数据码 结束码”的结构数据码里每一位用不同长度的高低电平组合表示 0 和 1。以常见的某类协议为例引导码是 9ms 低电平加 4.5ms 高电平逻辑 0 是 0.56ms 低加 0.56ms 高逻辑 1 是 0.56ms 低加 1.69ms 高。这些时间参数必须和你的空调遥控器实测一致不能照搬网上的通用值。数据组织上我一般会定义一个二维数组或者用宏来存放每个功能对应的码字。因为空调状态是组合的更稳妥的做法是写一个编码函数根据状态结构体逐位填充缓冲区而不是为每个状态预先存一条完整码。这样代码量小扩展新功能也方便。// 根据状态生成红外码缓冲区返回码的长度单位位 // buf 需要调用者保证足够大通常 64 位足够覆盖大多数空调协议 uint16_t ac_encode_frame(const AC_State_t *state, uint8_t *buf) { uint16_t idx 0; // 填充引导码标志实际发送时由驱动层处理 // 这里只组织数据位具体协议按实测调整 buf[idx] state-power; buf[idx] state-mode 1; buf[idx] state-mode 0x01; // 温度映射16 度对应码值 0依次递增 uint8_t temp_code state-temperature - 16; for (int i 3; i 0; i--) { buf[idx] (temp_code i) 0x01; } buf[idx] state-fan_speed 1; buf[idx] state-fan_speed 0x01; buf[idx] state-swing; return idx; }这个函数的逻辑说明它把状态结构体里的每个字段按位拆开依次放进缓冲区。温度做了偏移映射因为协议里 16 度通常对应码值 0。实际项目中你还需要在末尾加校验位或者固定填充位具体看你的空调协议。参数buf由调用者提供长度要足够一般 64 位够用。返回值是有效位数驱动层根据这个长度决定发送多少个脉冲。3.3 定时器与载波生成配置载波生成用 TIM 的 PWM 模式把频率设到 38kHz占空比设到 1/3 左右。占空比不是随便定的太大红外 LED 发热严重太小发射距离不够。我一般用 30% 到 50% 之间实测能稳定接收就行。// TIM3 通道1 输出 38kHz PWM假设系统时钟 72MHz // 预分频 0自动重装 72M / 38k / 2 - 1 ≈ 947 // 占空比 比较值 / (自动重装 1)这里取 1/3 左右 void ir_carrier_init(void) { // 使能时钟、配置 GPIO 为复用推挽的代码略 TIM3-PSC 0; TIM3-ARR 947; TIM3-CCR1 316; // 约 1/3 占空比 TIM3-CCMR1 | (6 4); // PWM 模式 1 TIM3-CCER | 1; // 使能通道1输出 TIM3-CR1 | 1; // 启动定时器 }这段配置里ARR决定了载波周期CCR1决定高电平时间。实际调试时如果空调没反应先用示波器或者逻辑分析仪看红外 LED 两端的波形确认 38kHz 是否真的出来了。没有仪器的话可以用手机摄像头对着红外 LED能看到微弱紫光说明基本在工作但看不到不代表没工作这个方法只能做粗略判断。4. 关键实现从状态更新到红外帧发送的完整链路4.1 应用层状态更新逻辑应用层要做的事情很单纯收到一个操作指令修改g_ac_state里对应的字段然后置一个“需要发送”的标志。比如按键按下温度加就把温度字段加一并做边界检查防止超过空调支持的范围。// 处理温度加操作返回是否需要发送红外帧 uint8_t ac_temp_up(void) { if (g_ac_state.temperature 30) { g_ac_state.temperature; return 1; // 状态变了需要发送 } return 0; // 已经到上限不发送 }这里返回一个标志而不是直接发送是为了把状态修改和红外发送解耦。主循环里根据这个标志决定要不要触发编码和发送。这样做的好处是如果后面要做“连续按多次只发最后一帧”的优化只需要改主循环的判断逻辑不用动状态更新函数。4.2 编码层状态机与发送流程编码层用一个状态机来实现每个状态对应红外帧里的一个时间段。状态机在定时器中断里推进根据当前是引导码、数据位还是结束码设置下一次中断的时间。这种方式比用delay函数阻塞发送要可靠得多也不会影响主循环响应按键。// 红外发送状态机在定时器中断中调用 typedef enum { IR_IDLE, // 空闲 IR_LEAD_LOW, // 引导码低电平 IR_LEAD_HIGH, // 引导码高电平 IR_BIT_LOW, // 数据位低电平 IR_BIT_HIGH, // 数据位高电平 IR_STOP // 结束 } IR_State_t; static IR_State_t ir_state IR_IDLE; static uint8_t ir_bit_index 0; static uint8_t ir_bit_count 0; static uint8_t ir_buf[16]; // 启动一次发送由主循环调用 void ir_send_start(void) { ir_bit_count ac_encode_frame(g_ac_state, ir_buf); ir_bit_index 0; ir_state IR_LEAD_LOW; // 设置定时器下一次中断时间具体代码略 }状态机的推进逻辑是每次中断根据当前状态和当前位是 0 还是 1决定下一个状态和定时器重装值。比如在IR_BIT_LOW状态固定延时 0.56ms 后进入IR_BIT_HIGH然后根据当前位是 0 还是 1决定高电平持续 0.56ms 还是 1.69ms。这个逻辑写清楚之后红外时序就完全可控了。4.3 接收端验证与调试手段发送做完了怎么知道空调真的收到了最直接的办法是看空调有没有“嘀”一声并且执行动作。但调试阶段不能只靠这个因为失败了你不知道是编码错了还是载波不对。我一般会先用一个红外接收头接在另一个 STM32 或者逻辑分析仪上把发送的波形抓下来和原遥控器的波形对比。没有逻辑分析仪的话可以用红外接收模块加串口把接收到的码打印出来和预期值比对。提示调试红外时先把空调放在离发射管 1 米以内的位置排除距离因素。确认编码正确后再逐步拉远测试实际覆盖范围。5. 避坑与常见问题红外项目里最容易翻车的几个点5.1 载波频率偏差导致空调无响应现象是代码逻辑看起来都对但空调就是没反应。原因通常是定时器配置算错了实际载波不是 38kHz而是偏到了 40kHz 以上或者 35kHz 以下。解决方法是重新核算PSC和ARR用示波器确认频率或者用红外接收头测接收到的信号是否稳定。我一般会在代码里把载波频率计算公式写成注释方便后面改主频时重新算。5.2 状态字段与编码表不对应现象是按了制冷空调却进入制热或者温度显示和预期差了好几度。原因是状态结构体里的枚举值和编码函数里的映射关系不一致比如结构体里 0 是制冷编码函数里却把 0 当成了制热。解决办法是定义一个统一的枚举或者宏应用层和编码层都引用同一份定义不要各写各的。5.3 中断里执行时间过长现象是红外发送时按键没反应或者系统偶尔死机。原因是把整个编码和发送逻辑都塞进了中断中断执行时间太长影响了其他任务。正确的做法是中断里只做状态机推进和定时器重装编码在主循环里完成中断只负责按位输出。这样中断执行时间可以控制在几微秒级别。5.4 电源噪声导致发射距离短现象是近距离能控制稍微远一点就不行。原因往往是红外 LED 的驱动电流不够或者电源上没有足够的去耦电容发射瞬间电压被拉低。解决办法是在红外 LED 附近加一个 100uF 左右的电解电容驱动三极管或者 MOS 管要选饱和压降低的型号。如果是用模块检查模块供电是否稳定。5.5 忽略空调状态回传现象是遥控器和 STM32 交替控制时STM32 发出的状态和空调实际状态不一致。原因是空调遥控器每次发的是完整状态而你的 STM32 维护的状态可能已经过期。解决办法是每次发送前尽量让 STM32 的状态和空调当前状态同步或者只做单向控制不依赖回传。如果空调支持状态查询可以加一个接收头做闭环但复杂度会上升不少。6. 验收与答辩怎么证明这套方案真的能跑验收的时候评委或者导师不会只看你代码写得多漂亮他们要看的是“你说能控制那就当场控一个”。所以提前准备一套可复现的演示流程比多写两百行注释有用得多。我一般会按这个顺序准备第一步静态展示硬件连接说明 STM32 型号、红外模块型号、供电方式让评委看到这不是一堆散线。第二步演示单个功能比如按一下按键空调开机再按一下温度加空调温度确实变了。第三步演示组合状态比如从制冷 26 度切到制热 22 度空调能正确响应。第四步展示调试数据比如逻辑分析仪抓的波形图或者串口打印的编码值证明你不是靠运气。验收项通过标准验证方式载波频率38kHz ± 1kHz示波器或逻辑分析仪开关机一次成功现场操作温度调节连续加减各三次无失败现场操作模式切换制冷/制热/送风均正确现场操作连续运行30 分钟无死机通电观察答辩时还有一个容易被问到的点为什么不用现成的红外码库我的回答通常是现成码库覆盖的协议有限而且空调协议各家差异大自己实现编码层才能保证可控性和可扩展性。这个回答既解释了技术选型也展示了你对项目的理解深度。从那以后我每次做红外相关的项目都会先把接收端验证环境搭好再开始写发送代码。没有接收端你就是在盲发出了问题只能靠猜。希望这套结构能帮到你把课程设计或者方案交付做得更顺一点。本文还有配套的精品资源点击获取