ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ESP32-S3 + MicroPython 打造桌面状态终端:从硬件选型到BLE/Wi-Fi通信

ESP32-S3 + MicroPython 打造桌面状态终端:从硬件选型到BLE/Wi-Fi通信

1. 从一块裸板到能用的桌面小终端:这个项目到底在做什么

Status Deck 这个概念,说白了就是一块放在桌面上、能实时显示各种状态信息的小屏幕终端。你可以把它理解成一个“物理仪表盘”——天气、待办事项、服务器负载、消息提醒、股票行情,甚至是你自己定义的各种数据,都能通过它直观地展示出来。我做的这个版本,核心是一块 ESP32-S3 开发板,配合一块小尺寸的 TFT 屏幕,通过无线网络和蓝牙两种方式跟外部世界通信,最终做成一个可以摆在桌上、随时瞟一眼就能获取关键信息的设备。

为什么选 ESP32-S3 而不是其他方案?这个问题的答案贯穿了整个项目的技术选型逻辑。市面上做类似桌面小终端的方案其实不少,比如用树莓派 Zero 加屏幕、用旧手机改造、用 Arduino 加各种模块拼装。但 ESP32-S3 有几个特性让它在这个场景下特别合适:第一,它自带 Wi-Fi 和蓝牙 5.0(LE),不需要额外挂模块,硬件结构可以做得非常简洁;第二,它的算力对于驱动一块小屏幕、解析 JSON 数据、跑一个轻量级的 UI 框架来说绰绰有余,双核 240MHz 的主频加上 512KB 的 SRAM 和可选的外部 PSRAM,处理这些任务毫无压力;第三,它的功耗控制得很好,如果做成电池供电的版本,续航可以做到几天甚至几周;第四,它的生态非常成熟,无论是乐鑫官方的 ESP-IDF 还是社区维护的 Arduino 核心和 MicroPython 固件,都有大量的文档和示例可以参考。

这个项目适合谁来参考?如果你手头正好有一块 ESP32-S3 的开发板,想做一个既有实用性又能学到东西的小项目,那这个内容就是为你准备的。如果你之前只用过 Arduino Uno 或者 STM32 这类没有无线功能的单片机,想跨入物联网设备开发的门槛,ESP32-S3 是一个非常好的切入点。甚至如果你是一个纯软件开发者,想了解一下嵌入式设备是怎么跟网络服务打交道的,这个项目也能给你一个完整的视角——从硬件选型到固件开发,从通信协议到数据格式,从 UI 渲染到实际部署,整条链路都会涉及到。

我在这个项目里踩过的坑不算少,从屏幕驱动的兼容性问题到蓝牙配对的稳定性,从 JSON 解析的内存溢出到 Wi-Fi 重连的逻辑漏洞,每一个问题都让我对这套技术栈有了更深的理解。接下来的内容会把这些经验完整地分享出来,包括技术选型的思考过程、核心模块的实现细节、实操中遇到的典型问题以及解决方案。

2. 技术栈选型:为什么是这套组合而不是别的

2.1 主控芯片:ESP32-S3 的硬实力与软生态

ESP32-S3 是乐鑫在 ESP32 基础上做的一次重要升级。它用的是 Xtensa LX7 双核处理器,主频最高 240MHz,相比初代 ESP32 的 LX6 在指令效率和 AI 加速方面都有提升。对于 Status Deck 这个场景来说,最关键的是它支持 2.4GHz Wi-Fi(802.11 b/g/n)和 Bluetooth 5 (LE),这意味着设备可以同时具备两种无线通信能力,而且不需要外挂任何模块。

内存方面,ESP32-S3 内置 512KB 的 SRAM,对于跑 MicroPython 或者轻量级的 C 代码来说基本够用。但如果你要驱动一块分辨率较高的屏幕、同时还要解析 JSON 数据、维护多个网络连接,内存就会比较紧张。这时候就需要用到外部 PSRAM。我用的这块开发板带了 8MB 的 Octal SPI PSRAM,实际使用下来,开了 PSRAM 之后整个系统的余量就非常充裕了,屏幕的帧缓冲、JSON 解析的临时缓冲区都可以放在 PSRAM 里,内部 SRAM 留给关键的任务栈和 DMA 缓冲区。

