资讯动态

ESP32-S3实战:双核任务下TFT彩屏显示与传感器采集记录

发布时间:2026/10/14 17:04:53 来源:尧图企业网站定制
前阵子帮朋友折腾一个小型桌面终端要求能接一块TFT彩屏、能刷WiFi做数据展示、还希望留出跑轻量AI模型的余地。翻了一圈开发板最后把目标锁定在了 ESP32S3 Dev Module 这块板子上。这类板子是目前 Arduino 生态里性价比非常高的选择双核 240MHz、板载 8MB PSRAM、16MB FlashWiFi 和 BLE 全都有Arduino IDE 开箱即用几分钟就能把屏点亮。这篇文章我就把自己从拿到板子到能稳定跑双核任务的过程完整写下来包括环境配置、屏幕驱动、传感器读取、任务分配和一堆实测踩坑记录给想入门或者已经在折腾的朋友做个参考。我用惯了普通单片机一开始对 ESP32-S3 还有点不放心担心它和 Arduino 生态的配合不够顺。实际用了之后会发现这块板子完全是“体力活选手”算力足够、内存够大、外设多而且社区里各种库已经非常成熟几乎你要的传感器、屏幕、通讯模块都有现成方案。正文里我会把关键的原理讲清楚也会给出可以直接抄的配置和代码尽量让刚接触的人看完就能上手少走我走过的弯路。1. 开发板定位与核心价值1.1 为什么选 ESP32-S3而不是老款 ESP32 或单核板子很多人的第一块板子可能是 Uno 或者 Nano这两类板子跑个传感器、控制个舵机没问题但一旦涉及屏幕刷新、网络请求、音频采样这些任务8 位单片机的资源就非常吃紧了。ESP32-S3 不一样它是一颗双核 Xtensa LX7 处理器主频最高能到 240MHz片上 SRAM 有 512KB配合外置 PSRAM 后内存空间更是相当充裕跑 LVGL、加载图片帧缓冲、做音频缓冲都完全不虚。老款 ESP32 用的是 Xtensa LX6 内核虽然也是双核但 ESP32-S3 在架构上做了一些调整。更重要的是S3 这颗芯片在指令集上增加了向量指令支持做一些轻量级音频特征提取、关键词唤醒、屏幕UI渲染加速时有明显优势。如果你的项目有“屏幕显示无线通信简单边缘处理”这几项需求ESP32-S3 比老款 ESP32 更值得选它把不必要的蓝牙经典模式砍掉了一些复杂度同时强化了低功耗、USB 和 GPIO 的灵活性。对比时我还考虑过树莓派 Pico 系列。Pico 的优点是超低功耗和实时响应但 WiFi 需要额外模块生态和例程也比不上 ESP32 阵营。ESP32-S3 的吸引力在于“一颗芯片解决几乎所有联网外设需求”不需要像 STM32ESP8266 那样拼两块板子也不用像树莓派那样跑完整操作系统适合物联网终端、桌面仪表、小游戏机、语音助手这类产品原型。1.2 Dev Module 板级资源盘点越用越顺手市面上最常见的 ESP32S3 Dev Module 形态一般是基于 WROOM-1 模组做的开发板板载一个 USB 口、一堆排母、复位和 Boot 按键。模组内部集成的是 16MB Flash 和 8MB PSRAM这个组合对我来说很关键。8MB PSRAM 意味着你可以开高分辨率屏幕的帧缓冲、缓存大数组在跑复杂 UI 时才不会动不动就内存溢出。板子上的 USB 口比较特殊它直接连接到芯片的 USB OTG 外设不是普通单片机那种只用来下载程序的串口芯片方式。也就是说这块板子既能被识别成串口设备做烧录和日志输出也能被配置成 USB 键盘、鼠标、U 盘之类的设备。用 Arduino 开发时通常把它当串口设备用但如果你喜欢后面的代码里也可以用 USB HID 功能做自定义键鼠扩展性比传统串口方案高不少。引脚方面Dev Module 把大部分 GPIO 都引出来了包括 SPI、I2C、UART、ADC、电容触摸等。需要注意的是ESP32-S3 的 GPIO26 到 GPIO32、GPIO33 到 GPIO37 这些引脚在作为普通数字输入使用时会有限制比如不能接外部上拉电阻或需要做特殊处理。实际项目里我会尽量避免用这些“有脾气”的引脚把普通 IO 需求都用常见引脚解决。板载的 Neopixel RGB 灯通常接在 GPIO48 上调试时很实用能用它快速区分程序跑没跑起来。这张表总结了我常用的板子资源分布资源数量/位置常用场景CPU双核 Xtensa LX7240MHz任务并行、事件处理SRAM512KB 片上变量、任务栈、协议栈PSRAM8MB 板载帧缓冲、大数组、UI缓存Flash16MB固件、字库、资源文件WiFi/BLE2.4GHz WiFi BLE 5网络通信、蓝牙透传USBUSB OTG 接口烧录、日志、HID 设备常用 GPIO多数引到排母屏幕、按键、传感器板载 LEDGPIO48状态指示2. Arduino 环境配置与板级参数调整2.1 开发板支持包安装与串口驱动排查在 Arduino IDE 里使用 ESP32-S3首先要安装 esp32 开发板支持包。我建议直接在“文件 - 首选项 - 附加开发板管理器网址”里填入官方维护的包地址这样后面的板型列表里才会出现 ESP32S3 Dev Module。安装过程需要联网下载编译器工具链时间长短取决于网络情况装完以后在“工具 - 开发板 - esp32”菜单下就能看到这块板子了。打开开发板管理器搜索 esp32安装 2.x 以上的版本比较稳。新版支持包已经统一了 S3 的编译环境对 USB CDC、PSRAM、双核任务的支持都挺完善。我遇到过有人装完支持包后板子选不到多半是 Arduino IDE 版本太老或者安装过程被中断建议用较新的 IDE 版本重新安装一遍支持包基本能解决。然后是串口识别问题。ESP32S3 Dev Module 有好几种硬件版本有的板子用板载 USB 转串口芯片有的直接走芯片的 USB CDC。如果你插上板子后在“端口”菜单里看到一个带“USB JTAG/serial debug unit”字样的设备那就说明用的是原生 USB 模式不用额外装驱动如果看到的是无法识别的设备或者用了 CH340、CP2102 之类的串口芯片那就要安装对应驱动。用 Windows 的朋友最容易在这里卡住我会先装一个驱动精灵类工具不行还是建议直接去芯片厂商官网下驱动稳定且干净。一个非常常见的问题数据线只供电不传输数据。USB 线内部只有两根电源线插上后板子能亮灯但系统里就是不出现串口。这种情况换一根确定支持数据传输的线就能解决看起来像笑话实际项目里我至少碰到过三次。2.2 烧录参数这样设定少踩一半坑板型选择正确只是第一步真正决定能不能顺利烧录和稳定运行的是工具菜单里那一堆参数。我把自己常用的配置组合整理在这里参数项我的推荐值说明BoardESP32S3 Dev Module选错板型会烧不进固件USB CDC On BootEnabled开启后 USB 口直接输出日志Flash Size16MB必须与实际 Flash 一致Partition SchemeHuge APP / No OTA大项目预留足够空间CPU Frequency240MHz跑复杂 UI 时尽量拉满Flash ModeQIO上传失败时改成 DIO 再试Upload Speed460800比默认值更稳适合新手PSRAMOPI PSRAM必须开启否则内存不够用第一次插上板子后我通常先做一件事按住 BOOT 键不放再按一下 RST 键进入下载模式。这样做的原理是芯片内部有一个从 UART 下载固件的启动分支如果应用固件本身有 bug 或者跑飞了串口监听程序会一直等待设备响应这时候手动拉低 BOOT 引脚重新进入下载模式是最可靠的。上传代码时如果看到“Connecting...____”卡住不动别着急先把上传波特率从 921600 降到 460800。高速波特率在实际 USB 转串口链路里容易丢包降低之后通常能正常烧录。还有一点经验Arduino IDE 里上传前不要开着另一个串口监视器两个程序同时占用同一个 COM 口会导致打开失败或上传冲突我每次都先关监视器再上传。3. 实操流程双核任务下的显示与传感器采集3.1 外设选型与接线第一步先保证供电稳定为了完整演示这块板子的能力我搭了一个“桌面环境监测终端”的模拟项目一块 SPI 接口的 240x320 彩色屏幕显示温度和湿度一个 DHT22 传感器负责采集环境数据。这套组合非常适合入门因为屏幕能直观地看到代码运行结果传感器又能说明任务调度逻辑两个外设加在一起还不算太复杂。屏幕我选了常见的 ST7789 驱动芯片 SPI 屏这类屏在 Arduino 生态里支持最好资料多、驱动库成熟。接线按照 SPI 的方式连接屏幕的 SCLK 接 GPIO12MOSI 接 GPIO11CS 接 GPIO10DC 接 GPIO9RST 接 GPIO8背光控制接 GPIO14。这些引脚可以随便改但为了后续代码一致建议先按这个顺序接。电源方面屏幕模块的 VCC 接 3.3V有些大尺寸屏幕需要 5V 背光供电最好查看模块丝印说明不要盲目全接 3.3V。DHT22 传感器则更简单VCC 接 3.3VGND 接 GNDDATA 接 GPIO4同时需要在 DATA 和 VCC 之间加一个 10k 欧姆上拉电阻。DHT22 本身是单总线协议上拉电阻是必须的如果没有装读取结果就会一直是 NaN 或者干脆读不到数据。很多市售模块已经把上拉电阻集成进去了但如果你买的是裸传感器记得自己加上。关于供电我要特别多说一句。ESP32-S3 的 3.3V 引脚通常由板载 LDO 稳压提供这颗 LDO 的电流余量很小如果同时给屏幕模块、传感器、还有外接的 GPS 或音频模块全部从 3.3V 引脚取电电流很容易超标导致电压跌落、屏幕闪烁、WiFi 掉线。我的做法是小功耗外设从板子 3.3V 取电大功耗模块直接用外部 5V 稳压供电并且共地。这个习惯帮我避免了很多奇怪的随机故障。3.2 屏幕驱动的关键配置TFT_eSPI 的 User_Setup.hArduino 下驱动 ST7789 屏幕我最常用的是 TFT_eSPI 库。这个库功能强大文字渲染、图形绘制、Sprite 帧缓冲都支持而且底层做了不少性能优化。安装好库之后真正要动的地方是库目录下的 User_Setup.h 配置文件里面要告诉库“你到底用的什么驱动、引脚怎么接的”。这段配置是屏幕能不能点亮的关键我每次都要仔细检查。打开 User_Setup.h把包含文件的部分改成下面这样不同版本位置略有差异但核心字段一致#define ST7789_DRIVER #define TFT_WIDTH 240 #define TFT_HEIGHT 320 #define TFT_MOSI 11 #define TFT_SCLK 12 #define TFT_CS 10 #define TFT_DC 9 #define TFT_RST 8 #define TFT_BL 14 #define SPI_FREQUENCY 40000000把这些字段设置好之后编译上传一个简单的画点测试程序屏幕如果能显示纯色的填充画面就说明驱动和接线没问题。如果屏幕没有任何反应不要急着改代码先用万用表确认每个引脚的对地电压和焊接情况很多时候问题出在杜邦线接触不良。关于刷新率我建议 ST7789 的 SPI 频率先设成 40MHz。虽然这个库在部分屏幕上能跑到 80MHz但长杜邦线、面包板的寄生电容会让高速信号变形出现花屏、条纹。40MHz 是一个很可靠的折中点显示流畅度已经足够日常使用。我实测用一个 240x320 的 Sprite 做全屏填充刷新40MHz 下的帧率肉眼感觉完全畅快只有在做视频播放那种场景才需要再压榨性能。另外要注意TFT_eSPI 建议用setRotation设置屏幕方向。我的布局是横屏显示所以初始化后调用tft.setRotation(1)后面的坐标轴就会对应旋转后的宽高写绘制代码时思路更清楚。3.3 双核任务怎么写采集与显示互不卡顿ESP32-S3 是双核芯片在 Arduino 环境里默认情况下setup()和loop()跑在 Core 1 上。如果我在loop()里既做 DHT22 的 2 秒延时读取、又做屏幕全屏刷新、还要处理网络请求很容易出现这种现象屏幕刷新到一半传感器读取卡住了或者网络连接超时了。解决方法是手动创建任务把不同功能分配到两个核上。DHT22 这种传感器读取很慢时序要求又严格读一次需要等待几十到上百毫秒如果把它放在主循环里整个显示流程都会被拖住。我在这个项目里把传感器采集独立成了一个任务固定放在 Core 0 上让它每 2 秒唤醒一次更新全局变量显示刷新则继续走loop()运行在 Core 1 上。两个核各干各的互不阻塞屏幕就能一直保持流畅刷新。引脚方法如下#include TFT_eSPI.h #include DHT.h #define DHTPIN 4 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); TFT_eSPI tft TFT_eSPI(); volatile float tempC 0.0f; volatile float humi 0.0f; void sensorTask(void *param) { float t 0.0f; float h 0.0f; for (;;) { t dht.readTemperature(); h dht.readHumidity(); if (!isnan(t)) { tempC t; } if (!isnan(h)) { humi h; } vTaskDelay(pdMS_TO_TICKS(2000)); } } void setup() { Serial.begin(115200); delay(1000); dht.begin(); tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_WHITE, TFT_BLACK); // 点灯验证 pinMode(48, OUTPUT); digitalWrite(48, HIGH); xTaskCreatePinnedToCore( sensorTask, // 任务函数 sensor, // 任务名称 4096, // 栈大小单位字节 NULL, // 任务参数 1, // 任务优先级 NULL, // 任务句柄 0 // 核心编号这里放到 Core 0 ); Serial.println(boot ok); } void loop() { static uint32_t last 0; if (millis() - last 300) { last millis(); // 全屏清屏后再绘制简单直接 tft.fillScreen(TFT_BLACK); tft.setTextDatum(MC_DATUM); tft.drawString(Temp, tft.width() / 2, 80, 4); tft.drawString(String(tempC, 1) C, tft.width() / 2, 130, 6); tft.drawString(Humi, tft.width() / 2, 210, 4); tft.drawString(String(humi, 0) %, tft.width() / 2, 260, 4); } }简单解释一下这个代码。sensorTask是一个 FreeRTOS 任务函数内部是个死循环vTaskDelay(pdMS_TO_TICKS(2000))让任务每 2 秒醒来一次。读取到的温湿度写入全局变量tempC和humi因为它们被另一个核上的loop()读取我特意声明成volatile避免编译器缓存优化造成读不到最新值。任务创建函数xTaskCreatePinnedToCore是 ESP32 Arduino 封装好的接口最后两个参数分别是优先级和核心编号。优先级 1 和 Arduino 的loopTask优先级相同在任务不是特别频繁触发的情况下能和谐共存核心编号 0 表示放到 Core 0 上。如果以后还要跑 WiFiWiFi 协议栈本身跑在 Core 0 上任务调度器会合理安排时间通常不会冲突。需要更快响应时把优先级调成 2 到 3 即可。loop()里我用了一个时间片判断每 300ms 做一次全屏刷新。这里每次都是fillScreen清屏再重新画文字优点是代码简单缺点是全屏填充稍微浪费性能。实际项目里更优雅的做法是只在数据变化时更新局部区域或者用 Sprite 先在内存里画好整帧再一次性推送到屏幕。对于演示项目来说这个代码已经足够直观了。启动时我把板载 LED 通过 GPIO48 点了一下用来快速判断固件有没有跑起来。这样即使屏幕出问题我也能从 LED 状态确认程序逻辑是否正常排查问题的时候能省很多时间。4. 高频问题与排查思路实录4.1 八个最常遇到的坑一张表说清楚整个开发过程中我把遇到的问题按频率排了个序整理成速查表。这些是我和身边几个朋友实测都遇到过的覆盖了从硬件到软件的常见坑。现象可能原因解决办法电脑不识别串口驱动未装 / 数据线只能供电安装对应驱动芯片驱动换传输线上传卡在 Connecting没有进入下载模式 / 波特率过高按住 BOOT 再上点或把速率降到 460800屏幕白屏无显示CS/DC 接错 / 引脚配置不对 / SPI 频率过高对照 User_Setup.h 逐项检查降低频率到 40MHz串口监视器无输出USB CDC 未开启 / 端口选错开启 USB CDC On Boot重新拔插 USB程序偶尔重启供电电流不足 / 复位引脚干扰使用外部稳压电源检查 EN 引脚上拉内存申请失败未开启 PSRAM / 分区太小工具菜单选择 OPI PSRAM换 Huge APP 分区DHT22 读数一直 NaN上拉电阻缺失 / 引脚不对加上拉电阻确认 DHT 连接稳定屏幕刷新卡顿明显SPI 频率太低 / 全屏填充过多调频率到 40 或 80MHz改用局部刷新这些坑里最隐蔽的是“串口有日志但烧录失败”。这种情况通常是 USB CDC 模式已经启动串口监视器占用着那个端口IDE 没法弹掉重新下载。遇到后我会拔掉 USB 重新插等 2 秒再点上传几乎每次都成功。另一个隐蔽问题是分区的坑。如果你在工具菜单里选择了 Huge APP但板子的实际 Flash 是 16MB下载时固件会被放到一个很大区域看起来没问题。但如果 Flash 型号是 8MB 而你还按 16MB 设置烧录会提示校验失败或者直接造成启动死循环。所以拿到板子第一件事就是确认模组具体型号不同批次配置可能不一样。4.2 排查思路比单个技巧更重要我的习惯是遵循“从电源到串口、从串口到外设”的顺序排查。板子没反应先看板载 LED 是否点亮测量 3.3V 引脚电压是否正常电压没问题再看串口能不能打印启动信息串口通了再逐个接外设每接一个就测试一次。全部正常后再写业务逻辑。最怕的情况是“屏幕偶尔闪、WiFi 时不时断”这种随机性问题九成和供电有关。我用过一块杂牌屏幕模块峰值电流接近半安培从板子 3.3V 引脚直接取电后主芯片电压被拉低导致 WiFi 连接都断。后来把屏幕供电改成外部 5V 经过单独稳压给 3.3V只保留共地连接问题立刻消失。这个经验后来被我用在了多个项目里凡是带屏幕或者电动机的项目我都会单独设计供电链路。调 DHT22 时还有个细节容易被忽略每次读取至少要间隔 2 秒因为传感器本身测量周期就是 2 秒左右如果你频繁读取它可能还在忙于上一次测量返回的数据要么是上一次的结果要么直接超时。我在任务里用vTaskDelay(2000)就是出于这个原因。真需要更高频率的环境数据建议换成数字 I2C 传感器比如常见的 SHT30 系列读取速度会快很多。关于 TFT_eSPI 的使用有另一个小技巧值得说。如果你在显示中同时用到中文字库库内置的字体是不包含中文的需要额外加载字库文件或者使用SPIFlash方式存储字库。这个项目我用的是英文字符串所以没踩这个坑。但如果是做中文天气站、菜单界面最好提前规划字库方案否则后期文字显示出来全是方块会很烦躁。使用 Arduino 环境做 ESP32-S3 开发并不复杂门槛主要在环境配置和硬件细节上。我第一次拿到板子时也经历了“连不上、烧不进、屏不亮”的三连坑后来把上述内容逐个落实整个流程变得非常顺滑。尤其是双核任务这个概念看起来有点吓人但当你真正体会到采集和显示互不阻塞的流畅感之后就会觉得这块芯片的设计确实很有意思。最后再分享一点我在实践中的体会无论你打算做天气站、小游戏机还是 IoT 网关先把“串口日志 板载 LED”这个最小调试链路打通再接入其他外设。这样做的好处是每次引入新变量都有明确的判断依据出了问题能快速定位是硬件、配置还是代码的锅。如果连串口日志都没有就先别急着写复杂的业务逻辑先解决供电、驱动和连接这三件小事。这套方法虽然朴素但真的能帮你省下大量排查时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