☰
基于STM32的红外空调控制方案:从遥控器学习到发射还原
2026/10/17 7:43:25 网站建设 项目流程

简介:这份资源面向嵌入式开发初学者与智能家居DIY爱好者,提供基于STM32微控制器驱动红外模块控制格力空调的完整工程实践。内容围绕STM32的GPIO推挽输出、定时器PWM波形生成、NEC红外编码与解码、格力空调协议适配等核心知识点展开,帮助读者理解从红外信号发射到空调响应的完整链路,并掌握Keil环境下的固件开发与示波器调试方法。压缩包共226个文件,约8.35MB,以c源码、h头文件、o目标文件、crf交叉引用文件及d依赖文件为主,另含uvprojx工程文件、sct分散加载脚本、hex烧录文件与map映射文件,构成可直接编译的完整工程目录。目前已有889人学习下载。读者可借此获得一套可复用的红外控制代码框架、定时器与GPIO配置范例,以及协议适配与信号调试的排错思路,对理解嵌入式无线通信与物联网项目具有实际参考价值。

1. 从遥控器堆里抢回控制权:这套STM32红外方案到底解决什么问题

家里空调遥控器又找不到了,或者电池漏液腐蚀了触点,这种场景几乎每个人都遇到过。更麻烦的是,在实验室、办公室这类需要统一管理多台空调的环境里,靠人工逐个去按遥控器,效率低还容易漏关。这套基于STM32和红外模块控制格力空调的方案,核心目标就是用一个几十块钱的单片机系统,替代原装遥控器,把空调的开关、温度、模式、风速全部接管过来。它适合正在做嵌入式课程设计的学生,也适合想给老旧空调加装智能控制但不想换整机的从业者。整个方案不依赖空调原厂协议文档,而是通过红外接收头抓取遥控器原始波形,再用发射管复现,属于典型的“学习型红外控制”思路。硬件上只需要一块STM32最小系统板、一个红外接收模块、一个红外发射模块和几根杜邦线,软件上则要解决载波频率匹配、时序还原和按键编码解析三个核心问题。下面从需求边界开始,把总体设计、模块实现、避坑要点和验收方法一层层拆开。

2. 需求拆解与技术选型:为什么是STM32加红外收发

2.1 功能需求与非功能需求

先明确这套系统要做什么。功能层面,它需要完成四件事:第一,学习并存储格力空调遥控器发出的红外编码;第二,通过按键或串口指令触发对应功能;第三,驱动红外发射管还原原始波形;第四,具备状态指示,让操作者知道当前是学习模式还是发射模式。非功能需求往往被忽略,但实际做的时候很关键:红外载波频率必须稳定在38kHz附近,误差超过2kHz就可能出现空调无响应;时序精度要控制在微秒级,因为格力遥控器的引导码、数据码和结束码之间的间隔直接决定解码成败;系统要能脱离电脑独立运行,否则每次上电都要重新学习就失去了实用价值。

通常的做法是,把学习到的编码存入STM32内部Flash的指定页,掉电不丢失。格力遥控器单次按键的编码长度一般在100到200个脉冲之间,每个脉冲的高低电平持续时间用定时器捕获,存储时只存时间值,不存原始电平,这样能省一半空间。以STM32F103C8T6为例,64KB Flash划出最后1KB专门存红外码,足够存十几组按键。

2.2 主控与红外器件的选型依据

主控选STM32F103系列,原因很直接:它有两个高级定时器,可以同时做输入捕获和PWM输出,而且主频72MHz,定时器分辨率足够做到1微秒。红外接收头选一体化接收头,比如常见的1838B,它内部已经完成了38kHz载波解调,输出的是干净的高低电平序列,直接接STM32的定时器捕获引脚就行。红外发射部分有两种方案:一种是直接用红外LED加三极管驱动,由STM32的定时器输出38kHz PWM波;另一种是用现成的红外发射模块,内部集成了载波发生电路,STM32只需要给高低电平。前者成本更低但占用一个定时器通道,后者接线简单但灵活性差。我一般会选前者,因为可以精确控制载波占空比,格力空调对载波占空比有一定容忍范围,但30%到50%之间最稳。