这里有一个选型时容易忽略的点:ESP32-S3 的 GPIO 数量和引脚复用能力。如果你选的屏幕用的是 SPI 接口,那至少需要 5 根线(SCK、MOSI、CS、DC、RST),再加上背光控制、触摸中断(如果用触摸屏的话),引脚消耗还是不少的。ESP32-S3 有 45 个可编程 GPIO,看起来很多,但很多引脚有默认功能(比如 strapping 引脚、USB 引脚、JTAG 引脚),实际能自由使用的并没有那么多。我在设计电路的时候,特意把屏幕的 SPI 引脚分配到了 IO11 到 IO14 这一组,避开了 strapping 引脚和 USB 相关的 IO19、IO20,这样在上电和下载程序的时候不会出现冲突。

2.2 开发框架:MicroPython 还是 ESP-IDF

这是一个在社区里经常被讨论的问题。MicroPython 的优势在于开发速度快,交互式的 REPL 环境让你可以一行一行地调试代码,特别适合快速验证想法。ESP-IDF 的优势在于性能和底层控制能力,你可以精确地管理内存、配置中断优先级、优化任务调度。

我最终选择了 MicroPython 作为主要开发框架,原因有几个。第一,Status Deck 这个项目的核心逻辑是“获取数据-解析数据-显示数据”,计算密集型的任务并不多,MicroPython 的性能完全够用。第二,MicroPython 的machine模块和network模块封装得很好,初始化 SPI、I2C、Wi-Fi 这些外设只需要几行代码,省去了大量底层配置的工作。第三,MicroPython 的json模块是内置的,解析 JSON 数据非常方便,不需要额外引入 cJSON 这样的第三方库。第四,社区里有大量针对 ESP32-S3 的 MicroPython 固件和示例代码,遇到问题比较容易找到参考。

当然,MicroPython 也有它的局限性。比如它的垃圾回收机制在某些情况下会导致不可预测的延迟,如果你在做需要严格实时响应的任务(比如高速 PWM 输出或者精确的脉冲计数),那还是得用 ESP-IDF 或者 Arduino 框架。但对于 Status Deck 来说,屏幕刷新率不需要很高(每秒几帧就够了),网络请求也不是高频操作,MicroPython 的实时性完全能满足需求。

2.3 屏幕选型:SPI TFT 还是 I2C OLED

屏幕的选择直接影响到显示效果和开发复杂度。市面上常见的选项有几种:SPI 接口的 TFT 彩屏(比如 ST7789、ILI9341 驱动芯片)、I2C 接口的 OLED 屏(比如 SSD1306)、以及 SPI 接口的墨水屏。

我最终选的是 1.14 英寸的 ST7789 SPI TFT 彩屏,分辨率 135x240。这个尺寸放在桌面上刚刚好,不会太占地方,显示几行文字和简单的图标完全够用。SPI 接口的刷新率比 I2C 高很多,全屏刷新可以做到每秒 30 帧以上,局部刷新更快。ST7789 这个驱动芯片在 MicroPython 里有现成的库支持,初始化参数也比较简单。

相比之下,I2C OLED 屏虽然接线更简单(只需要 SDA、SCL 两根线),但刷新率低,而且单色显示在展示丰富信息的时候不够直观。墨水屏的功耗极低,适合电池供电的场景,但刷新速度太慢,不适合需要频繁更新数据的 Status Deck。

这里有一个实操中的细节:ST7789 的初始化参数里有一个“颜色反转”的设置。不同厂家生产的屏幕,面板的默认颜色顺序可能不一样,有的需要设置invert=True,有的不需要。如果你发现显示的颜色跟预期相反(比如红色显示成青色),那就是这个参数设错了。我一开始就是被这个问题困扰了很久,后来查了屏幕的数据手册才确认需要开启颜色反转。

