1. 选型分析:为什么是 ESP32-C3 + SU03T + NPN 红外
1.1 主控选择:ESP32-C3 的优势与取舍
很多人一上来会选 ESP8266,毕竟便宜、资料多、生态也成熟。但放在语音遥控空调这个场景里,ESP8266 有几个短板特别明显:单核 80MHz 跑得慢,GPIO 紧张,还没法直接用 USB 烧录,得外接 USB-TTL 转接器,对新手来说多了一道坎。
ESP32-C3 的优势在于它是 RISC-V 核心,单核最高 160MHz,Flash 通常有 4MB,GPIO 数量足够用,关键是自带 USB 烧录功能,Type-C 线插上就能刷固件,完全不需要额外买烧录器。语音空调遥控这个项目里,ESP32-C3 的性能其实是溢出的,但性能有余量带来的好处很实在:即便你在处理红外发射的同时还要跑串口接收、按键消抖、状态灯控制,代码写得不够精简也不会卡顿。
另外,ESP32-C3 保留 Wi-Fi 能力。后期如果想做个手机 App 控制界面、或者把语音遥控功能接进家庭自动化系统,不需要换主控板,直接在现有硬件上加代码就行。这是 ESP8266 很难做到的,因为它的可玩性上限摆在那。
1.2 语音识别模块:SU03T 为什么值得用
SU03T 是国内 DIY 圈子里用得相当多的离线语音识别模块。它的核心价值在“离线”两个字:词条识别全部在本地完成,不连网、不上云,响应速度快,不会因为家里路由器重启或者宽带故障就罢工。
这个模块支持自定义词条,你可以把“打开空调”“关掉空调”“制冷模式”“风速加大”这些口语化指令烧录进去。词条数量可以配置,对空调遥控来说,8 到 12 个词条完全够用。识别到词条后,SU03T 会通过串口输出一组预设的数据,主控拿到数据后就能执行对应动作。整个链路清晰简单,没有任何云端的中间环节。
SU03T 最大的“坑”在于它的串口输出需要和主控之间建立约定。出厂默认波特率是 9600,返回格式也可以自行配置。很多人拿到模块去试,发现 ESP32-C3 收到的一堆乱码,其实就是波特率不一致或者共地没做好。这两个问题我会在后面的接线和调试章节专门展开。
1.3 红外发射方案:为什么选 NPN 三极管
红外发射电路看起来简单,一颗 940nm 红外发射管加一颗限流电阻就能发光。但问题在于 GPIO 驱动能力。ESP32-C3 的 GPIO 输出高电平时电流很有限,直接推红外发射管,压降和电流都撑不起来,实际发射距离会短得可怜,隔着一两米空调都可能收不到。
所以这里需要用 NPN 三极管做开关放大。我选用的是 S8050 这类常见小功率 NPN 三极管,基极串一颗 1kΩ 电阻接到 GPIO,集电极接红外发射管负极,发射管正极接 3.3V,发射极接地。GPIO 输出高电平时,基极电流让三极管进入饱和导通状态,红外管获得几十毫安的驱动电流,遥控距离轻松到 3 到 5 米,对室内空调场景绰绰有余。
要注意,不要把红外管直接用 5V 驱动。ESP32-C3 的 GPIO 电平体系是 3.3V,如果外部电路按 5V 电平设计,长期使用的可靠性会受影响。3.3V 供电配合三极管放大是最稳妥的方案。
2. 硬件接线与供电细节
2.1 三块硬件的连接思路
整个系统由三部分组成:ESP32-C3 开发板、SU03T 语音模块、红外发射电路。我用的 ESP32-C3 是常见 Type-C 口的合宙开发板,SU03T 是常见的 8PIN 封装小板,红外发射电路自己在面包板上搭。
接线规则如下:
- SU03T 的 VCC 接 5V 或 3.3V。我推荐从 ESP32-C3 的 5V 引脚取电,因为语音模块驱动扬声器的瞬时电流较大,单独从 USB 5V 供电更稳。
- SU03T 的 GND 必须与 ESP32-C3 的 GND 连接,这是串口通信稳定工作的前提。
- SU03T 的 TX 接 ESP32-C3 的 RX,我使用的是 GPIO20。
- SU03T 的 RX 接 ESP32-C3 的 TX,我使用的是 GPIO21。
- 红外发射管正极接 3.3V,负极接 S8050 的集电极。
- S8050 基极串联 1kΩ 电阻后接 ESP32-C3 的 GPIO10。
- S8050 发射极接地。
画接线图时,一定要把 TX 和 RX 交叉连接这件事标注清楚。SU03T 的 TX 发数据,ESP32-C3 从 RX 收数据,两边一交叉数据才能流动。如果 TX 接 TX,等于两边都想往同一条线上发数据,直接废掉。
2.2 共地:最容易翻车的细节
关于共地我多说几句。SU03T 和 ESP32-C3 如果分别供电,它们的“地”之间可能并不等电位。USB 供电时所有 GND 都来自同一个电源,基本没有问题,但如果一个用电池、一个用充电头,或者两个用不同的适配器,地电位差会导致串口数据乱码,甚至直接损坏模块。
我踩过一回很典型的坑:SU03T 接了一个独立充电头,ESP32-C3 插在电脑 USB 口上,两个 GND 没接。结果串口收到的数据全是 0xFF 或者完全随机。后来把两边的 GND 接到一起,问题立刻消失。从那以后我凡是做多模块串口通信,第一件事就是检查共地。
建议:把 SU03T、ESP32-C3、红外驱动电路三者的 GND 统一连接到 ESP32-C3 的 GND 排针上。这样既保证串口电平参考一致,也让红外发射电路有了统一的回流路径。
2.3 电源稳定性与电流余量
如果你直接把 ESP32-C3 插在电脑 USB 口上调试,要留意电脑 USB 口的供电能力一般只有 500mA。启动瞬间 ESP32-C3 加上 SU03T 的电流峰值,偶尔会出现掉电重启的现象。我建议调试阶段使用带供电的 USB HUB,或者直接用 5V 2A 的电源适配器给开发板供电,SU03T 再从开发板的 5V 引脚取电。
SU03T 播报提示音时,扬声器驱动电路会突然拉高电流,如果电源线压降大,可能会让 ESP32-C3 的电源电压产生波动,进而干扰红外发射时序。因此电源线尽量短、线径粗一点,电源适配器的电流余量留足 1A 以上,不要让整个系统在极限电流下工作。
3. 红外码的抓取、识别与存储思路
3.1 为什么不能直接找现成的码库
空调遥控器和电视遥控器不一样,空调没有统一标准的红外协议。不同品牌、不同型号、不同年份的产品,协议差异很大。有些是 NEC 协议的变种,有些是类似 Mitsubishi 的帧格式,还有更多品牌使用自己私有的编码方式。就算是同一个品牌,不同系列的空调很可能用不同协议,直接套用网上的码表,大概率失灵。
正确做法是:先抓自己家里遥控器的码,然后让 ESP32-C3 照着码的时序原样发射。很多教程会介绍 IRremote 库直接解码,但 IRremote 库只支持常见协议,碰到私有协议会解码失败或者得到残缺数据。我的做法是使用 ESP32-C3 内部 RMT 外设,以微秒级精度记录遥控器发出的每个高低电平持续时间,拿到完整的原始时序,而不是依赖协议解析层。
3.2 抓码硬件怎么搭
抓码需要一颗红外接收头,型号通常选 VS1838B 或 TL1838。这颗接收头内置了 38kHz 载波解调电路,能够把遥控器发射的载波信号还原成串行电平。
接线非常简单,接收头有三个引脚,VCC 接 3.3V,GND 接 GND,OUT 接 ESP32-C3 的 GPIO9。接好后,先用 RMT 接收模式写一小段测试程序,把每次收到的脉冲时间数组打印到串口助手上。
下面是一个 RMT 接收逻辑的示意:
#include "driver/rmt_rx.h" // RMT 接收回调: // 红外数据本质上是连续的电平翻转,每个翻转间隔对应一个时间单位 // 将每次收到的 duration 存入数组,等待后续打印或分析我在这里不贴完整代码,因为不同版本的 ESP-IDF 或 Arduino 库函数接口差异很大。重点是理解原理:红外遥控码,本质上就是一连串“高电平持续时间 + 低电平持续时间”的时间数组。抓住这个数组,发送时按相同时间序列翻转 GPIO 即可。
3.3 空调码太大,怎么压缩存储
空调码比电视码复杂得多。电视码往往只有几十个脉冲,空调码常常有几百甚至上千个脉冲,而且很多协议需要在一帧内发送多段数据。比如某些品牌空调的完整红外帧可以长达 1400 个时间单位,直接存数组也不一定会爆内存,但如果你想存十几组码,还是要注意空间。
我采用的方法是:先用 RMT 抓出完整时间数组,再分析其中的重复规律。很多空调协议会把一帧分成“引导码 + 头码 + 数据 + 尾码”,数据部分又是按位组织的。如果你能总结出规律,可以写一个编码函数,用几十字节压缩信息生成完整波形。
但对于个人 DIY 项目,我反而不建议过度压缩。ESP32-C3 的 Flash 空间足够,直接定义多个uint16_t数组存脉冲时间,一个码顶多占 2KB,存十几组码也就 20KB 左右。真正需要关心的是载波频率,不同空调可能使用 38kHz 或 40kHz。抓码时要知道你用的接收头中心频率是多少,发送时配置 RMT 的载波频率与空调匹配。
4. 固件逻辑:从语音词条到红外动作的完整链路
4.1 固件功能框架
固件逻辑可以拆成三个部分:语音接收层、指令解析层、红外发射层。语音接收层从串口读取 SU03T 的识别结果;指令解析层通过识别结果映射到具体动作;红外发射层把动作对应的红外码通过 RMT 发送出去。
我写的程序骨架大致如下:
void setup() { Serial.begin(9600); // 与 SU03T 通信 pinMode(IR_LED_PIN, OUTPUT); rmtInit(); // 初始化红外发送通道 } void loop() { if (Serial.available()) { String cmd = Serial.readStringUntil('\n'); cmd.trim(); int action = parseCommand(cmd); if (action >= 0) { sendACCode(action); // 发送对应动作的红外码 } } }这里有个关键点:SU03T 的串口输出格式是可以配置的,但你必须先弄清楚当前模块实际返回的格式。我配置时把返回格式简化为十进制数字,“打开空调”对应 1,“关闭空调”对应 2,这样解析逻辑最简单,不会因为字符串匹配出错。
4.2 SU03T 自定义词条配置细节
SU03T 的词条配置必须通过配套的上位机软件完成。你需要在软件里新建产品、添加词条、设置唤醒词、配置串口输出格式,然后重新烧录模块固件。
我实际配置的词条列表如下:
- 唤醒词:小雅小雅
- 打开空调 -> 输出 1
- 关闭空调 -> 输出 2
- 制冷模式 -> 输出 3
- 制热模式 -> 输出 4
- 风速加大 -> 输出 5
- 风速减小 -> 输出 6
- 温度上调 -> 输出 7
- 温度下调 -> 输出 8
- 摆风开关 -> 输出 9
烧录完成后,先用 USB-TTL 模块单独测试 SU03T 的串口输出,确保每个词条都能触发相应数字输出,再接上 ESP32-C3,此时问题排查范围会小很多。
4.3 RMT 发送红外的实现要点
红外发送部分,我是用 ESP32-C3 的 RMT 外设实现的。RMT 可以精确生成高低电平切换时序,不需要 CPU 持续介入,这对于 38kHz 载波调制来说非常重要。如果用普通digitalWrite加delayMicroseconds去软件模拟,时序很容易出现抖动,空调可能不认。
配置 RMT 发送时,设置载波频率为 38kHz,然后把之前抓到的脉冲时间数组写入发送缓冲区,启动发送即可。RMT 支持一次发送多段数据,正好对应空调红外帧中的引导码、数据段、尾码。
另外,空调码通常需要“重复发送”。原厂遥控器在按住按键时会持续重复同一帧,有些空调对帧间间隔非常敏感。抓码时如果能抓到遥控器连续发帧的间隔,就把它一并记录。发送时按相同间隔重复多次,成功率最高。
5. 调试与避坑:串口乱码、红外距离短、程序不稳
5.1 SU03T 串口输出乱码排查链路
如果你发现 ESP32-C3 收到的数据不是预期数字,按下面这个顺序排查,能省掉大量时间:
- 单独给 SU03T 供电,用 USB-TTL 转接模块接它的 TX,打开串口助手观察识别结果。这一步能排除 ESP32-C3 程序的问题。
- 检查波特率是否一致。SU03T 默认 9600,如果 ESP32-C3 的
Serial.begin写成了 115200,数据必乱。 - 检查共地是否做好。两个模块之间必须共地,这是最容易被忽略的一步。
- 检查 RX/TX 有无交叉接反。SU03T 的 TX 接 ESP32-C3 的 RX,反了也收不到数据。
- 检查 SU03T 的触发模式。有些固件默认是按键触发,不是一直监听语音,必须在上位机里改成语音唤醒模式。
第 5 条最容易踩。我第一次用 SU03T,默认固件需要按压模块上的按键才会开始识别,不是上电就处于监听状态。如果你唤醒后听不到提示音,先查触发模式再查硬件连接。
5.2 红外距离近:从驱动和载波频率两个方向查
红外发射距离短,通常有三个原因。
第一,驱动电流不足。如果限流电阻选得太大,比如 100Ω 以上,红外发射管的电流可能只有十几毫安,发射功率很低。我实测把限流电阻从 100Ω 降到 33Ω(在发射管允许的峰值电流范围内),遥控距离明显提升。
第二,载波频率错位。空调接收头对载波频率敏感,部分品牌用 38kHz,部分用 40kHz。如果你发送时配置的载波频率和空调实际使用的频率不一致,接收灵敏度会急剧下降。解决方法是发送前确认抓码接收头的中心频率,并让 RMT 发送配置与之匹配。
第三,发射管角度和朝向。红外发射管有较强的方向性,哪边对着空调室内机的接收窗口,哪边效果最好。安装时尽量把发射管朝向空调接收窗,或者使用两颗红外发射管并联来扩大覆盖角度。
提示:如果把两颗红外发射管并联,建议把 NPN 三极管的基极驱动电流适当加大,让集电极电流达到 80mA 以上,保证每颗管子都能获得足够的驱动电流。
5.3 程序不稳定与内存问题
ESP32-C3 的 RMT 外设发送几百个脉冲没有压力,真正的问题是如果一直在循环里动态分配发送缓冲区,时间一长会出现内存碎片。我的做法是在setup里初始化发送缓冲区和 RMT 通道,运行时只更新数据,不反复创建任务。
另外,ESP32-C3 本身带蓝牙,如果同时开启 BLE,串口中断和 RMT 定时器偶尔会发生微秒级冲突。在语音遥控空调这个项目里完全用不到 BLE,所以我直接在配置里关闭蓝牙功能,把 CPU 中断负担降到最低。
我还给系统加了一颗状态指示灯,SU03T 识别到词条后,ESP32-C3 会点亮 LED 表示收到了指令,红外发射后再灭掉。这样调试时不用盯着串口看,用眼睛余光就能判断链路是否通畅。
6. 进阶改造:模块化码表与多台空调适配
6.1 让一套硬件控制多台空调
如果家里不止一台空调,你可以让一套硬件控制多台设备。思路是把红外码按照品牌和型号组织成二维结构,用 SU03T 的词条切换当前目标设备。
在 SU03T 里增加“客厅空调”“卧室空调”两个设备选择词条。ESP32-C3 收到设备选择指令后,把内部变量currentDevice设置成对应设备号。后续用户说“打开空调”时,主控根据currentDevice选择合适的红外码数组发送。这样新增一台空调,只需要抓取它的遥控码,再增加一组码表和一个设备词条。
6.2 本地优先,延续扩展能力
这套方案最大的优势是本地优先。从语音识别到红外发射,全程不依赖任何云服务。你可以把整个系统做成一个独立小盒子,放在空调附近,只接电源就可以运行。不会因为厂商服务器维护导致设备失效,也不会把家里的使用习惯数据上传出去。
如果后续想进一步扩展,可以在 ESP32-C3 上挂一颗温湿度传感器,让空调根据室内温度和用户语音指令自动调节模式。语音指令作为最高优先级的手动干预,传感器数据作为自动调节依据,本质上是从“遥控器”升级为“控制器”。
6.3 温度调节与多段按键实现的技巧
空调遥控器上的温度调节,通常只有“升温”和“降温”两个按键。想实现“调到 25 度”这样的语音指令,有两个思路。第一种是抓取 16 到 30 度之间每一档温度的完整红外码,语音识别后直接发射对应的那一组;第二种是模拟多次按键,识别到“调到 25 度”后,如果当前是 28 度,就连续发送三次降温键。
我实际选择的是第二种,原因是抓 15 组完整温度码工作量太大,而模拟多次按键操作实现简单。不过要注意,连续发送多个按键码后,空调可能需要一点处理时间,如果发送间隔太短,可能会漏掉一两度。我的做法是在每组按键码之间加 200ms 左右的延时,发送结束后再让 SU03T 播报一句“温度已调整”,给用户明确反馈。
收个尾吧。这套 ESP32-C3 + SU03T + NPN 红外的方案,我从立项到跑通用了大概三个晚上。最难的不是焊接,也不是写代码,而是理解空调红外协议的多变性,以及 SU03T 串口配置和主控程序之间必须建立明确约定。如果你也要复现,我强烈建议把所有红外码全部抓完、整理好之后,再开始写发送逻辑。顺序一旦颠倒,后面很容易陷入“改代码烧录-测试-再改再烧录”的低效循环。
最后分享一个小技巧:调试红外码的时候,最烦人的是每次改完发送参数都要重新插拔 USB 烧录。我的做法是在 ESP32-C3 上留一个 GPIO 按键作为“调试模式开关”,按下后进入循环发送同一帧红外码,省去反复烧录的麻烦。这样整个项目虽然不大,但调试体验会舒服很多。