注意:红外接收头和发射管不能接在同一个定时器上,否则捕获和PWM输出会互相干扰。通常用TIM2做输入捕获,TIM3做PWM输出。

2.3 格力红外协议的关键特征

格力空调的红外协议属于私有格式,没有公开的官方文档,但通过逻辑分析仪抓取波形可以总结出规律。一次完整的按键编码由引导码、数据码和结束码组成。引导码通常是9ms低电平加4.5ms高电平,数据码是若干组“0.56ms低电平加不同时长高电平”的组合,高电平0.56ms代表逻辑0,高电平1.69ms代表逻辑1,结束码是一个短脉冲。数据码长度根据按键不同有差异,开关机键通常最短,温度调节键稍长。关键点在于,格力遥控器每次按键会发送完整的一帧,帧与帧之间间隔约40ms,如果空调没收到,遥控器不会自动重发,所以发射时必须保证一次成功。

3. 总体架构设计:三个模块与两条数据流

3.1 硬件连接与信号流向

整个系统分成三个硬件模块:STM32最小系统板、红外接收模块、红外发射模块。接收模块的输出脚接STM32的PA0,对应TIM2的通道1,配置为输入捕获模式,上升沿和下降沿都触发。发射模块的信号输入脚接STM32的PB1,对应TIM3的通道4,配置为PWM输出模式,频率设为38kHz,占空比50%。另外接两个按键,一个用于切换学习模式和发射模式,另一个用于选择要发射的按键编号。状态指示用一个LED,学习模式慢闪,发射模式快闪。

数据流有两条:学习时,红外接收头把解调后的电平信号送入TIM2,TIM2捕获每次跳变的时间戳,存入数组,学习结束后把数组写入Flash。发射时,从Flash读出时间数组,根据每个时间值控制TIM3的PWM输出使能或关闭,还原出原始波形。这里有个细节:TIM2捕获到的是解调后的信号,已经去掉了38kHz载波,所以发射时需要用TIM3把载波加回去,否则空调收不到。

3.2 软件分层与状态机设计

软件上分三层:底层驱动层负责定时器捕获、PWM输出、Flash读写和按键扫描;中间逻辑层负责编码解析、存储管理和发射时序控制;应用层就是主循环里的状态机。状态机有三个状态:空闲态、学习态、发射态。空闲态下LED慢闪,等待按键;按下学习键进入学习态,LED常亮,等待红外接收头捕获到完整帧后自动存Flash并返回空闲态;按下发射键进入发射态,根据当前选择的按键编号从Flash读数据并发射,发射完成后返回空闲态。

状态机的好处是逻辑清晰,不会出现学习到一半去发射的混乱情况。实际写代码时,学习态里要加一个超时机制,比如5秒内没收到完整帧就自动退出,避免卡死。发射态里要关掉接收中断,防止自己发的红外被自己收到造成误触发。

3.3 存储结构与数据格式定义

Flash里存的数据格式直接决定读写效率和可靠性。我一般会定义一个结构体,包含帧长度和脉冲时间数组。帧长度用两个字节表示,脉冲时间数组每个元素用两个字节,单位是微秒。一组按键编码大概100到200个脉冲,占200到400字节。Flash的一页是1KB,所以一页可以存两组按键。存储时按页擦除,写入前先判断目标地址是否为空,不为空则先擦除整页再写。

// 红外编码存储结构体定义 typedef struct { uint16_t frame_len; // 脉冲个数 uint16_t pulse_times[200]; // 每个脉冲的高/低电平持续时间,单位us } IR_Code_t; // Flash写入示例(STM32F103) void IR_SaveToFlash(uint8_t key_index, IR_Code_t *code) { uint32_t addr = FLASH_IR_BASE + key_index * sizeof(IR_Code_t); FLASH_Unlock(); FLASH_ErasePage(addr & 0xFFFFF800); // 按页擦除 uint16_t *p = (uint16_t *)code; for (int i = 0; i < sizeof(IR_Code_t) / 2; i++) { FLASH_ProgramHalfWord(addr + i * 2, p[i]); } FLASH_Lock(); }