2.4 通信协议:BLE GATT 与 Wi-Fi TCP 的分工

Status Deck 需要跟外部世界通信,获取要显示的数据。我设计了两条通信路径:一条是 Wi-Fi TCP,用于设备主动从网络服务拉取数据;另一条是 BLE GATT,用于手机或者其他蓝牙设备向 Status Deck 推送数据。

为什么不用一种协议搞定所有事情?因为这两种场景的需求不一样。Wi-Fi TCP 适合设备主动去请求数据,比如定时从天气 API 获取天气信息、从待办事项服务获取任务列表。这种方式下,设备是客户端,主动发起连接,逻辑清晰,实现简单。而 BLE GATT 适合被动接收数据,比如手机上的 App 想把一条通知推送到 Status Deck 上显示,这时候 Status Deck 作为 BLE 外设(Peripheral),手机作为中心设备(Central),通过 GATT 特征值来传输数据。

BLE GATT 的数据模型需要理解几个核心概念。一个 BLE 设备可以定义多个 Service(服务),每个 Service 下面有多个 Characteristic(特征值)。每个 Characteristic 有一个 UUID 来标识,还有一组属性(读、写、通知等)。Status Deck 上我定义了一个自定义 Service,里面包含两个 Characteristic:一个用于接收文本数据(可写),一个用于发送状态回执(可通知)。手机端往可写的 Characteristic 里写入 JSON 格式的数据,Status Deck 解析后更新屏幕显示,然后通过可通知的 Characteristic 把处理结果发回去。

Wi-Fi TCP 这边,我用的是一种简化的请求-响应模式。Status Deck 作为 TCP 客户端,连接到指定的服务器地址和端口,发送一个简单的请求(比如一个 JSON 格式的查询指令),服务器返回 JSON 格式的数据,Status Deck 解析后更新显示。这种方式比 HTTP 更轻量,省去了 HTTP 头部的开销,对于嵌入式设备来说更友好。当然,如果你要对接的是标准的 RESTful API,那还是得用 HTTP,MicroPython 的urequests模块可以处理这种情况。

2.5 数据格式:为什么选 JSON 而不是其他

JSON 作为数据交换格式,在嵌入式场景下有几个明显的优势。第一,它是纯文本格式,可读性好,调试的时候可以直接在串口里看到原始数据,不需要额外的解析工具。第二,MicroPython 内置了json模块,json.loads()和json.dumps()两个函数就能完成序列化和反序列化,不需要引入第三方库。第三,JSON 的结构灵活,可以表示嵌套的对象和数组,适合传输结构化的数据。

但 JSON 也有它的缺点。相比 MessagePack 或者 CBOR 这样的二进制格式,JSON 的冗余信息比较多,同样的数据量,JSON 的体积可能是二进制格式的两三倍。对于 Status Deck 来说,每次传输的数据量并不大(通常几百字节到几KB),这点开销可以接受。而且 JSON 的可读性带来的调试便利性,在开发阶段是非常有价值的。

在 MicroPython 里解析 JSON 有一个需要注意的地方:内存分配。json.loads()会把整个 JSON 字符串解析成一个 Python 字典或列表,这个过程会分配新的内存。如果你的 JSON 数据很大(比如超过 10KB),在内存紧张的 ESP32-S3 上可能会触发MemoryError。我的做法是尽量控制单次传输的数据量,把大的数据结构拆分成多次小的请求。另外,解析完成后及时用del删除不再使用的对象,帮助垃圾回收器尽快释放内存。

3. 核心模块拆解:从硬件连接到软件架构

3.1 硬件连接与引脚分配

先说一下我的硬件配置清单。主控是 ESP32-S3-DevKitC-1 开发板,带 8MB PSRAM 和 16MB Flash。屏幕是 1.14 英寸 ST7789 SPI TFT,分辨率 135x240。另外还有一个 EC11 旋转编码器,用来做菜单导航和参数调节。供电方面,开发阶段直接用 USB 供电,后续如果要做成独立设备,可以用 18650 锂电池加 TP4056 充电模块。

