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 进行正常显示更新时的功耗曲线(定时唤醒,发送设备状态,获取新图像,并在电子纸显示屏上显示):

您看到的波峰和波谷代表了 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(睡眠模式)”设置(见下方截图):

例如——通过将每天的总活动时间减少 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 按钮

7. 编译过程完成后,您应该在控制台中看到一条消息。

8. 您可以在图中显示的文件夹中找到编译后的文件。

## **上传指南 (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" 的内容。

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:

### Step 2 - 添加二进制文件
1. 在顶部空白处旁边,点击 “...” 点并选择 bootloader 二进制文件,然后输入
2. 点击 “...” 点并选择 partitions 二进制文件,然后输入
3. 点击 “...” 点并选择 boot_app0 二进制文件,然后输入
4. 点击 “...” 点并选择 firmware 二进制文件,然后输入

最后,在 Flash Tool 界面底部设置以下参数:

### Step 3 - 连接并刷写设备
1. 打开 Windows “设备管理器” 程序并滚动到底部,在那里可以找到 USB 设备。每台机器会有不同的可用设备,但请寻找类似这样的部分:

2. 接下来,使用 USB-C 线缆将 PCB 连接到 Windows 机器。确保 USB 端口位于右侧,并且 PCB 的开/关开关向下拨动为 “off(关)”。
3. 按住 BOOT 按钮(位于开/关拨动开关下方),通过将上述开关向上拨动来开启设备。您可能会听到 Windows 机器发出的声音。检查界面底部的设备管理器连接,应该会出现一个新设备。它可能是 “USB Component {{ Num }},” 或类似下面的内容:

4. 记下此设备的名称,那就是我们的 TRMNL PCB。然后回到 Flash Tool 内部,点击右下角的 “COM” 下拉菜单并选择 TRMNL PCB。最后,点击 “START” 按钮。

### Step 4 - 准备刷写新设备
在 Flash Tool 内部点击 “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):

然后点击测试按钮运行 platformio 测试(点 2)。
标签:Captive Portal, ESP32, IoT, Python脚本, TRMNL, UML, WiFi配网, 低功耗, 固件开发, 固件算法, 嵌入式固件, 嵌入式系统, 开源硬件, 显示驱动, 智能仪表盘, 智能显示屏, 深度睡眠, 物联网, 电子墨水屏, 电子纸, 电源管理, 硬件接口