这段代码里,FLASH_IR_BASE是自定义的Flash起始地址,通常选最后几页。FLASH_ErasePage的参数要按页对齐,所以用& 0xFFFFF800做掩码。写入时按半字编程,因为STM32F1的Flash编程粒度是16位。注意擦除和写入之间不能有中断打断,否则可能写入失败,所以操作前最好关总中断。

4. 模块实现:从捕获波形到还原发射

4.1 输入捕获配置与时间戳记录

TIM2的输入捕获配置是学习功能的核心。把PA0配置为浮空输入,TIM2的通道1设为上升沿和下降沿都捕获,预分频器设为71,这样定时器时钟是1MHz,计数周期1微秒。每次捕获中断里读取CCR1的值,与上一次的值相减得到时间间隔,存入数组。数组存满或者检测到超时(比如超过20ms没有新跳变)就认为一帧结束。

// TIM2输入捕获中断处理 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) { static uint16_t last_capture = 0; uint16_t current = TIM_GetCapture1(TIM2); uint16_t duration = current - last_capture; // 计算时间差 last_capture = current; if (ir_state == IR_LEARNING && ir_index < 200) { ir_code.pulse_times[ir_index++] = duration; ir_timeout = 0; // 喂狗,表示还在接收 } TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } }

这里用current - last_capture而不是直接读CCR,是因为定时器会溢出,减法能自动处理溢出情况。ir_timeout是一个软件计数器,在主循环里递增,超过阈值就认为一帧结束。注意数组边界检查,防止溢出。

4.2 PWM载波生成与发射时序控制

发射时,TIM3输出38kHz PWM波,但并不是一直输出,而是根据存储的时间数组来开关PWM。具体做法是:先把TIM3的PWM输出关闭,然后遍历时间数组,如果是低电平时间段,就关闭PWM输出(输出低电平);如果是高电平时间段,就使能PWM输出(输出38kHz载波)。每个时间段用一个定时器延时,延时时间就是数组里的值。

// 红外发射函数 void IR_Transmit(IR_Code_t *code) { for (int i = 0; i < code->frame_len; i++) { if (i % 2 == 0) { // 偶数索引对应低电平,关闭PWM TIM_CtrlPWMOutputs(TIM3, DISABLE); GPIO_ResetBits(GPIOB, GPIO_Pin_1); } else { // 奇数索引对应高电平,开启PWM TIM_CtrlPWMOutputs(TIM3, ENABLE); } delay_us(code->pulse_times[i]); } TIM_CtrlPWMOutputs(TIM3, DISABLE); // 发射结束关闭PWM }

delay_us需要自己实现,可以用SysTick或者DWT。注意延时精度直接影响发射成功率,微秒级延时不能用普通循环,要用硬件定时器。另外,发射过程中要关掉接收中断,否则自己发的红外会被接收头收到,干扰状态机。

4.3 按键编码解析与存储管理

学习到的原始时间数组不能直接存,因为里面可能包含噪声或者不完整的帧。通常的做法是加一个校验:一帧的脉冲数在80到200之间,引导码的低电平时间在8.5ms到9.5ms之间,高电平在4.0ms到5.0ms之间。满足这些条件才认为是有效帧,否则丢弃。存储时按按键编号索引,比如0号存开关机,1号存温度加,2号存温度减,依次类推。读取时根据当前选择的编号从Flash对应地址读。

提示:格力遥控器不同按键的编码长度不同,但引导码和结束码格式一致。学习时最好把常用按键都学一遍,每个按键单独存一组,不要试图用一个编码覆盖所有功能。

5. 避坑与常见问题排查

5.1 空调没反应,但示波器能看到波形

现象:用示波器测红外发射管两端,能看到38kHz载波和包络,但空调就是没反应。原因通常是载波频率偏了或者占空比不对。格力空调对载波频率的容忍范围大概是37kHz到39kHz,超出这个范围就可能不响应。占空比一般要求在1/3左右,如果设成50%也可能出问题。解决方法是把TIM3的PWM频率重新计算,确保ARR和PSC的组合精确输出38kHz,占空比设为33%。另外检查发射管的驱动电流,太小会导致发射距离不够,一般要保证峰值电流在100mA以上。

5.2 学习到的编码发射后空调乱动

