简介
家里一台美的挂机,原配遥控器 R06E/BGE 不知道塞到哪儿去了,本想用 ESP8266/ESP32 凑一个 Wi-Fi 遥控,后来翻出一块吃灰的 Seeeduino Wio Terminal——它本身就带红外发射器、320×240 LCD、五向摇杆和三个按键,相当于一个自带屏幕和按键的开发板。需求是:插上电就能用,不需要电脑、不需要网络、不需要额外红外接收器,按几下就能把空调打开发到 26°C 制冷。
最初打算只做 COOLIX 协议(兼容面更广,开源库支持 Beko / 美的 / 东芝等品牌),但 R06E/BGE 这个具体型号在 IRremoteESP8266 的兼容列表里没有出现,所以又加了一套 MIDEA 协议做兜底,开机时长按 C 两秒可以在两个协议之间切换。最终代码全部塞在一个 midea_remote.ino 里,加一个协议头文件 ac_protocol.h,依赖只有 Seeed_Arduino_IR 和 Seeed 开发板包自带的 Seeed_Arduino_LCD(屏幕实际用的是 TFT_eSPI,随板包安装)。直接用 Arduino IDE 打开上传即可。
下面记录整体架构、协议实现、屏幕和按键、自检与串口辅助命令,以及实际试机的注意事项。
整体架构
整个工程分四块:
模块位置职责
| ACSender 类 | midea_remote.ino | 继承 IRsendBase,按协议组织 38kHz 调制的 mark/space 序列 |
| ac_protocol.h | 头文件 | 定义状态结构 ACSettings、COOLIX / MIDEA 编码函数、协议自检 |
| 按键扫描与状态机 | midea_remote.ino | 5 向摇杆 + A/B/C 三键的去抖、长按识别、模式/风速/温度编辑 |
| 屏幕与串口 | midea_remote.ino | 离屏 sprite 绘制、串口 T/S 命令(自检结果、屏幕导出) |
数据流非常直接:按键产生状态变更 → settings 更新并把 dirty 置位 → 主循环每 50ms 重绘屏幕 → 用户按下摇杆时调用 sendSettings(),由 ACSender 输出对应的红外波形。整机的状态变量就一个 ACSettings:
struct ACSettings { bool power=false; uint8_t temp=26,mode=0,fan=0; };mode 取值 0–4 对应 COOL/DRY/AUTO/HEAT/FAN,fan 取值 0–3 对应 AUTO/LOW/MEDIUM/HIGH。power 表示待发送的开/关状态,不是空调的实际状态——这一点 README 里专门做了说明,避免误解。
协议实现
COOLIX(24 位)
COOLIX 的一帧是 24 位,前缀 0xB2 是品牌特征。每一个数据字节后面紧跟它的反码,所以底层会按"字节 + ~字节"成对发送。关空调有专用指令 0xB27BE0,不靠 power 位生成。
uint32_t coolixCode(const ACSettings& s){
if(!s.power)return 0xB27BE0;
const uint8_t temperatures[]={0,1,3,2,6,7,5,4,12,13,9,8,10,11};
const uint8_t fans[]={5,4,2,1};
uint8_t temp=s.mode==4?14:temperatures[s.temp-17];
uint8_t mode=s.mode==4?1:s.mode;
uint8_t fan=fans[s.fan];if(s.fan==0&&(s.mode==1||s.mode==2))fan=0;
return 0xB20000UL|((uint32_t)((fan<<5)|31)<<8)|(temp<<4)|(mode<<2);
}这里有几个非显然的细节:
温度码表是错位排列的,例如 17°C 是 0b0000、19°C 是 0b0011、25°C 是 0b1100,与直觉相反。这是因为 COOLIX 的温度用了格雷风格的编码。
风扇码表也不连续,AUTO/LOW/MEDIUM/HIGH 对应 5/4/2/1;而且在 DRY/AUTO 模式下强制把风扇拉成 0,否则机型可能不响应。
送风模式(FAN)用的是专用温度码 0b1110(即 14)和专用 mode 值 1,跟正常的 COOL/DRY/AUTO/HEAT 通路不一样。
红外波形按 IRremoteESP8266 的实测序列:头码 4692/4416,bit0=552/552,bit1=552/1656,结尾 552/5244,整帧发送两遍。代码里 byteOut 内部用 mark(pulse) + space(0/1) 拼位,对应 enableIROut(38) 的硬件 38kHz PWM。
MIDEA(48 位 + 校验)
MIDEA 是 48 位数据加 1 字节校验,发射时还要把整帧取反再发一次(即两次发射,一次正码、一次反码)。校验算法是:把第 1–5 字节(数据区)逐字节按位反转后求和,结果取反后再反转一次,存到 byte 0:
uint8_t reverseByte(uint8_t b){uint8_t r=0;for(int i=0;i<8;i++){r=(r<<1)|(b&1);b>>=1;}return r;}
uint8_t mideaSum(uint64_t word){uint8_t sum=0;for(int i=1;i<=5;i++)sum+=reverseByte(word>>(i*8));return reverseByte((uint8_t)(0-sum));}数据布局(高位在前):
uint64_t word=(0xA1ULL<<40) // 前缀 0xA1 |((uint64_t)control<<32) // power/fan/mode |((uint64_t)(0x40+s.temp-17)<<24) // 温度偏移 17→0x40 |0xFFFF00ULL; // 固定填充 return word|mideaSum(word);
红外波形:头码 4480/4480,bit0=560/560,bit1=560/1680,结尾 560/5440,然后立刻把 48 位取反 (~data) 再发一遍相同结构——这部分在 ACSender::transmit 里用 data=~data 完成,不需要重新计算校验。
if(midea){
uint64_t data=mideaCode(settings);
for(int frame=0;frame<2;frame++){
mark(4480);space(4480);
for(int shift=40;shift>=0;shift-=8)byteOut((uint8_t)(data>>shift),560,1680);
mark(560);space(5440);data=~data;
}
}协议自检
上电时跑一次 protocolSelfTest(),核对开源测试中的几个已知码字,只跑算法、不发射红外:
bool protocolSelfTest(){
ACSettings s;s.power=true;s.temp=25;s.mode=2;
if(coolixCode(s)!=0xB21FC8)return false; // COOLIX 25°C AUTO
s.power=false;if(coolixCode(s)!=0xB27BE0)return false; // 关指令
if(mideaSum(0xA1826FFFFF00ULL)!=0x62||mideaSum(0xA18177FFFF00ULL)!=0x70)return false;
s.power=true;s.temp=26;s.mode=0;if(coolixCode(s)!=0xB2BFD0)return false; // 26°C COOL
return true;
}屏幕底部会显示当前状态:Protocol check failed(橙色)/ Ready - press to send(灰)/ Signal sent / not confirmed(绿)。自检失败时 sendSettings() 直接 return,避免发出错误码把空调搞乱。
屏幕、按键与状态机
屏幕是 320×240 TFT(横屏)。TFT_eSprite 离屏缓冲是必须的——每帧直接画到屏幕会出现明显撕裂。初始化时尝试 16 位色深,分配失败就退回 8 位,再失败直接死机并在屏幕上提示 Display memory error,因为没有屏幕基本没法操作。
按键去抖 30ms,C 键单独识别长按 2 秒切换协议配置(只切配置,不发射):
if(stable[5]&&!cLong&&now-cDown>=2000){midea=!midea;cLong=true;changed=true;dirty=true;}摇杆、A、B、C 在 pins[] 里按上/下/按/左/右/A/B/C 顺序排好,扫描循环里把所有按键都视作下沿触发,但 C 键额外记录按下时刻用于长按判定。完整按键表:
操作功能
| 摇杆上 / 下 | 调高 / 调低温度,范围 17–30°C |
| B | 循环模式:COOL → DRY → AUTO → HEAT → FAN;切换时风速恢复 AUTO |
| C 短按 | 循环风速:AUTO → LOW → MEDIUM → HIGH |
| C 长按 2 秒 | COOLIX / MIDEA 协议切换,只切不发射 |
| 摇杆按下 | 立即发送当前屏幕上的全部设置 |
| A | 翻转 settings.power 并立即发送(开机/关机) |
| 摇杆右 | 显示操作帮助 |
| 摇杆左 | 返回主页 |
为了避免连按 A 时反复触发,发送后 500ms 内的 A 键会被忽略。
屏幕主界面我抓了一张图(screen-preview.png,通过 S 命令导出):

