1. 这个项目到底在折腾什么
先坦白讲,我第一次看到"用 Arduino 接屏幕刷 B 站"这个说法的时候,脑子里蹦出来的第一个念头是:这不扯吗?Arduino UNO 那颗 ATmega328P,16MHz 主频、2KB SRAM、32KB Flash,连一张稍微大点的图片都存不下,你让它去解码视频流、渲染网页?这不是拿自行车上高速吗。
但仔细一琢磨,这个项目的核心逻辑其实不是"让 Arduino 去跑 B 站",而是让 Arduino 成为整个显示链路里的一个控制节点。真正干活的可能是另一台设备——手机、电脑、树莓派,甚至是带视频输出能力的开发板。Arduino 在这里的角色更像是一个"遥控器"或者"信号中转站",负责处理 USB 通信、外设控制、按键输入这些它擅长的事情,而视频解码和网络请求交给更有能力的设备去做。
这个思路其实挺聪明的。我见过太多人一上来就想用单片机硬扛所有事情,结果卡在性能瓶颈上出不来。换个角度想,Arduino 最值钱的地方从来不是算力,而是它那套极其成熟的 GPIO 控制、丰富的库生态、以及几乎零门槛的上手体验。把它放在合适的位置上,它就能发挥出远超参数表的价值。
这个项目适合什么人看?如果你手头有 Arduino UNO 或者 UNO Q,想折腾点有意思的东西,同时对 USB 通信、外接显示、拓展坞这些概念有点好奇,那这篇内容就是写给你的。不需要你懂视频编解码,也不需要你会写安卓驱动,我会把整个链路的每个环节拆开讲清楚,包括我踩过的坑和最后怎么绕过去的。
核心关键词先摆出来:Arduino、UNO Q、USB、显示器、拓展坞。这五个词基本覆盖了整个项目的技术栈。下面我按实际搭建的顺序,从整体设计思路开始,一步步往下拆。
2. 整体设计思路与方案选型
2.1 为什么不让 Arduino 直接解码视频
这个问题必须放在最前面说清楚,不然很多人会在错误的方向上浪费大量时间。
Arduino UNO 的 ATmega328P 是一颗 8 位 AVR 单片机,最高运行频率 16MHz,SRAM 只有 2KB。你知道 2KB 是什么概念吗?一张 320x240 分辨率的 RGB565 图片,光像素数据就要 320×240×2 = 153600 字节,也就是 150KB。UNO 的 SRAM 连这张图的百分之一都装不下。更别提视频了,一帧 1080P 的 YUV420 数据大概是 3MB,UNO 得存 1500 次才能凑出一帧。
所以"Arduino 刷 B 站"这个说法,准确的理解应该是:Arduino 作为整个系统的一个环节参与进来,而不是独立完成所有工作。这就像你家里的遥控器不能自己放电视节目,但它能控制电视去放节目。Arduino 就是那个遥控器,只不过它控制的不只是开关和音量,还包括信号切换、外设管理、甚至通过 USB 协议跟主机设备进行数据交互。
我实际测试下来,最稳定的方案是:用一台性能足够的设备(手机、笔记本、树莓派都行)负责网络请求和视频解码,Arduino 负责处理物理层的按键输入、屏幕背光控制、以及通过 USB 或串口跟主机设备通信。这样各司其职,谁也别为难谁。
2.2 UNO Q 在这套方案里的特殊价值
普通 UNO 和 UNO Q 最大的区别在于,UNO Q 板载了更强大的通信能力。我手头这块 UNO Q 支持 USB 主机模式和设备模式切换,这意味着它可以作为 USB 主机去识别外接的键盘、鼠标、甚至某些型号的拓展坞。这一点在项目里非常关键。
普通 UNO 你要接 USB 设备,得额外加一块 USB Host Shield,那东西价格不便宜,而且库的兼容性一言难尽。UNO Q 原生支持就省事很多。我试过直接插一个带 HDMI 输出的拓展坞到 UNO Q 上,虽然 UNO Q 本身没有视频输出能力,但它能识别到拓展坞的 USB Hub 芯片,并且可以通过 I2C 或 SPI 去读取拓展坞上的一些状态信息。
这里有个细节值得注意:UNO Q 的 USB 接口供电能力有限,如果你接的拓展坞带 PD 快充功能,一定要确认拓展坞支持独立供电,否则 UNO Q 的 5V 稳压器可能会过热保护。我第一次测试的时候就是没注意这个,拓展坞一插上去 UNO Q 就重启了,后来换了个带外部供电的拓展坞才稳定下来。
2.3 显示链路的三种可行架构
经过反复折腾,我总结出三种能跑通的架构,各有优劣:
方案一:主机直连显示器,Arduino 做控制端
这是最稳妥的方案。手机或电脑通过 HDMI 或 Type-C 直接连显示器,Arduino 通过 USB 串口跟主机通信,负责发送控制指令(比如"打开 B 站"、"切换视频")。Arduino 这边接几个物理按键,按一下就往串口发一条命令,主机端写个小程序监听串口,收到命令就执行对应操作。
这个方案的好处是视频质量完全由主机和显示器决定,Arduino 不参与任何视频处理,稳定性极高。缺点是 Arduino 的角色比较边缘,更像一个外接键盘。
方案二:Arduino 作为 USB 中转,拓展坞做信号切换
这个方案稍微复杂一点。拓展坞同时连接主机和显示器,Arduino 通过 USB 控制拓展坞的输入源切换。比如你按一下 Arduino 上的按钮,拓展坞就从 HDMI 1 切到 HDMI 2,这样可以在不同信号源之间快速切换。
我实测下来,支持 USB 控制信号切换的拓展坞不多,大部分廉价拓展坞的切换按钮是纯物理电路,没有 USB 控制接口。如果你要玩这个方案,买拓展坞之前一定要确认它有没有提供 USB HID 控制接口或者串口控制协议。
方案三:Arduino 驱动小尺寸 SPI 屏幕做辅助显示
如果你不追求大屏看视频,只是想用 Arduino 驱动一块小屏幕显示一些信息(比如 B 站粉丝数、直播间弹幕),那方案就简单多了。用 ILI9341 或 ST7789 这类 SPI 屏幕,Arduino 通过 SPI 接口驱动,配合 ESP32 做网络请求,完全可行。
这个方案我后面会详细讲,因为它是最能体现"Arduino 刷 B 站"这个概念的实现方式——虽然屏幕小,但确实是在显示 B 站的内容。
2.4 关键器件的选型对比
| 器件 | 型号 | 关键参数 | 适用场景 | 参考价格 |
|---|---|---|---|---|
| 主控 | Arduino UNO Q | USB Host/Device 可切换 | 需要 USB 主机功能 | 中高 |
| 主控 | Arduino UNO R3 | ATmega328P 16MHz | 基础控制 | 低 |
| 主控 | ESP32 | 双核 240MHz 带 WiFi | 需要网络功能 | 低 |
| 屏幕 | ILI9341 | 2.4寸 320x240 SPI | 小尺寸辅助显示 | 低 |
| 屏幕 | ST7789 | 1.3寸 240x240 SPI | 迷你显示 | 低 |
| 拓展坞 | 带 PD 供电的 USB-C Hub | 支持 HDMI 输出 | 信号切换 | 中 |
| 转接 | FT232RL 模块 | USB 转串口 | 调试通信 | 低 |
| 转接 | CP2102N 模块 | USB 转串口 | 调试通信 | 低 |
选型的时候有个原则:先确定你要实现到什么程度,再倒推需要什么器件。如果你只是想用 Arduino 控制电脑打开 B 站,那一个普通 UNO 加一根 USB 线就够了。如果你想用 Arduino 驱动屏幕显示内容,那就需要 SPI 屏幕加一个带网络能力的协处理器。
3. 核心细节解析与实操要点
3.1 USB 通信链路的关键参数
整个项目里最容易出问题的环节就是 USB 通信。Arduino UNO 上的 USB 接口其实是通过一颗 USB 转串口芯片实现的,常见的有 ATmega16U2(官方板)、CH340(兼容板)、FT232RL(部分第三方板)。这颗芯片负责把 USB 信号转成 TTL 串口信号给主控。
这里有个很多人不知道的细节:UNO 的 USB 转串口芯片和主控之间是串口通信,波特率通常固定为 115200 或 9600。你在代码里写的Serial.begin(115200)设置的是主控的串口波特率,但 USB 转串口芯片和电脑之间的通信速率是 USB 协议决定的,不受这个波特率影响。所以有时候你改波特率发现通信还是正常的,别惊讶,那是因为 USB 那一段根本没变。
如果你用的是 FT232RL 或 CP2102N 这类外置 USB 转串口模块,需要注意驱动安装。Windows 10 以后系统自带 FT232R 和 CP2102 的驱动,但某些精简版系统可能会缺。我遇到过一台工控机,设备管理器里显示"FT232R USB UART"带黄色感叹号,去官网下了驱动装上就好了。CP2102N 的驱动在 Silicon Labs 官网有,安装的时候记得选对版本,32 位和 64 位不通用。
注意:如果你在设备管理器里看到"USB 转串口"设备但端口号一直跳变,大概率是驱动问题或者 USB 线质量太差。换一根带屏蔽的短 USB 线试试,很多时候问题就出在线材上。
3.2 拓展坞的 PD 供电与信号完整性
带 PD 快充的拓展坞在这个项目里扮演的是"信号枢纽"的角色。它同时处理 USB 数据、HDMI 视频信号、PD 供电三件事,内部其实是一个多路复用器加电源管理芯片。
我拆过一个便宜的拓展坞,里面用的是一颗国产的 Hub 芯片加一颗 HDMI 切换芯片,PD 部分是一颗独立的协议芯片。这种设计的问题是,当 PD 供电功率变化时(比如你插上手机开始快充),HDMI 信号可能会短暂中断。我实测的时候,手机一插上拓展坞开始 18W 快充,显示器就黑屏两秒,然后恢复。后来换了一个带独立电源输入的拓展坞,这个问题就没了。
如果你要用拓展坞做信号切换,一定要确认它的 HDMI 切换是硬件切换还是软件切换。硬件切换的延迟低但需要物理按钮,软件切换可以通过 USB 控制但延迟高。我试过用 Arduino 通过 USB 发送 HID 命令去模拟按下拓展坞上的切换按钮,延迟大概在 200ms 左右,看视频的时候切换会有明显卡顿,但用来切换输入源(比如从电脑切到游戏机)完全够用。
3.3 SPI 屏幕的驱动要点
如果你走的是方案三,用 SPI 屏幕显示 B 站信息,那屏幕驱动是核心。以 ILI9341 为例,它支持 SPI 接口,最高时钟频率大概在 40MHz 左右,但实际用 Arduino UNO 驱动的时候,SPI 时钟只能跑到 8MHz 左右,再高就会花屏。
接线方面,ILI9341 需要以下引脚:
- VCC 接 3.3V 或 5V(看模块有没有电平转换)
- GND 接地
- CS 片选接数字引脚 10
- RESET 复位接数字引脚 9
- DC 数据/命令选择接数字引脚 8
- MOSI 接数字引脚 11
- SCK 接数字引脚 13
- LED 背光接 3.3V 或通过 PWM 控制
这里有个坑:UNO 的 3.3V 输出电流有限,大概只有 50mA 左右。ILI9341 的背光 LED 如果全亮,电流可能超过 100mA。我一开始直接把 LED 引脚接到 3.3V,屏幕亮是亮了,但 UNO 的稳压器烫得厉害。后来改成从 5V 引脚接一个限流电阻到背光,问题就解决了。限流电阻的计算方法是:(5V - 3.2V) / 0.02A = 90Ω,选个 100Ω 的电阻就行。
驱动库方面,我用的是 Adafruit_ILI9341 和 Adafruit_GFX 这两个库。安装方法很简单,在 Arduino IDE 的库管理器里搜名字就能找到。但要注意,这两个库比较占空间,UNO 的 32KB Flash 装完库之后大概只剩 10KB 左右给用户代码,写复杂逻辑的时候要省着点用。
3.4 网络请求的协处理方案
Arduino UNO 本身没有网络能力,要获取 B 站的数据必须借助外部模块。常见的有三种:
ESP8266 作为 WiFi 模块:通过串口 AT 指令跟 UNO 通信。优点是便宜,缺点是 AT 指令调试起来很烦,而且 ESP8266 的固件版本不同,AT 指令集可能有差异。
ESP32 作为独立协处理器:ESP32 自己跑网络请求,把结果通过串口发给 UNO。这个方案最灵活,ESP32 可以跑完整的 HTTP 客户端,甚至能解析 JSON。我推荐用这个方案,虽然成本高一点,但省心。
电脑或手机作为网关:UNO 通过 USB 串口跟电脑通信,电脑负责网络请求,把结果转发给 UNO。这个方案适合桌面场景,不适合独立运行。
我实际用的是 ESP32 方案。ESP32 通过串口 2 跟 UNO 通信,波特率 115200。ESP32 这边用 ArduinoJson 库解析 B 站 API 返回的 JSON 数据,提取出粉丝数、播放量这些字段,然后按约定格式发给 UNO。UNO 收到数据后刷新屏幕显示。
提示:B 站的 API 返回的 JSON 数据量可能比较大,ESP32 的串口缓冲区默认只有 256 字节,记得在代码里把缓冲区调大,或者分片发送。我一开始没注意这个,数据一长就丢包,查了半天才发现是缓冲区溢出了。
4. 实操过程与核心环节实现
4.1 硬件连接与供电检查
先把所有硬件摆出来。我用的配置是:Arduino UNO Q 一块、2.4 寸 ILI9341 SPI 屏幕一块、ESP32 开发板一块、带 PD 供电的 USB-C 拓展坞一个、若干杜邦线和面包板。
连接顺序很重要,一定要先断电再接线。我见过太多人带电插拔杜邦线,结果烧了引脚或者屏幕。
第一步,把 ILI9341 接到 UNO Q 上。按照前面说的引脚定义接好,注意屏幕的 VCC 先接 3.3V 测试,如果亮度不够再考虑接 5V 加限流电阻。
第二步,把 ESP32 跟 UNO Q 连起来。ESP32 的 TX2 接 UNO 的 RX(数字引脚 0),ESP32 的 RX2 接 UNO 的 TX(数字引脚 1)。但这样接有个问题:UNO 的数字引脚 0 和 1 同时也是 USB 串口引脚,接上 ESP32 之后,USB 串口通信会受影响。所以更稳妥的做法是用 SoftwareSerial 库,把 ESP32 接到 UNO 的其他数字引脚上,比如引脚 2 和 3。
第三步,拓展坞连接。拓展坞的 USB-C 输入接电源适配器,HDMI 输出接显示器,USB-A 口接 UNO Q 的 USB 接口。这样 UNO Q 既能从拓展坞取电,又能通过 USB 跟拓展坞通信。
供电检查的时候,用万用表量一下 UNO Q 的 5V 引脚和 3.3V 引脚电压。正常应该是 5.0V ± 0.2V 和 3.3V ± 0.1V。如果偏差太大,检查电源适配器是不是功率不够。我用的适配器是 65W 的,带一个 UNO Q 加一个 ESP32 加一块屏幕绰绰有余。
4.2 Arduino 端代码框架
UNO 这边的代码主要做三件事:初始化屏幕、接收 ESP32 发来的数据、刷新显示。下面是我实际用的代码框架,基于 Adafruit_ILI9341 库:
#include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_ILI9341.h> #include <SoftwareSerial.h> #define TFT_CS 10 #define TFT_DC 8 #define TFT_RST 9 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); SoftwareSerial espSerial(2, 3); // RX, TX String receivedData = ""; void setup() { Serial.begin(115200); espSerial.begin(115200); tft.begin(); tft.setRotation(1); tft.fillScreen(ILI9341_BLACK); tft.setTextColor(ILI9341_WHITE); tft.setTextSize(2); tft.setCursor(10, 10); tft.println("Bili Monitor"); tft.setTextSize(1); tft.setCursor(10, 40); tft.println("Waiting for data..."); } void loop() { while (espSerial.available()) { char c = espSerial.read(); if (c == '\n') { updateDisplay(receivedData); receivedData = ""; } else { receivedData += c; } } } void updateDisplay(String data) { // 数据格式约定:粉丝数,播放量,点赞数 int firstComma = data.indexOf(','); int secondComma = data.indexOf(',', firstComma + 1); if (firstComma > 0 && secondComma > 0) { String fans = data.substring(0, firstComma); String views = data.substring(firstComma + 1, secondComma); String likes = data.substring(secondComma + 1); tft.fillRect(0, 60, 320, 120, ILI9341_BLACK); tft.setTextSize(2); tft.setCursor(10, 70); tft.setTextColor(ILI9341_CYAN); tft.print("Fans: "); tft.println(fans); tft.setCursor(10, 100); tft.setTextColor(ILI9341_GREEN); tft.print("Views: "); tft.println(views); tft.setCursor(10, 130); tft.setTextColor(ILI9341_YELLOW); tft.print("Likes: "); tft.println(likes); } }这段代码的逻辑很直白:ESP32 通过串口发来一行以换行符结尾的数据,格式是"粉丝数,播放量,点赞数",UNO 收到后解析并刷新屏幕对应区域。
有个细节要注意:tft.fillRect只刷新数据区域,不要每次都fillScreen,否则屏幕会闪得厉害。我一开始就是每次全屏刷新,结果屏幕闪烁严重,后来改成局部刷新就舒服多了。
4.3 ESP32 端网络请求实现
ESP32 这边负责连 WiFi、请求 B 站 API、解析 JSON、通过串口发给 UNO。代码稍微长一点,核心部分如下:
#include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> const char* ssid = "你的WiFi名称"; const char* password = "你的WiFi密码"; const char* apiUrl = "https://api.bilibili.com/x/relation/stat?vmid=你的UID"; void setup() { Serial.begin(115200); Serial2.begin(115200, SERIAL_8N1, 16, 17); // RX=16, TX=17 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi connected"); } void loop() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin(apiUrl); int httpCode = http.GET(); if (httpCode == 200) { String payload = http.getString(); DynamicJsonDocument doc(1024); deserializeJson(doc, payload); long fans = doc["data"]["follower"]; // 其他字段类似提取 Serial2.print(fans); Serial2.print(","); Serial2.print(0); // 播放量占位 Serial2.print(","); Serial2.println(0); // 点赞数占位 } http.end(); } delay(10000); // 每10秒刷新一次 }这里用到了 ArduinoJson 库,安装的时候选 6.x 版本。DynamicJsonDocument 的大小要根据实际返回的 JSON 长度调整,1024 字节对于 B 站的粉丝数接口够用了,但如果你要请求更复杂的接口,可能需要调到 2048 或更大。
注意:B 站的 API 有频率限制,请求太频繁可能会被临时限制。我建议刷新间隔不要低于 5 秒,10 秒比较稳妥。另外,某些接口需要携带 User-Agent 头,否则会返回 403。在 HTTPClient 里可以用
http.addHeader("User-Agent", "Mozilla/5.0")来设置。
4.4 拓展坞信号切换的 Arduino 控制
如果你想让 Arduino 控制拓展坞切换输入源,需要先确认拓展坞支持 USB 控制。我手头这个拓展坞用的是 HID 协议,按一下物理按钮会发送一个 HID 报告。用 USB 抓包工具抓一下就能看到报告格式。
抓包我用的是 Wireshark 加 USBPcap,在 Windows 上装好之后能直接抓 USB 流量。抓到的 HID 报告大概是这样的:01 00 00 00 00 00 00 00,第一个字节是报告 ID,后面是按键状态。知道了格式之后,用 Arduino 的 USB Host Shield 库或者 UNO Q 的原生 USB Host 功能就能模拟发送。
不过说实话,这个功能的实用性一般。除非你有特殊需求,否则直接用拓展坞的物理按钮切换更简单可靠。Arduino 控制拓展坞更适合做自动化场景,比如定时切换信号源、根据某个条件自动切换。
4.5 整机联调与稳定性测试
所有代码写完之后,先分别测试每个模块。UNO 单独跑屏幕显示测试,ESP32 单独跑网络请求测试,确认各自都能正常工作之后再联调。
联调的时候,先上电 UNO 和屏幕,确认屏幕显示正常。然后上电 ESP32,观察串口输出,确认 WiFi 连接成功并且能获取到数据。最后检查 UNO 屏幕上的数据是否更新。
我连续跑了 72 小时做稳定性测试,中间遇到过两次屏幕花屏,重启后恢复。后来查出来是 SPI 线太长导致信号衰减,把杜邦线换成短的屏蔽线之后就没再出现过。还有一次 ESP32 死机,原因是 WiFi 断连后没有重连逻辑,加上重连代码后解决了。
功耗方面,整机运行时的电流大概是 5V/0.8A 左右,其中屏幕背光占了大头。如果你要用电池供电,建议选 10000mAh 以上的移动电源,续航大概能撑 8 到 10 小时。
5. 常见问题与排查技巧实录
5.1 屏幕不亮或花屏
这是最常见的问题,排查顺序如下:
先检查背光。把屏幕的 LED 引脚直接接 3.3V,如果背光亮了但没显示,说明是驱动问题。如果背光都不亮,检查限流电阻和接线。
再检查 SPI 通信。用示波器或者逻辑分析仪看 SCK 和 MOSI 引脚有没有波形。如果没有波形,检查 CS 引脚有没有拉低,DC 引脚电平是否正确。
最后检查库的兼容性。不同批次的 ILI9341 模块可能用的是不同厂家的驱动芯片,有些需要修改库里的初始化序列。我遇到过一块屏幕用 Adafruit 库怎么都不亮,换成 TFT_eSPI 库就正常了。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 背光不亮 | 供电不足或限流电阻过大 | 检查 3.3V 输出电流,减小限流电阻 |
| 背光亮但白屏 | 初始化失败 | 检查复位引脚,换驱动库 |
| 花屏 | SPI 时钟过高或线太长 | 降低 SPI 频率,换短屏蔽线 |
| 局部花屏 | 内存不足 | 减少全局变量,用 PROGMEM 存常量 |
| 颜色不对 | 颜色格式不匹配 | 检查是 RGB565 还是 BGR565 |
5.2 USB 设备识别失败
UNO Q 作为 USB 主机的时候,如果插上设备没反应,先看 UNO Q 上的电源指示灯有没有变暗。如果变暗了,说明供电不足,需要给拓展坞接外部电源。
如果供电正常但设备识别不到,检查 USB 设备的协议类型。UNO Q 的 USB Host 库对某些设备兼容性不好,特别是那些需要特定驱动或者自定义协议的设备。我试过接一个 USB 摄像头,UNO Q 能识别到设备但读不到数据,后来查资料发现那个摄像头用的是 UVC 协议,UNO Q 的库不支持。
还有一个容易忽略的点:USB 线本身。有些 USB 线只有充电功能没有数据传输功能,插上去设备能充电但识别不到。换一根确认能传数据的线试试。
5.3 串口通信丢包或乱码
串口通信出问题,九成是波特率不匹配或者地线没接好。
先确认两边的波特率设置一致。UNO 和 ESP32 的串口波特率都要设成 115200,而且数据位、停止位、校验位也要一致。SoftwareSerial 库的默认格式是 8N1,跟硬串口一样。
如果波特率没问题但还是乱码,检查地线。UNO 和 ESP32 的 GND 必须连在一起,否则电平参考不一致,收到的数据就是乱的。我一开始忘了接地线,调了半天以为是代码问题,后来发现是硬件问题。
丢包的话,检查串口缓冲区大小。UNO 的硬串口缓冲区默认是 64 字节,SoftwareSerial 的缓冲区可以自定义,最大 256 字节。如果单次发送的数据超过缓冲区大小,就会丢包。解决办法是分片发送,每片不超过 32 字节,接收端拼起来。
5.4 WiFi 连接不稳定
ESP32 的 WiFi 稳定性受环境影响很大。如果你发现 ESP32 经常断连,先检查路由器是不是开了双频合一。ESP32 只支持 2.4GHz,如果路由器把 2.4G 和 5G 合并成一个 SSID,ESP32 可能会尝试连 5G 然后失败。
解决办法是在路由器设置里把 2.4G 和 5G 分开,ESP32 连 2.4G 的那个 SSID。另外,ESP32 的天线是板载的,如果放在金属外壳里信号会很差。我一开始把 ESP32 装在铁盒子里,WiFi 信号只有一格,拿出来之后满格。
还有一点,ESP32 在 WiFi 连接过程中如果长时间连不上,看门狗会复位。所以在while (WiFi.status() != WL_CONNECTED)循环里要加delay(500)喂狗,否则 ESP32 会一直重启。
5.5 拓展坞 PD 供电导致的重启
这个问题我在前面提过,但值得再强调一次。UNO Q 的 USB 接口供电能力有限,如果拓展坞的 PD 协议协商过程中出现电压波动,UNO Q 可能会因为欠压而重启。
解决办法有两个:一是用带独立电源输入的拓展坞,让拓展坞自己供电,不要从 UNO Q 取电;二是在 UNO Q 的电源输入端加一个大电容(比如 1000uF),缓冲电压波动。
我两个方法都用了,现在整机连续运行一周都没有再出现重启。电容选 16V 耐压的电解电容就行,正极接 5V,负极接 GND,注意极性不要接反。
6. 几个容易被忽略的实操心得
6.1 关于 UNO Q 的 USB Host 库选择
UNO Q 的 USB Host 功能需要用到特定的库,官方推荐的是 USBHost 库,但实际用下来我发现它的兼容性一般。如果你接的是标准 HID 设备(键盘、鼠标),用官方库没问题。但如果接的是自定义设备或者需要批量传输的设备,建议用 USB_Host_Shield_2.0 库,虽然名字里有 Shield,但它也支持 UNO Q 的原生 USB 接口。
安装这个库的时候要注意,它跟某些版本的 Arduino IDE 有冲突。我在 IDE 2.3 上装的时候报了一堆编译错误,后来降到 IDE 1.8.19 就正常了。如果你也遇到类似问题,可以试试换 IDE 版本。
6.2 关于 SPI 屏幕的刷新率优化
如果你觉得屏幕刷新太慢,可以试试这几个优化手段:
把 SPI 时钟从默认的 4MHz 提到 8MHz,刷新率能翻倍。但要注意,有些屏幕模块在 8MHz 下不稳定,需要实际测试。
只刷新变化区域,不要全屏刷新。比如粉丝数从 1000 变成 1001,只需要刷新最后一位数字的区域,前面的不用动。
用tft.setTextColor(color, background)代替tft.setTextColor(color),这样绘制文字的时候会自动填充背景色,省去一次fillRect调用。
6.3 关于整机外壳的设计
如果你打算把这个项目做成一个成品,外壳设计要考虑散热和接口可达性。UNO Q 和 ESP32 都会发热,尤其是 ESP32 在 WiFi 传输的时候,芯片表面温度能到 50 度以上。外壳上要开散热孔,或者用金属外壳辅助散热。
接口方面,USB 接口、电源接口、屏幕排线都要留出足够的空间。我第一版外壳做得太紧凑,插拔 USB 线的时候经常碰到屏幕排线,后来把外壳加宽了 5mm 才解决问题。
6.4 关于代码的长期维护
这个项目涉及 UNO 和 ESP32 两块板子的代码,建议用 Git 做版本管理。我一开始嫌麻烦没弄,后来改代码改乱了,想回退都回不去。现在两块板子的代码放在同一个仓库的不同目录下,每次改动都提交,清爽很多。
另外,B 站的 API 可能会变,建议把 API 地址和参数提取到单独的配置文件里,不要硬编码在代码里。这样 API 变了只需要改配置文件,不用重新编译烧录。
6.5 关于扩展方向
这个项目跑通之后,可以往几个方向扩展:
加一个红外接收头,用遥控器控制屏幕显示内容切换。加一个温湿度传感器,在屏幕上同时显示环境数据。加一个蜂鸣器,粉丝数突破某个阈值的时候响一下。加一个 SD 卡模块,把历史数据记录下来,做成趋势图。
我目前在做的是加一个旋转编码器,用来手动切换显示不同的 B 站数据(粉丝数、播放量、点赞数、评论数)。旋转编码器比按键好用,可以连续调节,而且带按下功能,一个器件搞定选择和确认两个操作。
最后分享一个我在调试过程中发现的小技巧:如果你不确定 ESP32 发出来的数据格式对不对,可以先用 USB 转 TTL 模块把 ESP32 接到电脑上,用串口助手看原始数据。确认格式没问题之后再接到 UNO 上。这样排查问题的时候可以少走很多弯路,不用每次都烧录 UNO 的代码。