现象:学习了一个按键,发射后空调执行了别的功能,或者温度乱跳。原因通常是捕获到了不完整的帧,或者把遥控器连发的两帧当成一帧存了。格力遥控器按键后会连续发两帧相同编码,间隔约40ms。如果学习时没有在帧结束后及时停止捕获,就会把两帧拼在一起。解决方法是在捕获中断里加超时判断,超过20ms没有新跳变就强制结束当前帧,并且只保留第一帧的数据。另外,学习时遥控器要对准接收头,距离控制在5到10厘米,太远信号弱容易丢脉冲。

5.3 Flash写入后读出来全是0xFF

现象:学习完断电重启,发射没反应,读Flash发现数据全是0xFF。原因是Flash擦除后没有正确写入,或者写入地址计算错了。STM32F1的Flash编程必须先擦除再写入,而且擦除是按页的,如果目标地址所在页有其他数据,会被一起擦掉。解决方法是把红外码存在Flash最后一页,并且写入前先判断该页是否为空,不为空则先读出原有数据,擦除后再把新数据和旧数据一起写回。另外注意FLASH_ProgramHalfWord的地址必须是偶数。

5.4 发射距离短,必须对准才能控制

现象:红外发射管离空调两三米就失灵,必须正对着才行。原因是发射管的驱动电流不够,或者载波占空比太低导致峰值功率不足。红外LED的瞬时电流可以到几百毫安,但平均电流不能超。通常用三极管驱动,基极电阻选1kΩ左右,集电极接红外LED和限流电阻。限流电阻根据供电电压算,比如5V供电,红外LED压降1.2V,想要100mA电流,电阻选(5-1.2)/0.1=38Ω,实际用33Ω或47Ω都可以。另外发射管和接收头一样,都有指向性,安装时尽量让发射管正对空调接收窗。

5.5 中断嵌套导致时序错乱

现象:学习时偶尔丢脉冲,或者发射时波形中间有断裂。原因是中断优先级配置不当,TIM2捕获中断被其他中断打断,导致时间戳记录延迟。解决方法是把TIM2和TIM3的中断优先级设为最高,并且在学习或发射期间关掉不相关的中断,比如串口接收中断和SysTick中断。如果用了RTOS,要把红外任务设为最高优先级,并且临界区里关调度。

6. 验收方法与答辩技巧:怎么证明这套系统真的能用

验收这套系统,不能只看代码能不能编译,要拿真实空调做端到端测试。我一般会按下面这个流程走一遍,每一步都有明确的通过标准。

测试项测试方法通过标准
载波频率示波器测发射管两端38kHz ± 1kHz,占空比30%~40%
学习功能用遥控器对着接收头按开关机串口打印出脉冲数在80~200之间,引导码9ms/4.5ms
存储功能学习后断电,重新上电读Flash数据与学习时一致,无0xFF
发射功能对准空调按发射键空调响应,功能与遥控器原按键一致
距离测试发射管离空调3米,正对一次发射成功,无需重复
连续测试连续发射10次开关机10次全部成功,无漏发或误触发

答辩时,评委最常问的三个问题是:为什么选38kHz、怎么保证时序精度、掉电后数据怎么不丢。第一个问题回答载波频率是红外接收头决定的,1838B的中心频率就是38kHz。第二个问题回答用硬件定时器捕获和PWM输出,不依赖软件延时。第三个问题回答存在Flash最后一页,写入前先擦除,并且加了校验。如果评委问“能不能控制其他品牌空调”,如实回答这套方案只针对格力协议做了优化,换品牌需要重新学习编码,但硬件和框架不用改。

还有一个技巧:答辩现场带一个逻辑分析仪或者示波器,当场抓一次发射波形给评委看。波形上能清楚看到引导码、数据码和38kHz载波,比口头解释有说服力得多。另外准备一个备用方案,如果现场空调不响应,可能是环境光干扰,用纸筒罩住发射管再试一次,通常能成功。

从那以后我每次做红外项目,都会在发射管前面加一个黑色热缩管,减少环境光干扰,并且把学习到的第一帧数据打印出来人工核对一遍引导码。这个习惯帮我省了很多返工时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询