引脚分配是这样的:屏幕的 SPI 接口用 SPI2(ESP32-S3 有两个 SPI 控制器,SPI0 和 SPI1 通常保留给 Flash 和 PSRAM,用户可用的是 SPI2 和 SPI3)。SCK 接 IO12,MOSI 接 IO11,CS 接 IO10,DC 接 IO9,RST 接 IO8,背光 BLK 接 IO7。旋转编码器的 A 相接 IO4,B 相接 IO5,按键接 IO6。

这里有一个细节值得展开说一下:SPI 的时钟频率。ST7789 支持的最高 SPI 时钟是 62.5MHz,但实际能跑多快取决于你的布线质量和屏幕模块的质量。我一开始设的是 40MHz,发现偶尔会出现花屏,降到 26MHz 之后就非常稳定了。如果你也遇到类似的问题,可以先把频率降下来测试,确认是频率问题之后再逐步往上调。

3.2 软件架构:任务划分与内存管理

MicroPython 在 ESP32-S3 上跑的时候,底层其实是 FreeRTOS 在调度。但 MicroPython 层面,我们通常用uasyncio来实现并发。我的软件架构分成了几个协程:一个负责屏幕刷新,一个负责 Wi-Fi 数据拉取,一个负责 BLE 数据接收,还有一个负责处理旋转编码器的输入。

为什么用uasyncio而不是多线程?因为 MicroPython 的_thread模块虽然可用,但线程间的资源共享和同步比较麻烦,容易出现竞态条件。uasyncio的协程模型是协作式的,同一时间只有一个协程在运行,不存在抢占的问题,逻辑上更简单。当然,这也意味着如果一个协程阻塞了(比如在等待网络响应),其他协程也会被卡住。所以我在写网络请求的时候,都会设置超时时间,避免无限等待。

内存管理方面,除了前面提到的用 PSRAM 存放帧缓冲和 JSON 数据之外,还有一个技巧是预分配缓冲区。比如屏幕的帧缓冲,我在初始化的时候就分配好一块固定大小的bytearray,后续刷新的时候直接往里面写数据,而不是每次都创建新的对象。这样可以减少垃圾回收的压力,让系统运行更稳定。

3.3 BLE GATT 服务的设计与实现

BLE GATT 服务的设计需要考虑几个问题:Service 和 Characteristic 的 UUID 怎么定?数据怎么编码?通知怎么发?

UUID 方面,我用了自定义的 128 位 UUID,而不是蓝牙 SIG 定义的标准 UUID。标准 UUID 是 16 位的,比如电池服务是 0x180F,设备信息服务是 0x180A。自定义 UUID 的好处是不会跟标准服务冲突,而且可以用在线工具生成,保证唯一性。我的 Service UUID 是6E400001-B5A3-F393-E0A9-E50E24DCCA9E,这个其实是 Nordic UART Service 的 UUID,社区里用得比较多,各种 BLE 调试工具都能识别。

Characteristic 的设计上,我定义了两个:一个是 RX Characteristic(可写),UUID 是6E400002-B5A3-F393-E0A9-E50E24DCCA9E,用于接收手机发来的数据;另一个是 TX Characteristic(可通知),UUID 是6E400003-B5A3-F393-E0A9-E50E24DCCA9E,用于向手机发送回执。

数据编码方面,BLE 的 Characteristic 传输的是字节数组,不是字符串。所以发送 JSON 数据的时候,需要先encode('utf-8')转成字节,接收的时候再decode('utf-8')转回字符串。这里有一个坑:BLE 单次传输的数据量有限制,默认的 MTU(最大传输单元)是 23 字节,减去 3 字节的 ATT 头部,实际能传的数据只有 20 字节。如果要传更大的数据,要么协商更大的 MTU(ESP32-S3 支持最大 517 字节),要么在应用层做分包处理。我的做法是在初始化的时候请求一个较大的 MTU,然后在发送端做分包,接收端做重组。

