usetrmnl/trmnl-firmware

GitHub: usetrmnl/trmnl-firmware

TRMNL 电子墨水屏设备的开源固件,实现了低功耗深度睡眠、WiFi 配网及 OTA 远程升级功能。

Stars: 908 | Forks: 167

# TRMNL 固件 为 [TRMNL](https://trmnl.com) 电子墨水屏创建。 ## **算法流程图** ``` graph TB Start(["Start"]) Init("Init peripherals") Start --> Init IsLongRst{"Reset button pressed > 5000 ms?"} Init --> IsLongRst ClearWifi("Wi-Fi credentials clear") IsLongRst -->|"Yes"| ClearWifi DisplayInit("Display init") IsLongRst -->|"No"| DisplayInit ClearWifi --> DisplayInit WakeReason{"Wake by user or timer?"} DisplayInit --> WakeReason ClearDisplay("Display clear") WakeReason -->|"User"| ClearDisplay IsWiFiSetup{"Wi-Fi saved?"} WakeReason -->|"Timer"| IsWiFiSetup ClearDisplay --> IsWiFiSetup NeedConfig("Show set-up message") IsWiFiSetup -->|"No"| NeedConfig %% Config Wifi RunSetup("Start config portal") NeedConfig --> RunSetup IsReset1{"Device reset?"} RunSetup -->|"Yes"| IsReset1 WipeConfig1("API key, friendly ID and WiFi clear") IsReset1 -->|"Yes"| WipeConfig1 Reboot1(["Reboot"]) WipeConfig1 --> Reboot1 IsWifiConnect{"WiFi connected?"} IsReset1 -->|"No"| IsWifiConnect %% Main Body TryConnect{"WiFi connected (5tries)?"} IsWiFiSetup -->|"Yes"| TryConnect ConnectError("Show connection error") IsWifiConnect -->|"No"| ConnectError TryConnect -->|"No"| ConnectError Sleep1(["Sleep"]) ConnectError --> Sleep1 ClockSync("Check synchronization") IsWifiConnect -->|"Yes"| ClockSync TryConnect -->|"Yes"| ClockSync IsApiSetup{"API key and friendly ID exist?"} ClockSync --> IsApiSetup %% Setup CallSetup("Ping /api/setup") IsApiSetup -->|"No"| CallSetup IsSetupSuccess{"Setup success?"} CallSetup --> IsSetupSuccess SetupError("Show setup error") IsSetupSuccess --> SetupError Sleep2(["Sleep"]) SetupError --> Sleep2 %% Check update PingServer{"Ping server, success?"} IsApiSetup -->|"Yes"| PingServer IsSetupSuccess -->|"Yes"| PingServer PingError("Show server error") PingServer -->|"No"| PingError Sleep3(["Sleep"]) PingError --> Sleep3 %% Act on update IsNeedReset{"Need to reset the device?"} PingServer -->|"Yes"| IsNeedReset IsNeedReset -->|"Yes"| WipeConfig1 IsNeedUpdate{"Need to update?"} IsNeedReset -->|"No"| IsNeedUpdate IsNeedUpdate -->|"No"| Sleep3 Update("Download and update") IsNeedUpdate -->|"Yes"| Update Update --> Sleep3 ``` ## **Web Server 端点** 通过 captive portal 连接 WiFi 后,设备会将其 Mac 地址更换为从服务器获取的 API Key 和 Friendly ID(这些信息会保存在设备上)。 ``` GET /api/setup headers = { 'ID' => 'XX:XX:XX:XX:XX' # mac address } response example (success): { "status": 200, "api_key": "2r--SahjsAKCFksVcped2Q", "friendly_id": "917F0B", "image_url": "https://trmnl.com/images/setup/setup-logo.bmp", "filename": "empty_state" } response example (fail, device with this Mac Address not found) { "status" => 404, "api_key" => nil, "friendly_id" => nil, "image_url" => nil, "filename" => nil } ``` 假设 Setup 端点成功响应,后续的请求将仅用于获取图像 / 显示内容: ``` GET /api/display headers = { 'ID' => 'XX:XX:XX:XX', 'Access-Token' => '2r--SahjsAKCFksVcped2Q', 'Refresh-Rate' => '1800', 'Battery-Voltage' => '4.1', 'FW-Version' => '2.1.3', 'RSSI' => '-69' } response example (success, device found with this access token): { "status"=>0, # will be 202 if no user_id is attached to device "image_url"=>"https://trmnl.s3.us-east-2.amazonaws.com/path-to-img.bmp", "filename"=>"2024-09-20T00:00:00", "update_firmware"=>false, "firmware_url"=>nil, "refresh_rate"=>"1800", "reset_firmware"=>false } response example (success, device found AND needs soft reset): { "status"=>0, "image_url"=>"https://trmnl.s3.us-east-2.amazonaws.com/path-to-img.bmp", "filename"=>"name-of-img.bmp", "update_firmware"=>false, "firmware_url"=>nil, "refresh_rate"=>"1800", "reset_firmware"=>true } response example (success, device found AND needs firmware update): { "status"=>0, "image_url"=>"https://trmnl.s3.us-east-2.amazonaws.com/path-to-img.bmp", "filename"=>"name-of-img.bmp", "update_firmware"=>true, "firmware_url"=>"https://trmnl.s3.us-east-2.amazonaws.com/path-to-firmware.bin", "refresh_rate"=>"1800", "reset_firmware"=>false } response example (fail, device not found for this access token): {"status"=>500, "error"=>"Device not found"} if 'FW-Version' header != web server `Setting.firmware_download_url`, server will include absolute URL from which to download firmware. ``` 如果设备检测到 `api/display` 端点的响应数据存在问题,日志将被发送到服务器。 ``` POST /api/log # 示例请求 tbd ``` ## **功耗** 首先介绍一点背景知识。TRMNL OG 内部的 ESP32-C3 是 Espressif 较新、更高效的微控制器之一。对于电池供电的应用,其设计旨在当您的项目无需处于活动状态时,通过休眠来节省电量。有两种睡眠模式——light(轻度)和 deep(深度)。Deep sleep 节省的电量最多,但代价是会丢失主内存中的内容。在定时唤醒的情况下,最低功耗约为 4uA @ 3V,但 TRMNL 需要能够通过按键唤醒。在 deep sleep 期间保持 GPIO 活动(以检测按键按下)平均使用约 100uA(参见下方的功耗曲线)。这意味着一块 2500mAh 的电池理论上可以让 TRMNL 在此状态下续航约 25,000 小时。 ![TRMNL deep sleep 功耗](/pics/deep-sleep-power-consumption.png) 当然,一个永久处于睡眠状态的设备并没有太大用处,因此下方展示的是 TRMNL 进行正常显示更新时的功耗曲线(定时唤醒,发送设备状态,获取新图像,并在电子纸显示屏上显示): ![TRMNL 设备完整周期功耗](/pics/full-cycle.png) 您看到的波峰和波谷代表了 ESP32-C3 在约 10.5 秒的更新周期中不同时间点汲取的电流(功率)变化。大部分能量在 WiFi 处于活动状态时被消耗(在第 3 到 9 秒之间)。图表最后部分的高频波峰来自电子纸显示屏的更新循环(平均功率非常低)。更新所需的总电荷显示在右下角(0.67c)。该值以库仑 为单位,代表流经电路的电子数量。 0.67 C = 0.186111 mAh 如果我们忽略 ESP32 的睡眠时段,每次显示更新消耗的能量将允许进行 2500/0.186111 = 13433 次更新。如果我们将 TRMNL 账户配置为每 15 分钟更新一次信息,我们将每天请求 96 次更新,电池电量可以维持 140 天(13433 / 96)。这与实际结果相差不远。在释放全部能量之前,电池电压会降至安全阈值以下,而且在上述公式中,我们没有计算睡眠期间消耗的能量,也没有计算 TRMNL 电源(电池和 ESP32 之间)中的能量损耗。实际结果在充满电的情况下将接近 120 天。 我们可以通过在睡眠时间禁用更新来进一步延长电池寿命。在 TRMNL Web 门户中,有一个“Sleep Mode(睡眠模式)”设置(见下方截图): ![TRMNL 睡眠模式](/pics/sleep-mode.png) 例如——通过将每天的总活动时间减少 8 小时,每天的更新次数(设置为如上所述的 15 分钟间隔)从 96 次变为 64 次。睡眠模式设置为 8 小时后,我们的电池寿命得到延长: 13433 / 64 = 210 天(理论最大值) **专为效率设计** 锂电池根据其电量状态输出 3.7 到 4.2 伏特之间的电压。ESP32 的工作电压在 2.8V 到 3.3V 之间。为了从电池为 ESP32 供电,需要降低电压。 主要有两种类型的电源稳压器——[buck(降压)](https://en.wikipedia.org/wiki/Buck_converter) 转换器和 [linear(线性)](https://en.wikipedia.org/wiki/Linear_regulator) 稳压器。许多 ESP32 产品使用线性稳压器,因为它们更便宜。这种节省是有代价的——它们会将高达 20% 的电池能量作为废热丢弃。您的 TRMNL 设计采用了 buck 转换器,以安全高效地为 ESP32 供电。这确保了电池能量的最佳利用。在 TRMNL,我们一直在寻找能够改善电池寿命的额外软件优化方案。 ## **低电量** 锂离子电池在我们的生活中无处不在;它们几乎在每个人的口袋/钱包里以及您每天使用的许多其他设备中。它们带来了一系列好处,但也存在一些风险,并且需要保养以保持最佳工作状态。您的 TRMNL 可以防止电池过度充电,但在电量低时需要您的帮助以防止出现问题。耗尽的锂离子电池会出现两个主要问题: 1. 如果它们低于特定电压,则需要使用特殊的涓流充电器进行充电;TRMNL 的充电电路将无法为它们充电。 2. 长时间处于无电状态的电池可能会“放气”。这意味着电解液发生化学反应并释放氢气。您可能见过处于这种状态的电池——金属外壳像气球一样膨胀。 应避免上述情况,但 `#2` 也可能具有危险性。如果您的电池膨胀,请在当地回收点安全处置,并联系 TRMNL 支持以获取新电池。 即使您的 TRMNL 已断开连接(电源开关处于关闭位置),其电池也会缓慢自放电。为了保持 TRMNL 电池的最佳性能: 1. 当显示低电量图像时,请立即为设备充电。 2. 如果您关闭 TRMNL 进行存储/移动,请确保电池电量未处于低电量状态。 ## **版本日志** 参见 [releases](https://github.com/usetrmnl/firmware/releases)。如需旧版本,请访问 [此处](https://github.com/usetrmnl/firmware/issues/95)。 ## **编译指南** 刷写固件有技术和非技术选项。 **无需代码** * 直接从 Web 浏览器刷写:https://trmnl.com/flash * 从您的 TRMNL 仪表板 > Device settings 启用 OTA 更新(仅限原生硬件) **面向开发者** 1. 安装 VS Code:https://code.visualstudio.com 2. 安装 PlatformIO:https://platformio.org/install/ide?install=vscode 3. 安装 Git:https://git-scm.com/book/en/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Visual-Studio-Code 4. 克隆此仓库:https://github.com/usetrmnl/trmnl-firmware 5. 在 VS Code 工作区中打开项目 6. 配置项目后,点击位于屏幕底部的 PlatformIO -> Build 按钮 ![Image Alt text](/pics/build_icon.JPG "Build") 7. 编译过程完成后,您应该在控制台中看到一条消息。 ![Image Alt text](/pics/console.JPG "Console") 8. 您可以在图中显示的文件夹中找到编译后的文件。 ![Image Alt text](/pics/bin_folder.png "Bin") ## **上传指南 (PlatformIO)** 1. 关闭 PCB。使用 USB-C 线缆将 PCB 连接到 PC。按住 boot 按钮的同时,打开 PCB。松开 boot 按钮。这将使板子进入刷写模式。 2. Mac/Windows:从下拉列表中选择正确的 COM 端口(或保留为 "Auto")。Ubuntu:通过 lsusb 查找类似 "/dev/ttyACMO USB JTAG/serial debug unit" 或 "Espressif USB JTAG/serial debug unit" 的内容。 ![Image Alt text](/pics/fs.jpg "FS") 3. 点击 "PlatformIO: Upload" 按钮。 ## **上传指南 (ESP32 Flash Download Tool)** 所需工具: 1. Windows OS 2. Flash Tool 3.9.5 3. 要合并的二进制文件 - `bootloader.bin`, `firmware.bin`, `partitions.bin`(见上文编译指南) 4. Bootloader 二进制文件(`boot_app0.bin`,位于 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/) ### Step 1 - 配置 flash 工具 打开 Flash Tool(可执行文件),选择这些参数,然后点击 OK: ![Image Alt text](/pics/select_screen.jpg "select screen") ### Step 2 - 添加二进制文件 1. 在顶部空白处旁边,点击 “...” 点并选择 bootloader 二进制文件,然后输入 2. 点击 “...” 点并选择 partitions 二进制文件,然后输入 3. 点击 “...” 点并选择 boot_app0 二进制文件,然后输入 4. 点击 “...” 点并选择 firmware 二进制文件,然后输入 ![Image Alt text](/pics/binaries.jpg "binaries") 最后,在 Flash Tool 界面底部设置以下参数: ![Image Alt text](/pics/settings.jpg "settings") ### Step 3 - 连接并刷写设备 1. 打开 Windows “设备管理器” 程序并滚动到底部,在那里可以找到 USB 设备。每台机器会有不同的可用设备,但请寻找类似这样的部分: ![Image Alt text](/pics/devices.jpg "devices") 2. 接下来,使用 USB-C 线缆将 PCB 连接到 Windows 机器。确保 USB 端口位于右侧,并且 PCB 的开/关开关向下拨动为 “off(关)”。 3. 按住 BOOT 按钮(位于开/关拨动开关下方),通过将上述开关向上拨动来开启设备。您可能会听到 Windows 机器发出的声音。检查界面底部的设备管理器连接,应该会出现一个新设备。它可能是 “USB Component {{ Num }},” 或类似下面的内容: ![Image Alt text](/pics/select_device.jpg "select_device") 4. 记下此设备的名称,那就是我们的 TRMNL PCB。然后回到 Flash Tool 内部,点击右下角的 “COM” 下拉菜单并选择 TRMNL PCB。最后,点击 “START” 按钮。 ![Image Alt text](/pics/start.jpg "start") ### Step 4 - 准备刷写新设备 在 Flash Tool 内部点击 “STOP” 按钮。 ![Image Alt text](/pics/stop.jpg "stop") 接下来关闭(向下拨动)并拔掉 PCB。您现在已准备好刷写另一台设备 - 参见 Step 1。 ## **修改指南** 如果您想运行本地测试,您需要在 PATH 中安装 g++/gcc(例如,作为 MinGW 的一部分): - 从 https://github.com/niXman/mingw-builds-binaries/ 获取 MinGW 在线安装程序 - 将已安装文件夹中的 `bin` 路径(例如 `c:\mingw64\bin`)添加到您的 PATH - 重启 Visual Studio Code 现在您可以点击工作室底部,从 "env:esp32..." 切换到 "esp:native"(点 1): ![](https://static.pigsec.cn/wp-content/uploads/repos/2026/03/0226420710143154.png) 然后点击测试按钮运行 platformio 测试(点 2)。
标签:Captive Portal, ESP32, IoT, Python脚本, TRMNL, UML, WiFi配网, 低功耗, 固件开发, 固件算法, 嵌入式固件, 嵌入式系统, 开源硬件, 显示驱动, 智能仪表盘, 智能显示屏, 深度睡眠, 物联网, 电子墨水屏, 电子纸, 电源管理, 硬件接口