ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ESP32个人财务看板:从数据接口到TFT屏幕的完整实现

ESP32个人财务看板:从数据接口到TFT屏幕的完整实现 1. 项目缘起与整体设计思路1.1 为什么我要用一块 ESP32 做个人财务看板先说清楚这个项目到底在做什么。ESP32 Personal Finance Loan Payment Dashboard翻译过来就是基于 ESP32 的个人财务与贷款还款看板。它的核心功能是用一块 ESP32 开发板驱动一块 TFT LCD 屏幕通过 Wi-Fi 连接到本地网络从自建或第三方的数据接口拉取个人财务数据账户余额、月度支出、贷款剩余本金、下期还款金额与日期等把关键数字实时渲染到屏幕上做成一个摆在书桌上、抬眼就能看到的财务仪表盘。这个东西解决的是一个很具体的痛点。我自己有房贷和一笔消费贷每个月要还的钱不一样剩余本金也在变。以前我习惯打开手机 App 查但手机一拿起来就容易被消息带走注意力查完余额顺手刷了半小时短视频。后来我想能不能做一个只读的物理设备它只显示数字不能交互、不能刷放在桌角每天看一眼心里有数就行。这就是这个项目最原始的动机。它适合谁我认为有三类人值得动手做一遍。第一类是刚入门 ESP32、想找一个有真实用途的练手项目的开发者比起点灯和读温湿度财务看板涉及网络请求、JSON 解析、屏幕驱动、定时任务是一个综合性很强的练手载体。第二类是有嵌入式基础、想给自己做点实用小工具的爱好者。第三类是想理解数据从接口到屏幕完整链路的学生或转行者这个项目麻雀虽小五脏俱全。从技术选型上看ESP32 是这个项目最合适的主控没有之一。原因很直接它自带 Wi-Fi省掉了一个独立的网络模块它有双核可以一个核跑网络请求、一个核跑屏幕刷新互不阻塞它的价格便宜一块普通开发板几十块钱做坏了也不心疼它的生态成熟Arduino 框架下有大量现成的库可以直接用。相比之下如果用 STM32 做同样的事你得外挂一个 Wi-Fi 模块还得自己处理 TCP/IP 协议栈工作量翻倍。1.2 硬件选型屏幕、主控与供电的取舍屏幕我选的是ILI9341 驱动的 2.4 寸 TFT LCD分辨率 240x320SPI 接口。为什么不用热搜里提到的 1.8 寸 128x160 屏因为财务看板要显示的信息量不小——至少要有总资产本月支出贷款剩余下期还款四组数据每组还要配标签和单位。128x160 的分辨率塞下这些内容会非常挤字号一小就看不清楚。240x320 就从容多了可以做成 2x2 的卡片式布局每张卡片显示一组数据视觉上清爽。ILI9341 这块屏在 Arduino 生态里的支持非常成熟TFT_eSPI 库对它的优化做得很好SPI 时钟可以跑到 40MHz 甚至更高刷全屏也就几十毫秒完全不会闪。这里有个细节要提醒ILI9341 模块市面上有两种一种是带触摸的一种是不带的。做财务看板不需要触摸买不带触摸的版本更便宜接线也更简单少接几根线就少几个出错点。主控我用的是一块ESP32-WROOM-32 核心板也就是常说的 ESP32 DevKit。选它而不是 ESP32-S3 或 C3是因为 WROOM-32 的 GPIO 数量足够、资料最多、踩坑时最容易搜到答案。财务看板需要占用的引脚不多SPI 的 SCK、MOSI、CS、DC、RST 五根加上背光控制一根一共六根WROOM-32 随便挑一组都够用。供电方面我用的是5V/2A 的 USB 电源适配器直接插开发板的 USB 口。这里有个坑我后面会详细讲TFT 背光电流不小如果和 ESP32 共用一路 LDOWi-Fi 发射瞬间的电流尖峰可能导致屏幕闪烁甚至 ESP32 复位。稳妥的做法是屏幕背光单独走一路或者至少保证电源适配器有足够的余量。1.3 软件架构双核分工与数据流设计软件层面我给这个项目定的架构原则是网络归网络显示归显示两者通过一块共享内存通信。ESP32 是双核的Core 0 和 Core 1Arduino 框架默认把loop()跑在 Core 1 上Wi-Fi 协议栈跑在 Core 0 上。我可以再创建一个任务专门跑在 Core 0 上做网络请求让loop()专心做屏幕刷新。数据流是这样的网络任务每隔一段时间我设的是 5 分钟发起一次 HTTP 请求拿到 JSON 格式的财务数据解析后写入一个全局结构体同时置一个数据已更新的标志位。显示任务在loop()里检查这个标志位如果数据更新了就重绘屏幕没更新就什么都不做保持屏幕内容不变。这样屏幕不会频繁刷新既省电又不会闪。为什么用 HTTP JSON 而不是别的因为这是最容易实现、最容易调试的方案。你可以在电脑上先用浏览器访问接口确认返回的数据长什么样再让 ESP32 去请求同样的地址。如果接口返回的是 JSONESP32 端用 ArduinoJson 库解析几行代码就能把字段取出来。相比之下如果用 MQTT你还得搭一个 broker如果用 WebSocket调试起来更麻烦。对于个人项目HTTP 轮询的简单直接就是最大的优势。提示如果你的财务数据来自某个不提供公开接口的服务不要想着去逆向它的私有接口那既不稳定也不合规。正确做法是自己维护一份数据比如用表格记录再通过一个简单的自建服务暴露成 JSON。这个项目展示的是数据到屏幕的链路数据源本身应该由你自己掌控。2. 核心细节解析与实操要点2.1 ILI9341 屏幕接线与 TFT_eSPI 配置详解接线是这个项目第一个容易翻车的地方。ILI9341 是 SPI 屏ESP32 有多个 SPI 控制器但 TFT_eSPI 库默认用的是 VSPI也就是 SPI3。我用的引脚分配是这样的屏幕引脚ESP32 引脚说明VCC3.3V逻辑供电GNDGND共地CSGPIO 15片选RESETGPIO 4复位DC/RSGPIO 2数据/命令选择MOSI/SDIGPIO 23主出从入SCK/CLKGPIO 18时钟LED/BLGPIO 21背光控制MISO/SDOGPIO 19主入从出可不接这里要重点说几个坑。第一GPIO 2 在 ESP32 上是启动模式引脚之一如果它在上电时被拉低芯片会进入下载模式。ILI9341 的 DC 引脚平时是高阻或高电平一般不会把 GPIO 2 拉低但如果你发现板子一上电就不运行、串口一直打印等待下载的信息第一个要怀疑的就是 GPIO 2 被屏幕模块拉低了。解决办法是换一个引脚比如把 DC 改到 GPIO 27。第二背光引脚 LED/BL 不要直接接 3.3V。有些模块的背光是通过一个限流电阻直接连到 LED 的接 3.3V 会一直亮没法调亮度而且电流可能超过 GPIO 的驱动能力。正确做法是接一个 GPIO用 PWM 控制亮度。我用的是ledcSetup和ledcAttachPin把背光调到 70% 左右既够亮又不刺眼。TFT_eSPI 库的配置不在代码里而在库目录下的User_Setup.h文件里。你需要手动改这个文件把对应的驱动和引脚定义打开。很多人第一次用这个库会懵因为改了代码里的引脚没用必须改这个头文件。我的配置是这样的#define ILI9341_DRIVER #define TFT_CS 15 #define TFT_DC 2 #define TFT_RST 4 #define TFT_MOSI 23 #define TFT_SCLK 18 #define TFT_MISO 19 #define SPI_FREQUENCY 40000000SPI_FREQUENCY设成 40MHz 是经过实测的。再高比如 80MHz有些模块的排线一长就会花屏40MHz 是稳定性和速度的平衡点。如果你用的是很短的排线可以试着往上调但财务看板对刷新率要求不高40MHz 完全够用。2.2 Wi-Fi 连接与网络请求的稳定性处理Wi-Fi 连接本身不难WiFi.begin(ssid, password)然后等WiFi.status() WL_CONNECTED就行。但真正让项目能长期稳定运行的是断线重连和超时处理。我踩过的坑是路由器偶尔重启或者信号波动ESP32 会掉线而默认的 Wi-Fi 库不会自动重连屏幕上的数据就永远停在最后一次成功请求的状态你还以为数据没变。我的处理方式是写一个ensureWiFi()函数在每次网络请求前调用。它检查WiFi.status()如果不是已连接状态就重新WiFi.begin()并等待最多等 10 秒。如果 10 秒还没连上就返回失败本次请求跳过等下一个周期再试。这样即使网络暂时不可用设备也不会卡死恢复后会自动继续工作。HTTP 请求我用的是HTTPClient库配合WiFiClient。这里有个细节一定要设置超时。默认超时可能很长如果服务器不响应网络任务会一直阻塞。我设的是 5000 毫秒HTTPClient http; http.setTimeout(5000); http.begin(client, apiUrl); int httpCode http.GET(); if (httpCode HTTP_CODE_OK) { String payload http.getString(); // 解析 JSON } http.end();http.end()这行千万别漏。它释放底层连接资源如果不调用几次请求之后内存就会耗尽表现为设备越来越卡最后死机。这是新手非常容易忽略的一点。2.3 JSON 数据解析与内存管理财务数据我用 JSON 格式传输结构大概是这样{ total_assets: 125600.50, month_expense: 4320.00, loan_remaining: 380000.00, next_payment: 6850.00, next_payment_date: 2025-07-15 }解析用 ArduinoJson 库。这里有个内存管理的要点不要用DynamicJsonDocument分配一个巨大的缓冲区而是根据实际数据大小精确分配。上面这个 JSON 大概 150 字节我分配 256 字节就够了。分配太大浪费内存ESP32 的堆虽然不小但长期运行要养成精打细算的习惯。DynamicJsonDocument doc(256); DeserializationError err deserializeJson(doc, payload); if (!err) { financeData.totalAssets doc[total_assets].asfloat(); financeData.monthExpense doc[month_expense].asfloat(); financeData.loanRemaining doc[loan_remaining].asfloat(); financeData.nextPayment doc[next_payment].asfloat(); strlcpy(financeData.nextPaymentDate, doc[next_payment_date] | N/A, sizeof(financeData.nextPaymentDate)); financeData.updated true; }注意doc[next_payment_date] | N/A这个写法它是 ArduinoJson 的默认值语法如果字段不存在就返回 N/A避免取到空指针。这种防御性写法在网络数据解析里非常重要因为服务端返回的字段可能因为各种原因缺失。2.4 屏幕布局设计与字体选择240x320 的屏幕我把它分成上下两部分。上半部分是标题栏显示个人财务看板和最后更新时间下半部分是 2x2 的四张卡片分别显示总资产、本月支出、贷款剩余、下期还款。字体方面TFT_eSPI 内置了几种字体我标题用 4 号字体比较大数据用 6 号字体数字清晰标签用 2 号字体。这里有个经验数字一定要用等宽或者至少数字宽度一致的字体否则数字变化时比如从 999 变成 1000整行文字会左右跳动看起来很不舒服。TFT_eSPI 的 6 号字体数字宽度是一致的可以放心用。颜色搭配上我用深色背景0x18E3接近深灰蓝数据用白色标签用浅灰贷款相关的数字用橙色提醒性质资产用绿色。这种配色在暗光环境下不刺眼白天也能看清。不要用纯黑背景配纯白文字对比度太高长时间看眼睛累。刷新策略上我一开始是每次数据更新就fillScreen全屏重绘结果发现屏幕会明显闪一下。后来改成只重绘变化的区域也就是四张卡片的数据部分标题栏和卡片边框不动。这样刷新时几乎看不出闪烁。实现方式是把每张卡片的数据区域坐标记下来更新时只fillRect那一小块再重绘文字。3. 实操过程与核心环节实现3.1 开发环境搭建与库安装开发环境我用的是Arduino IDE 2.x配合 esp32 的板级支持包。安装步骤是先在首选项的附加开发板管理器网址里填入 esp32 的包地址然后在开发板管理器里搜索 esp32 并安装。这个过程网络可能比较慢耐心等。需要安装的库有三个TFT_eSPI、ArduinoJson、以及 ESP32 自带的HTTPClient和WiFi这两个不用额外装。TFT_eSPI 装完后一定要去改User_Setup.h这一步前面讲过了。ArduinoJson 建议装 6.x 版本7.x 的 API 有些变化网上的教程大多是 6.x 的用 6.x 能少踩坑。板子选择上在工具菜单里选 ESP32 Dev ModuleFlash Size 选 4MBPartition Scheme 选 Default 4MB with spiffs。上传速度我用 921600如果烧录失败就降到 115200。烧录时如果一直卡在 Connecting...按住开发板上的 BOOT 键再点上传看到开始写入后松开。3.2 网络任务与显示任务的代码实现核心代码分成两块。网络任务用 FreeRTOS 的xTaskCreatePinnedToCore创建固定在 Core 0 上跑void networkTask(void *pvParameters) { for (;;) { if (ensureWiFi()) { fetchFinanceData(); } vTaskDelay(pdMS_TO_TICKS(300000)); // 5分钟 } } void setup() { // ... 屏幕初始化 ... xTaskCreatePinnedToCore( networkTask, NetTask, 8192, NULL, 1, NULL, 0 // Core 0 ); }注意任务栈大小我给了 8192 字节。HTTP 请求和 JSON 解析比较吃栈给少了会栈溢出表现为设备随机重启。8192 是实测稳定的值如果你请求的数据更大可以再加。显示任务就是普通的loop()跑在 Core 1 上void loop() { if (financeData.updated) { financeData.updated false; drawDashboard(); } delay(100); }这里financeData.updated是一个volatile bool因为它在两个核之间共享。不加volatile编译器可能会优化掉对它的读取导致显示任务永远看不到更新。这是双核编程的一个经典坑。3.3 数据接口的搭建与测试ESP32 端要请求的接口我用一个最简单的自建服务来提供。你可以用任何你熟悉的方式比如用 Python 的 Flask 写一个返回 JSON 的接口跑在局域网内的电脑或树莓派上。关键是接口地址要固定ESP32 才能稳定请求。测试顺序很重要先在电脑浏览器里访问接口确认返回的 JSON 格式正确再让 ESP32 去请求。如果 ESP32 请求失败先看串口打印的 HTTP 状态码404 说明地址错了超时说明网络不通500 说明服务端出错。把问题分层定位比盲目改代码高效得多。我实际测试时遇到过一个情况接口返回的 JSON 里数字是字符串类型带引号比如total_assets: 125600.50。ArduinoJson 的asfloat()对字符串数字也能解析但如果你用isfloat()判断就会失败。所以要么让服务端返回正确的数字类型要么在解析时用asfloat()直接转换不要做类型判断。3.4 外壳与摆放的实用处理电子部分做完还得考虑怎么摆。我一开始是裸板放在桌上屏幕背面朝上看着很乱。后来用 3D 打印了一个简单的支架把屏幕立起来开发板藏在后面。如果你没有 3D 打印机用亚克力板加铜柱也能搭一个或者干脆买一个现成的相框把屏幕嵌进去。散热方面ESP32 跑 Wi-Fi 会发热但财务看板负载很低5 分钟才请求一次发热可以忽略。屏幕背光倒是持续发热不过 2.4 寸屏功率不大摸上去温温的不用担心。真正要注意的是电源线的质量劣质 USB 线压降大会导致 ESP32 供电不足表现为 Wi-Fi 连不上或者随机重启。换一根粗一点的线问题往往就解决了。4. 常见问题与排查技巧实录4.1 屏幕花屏、白屏、不亮的排查顺序屏幕问题是这个项目最高频的故障。我整理了一个排查顺序按这个顺序走基本能定位到问题。现象可能原因排查方法完全白屏背光亮但无数据检查 CS、DC、RST 接线确认 User_Setup.h 引脚一致花屏、雪花SPI 速率过高或线太长把 SPI_FREQUENCY 降到 20MHz 试试屏幕不亮背光未供电检查 LED/BL 引脚确认背光 GPIO 有输出颜色反了颜色顺序配置错误在 User_Setup.h 里调整 RGB/BGR 顺序显示偏移屏幕偏移量未设置设置 TFT_OFFSET 或换用正确的驱动宏我遇到过一次花屏折腾了半天以为是接线问题最后发现是User_Setup.h里同时打开了两个驱动的宏库不知道该用哪个。这个文件里只能有一个驱动宏被打开其他都要注释掉。这个坑很隐蔽因为编译不会报错只是显示不正常。4.2 Wi-Fi 连不上与请求超时的处理Wi-Fi 连不上先确认 SSID 和密码没写错特别是密码里的特殊字符。然后确认路由器是 2.4GHz 的ESP32 不支持 5GHz Wi-Fi这是新手最容易忽略的一点。如果你的路由器是双频合一把 2.4GHz 单独分出来一个 SSID 给 ESP32 用。请求超时的话先 ping 一下接口服务器的 IP确认网络可达。如果服务器在电脑上检查电脑的防火墙有没有拦截。还有一个隐蔽的问题ESP32 和服务器不在同一个网段比如 ESP32 连的是访客网络服务器在主网络两者隔离自然请求不通。把设备放到同一个网络下就好。4.3 设备运行几天后死机的排查长期运行死机八成是内存泄漏。重点检查三个地方HTTP 请求后有没有http.end()JSON 文档有没有用DynamicJsonDocument在循环里反复分配应该用固定大小的或者复用字符串拼接有没有用String类反复创建临时对象。我的做法是定期打印ESP.getFreeHeap()观察空闲堆内存的变化。如果发现它随着时间缓慢下降那就是有泄漏。正常情况应该稳定在一个值附近波动。我实测下来稳定运行时空闲堆在 200KB 左右如果掉到 50KB 以下就要警惕了。注意不要用delay()在任务里做长时间等待它会阻塞整个任务。网络任务里我用的是vTaskDelay这是 FreeRTOS 的延时会让出 CPU 给其他任务是正确做法。4.4 数据不更新但屏幕也没报错的排查这种情况最迷惑人屏幕显示正常但数字一直不变。排查思路是分三段数据源、网络、显示。先看数据源直接在浏览器访问接口确认数据本身在变。如果数据源没变那是服务端的问题跟 ESP32 无关。再看网络看串口有没有打印请求成功的日志如果请求成功但数据没变可能是 JSON 字段名对不上解析出来是默认值。最后看显示确认financeData.updated标志位有没有被正确置位和清除如果显示任务没读到标志位屏幕就不会刷新。我踩过一次这个坑原因是网络任务里解析 JSON 后忘了置updated true数据其实拿到了但显示任务不知道。加了一行代码就好了。这种低级错误在调试时很常见所以日志一定要打全每个关键节点都打印一下出问题时一眼就能看出卡在哪。4.5 独家避坑经验汇总最后分享几条我实际做下来觉得最有价值的经验。第一先让屏幕单独跑通再接网络。很多人一上来就把所有代码写完再烧录结果屏幕不亮、网络也不通两个问题混在一起根本没法排查。正确顺序是先烧一个只显示静态文字的测试程序确认屏幕没问题再加网络功能。第二接口地址和 Wi-Fi 密码不要硬编码在代码里用WiFiManager库或者存在 SPIFFS 里。这样换网络时不用重新烧录对调试和长期使用都方便。我一开始硬编码换了个路由器就得改代码重烧很麻烦。第三给设备加一个看门狗。ESP32 有内置的看门狗但默认可能没开。在setup()里调用esp_task_wdt_init和esp_task_wdt_add让看门狗监控主任务如果任务卡死超过设定时间就自动重启。财务看板是长期运行的设备看门狗能避免它卡死后一直没人发现。第四屏幕亮度别设太高。我一开始设 100%晚上看很刺眼而且背光发热明显。调到 60% 到 70% 之间白天够用晚上也不刺眼还能延长背光寿命。这个细节看起来小但直接影响你愿不愿意把它一直摆在桌上。第五数据刷新频率别太频繁。我一开始设的是 30 秒刷新一次结果发现完全没必要财务数据又不是股票5 分钟甚至 15 分钟刷新一次都够。刷新频率低网络请求少设备更稳定也更省电。如果你用电池供电这一点尤其重要。这个项目我前后做了大概两个周末中间踩的坑基本都写在上面了。它不是什么高深的东西但胜在实用做完之后确实改变了我查看财务数据的习惯。如果你也想动手建议先从屏幕点亮开始一步步来别急着一次做完。
返回列表