3.4 Wi-Fi 连接与 TCP 通信的稳定性处理

Wi-Fi 连接的稳定性是嵌入式设备的一个老大难问题。ESP32-S3 的 Wi-Fi 驱动虽然已经比较成熟,但在实际使用中还是会遇到断连、重连失败、IP 获取超时等问题。

我的处理策略是:第一,在连接 Wi-Fi 的时候设置合理的超时时间,不要无限等待。第二,连接成功后启动一个看门狗协程,定期检查 Wi-Fi 状态,如果发现断连就自动重连。第三,重连的时候采用指数退避策略,第一次等 1 秒,第二次等 2 秒,第三次等 4 秒,最多等到 30 秒,避免频繁重连导致路由器拒绝服务。第四,在 TCP 通信的时候,每次请求都设置超时,如果超时就关闭连接重新建立,不要在一个已经失效的连接上反复尝试。

TCP 通信这边,我用的是socket模块。创建一个 socket,连接到服务器,发送请求,接收响应,然后关闭连接。这种短连接的方式虽然每次都要重新建立连接,但逻辑简单,不容易出现连接状态不一致的问题。如果你需要保持长连接,那就需要处理心跳包和断线重连的逻辑,复杂度会高不少。

4. 实操过程:从零搭建一个可运行的 Status Deck

4.1 固件烧录与环境搭建

第一步是给 ESP32-S3 烧录 MicroPython 固件。去 MicroPython 官网下载最新的 ESP32-S3 固件(注意要选对型号,ESP32-S3 和 ESP32-S3 带 PSRAM 的版本固件不一样)。然后用esptool.py烧录:

esptool.py --chip esp32s3 --port /dev/ttyUSB0 erase_flash esptool.py --chip esp32s3 --port /dev/ttyUSB0 write_flash -z 0x0 ESP32_GENERIC_S3-20240105-v1.22.1.bin

烧录完成后,用串口工具(比如mpremote或者screen)连接到开发板,应该能看到 MicroPython 的 REPL 提示符。这时候可以测试一下基本功能:

import machine import time led = machine.Pin(2, machine.Pin.OUT) while True: led.value(not led.value()) time.sleep(0.5)

如果板载 LED 开始闪烁,说明固件烧录成功,环境搭建完成。

4.2 屏幕驱动与显示测试

接下来是屏幕驱动。ST7789 的 MicroPython 驱动在 GitHub 上有好几个版本,我用的是st7789_mpy这个库。把驱动文件上传到开发板后,初始化屏幕:

import st7789 import machine spi = machine.SPI(2, baudrate=26000000, polarity=0, phase=0, sck=machine.Pin(12), mosi=machine.Pin(11)) tft = st7789.ST7789(spi, 135, 240, reset=machine.Pin(8, machine.Pin.OUT), dc=machine.Pin(9, machine.Pin.OUT), cs=machine.Pin(10, machine.Pin.OUT), backlight=machine.Pin(7, machine.Pin.OUT), rotation=1) tft.init() tft.fill(st7789.BLACK) tft.text("Hello Status Deck", 10, 10, st7789.WHITE)

如果屏幕显示正常,说明驱动配置正确。如果出现花屏或者颜色不对,检查前面提到的颜色反转设置和 SPI 频率。

4.3 BLE GATT 服务的代码实现

BLE 部分的代码基于 MicroPython 的bluetooth模块。先初始化 BLE:

import bluetooth ble = bluetooth.BLE() ble.active(True)

然后定义 Service 和 Characteristic 的 UUID,注册 GATT 服务:

