# esp32-s3-lua **Repository Path**: michaelhyg/esp32-s3-lua ## Basic Information - **Project Name**: esp32-s3-lua - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-20 - **Last Updated**: 2026-05-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ESP32-S3 Demo 这是一个 ESP-IDF 示例工程,用户代码统一放在 `User/` 目录中,并按 `app / services / drivers` 分层。 ## 功能 - `User/app/` 只保留应用入口 - `User/services/` 放业务和 demo 逻辑 - `User/drivers/` 放可复用的底层驱动 - 内置一个 WS2812 RMT 驱动 - Lua 侧 WS2812 默认使用 `GPIO48` - 同时接入项目本地 Lua 服务,支持脚本执行、保存和 HTTP 调试 - 设备本地起 HTTP Web 调试页,界面风格对齐 STM32 的 Lua 调试台 - Lua 脚本支持按槽位保存,可通过 `script.set_boot("xxx.lua")` 指定启动脚本 - Lua 侧提供 `runtime.*` 运行状态查询、`module.*` 设备能力发现和 `log.*` 日志尾部查询 - Lua 侧提供 `dbg.*`、`inspect.*`、`rpc.*` 作为第一版弱调试服务 - Lua 里可以直接 `dofile("xxx.lua")` 调用网页里保存的脚本 - Wi-Fi Lua API 支持最多 5 个 profile,按优先级顺序依次尝试连接 - Wi-Fi profile 可以通过 Lua 的 `wifi.save()` / `wifi.load()` 持久化到 NVS - Wi-Fi 连接、扫描、AP、固定 IP 和 DHCP 现在统一由 Lua 管理 - 网页里提供了 5 槽 Wi-Fi 可视化编辑器,可以直接读取、修改和保存 profile - 另有可选的 `socket.*` BSD socket 模块,以及基于 `wifi_manager` 的 `wifi_manager.*` 网络封装 ## WS2812 引脚说明 WS2812 可以使用 ESP32-S3 上几乎所有“可输出”的 GPIO。 当前 Lua 模块默认使用 `GPIO48`。如果你要换脚,改 [User/services/lua/lua_modules.c](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/lua_modules.c) 里的 `LUA_WS2812_DEFAULT_PIN` 即可。 ## 运行方式 1. 安装并配置好 ESP-IDF 2. 进入工程目录后执行: ```bash idf.py set-target esp32s3 idf.py build idf.py flash monitor ``` 当前默认不再由 C 代码自动起 Wi-Fi,避免和 Lua 侧连接逻辑冲突。 如果你要访问 Lua Web 调试页,先通过串口 Lua 调试或启动脚本执行: ```lua wifi.ap("ESP32S3-DEMO", "12345678", 1, 4) ``` 然后再打开 `http://192.168.4.1/`。 ## 目录结构 - [main/](/Users/michael/hu/esp32/esp32-s3-base/main) 只保留系统入口 - [User/app/](/Users/michael/hu/esp32/esp32-s3-base/User/app) 放应用入口 - [User/services/](/Users/michael/hu/esp32/esp32-s3-base/User/services) 放业务和 demo - [User/services/lua/](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua) 放 Lua 运行时和调试服务 - [User/services/lua/modules/](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/modules) 放从 `lua_modules.c` 拆出的独立 Lua 模块实现 - [User/third_party/lua/](/Users/michael/hu/esp32/esp32-s3-base/User/third_party/lua) 放 Lua 解释器源码 - [User/drivers/](/Users/michael/hu/esp32/esp32-s3-base/User/drivers) 放驱动 ## Lua Lua 运行时和调试接口现在放在本工程自己的 `User/services/lua/` 下面, Lua 解释器源码也被放进 `User/third_party/lua/`,不再依赖仓库根目录的 公共组件。这样 C3 和 S3 可以各自独立维护 Lua 入口、服务和模块注册逻辑。 网页保存脚本时填写 `slot` 名称即可保存不同的 Lua 文件,例如 `main.lua`、 `test_uart.lua`、`test_ws2812.lua`。在脚本里可以用 `dofile("test_uart.lua")` 去调用已保存的脚本。 脚本启动选择现在按下面规则执行: - 如果显式设置了 `script.set_boot("xxx.lua")`,优先运行这个脚本 - 如果没有显式启动脚本,则按 `main.lua`、`startup.lua`、列表首个脚本的顺序回退 - `script.set_boot()` 传空或 `nil` 时会清除显式启动脚本配置 脚本管理 API 第一版示例: ```lua print(script.boot().slot_name) for _, item in ipairs(script.list()) do print(item.slot_name, item.script_length, item.is_boot) end local code = script.load("test_uart.lua") print(#code) script.set_boot("test_uart.lua") ``` 运行状态和模块能力发现 API 示例: ```lua print(runtime.state(), runtime.script()) print(runtime.uptime(), runtime.last_error()) local log_state = log.level() print(log_state.level, log_state.count) for _, item in ipairs(log.tail(20)) do print(item.seq, item.level, item.message) end log.clear() for _, item in ipairs(module.list()) do print(item.name, item.category, item.version) end if module.exists("wifi") then print(module.info("wifi").summary) end ``` 第一版弱调试 API 示例: ```lua print(dbg.status().runtime_state) print(dbg.last_error()) inspect.set("threshold", 42) print(inspect.get("threshold")) inspect.watch("threshold") for _, item in ipairs(inspect.watches()) do print(item.name, item.summary) end rpc.register("read_threshold", function() return threshold end) print(rpc.call("read_threshold")) ``` 当前 `dbg.pause()` / `dbg.step()` 主要用于预留上位机调试流程;真单步和抢占式暂停会在后续串口调试协议升级时再做。 Lua 协作任务 API 第一版示例: ```lua task.create(function() print("worker start") task.sleep(200) print("worker done") end, { name = "worker" }) task.on("hello", function(data) print("event", data) task.yield() print("event resumed") end) task.emit("hello", "world") for _, item in ipairs(task.list()) do print(item.id, item.name, item.state) end ``` `task.*` 基于同一个 Lua VM 内的 coroutine 协作调度,不会为每个 Lua task 创建 FreeRTOS task。 完整使用说明见 [docs/LUA_TASK_API.md](/Users/michael/hu/esp32/esp32-s3-base/docs/LUA_TASK_API.md)。 Wi-Fi API 用法示例: ```lua wifi.profile(1, "HomeWiFi", "password1", 15000) wifi.profile(2, "OfficeWiFi", "password2", 15000) wifi.profile(3, "Hotspot", "password3", 10000) wifi.connect() wifi.save() ``` `wifi.connect()` 会按 1 到 5 的顺序依次尝试 `wifi.list()` 中配置好的 profile, 连不上就自动切换到下一个。 更多 Wi-Fi Lua API: ```lua wifi.scan() wifi.scan_results() wifi.ap("ESP32S3-DEMO", "12345678", 1, 4) wifi.ap_info() wifi.ap_clients() wifi.set_mac("sta", "02:00:00:00:00:01") wifi.set_ip("sta", "192.168.2.33", "255.255.255.0", "192.168.2.1") wifi.dhcp("sta") wifi.info() wifi.disconnect() wifi.on_connect(function(connected, st) print("wifi changed", connected, st.ip, st.ssid) end) ``` ## Lua 测试脚本 STM32H743 base 那套模块测试 demo 已经同步到本工程的 `User/test/`: - `main.lua` - `test_memory.lua` - `test_core_tooling.lua` - `test_script_probe.lua` - `test_uart.lua` - `test_i2c.lua` - `test_spi.lua` - `test_pwm.lua` - `test_tmr_gpio.lua` - `test_ws2812.lua` - `test_task.lua` - `test_task_control.lua` - `test_mqtt.lua` - `test_mqtt_config.lua` - `test_mqtt_common.lua` - `test_mqtt_connect.lua` - `test_mqtt_status.lua` - `test_mqtt_pubsub.lua` - `test_mqtt_full.lua` - `test_wifi.lua` - `test_wifi_dhcp.lua` - `test_wifi_scan.lua` - `test_wifi_ap.lua` - `test_wifi_info.lua` - `test_wifi_static_ip.lua` - `test_wifi_events.lua` - `test_serial.lua` - `test_fs_kv_sys.lua` 你可以直接把这些脚本贴到网页 Lua 调试台里运行,用来逐个验证模块 API。 其中 `main.lua` 是默认入口脚本的源码模板,保存到设备后可以作为 `main.lua` 启动槽位使用。 这些脚本默认只保存在仓库里,不会随固件自动写入 LittleFS;如果要在设备上 长期保留,需要通过网页保存或串口 `@@LUA_STORE` 再写入一次。 其中 `test_ws2812.lua` 在 S3 上默认走 `GPIO48`,和 Lua 模块默认引脚保持一致。 如果要组合多个脚本,建议把公共逻辑单独存成一个 slot,然后在主脚本里 通过 `dofile("公共脚本名.lua")` 复用。 MQTT 这组脚本里,`test_mqtt_config.lua` 和 `test_mqtt_common.lua` 是公共依赖; `test_mqtt.lua` 会直接转到 `test_mqtt_pubsub.lua`,因此先把这两个公共脚本存到设备里会更顺手。 ## Lua 存储分区 当前工程已经切到 `16MB flash` 方案,并在 Flash 中新增了 `littlefs` 分区来保存 Lua 脚本文件。 默认的系统 `nvs` 仍然保留给 Wi-Fi 和系统数据,旧 `lua_nvs` 已经从当前分区表中移除。 现在分区表已经进一步整理成连续布局,回收了历史遗留的空洞空间,并把这部分容量并回给 `littlefs`。 当前行为如下: - 新固件只从 `littlefs:/lua/` 读取和写入 Lua 脚本 - 旧 `lua_nvs` 备份数据在确认迁移完成后已经删除 - 旧系统 `nvs` 的 Wi-Fi 和其他系统数据不会被一起擦掉 - 由于本次回收空洞后 `littlefs` 起始偏移改变,设备里旧布局下的 Lua 文件不会继续沿用,需要按新分区重新保存 当前分区重点如下: - `factory`:`0x20000`,`3MB` - `ota_0`:`0x320000`,`3MB` - `ota_1`:`0x620000`,`3MB` - `littlefs`:`0x920000`,`0x6E0000`,约 `6.875MB` - `0x10000 ~ 0x1FFFF`:保留给 `otadata` 对齐和应用分区对齐 处理器内部的 Lua 文件结构现在是: - 挂载点:`/littlefs` - 脚本目录:`/littlefs/lua` - 实际文件名:`/littlefs/lua/.lua` - 内部标记文件:`/littlefs/lua/.ready` 这样切换后,Lua 脚本和系统参数会彻底分开,脚本容量也不会再受旧 `nvs` 限制。 ## 串口 Lua 调试 本工程已经内置串口 Lua 调试服务,串口默认使用 `UART0`,波特率 `115200`。 当前协议已经切到二进制帧: ```text AA55 + payload_len_u32_le + cmd_u8 + seq_u8 + payload + crc16_ccitt_be ``` CRC 使用 `CRC16-CCITT`,初值 `0xFFFF`,覆盖 `header + payload`,不覆盖 `AA55` 同步头。Qt 上位机现在通过这套协议发送 `PING`、`EXEC`、`STORE`、`LIST`、 `MODULE_LIST`、`RESET` 等命令,普通 `print()` 输出仍然作为串口日志显示。 当前串口调试实现要点如下: - `EXEC` 和 `SCRIPT_RUN` 不再由串口接收任务直接执行 Lua,而是投递到独立的 `lua_exec` FreeRTOS 任务。 - 串口接收任务保持响应,即使用户脚本写成 `while true do end`,上位机仍可发送 `RESET` 让设备重启。 - 设备侧同一时间只允许一个 Lua 执行任务运行;如果已有脚本在跑,新的 `EXEC` / `SCRIPT_RUN` 会返回 `lua script already running`。 - 串口响应帧发送加了互斥保护,避免日志、异步执行结果和控制命令回包互相打乱。 - Lua 控制台初始化提前到启动脚本之前,避免开机脚本死循环时上位机完全接不进设备。 如果要快速验证串口链路,可以先用 [User/test/test_serial.lua](/Users/michael/hu/esp32/esp32-s3-base/User/test/test_serial.lua) 做最小脚本,然后通过 Qt 上位机发送执行。 ## 最近固件变更 当前代码相对上一版主要做了这些调整: - Lua 运行时已打开 `math` 标准库,可以直接使用 `math.floor()`、`math.abs()`、`math.random()` 等函数。 - `lmathlib.c` 已加入 `User` 组件编译源,避免只注册库名但链接不到 `luaopen_math`。 - 本地 `managed_components/joltwallet__littlefs` 已显式加入 `EXTRA_COMPONENT_DIRS`,`User` 组件也显式依赖它,离线构建时不再因为 component manager 没启用而找不到 `esp_littlefs.h`。 - 串口 Lua 调试改成异步执行脚本,解决 Lua 死循环时上位机 `RESET` 无法被处理的问题。 - 设备已验证:发送 `while true do end` 后继续发送 `RESET`,设备能够返回重启响应帧并复位。 - 本次 `task.*` 相关改动重点是补齐 task 控制 API、mailbox 消息收发,以及基于优先级的协作调度能力。 - 新增 `task.*` Lua 协作任务模块,支持 `task.create()`、`task.sleep()`、`task.yield()`、`task.list()`、`task.on()` 和 `task.emit()`。 - `task.*` 继续补齐 `task.stop()`、`task.pause()`、`task.resume()`、`task.restart()`、`task.set_priority()`、`task.send()`、`task.recv()` 和 `task.current()`。 - `task.*` 使用同一个 Lua VM 内的 coroutine 协作调度,不会为每个 Lua task 创建 FreeRTOS task;当前已支持 `waiting`、`paused`、`stopping` 等控制状态,单个 task 报错会进入 `error` 状态并记录 `last_error`。 - `task.*` 实现已从 [User/services/lua/lua_modules.c](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/lua_modules.c) 拆到 [User/services/lua/modules/lua_task_module.c](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/modules/lua_task_module.c),`lua_modules.c` 只保留注册、轮询和统计转接。 - `tmr.*`、`ws2812.*` 以及 `gpio/uart/i2c/spi/pwm` 这一组 peripheral Lua API 也已继续拆到 [User/services/lua/modules/](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/modules),其中公共 pin/总线/打包辅助收口到 `lua_peripheral_common.*`,方便后续继续拆剩余模块。 - `sys/log/runtime/module/dbg/inspect/rpc/script` 这一组上位机调试核心模块也已拆到 [User/services/lua/modules/](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/modules);其中 `module.list()` / `module.info()` / `module.exists()` 以及上位机模块扫描依赖的目录表收口到 `lua_module_catalog.*`,`runtime.tasks()` 继续通过 `task`/`tmr` 模块统计接口取数,避免共享状态散落回 `lua_modules.c`。 - `wifi/mqtt` 这一组网络模块也已从 [User/services/lua/lua_modules.c](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/lua_modules.c) 完整拆到 [User/services/lua/modules/lua_legacy_wifi_module.c](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/modules/lua_legacy_wifi_module.c) 和 [User/services/lua/modules/lua_mqtt_module.c](/Users/michael/hu/esp32/esp32-s3-base/User/services/lua/modules/lua_mqtt_module.c),`lua_modules.c` 现在只保留模块目录、Wi-Fi profile wrapper 以及各模块 `init/open/poll` 转接。 - `lua_module_catalog.*` 继续作为上位机模块扫描唯一来源;`wifi` 只有在 `CONFIG_LUA_MOD_WIFI_MANAGER` 或 `CONFIG_LUA_MOD_WIFI` 编译打开时才会上报,关闭后重新编译烧录即可从模块列表消失。 - 新增 `CONFIG_LUA_MOD_TASK` 条件编译开关,并加入 `sdkconfig.csv`,后续可以和其他 Lua 模块一样裁剪。 - [tools/apply_sdkconfig_csv.py](/Users/michael/hu/esp32/esp32-s3-base/tools/apply_sdkconfig_csv.py) 现在会优先复用 `.vscode/settings.json` 里的 `idf.buildPath`,默认走 `build_s3` 做 `idf.py -B reconfigure`,避免误用旧的 `build/` 目录;[docs/SDKCONFIG_CSV.md](/Users/michael/hu/esp32/esp32-s3-base/docs/SDKCONFIG_CSV.md) 也同步补充了构建目录选择规则。 - 每个 task 现在带有独立 mailbox,支持 `task.send()` / `task.recv()` 做轻量消息传递;`task.set_priority()` 可以调整同一轮 Lua 轮询中的执行先后顺序。 - `task.list()` 已补充 `priority`、`mailbox_count`、`paused_state` 等字段;`runtime.tasks()` 也已补充 Lua task 数量、ready/sleeping/waiting/paused/error 统计,`module.list()` 也能发现 `task` 模块。 - 新增 [docs/LUA_TASK_API.md](/Users/michael/hu/esp32/esp32-s3-base/docs/LUA_TASK_API.md) 作为 task Lua 使用文档。 - 新增 [User/test/test_task.lua](/Users/michael/hu/esp32/esp32-s3-base/User/test/test_task.lua),用于测试无限打印 task 和 WS2812 彩虹呼吸 task 并行运行;长循环使用 `task.sleep()`,避免阻塞 Lua VM。 - 新增 [User/test/test_task_control.lua](/Users/michael/hu/esp32/esp32-s3-base/User/test/test_task_control.lua),用于覆盖 task 暂停、恢复、重启、优先级和 mailbox 收发。 上位机如果想直接拉 HTTP 数据,也可以请求 `GET /api/modules`,返回当前固件里 已经编译进去的模块名称列表,适合启动时做一次能力发现并打印到日志窗口。