ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于SU-03T与STM32F1的离线语音红外空调遥控方案

基于SU-03T与STM32F1的离线语音红外空调遥控方案 简介基于STM32F1与SU-03T语音识别模块、通过红外发射电路控制美的空调的完整方案面向嵌入式初、中级开发者及物联网爱好者解决语音控制家电的产品级复刻问题。资源包共245个文件约9.15MB以C/H源码、编译生成的o/d/axf库文件、Keil5的uvprojx工程配置为主并包含bin/hex固件、原理图/PCB设计图、MD说明文档及Git仓库文件等目录结构清晰便于按模块索引与二次开发。已有200人浏览/学习适合想从软硬件结合角度快速跑通“语音识别—红外发射—空调控制”链路的读者。具体内容包括STM32F1标准库工程、SU-03T交互词配置示例、红外编码发射逻辑、硬件原理图与PCB布局、编译烧录脚本以及实物调试要点可直接编译生成固件并复刻出可用实物也可通过修改源码自定义唤醒词和交互词汇为扩展其他红外家电控制提供参考。1. 用语音代替遥控器这套项目为什么值得复刻空调遥控器找不到了、晚上懒得摸黑按、家里老人不会用复杂面板——SU-03T 语音识别模块加 STM32F1 红外的方案本质是把“人的一句话”变成“空调能听懂的一串电平”。这类项目的价值不在单点技术而在把离线语音识别、串口解析、38kHz 红外编码三条链路串成一个能稳定复位的闭环。网上类似方案不少但多数只给了原理图或只有单片机端代码缺了 SU-03T 指令配置和数据帧格式导致照抄往往卡在“模块能识别、单片机没反应”。这篇文从硬件接线、串口协议、红外驱动到空调码分析讲完整适合已经会基础 STM32 GPIO 和定时器、想挑战一个完整小家电项目的开发者。全文以 NEC 协议和美的空调实测为前提但方法可以平移到格力、海尔等品牌。2. SU-03T 语音识别模块与 STM32F1 的串口通信链路2.1 SU-03T 是离线识别先搞清它和马文等模块的本质区别SU-03T 属于离线低成本语音识别方案本身不依赖云端内置了麦克风、音频前端和识别引擎。它和常见的 LD3320 这类模块不同LD3320 需要在主控侧做关键词列表管理而 SU-03T 的识别模型在出厂后通过配套工具烧录配置好的唤醒词和命令词模块识别到对应内容后直接通过 UART 输出一帧数据。这意味着 STM32F1 侧的负担很低只需要接收串口字节、解析出指令码不需要跑任何音频算法。这对资源有限的 F1 来说非常友好因为 F1 的算力本就有限如果自己接麦克风做识别Memory 和 CPU 都会被占用大半得不偿失。在选型上还要注意 SU-03T 有几个硬件版本有的板载了功放可以外接喇叭有的只有 MIC 接口输出接口通常是 UART_TX、UART_RX 和 GND/VCC。买模块时务必要确认是 5V 还是 3.3V 供电版本有些板子自带稳压直接接 5V 就行有些则必须用 3.3V接 5V 会烧掉。这个细节在批量购买时特别容易踩建议焊板前用万用表测一下模块上主控芯片的供电脚电压。2.2 串口帧格式解析STM32F1 端不能只收不校验SU-03T 出厂固件默认使用 9600 波特率8 位数据位无校验1 位停止位。当模块识别到命令后会发送一帧固定格式的数据。常见格式为帧头2 字节、命令字1 字节、地址2 字节、数据N 字节、校验1 字节。但不同批次固件可能略有差异所以第一步不是在 STM32 上写死解析而是先用 USB-TTL 把模块接到电脑串口助手直接观察原始字节。// 示例串口接收中断中缓存一帧数据的代码骨架 // 假设帧头是 0xAA 0xA5命令字 0x01 表示识别结果 // 数据第3字节是命令词ID具体偏移以实测为准 uint8_t rx_buf[16]; uint8_t rx_index 0; uint8_t frame_ready 0; void USART1_IRQHandler(void) { uint8_t ch; if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { ch USART_ReceiveData(USART1); if (rx_index 0 ch ! 0xAA) return; // 等待帧头 rx_buf[rx_index] ch; if (rx_index 2 rx_buf[0] 0xAA rx_buf[1] 0xA5) { if (rx_index 6) // 假设固定帧长6字节 { frame_ready 1; rx_index 0; } } if (rx_index sizeof(rx_buf)) rx_index 0; // 防溢出 } }这段代码的思路是先确认帧头字节再把后续数据按固定长度收完。帧长必须在串口助手上数清楚不能靠猜。SU-03T 每次识别完还会带一个“识别结束”的命令帧接收端要做状态去抖避免一次唤醒产生多次触发。另外注意SU-03T 上电时会输出一段版本信息有些固件还会在模块刚上电时输出测试帧代码里要加一个“准备就绪”标志忽略上电头几帧。2.3 配置工具里要改的关键参数SU-03T 有一套 PC 端配置工具通过 USB 烧录固件和命令词表。要复刻这个项目必须在工具中做三件事第一选择串口协议模式把识别结果映射到固定串口指令第二每个命令词的回复内容可以留空因为不需要模块播放语音第三设置唤醒方式可支持连续唤醒但实测连续唤醒会提高误触发率。这块配置直接决定 STM32F1 能否分辨“打开空调”和“关闭空调”。建议在配置工具里为每个命令手动指定一个数字 ID然后在 STM32F1 代码里用宏定义对齐比如#define CMD_AC_ON 0x01 #define CMD_AC_OFF 0x02 #define CMD_TEMP_UP 0x03 #define CMD_TEMP_DOWN 0x04之所以建议手动指定 ID是因为工具默认的 ID 排序可能和添加顺序有关一旦后续扩展命令比如加“摆风”“睡眠”ID 就会错位。手动指定后在固件里用 switch 分发更直观排查时对照配置表一眼能看出问题。3. 红外发射电路设计从定时器载波到驱动三极管3.1 熟悉 38kHz 载波你的空调才会理你空调遥控器的红外通信采用 38kHz 左右的载波调制。所谓调制就是把要发送的二进制码例如 1 和 0叠加在 38kHz 方波上通过红外发射管发出去接收端通过滤掉环境光的干扰后解调出原始码。若省略载波直接给发射管通电发光遥控器就没有意义因为接收头无法区分信号与环境杂光。STM32F1 生成 38kHz 载波最常用的手段是利用定时器输出比较PWM模式。例如使用定时器 TIM2时钟 72MHz预分频设为 1自动重载值设为 947计算结果为 72MHz / (2 * 948) ≈ 37.97kHz接近 38kHz。这个误差对红外接收头来说完全可接受接收头一般都有较宽的带宽。需要注意的是载波频率不是越准越好关键是占空比一般取 1/3 或 1/2优先选 1/3 可以延长发射管寿命。// 使用 TIM2_CH1 输出 38kHz PWM占空比约 1/3 // PA0 作为输出引脚 TIM_TimeBaseInitTypeDef TIM_BaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_BaseInitStructure.TIM_Period 947 - 1; // 自动重载值 TIM_BaseInitStructure.TIM_Prescaler 2 - 1; // 预分频 TIM_BaseInitStructure.TIM_ClockDivision 0; TIM_BaseInitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_BaseInitStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 316; // 947/3 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_Cmd(TIM2, ENABLE);代码里几个参数要交代预分频设为 2 并没有完全 72 分频而是把计数器时钟变为 36MHz重载 947 后得到约 38kHz。很多人直接照抄网络上的值发现频率不对往往是因为系统时钟或 APB1 分频不同。在你自己的工程里务必用调试器读取 RCC_GetClocksFreq确认定时器时钟。3.2 发射管驱动电路GPIO 不能直接推STM32 的 GPIO 灌电流虽能达到 20mA 左右但红外发射管在瞬间脉冲下需要更大的峰值电流来保证发射距离输出高电平方式直接驱动电平质量和发射距离都不够。常见做法是用一颗 NPN 三极管如 S8050做开关MCU 的 PWM 控制基极集电极接红外发射管串限流电阻到 VCC。实测这种电路在 5V 供电下发射距离可以做到 8 米以上足以覆盖普通卧室。设计时注意红外发射管的正向导压降约 1.2V 到 1.5V若限流电阻 100 欧姆峰值电流约为 (5 − 1.3) / 100 ≈ 37mA这对短时间脉冲是安全的。S8050 的基极电阻建议 1k 到 2k避免单片机引脚过流。layout 上引脚尽量短必要时加一个 10uF 的电解电容在电源近端否则发射瞬间可能拉低 VCC 造成单片机复位。3.3 用 GPIO 控制通断还是直接复用 PWM在发送红外编码时普通 NEC 协议要求发射载波 9ms、停止 4.5ms 等不同时间段表示引导码。最简单的策略是不断开启和关闭 TIM2 的输出而不是在 GPIO 上手动翻转。TIM_Cmd可以开启/关闭定时器但调用开销较大有更优雅的办法用 TIM_SetCompare1 将比较值设为 0 关闭输出或设为有效值重新打开。我推荐的做法是底层函数只控制一个宏#define IR_SEND_START() TIM_SetCompare1(TIM2, 316) #define IR_SEND_STOP() TIM_SetCompare1(TIM2, 0)这样避免了反复开关时钟带来的毛刺也让载波启动时间一致。实测在 9600 波特率下SU-03T 的响应到 MCU 发码之间的延迟可以稳定在 50ms 内体感就是“说完话空调马上就嘀”。4. 美的空调红外协议分析与命令映射4.1 空调红外码和电视遥控器的关键差异空调的遥控码和电视不同最大的特点是长。电视遥控器通常一个 NEC 帧只有 32 位加上引导码和停止位占用约 67ms而空调的设定温度、模式、风速、摆风都需要在一条指令中表达美的常用的是 96 位甚至更长的帧。发送时需要注意不能在两帧之间随意插入长时间停顿否则空调可能只解析到部分数据。美的的编码基础也是 NEC 协议引导码为 9ms 载波加 4.5ms 空闲但数据位采用不同的时间定义逻辑 1 通常是 560us 载波加 1.69ms 空闲逻辑 0 是 560us 载波加 560us 空闲。这些是 NEC 常见定义但美的部分型号完全兼容 560us 体系也有个别采用 600us 的变种。所以拿到新空调时不要套用网上的码表直接烧而是用逻辑分析仪先抓原装遥控器的波形。4.2 用逻辑分析仪抓出真实码流本地复刻的常见操作是使用 10 元左右的 24MHz 逻辑分析仪或示波器将探头夹在红外接收头输出端按遥控器上的“开关”和“温度/-”按键采集波形后用 PulseView 解码。具体参数设置如下表参数项推荐值说明采样率2MHz 或更高太低抓不到 38kHz 载波细节触发方式下降沿对准引导码起始位置数据位宽96 位如果超出则按实际帧长调整协议解码NEC 扩展若识别失败改为自定义解码抓到原始波形后把每位的时间长度导出成数组在 STM32 代码中用一个延时函数按微秒级别还原。注意不要直接用HAL_Delay对 1ms 太粗而是使用 DWT 或 SysTick 做 us 级延时。这里有个容易被忽略的难点STM32F1 主频 72MHz执行普通循环延时到大循环时精度会受到中断影响建议在发送红外码前关掉串口中断或者用一个 DMA 缓冲区先把整帧电平序列准备好再做定时输出。后者相对更平滑但实现复杂度偏高。如果只是复刻这个项目短时间关闭中断即可。4.3 语音词表与空调动作的映射程序结构uint8_t temp_target 26; uint8_t ac_power 0; void handle_voice_cmd(uint8_t cmd_id) { switch (cmd_id) { case CMD_AC_ON: ac_power 1; send_ac_frame(AC_MODE_COOL, temp_target, AC_WIND_AUTO); break; case CMD_AC_OFF: ac_power 0; send_ac_frame(AC_MODE_OFF, 0, 0); break; case CMD_TEMP_UP: temp_target; if (temp_target 30) temp_target 30; send_ac_frame(AC_MODE_COOL, temp_target, AC_WIND_AUTO); break; case CMD_TEMP_DOWN: temp_target--; if (temp_target 17) temp_target 17; send_ac_frame(AC_MODE_COOL, temp_target, AC_WIND_AUTO); break; default: break; } }映射逻辑的关键是保持“状态缓存 全量发送”的模式。不同于电视遥控器发送一次有效空调每次发码时整套状态模式/温度/风速都会体现在码里因此 STM32F1 内部必须保存一份当前状态修改时更新缓存再整帧发送。这里最容易出的 bug 是“只把温度递增却从网络上抄来一个把温度字段变掉的原始码”。send_ac_frame内部要做的事情是首先根据状态拼装一个字节数组比如长度 12然后按位翻转成电平序列最后调用微秒级延时驱动载波发射。每家空调的字节序、校验和可能不同这部分只能靠逐位比对抓取结果来修正。4.4 校验与防误触发的细节美的一些遥控器会在帧尾附带校验字节有的用累加和、有的用异或。如果你抓到的帧在修改温度后校验位不变大概率是遇到了固定校验如果每次修改数据后最后两位也变那么你需要反推校验算法。一个实用的技巧是固定模式为制冷 26 度只改温度为 27、28、29分别抓帧然后对比相邻帧的差异通常能看到校验位的异或或加法关系。在语音侧SU-03T 有“本地命令”和“扩展命令”两种识别粒度。为避免误触发建议只开启特定唤醒词如“小度小度”或自定义唤醒并且在命令词表中避免加入和空调无关的词语。代码层面可以在收到同一命令 ID 后延时 300ms 再发红外码做软件消抖。这套组合下来误触发率在实测中几乎为零。5. 串口乱码与红外盲区复刻时的三个实战调整5.1 上电不稳定给 SU-03T 加一个独立的使能时序SU-03T 上电后会先输出一段 bootloader 相关的版本字符串。有些 STM32F1 工程启动后立即初始化串口会把这段版本信息误当成指令帧解析导致后续所有命令错位。解决方案是在主循环延时 500ms 后再清空接收缓冲。更稳妥的办法是在硬件上让 STM32F1 的复位电路和 SU-03T 分开避免两者同时上电时 VCC 波动。关于 bootloader 还有一层含义如果之后想给 STM32F1 升级固件可预留 BOOT0 跳线平时拉低走闪存启动需要烧录时拉高进系统自带 Bootloader用串口直接下载程序。5.2 红外发射距离不够的调整如果你发现空调只在 1 米内响应先检查三极管基极电阻是否过大、限流电阻是否过大、载波频率是否偏移。把基极电阻从 10k 换成 1k 能让发射管饱和导通更深把占空比从 1/2 改为 1/3 后峰值电流反而可以加大距离也会提升。另外部分空调接收窗位置比较偏可以尝试把红外发射管加一个热缩管做聚光罩实测有 20% 左右的定向距离提升。5.3 实用调试技巧用一个红外接收头做闭环验证如何确认 STM32F1 发出的红外码和原装遥控一致空口比较困难常见做法是把单片机发的信号用同一个红外接收头接回示波器/逻辑分析仪对比波形与遥控器抓取波形。这个方法不适合用单片机自己接收自己的发射——发射管和接收头距离太近会造成自激。比较时重点看引导码后的每一位宽度而不是只看 0/1 序列时间方向有时会因为电路极性翻转导致反向编码。如果你遇到“代码看起来一样空调就是没反应”大概率是极性反了把发射管两个引脚互换试试即可。最终以空调的实际响应为准不同批次空调对同一帧的容忍度也会有差异。在这个项目的复刻过程中最花时间的是空调码的逆向但一旦把抓帧、解码、对比的流程跑通这个技能可以直接复用到其他红外设备比如风扇、投影仪、电视。从硬件到软件全套可以固定在一张洞洞板上做成一个小型桌面语音遥控中枢为后续接入更多家电打好框架。本文还有配套的精品资源点击获取
返回列表