from ble_advertising import advertising_payload _IRQ_CENTRAL_CONNECT = 1 _IRQ_CENTRAL_DISCONNECT = 2 _IRQ_GATTS_WRITE = 3 RX_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") TX_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") RX_CHAR = (RX_UUID, bluetooth.FLAG_WRITE | bluetooth.FLAG_WRITE_NO_RESPONSE) TX_CHAR = (TX_UUID, bluetooth.FLAG_NOTIFY) SERVICE = (bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"), (RX_CHAR, TX_CHAR)) ((rx_handle, tx_handle),) = ble.gatts_register_services((SERVICE,))

注册完成后,开始广播,让手机能发现这个设备:

payload = advertising_payload(name="StatusDeck") ble.gap_advertise(100000, adv_data=payload)

手机端用 BLE 调试助手(比如 nRF Connect 或者 LightBlue)连接后,往 RX Characteristic 写入 JSON 数据,Status Deck 就能收到并处理。

4.4 Wi-Fi 数据拉取与 JSON 解析

Wi-Fi 连接和 TCP 通信的代码大概长这样:

import network import socket import json wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect("SSID", "PASSWORD") while not wlan.isconnected(): time.sleep(0.5) addr = socket.getaddrinfo("api.example.com", 80)[0][-1] s = socket.socket() s.settimeout(5) s.connect(addr) s.send(b'GET /status HTTP/1.0\r\nHost: api.example.com\r\n\r\n') data = s.recv(1024) s.close() json_str = data.decode('utf-8').split('\r\n\r\n')[1] status = json.loads(json_str)

解析出来的status是一个 Python 字典,可以直接用来更新屏幕显示。

4.5 主循环与任务调度

最后把所有模块整合到一个主循环里,用uasyncio来调度:

import uasyncio as asyncio async def display_task(): while True: tft.fill(st7789.BLACK) tft.text(f"Temp: {status.get('temp', '--')}C", 10, 10, st7789.WHITE) tft.text(f"Humidity: {status.get('humidity', '--')}%", 10, 30, st7789.WHITE) await asyncio.sleep(1) async def wifi_task(): while True: await fetch_data() await asyncio.sleep(30) async def main(): await asyncio.gather(display_task(), wifi_task()) asyncio.run(main())

这样,屏幕每秒钟刷新一次,Wi-Fi 数据每 30 秒拉取一次,两个任务互不干扰。

5. 常见问题与排查技巧实录

5.1 BLE 配对失败或连接不稳定

这是我在开发过程中遇到的最头疼的问题之一。现象是手机能搜索到 Status Deck 的广播,但连接的时候经常失败,或者连接成功后很快又断开。

排查下来,原因主要有几个。第一,广播间隔设得太短,导致 BLE 射频一直在工作,功耗高而且容易跟 Wi-Fi 产生干扰。我把广播间隔从 100ms 调整到 500ms 之后,稳定性明显提升。第二,MTU 协商失败。有些手机默认的 MTU 比较小,如果 Status Deck 请求的 MTU 超过了手机支持的范围,协商就会失败。我的做法是先请求一个保守的值(比如 128 字节),如果协商成功再尝试更大的值。第三,Wi-Fi 和 BLE 共存时的射频冲突。ESP32-S3 的 Wi-Fi 和 BLE 共用同一个射频前端,如果 Wi-Fi 正在大量传输数据,BLE 的响应就会变慢甚至丢包。解决办法是尽量错开 Wi-Fi 和 BLE 的高峰期,比如 Wi-Fi 拉取数据的时候暂停 BLE 广播。

5.2 JSON 解析报错或内存不足

JSON 解析报错通常有两种情况:一种是 JSON 格式本身有问题,比如缺少引号、多了逗号、括号不匹配;另一种是数据量太大,解析过程中内存不够。

对于格式问题,我的建议是在发送端做严格的校验,确保生成的 JSON 是合法的。接收端可以用try...except包裹json.loads(),捕获异常后打印原始字符串,方便定位问题。对于内存问题,前面提到过,尽量控制单次传输的数据量,把大的 JSON 拆分成多次小的请求。另外,MicroPython 的json模块在解析的时候会创建大量的临时对象,如果内存碎片比较严重,即使总内存够用也可能分配失败。定期手动调用gc.collect()可以帮助整理内存碎片。

5.3 屏幕显示异常(花屏、颜色错误、刷新慢)

屏幕问题我遇到过三种。第一种是花屏,屏幕上出现随机噪点或者条纹。这通常是 SPI 时钟太快或者布线太长导致的信号完整性问题。降低 SPI 频率、缩短杜邦线长度、在 SCK 和 MOSI 上串联 33 欧姆的电阻,都能改善这个问题。

第二种是颜色错误,比如红色显示成蓝色。这是颜色格式设置的问题。ST7789 支持 RGB565 和 BGR565 两种颜色格式,如果设置反了,红色和蓝色就会互换。在初始化的时候调整color_order参数即可。

第三种是刷新慢,全屏刷新要好几秒。这通常是因为没有使用 DMA(直接内存访问)传输。MicroPython 的 ST7789 驱动如果支持 DMA,刷新速度会快很多。另外,只刷新变化的部分(局部刷新)而不是全屏刷新,也能显著提升响应速度。

5.4 Wi-Fi 连接超时或频繁断连

Wi-Fi 问题我在前面已经提到了一些处理策略,这里再补充几个排查技巧。第一,检查路由器的信道设置。如果路由器用的是 12 或 13 信道,而 ESP32-S3 的 Wi-Fi 驱动默认只支持 1 到 11 信道,那就连不上。解决办法是在路由器里把信道改成 1 到 11 之间的值。第二,检查电源供应。ESP32-S3 在 Wi-Fi 传输的时候瞬间电流可能达到 500mA,如果 USB 线质量不好或者供电不足,就会导致电压跌落,Wi-Fi 断连。换一根短而粗的 USB 线,或者用带外部供电的 USB Hub,通常能解决。第三,如果周围 Wi-Fi 网络很多,信道拥堵严重,可以尝试在代码里主动扫描并选择最空闲的信道。

5.5 常见问题速查表

问题现象可能原因排查方法解决方案
BLE 连接失败广播间隔太短、MTU 协商失败用 nRF Connect 查看广播参数和 MTU增大广播间隔、降低 MTU 请求值
JSON 解析报错格式错误、内存不足打印原始字符串、检查内存使用校验 JSON 格式、拆分大数据、手动 gc
屏幕花屏SPI 频率太高、布线太长降低频率测试、检查接线降频、缩短线长、串联电阻
颜色错误颜色格式设置反了显示红绿蓝三色测试调整 color_order 参数
Wi-Fi 断连信道不兼容、供电不足检查路由器信道、测量电压改信道、换 USB 线、加电容
刷新慢未使用 DMA、全屏刷新查看驱动是否支持 DMA启用 DMA、改用局部刷新

6. 一些实操心得和后续扩展方向

这个项目从开始到基本能用,大概花了我两个周末的时间。其中大部分时间不是在写代码,而是在调试硬件和排查各种奇怪的问题。如果你也要做类似的东西,我的建议是先把最小系统跑通——屏幕能亮、Wi-Fi 能连、BLE 能广播——然后再逐步往上加功能。不要一上来就把所有模块都接好,那样出了问题很难定位是哪个部分的原因。

另外,MicroPython 的 REPL 是你最好的朋友。遇到问题的时候,不要急着改代码重新烧录,先在 REPL 里一行一行地测试,确认每个模块都能正常工作,再把它们组合起来。这样效率会高很多。

后续如果要扩展,有几个方向可以考虑。一是加入电池管理,做成完全无线的桌面设备;二是增加更多的数据源,比如接入日历、邮件、GitHub 通知等;三是做一个手机端的配置 App,通过 BLE 来修改 Status Deck 的显示内容和刷新频率;四是把外壳用 3D 打印做出来,让它看起来更像一个成品而不是一个开发板。这些扩展每一个都涉及到新的技术点,但核心的架构——ESP32-S3 加 MicroPython 加 BLE/Wi-Fi 加 JSON——是不变的。把这套基础打牢,后面的扩展就是水到渠成的事情。

返回列表