几个非显然的取舍:
"发送"和"切换 power"是绑在一起的:按 A 一定会发一次码,按下摇杆则用当前屏幕上的所有设置发一次码,包括 power 状态。所以"先按 A 开机,再调温度,再按摇杆"这条流程其实会在调温度后再发一次开机码,而不是只发温度。
信号发射没有任何反馈回路。屏幕只显示"Signal sent / not confirmed"——这是字面意思:调制信号已经出去了,但空调是否接收、是否动作,看不到。所以"是否开机"必须以空调本身的面板/声音/出风为准,不能凭屏幕判断。
屏幕文字"SET: ON / OFF"是待发状态,不是空调实时状态。
串口辅助命令
115200 串口连接打开后:
命令行为
| T | 打印 SELFTEST_OK 或 SELFTEST_FAILED + NO_IR_SENT,不发射红外 |
| S | 把当前屏幕以 RGB565 十六进制流(每像素 4 字符)逐行输出,每行后等待一个 K 字符作为流控 |
S 命令主要给远程抓图用。capture-screen.ps1 的工作流程是:先发 S、等 FRAME 320 240 头、按行读 1280 字符 RGB565、写回 K 继续下一行,最后用 System.Drawing 还原成 320×240 PNG:
$serial.Write('S')
do {$line=$serial.ReadLine().Trim()}while($line -ne 'FRAME 320 240')
for($y=0;$y -lt 240;$y++){
$rows[$y]=$serial.ReadLine().Trim()
if($rows[$y].Length -ne 1280){throw "Incomplete row $y ($($rows[$y].Length) characters)"}
$serial.Write('K')
}由于 K 必须等 Serial.available(),整套抓图大概要 4–5 秒,串口连着的时候用户能正常使用——scan() 一直在跑,draw() 由 dirty 触发,不依赖串口。
试机与验证
Wio Terminal 的红外发射管在背面,microSD 插槽左侧。第一次试机建议在 0.5–1 米、无遮挡的情况下,对着空调室内机的接收窗。屏幕默认 SET: OFF,按 A 触发"开机 + 当前设置"。如果没有响应:
换方向——很多挂机接收窗在右下角偏内侧。
拉远一点试试,部分美的机型接收灵敏度偏低。
长按 C 2 秒切到 MIDEA 配置,再按一次摇杆重发同一条指令。注意不要再按 A,否则会把待发状态翻成关机。
如果两种协议都没有响应,下一步需要用 38kHz 红外接收模块(如 VS1838B)连到 Arduino 或 USB 逻辑分析仪去抓原遥控器的码,再和本工程生成的 coolixCode / mideaCode 输出对照。Wio Terminal 自带的是发射器,没法直接学码。
发布前需要补充/确认:
本工程生成的红外码是否能让用户的 R06E/BGE 实际响应,取决于具体空调主控板的解码器。需要用红外接收器在空调旁抓原遥控器的码与本工程输出比对。
是否能控制制热、能否调整风速到指定档位,要等空调实机验证。
长距离(>3 米)发射的有效性未做统计。
踩坑与注意事项
1. 红外发射方向问题。 Wio Terminal 板子正面是 LCD,背面右上区域是 IR LED。如果竖着拿,板子背面朝空调时信号最强;正面(屏幕面)几乎收不到。这是最初很容易忽略的点。
2. 协议切换长按 C 不发射。 这是一个有意为之的设计:用户切换协议时如果同时触发发射,会让"试机"流程变得混乱(特别是 power 状态会被改)。所以长按 C 只改 midea 标志,不调 sendSettings()。
3. 协议兼容列表不能保证 R06E/BGE 可用。 COOLIX 兼容列表里有 Beko、Comfee、Tokio、Airwell、Kastron、Kaysun、Toshiba、Bosch、Fisher 等品牌下的若干型号;MIDEA 兼容列表里有 Pioneer、Comfee、Kaysun、Keystone、MrCool、Danby、Trotec、Lennox 等若干型号。美的 RG52D/BGE 在 COOLIX 列表里,但 R06E/BGE 两个都没列出来。 美的内部 OEM 体系比较杂,同一系列的不同挂机可能用不同子协议,所以本工程只能作为"试一下"的程度,承诺不了所有美的空调都能用。
4. 没有回读机制。 因为没接红外接收器,屏幕不知道空调当前是开是关、温度是多少。power 只是"上次按 A 时的待发状态"。一旦用原遥控器改了空调,Wio Terminal 这边的屏幕就和实际不同步了——重启会恢复默认配置。
5. 当前没做的功能。 摆风(swing)、定时、睡眠、遥控码学习都没实现。COOLIX48(带 timer 的扩展协议)也没做,所以"定时开机"这种需求用不了。
6. 内存分配失败的兜底。 screen.createSprite(320,240) 默认 16 位色深会要约 150KB RAM,Wio Terminal 的 SAMD51 有 192KB 总 RAM,扣掉其他变量和栈后接近极限。如果同时还做了其他占用内存的事情,sprite 会创建失败——所以加了 8 位色深的二次降级,再失败就在屏幕打 Display memory error 死循环。
7. 串口 S 命令不要在试机时打开。 抓图会暂停循环约 4–5 秒,期间按键扫描不更新可能造成"按键没响应"的错觉。
总结
本工程在 Wio Terminal 上实现了一个脱离电脑/网络的红外空调遥控器:
已实现:COOLIX(24 位)和 MIDEA(48 位)两套协议,模式/风速/温度编辑,A/B/C 与五向摇杆交互,离屏 sprite 屏幕渲染,协议编码自检,串口抓图。
已验证(代码层面):上电时 protocolSelfTest() 跑过的 4 个测试码字全部命中、COOLIX 关空调专用指令 0xB27BE0、MIDEA 校验算法匹配 0xA1826FFFFF00 → 0x62 与 0xA18177FFFF00 → 0x70、coolixCode 在 25°C/AUTO 和 26°C/COOL 两种状态下输出 0xB21FC8 和 0xB2BFD0,与 IRremoteESP8266 的单元测试一致。
待实机验证:在用户的 R06E/BGE 挂机上能否真正控制、距离有效范围、制热是否可用、风速档位实际响应。
合理后续:加 38kHz 红外接收器 + 一个"学习"模式,把原遥控器的码抓下来直接回放;加入 COOLIX48 协议以支持定时;增加掉电保存(EEPROM 或 Preferences)。
我要赚赏金
