LK 博客
从 0 到 1 让 ESP32-S3 跑 JavaScript:Moddable SDK 与 xsbug 调试初体验
嵌入式
约 1 分钟阅读 0 赞 0 条评论 思源黑体

从 0 到 1 让 ESP32-S3 跑 JavaScript:Moddable SDK 与 xsbug 调试初体验

Yukikaze
Yukikaze @Yukikaze
累计点赞 0 登录后每个账号只能点一次
内容长度 0 正文词元数
正文
目录会跟随阅读位置移动。
阅读进度

从 0 到 1 让 ESP32-S3 跑 JavaScript:Moddable SDK 与 xsbug 调试初体验

这篇记录的是一个最小可运行的 Moddable 工程搭建过程:在 Windows + VS Code 环境下,使用 Moddable SDK 编写 JavaScript,编译并烧录到 ESP32-S3,最后通过 GPIO46 实现 LED 闪烁。

工程目标很明确:先把环境跑通,再从最基础的“点灯”开始验证整条链路。

Moddable 是什么

Moddable 是一个面向嵌入式设备的 JavaScript 开发平台。它的核心是 XS JavaScript 引擎,配套提供了构建工具、硬件访问模块、调试器和各类设备平台适配。

传统 ESP32 开发通常直接写 C/C++,使用 ESP-IDF 的 CMake/Ninja 工具链完成编译和烧录。Moddable 并不是替代 ESP-IDF 的底层能力,而是在 ESP-IDF 之上增加了一层 JavaScript 应用开发模型:

  • 用 JavaScript 编写业务逻辑。
  • manifest.json 描述应用依赖和入口模块。
  • mcconfig 生成并驱动底层工程构建。
  • 在 ESP32 平台上继续依赖 ESP-IDF、CMake、Ninja 和芯片工具链。
  • 通过 xsbug 支持 JavaScript 级别的调试。

所以,使用 Moddable 开发 ESP32-S3,本质上还是会走 ESP-IDF 的构建体系。只是我们日常操作的入口从 idf.py build/flash 变成了 mcconfig

本工程目标

本工程是一个 ESP32-S3 的最小点灯示例:

  • 目标芯片:ESP32-S3。
  • LED 引脚:GPIO46。
  • 开发语言:JavaScript。
  • SDK:Moddable SDK。
  • 底层工具链:ESP-IDF v6.0。
  • 开发工具:VS Code。
  • 烧录端口:优先自动检测,当前设备曾识别为 COM9

当前工程已经开源,仓库地址:

https://github.com/Moyaoyyy/LED_ESP32_JS

如果要在本机复现,可以直接克隆工程:

git clone https://github.com/Moyaoyyy/LED_ESP32_JS

本次开发时,由于本机工程曾放在带空格的目录下(作者早年的傻逼设计),导致后面不得不专门加了一个路径映射脚本,避免部分构建工具在 Windows 下处理带空格路径时出问题。

目录结构

当前工程的核心文件如下:

LED_ESP32_JS
├─ main.js
├─ manifest.json
├─ README.md
├─ .vscode
│  ├─ settings.json
│  └─ tasks.json
├─ tools
│  ├─ env.cmd
│  ├─ check.cmd
│  ├─ build.cmd
│  ├─ flash.cmd
│  ├─ debug.cmd
│  ├─ clean.cmd
│  └─ run-mapped.cmd
└─ docs
   └─ moddable-esp32s3-gpio46-build-blog.md

这里没有创建复杂的应用框架。点灯阶段只保留最小代码和必要的工具脚本,目的是让编译、烧录、调试这些基础链路先稳定下来。

安装 Moddable SDK

Moddable SDK 按要求放在 D 盘根目录:

D:\moddable

获取方式是从官方仓库克隆:

git clone https://github.com/Moddable-OpenSource/moddable D:\moddable

为了让命令行可以找到 Moddable 工具,需要设置 MODDABLE

set MODDABLE=D:\moddable

实际工程里没有完全依赖系统全局环境变量,而是在 tools\env.cmd 里做了兜底:

if not defined MODDABLE set "MODDABLE=D:\moddable"

这样即使 VS Code 终端没有继承到全局变量,任务脚本也能找到 Moddable SDK。

ESP-IDF 环境

如果电脑上还没有 ESP-IDF,需要先安装 ESP-IDF,再回到这个工程执行编译和烧录。

原因是 Moddable 在 ESP32-S3 上并不是脱离 ESP-IDF 单独工作的。JavaScript 代码最终要被打包进 ESP32-S3 固件里,而固件构建、芯片工具链、CMake/Ninja、分区表、bootloader、烧录工具和串口/JTAG 调试能力都来自 ESP-IDF。简单说:

  • Moddable 负责 JavaScript 运行时、模块系统和 mcconfig 构建入口。
  • ESP-IDF 负责 ESP32-S3 的底层 SDK、交叉编译器、CMake/Ninja、烧录工具和芯片支持。
  • 没有 ESP-IDF,mcconfig -p esp32/esp32s3_cdc 就无法真正生成和烧录 ESP32-S3 固件。

没装 ESP-IDF 时怎么安装

ESP-IDF v6.0 及以上版本在 Windows 上推荐使用 ESP-IDF 安装管理器,也就是 EIM。最省事的方式是安装 GUI 版本:

winget install Espressif.EIM

如果更习惯命令行,也可以安装 CLI 版本:

winget install Espressif.EIM-CLI

安装好 EIM 后,推荐按下面步骤做:

  1. 打开 ESP-IDF 安装管理器
  2. 选择 新安装
  3. 如果只是普通 ESP-IDF 项目,可以用简易安装;本工程建议走自定义安装,选择 ESP-IDF v6.0
  4. 安装路径建议使用不带空格、不带中文的短路径,例如:
C:\esp\v6.0\esp-idf
  1. 安装完成后,打开 EIM 创建的 IDF 终端,或者在 VS Code ESP-IDF 插件里选择刚安装的 ESP-IDF。

如果使用 EIM CLI,可以先进入交互式安装向导:

eim wizard

如果版本列表里能看到 v6.0,也可以直接指定版本安装:

eim install -i v6.0

安装完成后,确认环境是否正常:

idf.py --version
where idf.py

如果已经手动克隆了 ESP-IDF,或者使用的是传统安装方式,也可以在 ESP-IDF 目录下安装 ESP32-S3 所需工具:

cd C:\esp\v6.0\esp-idf
install.bat esp32s3
export.bat

PowerShell 下对应命令是:

cd C:\esp\v6.0\esp-idf
.\install.ps1 esp32s3
.\export.ps1

这里的 install 会安装交叉编译工具链、CMake/Ninja、OpenOCD、esptool 以及 ESP-IDF 需要的 Python 虚拟环境;export 会把这些工具加入当前终端的环境变量。注意 export 只对当前终端会话生效,所以 VS Code 任务里才会专门调用 tools\env.cmd 自动激活 ESP-IDF。

本机已经安装 ESP-IDF,并且 VS Code ESP-IDF 插件也已经激活。当前配置记录在 .vscode\settings.json

{
    "idf.currentSetup": "C:\\esp\\v6.0\\esp-idf",
    "idf.portWin": "COM9",
    "idf.openOcdConfigs": [
        "board/esp32s3-builtin.cfg"
    ]
}

ESP32-S3 对应的 ESP-IDF 路径是:

C:\esp\v6.0\esp-idf

需要注意的是,Moddable 仍然会调用 ESP-IDF 的 export.batidf.py。这里看到 Python 并不表示工程变成了 Python 项目,idf.py 只是 ESP-IDF 官方提供的构建入口,它底层仍然会驱动 CMake 和 Ninja。

工程脚本里通过 tools\env.cmd 自动完成 ESP-IDF 环境激活:

call "%IDF_PATH%\export.bat" >nul

如果 IDF_PATH 没有提前设置,脚本会优先读取 .vscode\settings.json 里的 idf.currentSetup,再回退到 C:\esp\v6.0\esp-idf

Moddable 应用清单

Moddable 工程的入口不是 CMakeLists,而是 manifest.json。本工程的清单很短:

{
	"include": [
		"$(MODDABLE)/examples/manifest_base.json",
		"$(MODULES)/pins/digital/manifest.json"
	],
	"modules": {
		"*": "./main"
	}
}

这里做了三件事:

  • 引入 Moddable 的基础应用配置。
  • 引入 pins/digital,用于操作 GPIO。
  • 指定当前应用入口模块为 main.js

这也是 Moddable 工程比较典型的组织方式:应用代码尽量简单,平台和模块依赖写在 manifest 里。

点灯代码

当前 main.js 直接操作 GPIO46:

import Timer from "timer";
import Digital from "pins/digital";

const LED_PIN = 46;
const BLINK_INTERVAL = 500;

const led = new Digital(LED_PIN, Digital.Output);
let value = 0;

debugger;

led.write(value);

Timer.repeat(() => {
	value ^= 1;
	led.write(value);
}, BLINK_INTERVAL);

代码逻辑很直接:

  • Digital 用来把 GPIO46 配置为输出。
  • Timer.repeat 每 500ms 执行一次回调。
  • value ^= 1 在 0 和 1 之间翻转。
  • led.write(value) 把当前电平写到 GPIO46。

debugger; 是给 xsbug 调试用的断点入口。如果只想烧录运行,可以删除这一行;如果要验证 JavaScript 调试链路,可以保留。

VS Code 任务设计

为了日常使用方便,工程把常用操作都收敛到了 VS Code 任务里:

  • 环境检查
  • 编译
  • 烧录
  • 调试
  • 清除

.vscode\tasks.json 只负责调用脚本,不把复杂逻辑直接写在 task 里:

{
	"label": "编译",
	"type": "shell",
	"command": "tools\\build.cmd",
	"group": {
		"kind": "build",
		"isDefault": true
	},
	"problemMatcher": []
}

这样做的好处是任务名保持简洁,真正的环境处理、路径处理和平台选择都放进 tools 脚本,后续维护更方便。

环境检查任务

tools\check.cmd 用来确认当前终端是否能找到关键工具:

@echo off
call "%~dp0env.cmd" || exit /b %ERRORLEVEL%

echo MODDABLE=%MODDABLE%
echo IDF_PATH=%IDF_PATH%
if defined UPLOAD_PORT (echo UPLOAD_PORT=%UPLOAD_PORT%) else (echo UPLOAD_PORT=auto)
where mcconfig
where idf.py
where nmake
exit /b %ERRORLEVEL%

这个任务重点检查:

  • MODDABLE 是否正确。
  • IDF_PATH 是否正确。
  • 是否设置了 UPLOAD_PORT
  • mcconfigidf.pynmake 是否能被找到。

如果设备端口没有固定指定,脚本会显示:

UPLOAD_PORT=auto

也就是让 ESP-IDF 自动检测串口。

编译任务

编译脚本是 tools\build.cmd

@echo off
call "%~dp0env.cmd" || exit /b %ERRORLEVEL%
call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3_cdc -t build
exit /b %ERRORLEVEL%

关键命令是:

mcconfig -dn -m -p esp32/esp32s3_cdc -t build

参数含义:

  • -d:使用 debug 构建。
  • -n:不启动调试器。
  • -m:执行 make/build。
  • -p esp32/esp32s3_cdc:目标平台选择 ESP32-S3 CDC。
  • -t build:只执行构建,不烧录、不启动调试器。

这里选择 esp32/esp32s3_cdc,是因为当前 ESP32-S3 设备使用 USB CDC/JTAG 链路更合适。

烧录任务

烧录脚本是 tools\flash.cmd。它做了三层尝试:

@echo off
call "%~dp0env.cmd" || exit /b %ERRORLEVEL%

echo Trying ESP32-S3 CDC with automatic port detection...
call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3_cdc -t build
if not errorlevel 1 call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3_cdc -t deploy
if not errorlevel 1 exit /b 0

echo CDC failed. Trying ESP32-S3 UART...
call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3 -t build
if not errorlevel 1 call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3 -t deploy
if not errorlevel 1 exit /b 0

echo UART failed. Trying ESP32-S3 USB...
call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3_usb -t build
if not errorlevel 1 call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3_usb -t deploy
exit /b %ERRORLEVEL%

优先级是:

  1. esp32/esp32s3_cdc
  2. esp32/esp32s3
  3. esp32/esp32s3_usb

烧录没有强制写死 COM9,而是优先让 ESP-IDF 自动检测。如果自动检测失败,再考虑设置:

set UPLOAD_PORT=COM9

这里还有一个细节:烧录任务没有直接使用 mcconfig 的默认目标,而是拆成了 -t build-t deploy。这样可以避免默认流程在烧录后尝试启动 xsbug,导致没有调试器时烧录任务失败。

调试任务

调试脚本是 tools\debug.cmd

@echo off
call "%~dp0env.cmd" || exit /b %ERRORLEVEL%

if not exist "%MODDABLE%\build\bin\win\release\xsbug.exe" (
	echo xsbug.exe not found: %MODDABLE%\build\bin\win\release\xsbug.exe
	echo Debug task is unavailable until the Moddable xsbug tool is built.
	exit /b 1
)

call "%~dp0run-mapped.cmd" mcconfig -d -m -p esp32/esp32s3_cdc
exit /b %ERRORLEVEL%

调试任务依赖:

D:\moddable\build\bin\win\release\xsbug.exe

如果 xsbug.exe 不存在,脚本会直接提示,而不是让 Windows 弹出“找不到文件”的错误。

xsbug.exe 已经构建好时,执行:

mcconfig -d -m -p esp32/esp32s3_cdc

这会使用 debug 构建并启动 Moddable 的 JavaScript 调试器。main.js 里的 debugger; 可以作为调试入口断点。

清除任务

清除脚本是 tools\clean.cmd

@echo off
call "%~dp0env.cmd" || exit /b %ERRORLEVEL%

if exist build (
	echo Removing workspace build directory...
	rmdir /s /q build
) else (
	echo Workspace build directory not found.
)

call "%~dp0run-mapped.cmd" mcconfig -dn -m -p esp32/esp32s3_cdc -t clean
exit /b %ERRORLEVEL%

它只处理当前工程相关输出,不会删除 D:\moddable\build

这个边界很重要:D:\moddable\build 里有 Moddable SDK 自己编译出来的工具链,例如 mcconfig.batxsbug.exe 等,不应该被工程清理任务误删。

处理带空格路径

本次开发时的本机工程路径是:

E:\Code Warehouse\esp32\led_js

其中 Code Warehouse 中间有空格。为了减少 Windows 批处理、makefile 或底层工具对空格路径的兼容风险,工程加了 tools\run-mapped.cmd。如果你把开源工程克隆到没有空格的路径下,这个脚本通常不会触发映射逻辑。

它的策略是:

  • 如果当前路径包含空格,就临时把工程父目录映射到 M:
  • 在映射后的工程目录下执行实际命令。
  • 命令结束后取消 M: 映射。

核心逻辑如下:

echo "%RUN_DIR%" | find " " >nul
if not errorlevel 1 (
	subst M: /d >nul 2>nul
	subst M: "%PROJECT_PARENT%"
	cd /d "M:\%PROJECT_NAME%"
	set "USE_SUBST=1"
)

%*

这一步解决的是 Windows 嵌入式工具链里很常见的一类路径问题。

关键问题和处理过程

1. IDF_PATH 未设置

一开始执行任务时出现:

IDF_PATH is not set.

原因是普通 VS Code 任务终端不一定继承 ESP-IDF 插件激活后的环境变量。

解决办法是把环境激活放进 tools\env.cmd

  • 先读取 .vscode\settings.jsonidf.currentSetup
  • 再回退到固定路径 C:\esp\v6.0\esp-idf
  • 最后调用 export.bat 激活 ESP-IDF 环境。

2. Python 环境提示

ESP-IDF 激活时会检查 Python 版本和依赖:

Checking python version
Checking python dependencies

这不是说应用要用 Python 写,而是 ESP-IDF 的 idf.py 本身是 Python 工具。最终构建还是 CMake/Ninja 驱动的 ESP-IDF 工程。

3. ESP-IDF dirty 状态

ESP-IDF 目录曾经因为文件有改动,被识别成:

v6.0-dirty

Moddable 对 ESP-IDF 版本检查比较严格,ESP32-S3 要求匹配 ESP-IDF v6.0。这个问题通过恢复 ESP-IDF 目录里的非必要改动解决。

4. 烧录时错误启动 xsbug

烧录阶段曾经出现过类似:

xsbug.exe not found

问题不在烧录本身,而是默认目标链路会尝试进入调试流程。后面把烧录任务改成:

mcconfig -dn -m -p esp32/esp32s3_cdc -t build
mcconfig -dn -m -p esp32/esp32s3_cdc -t deploy

这样烧录只负责编译和下载固件,不依赖 xsbug.exe

5. 调试器缺失

调试任务需要:

D:\moddable\build\bin\win\release\xsbug.exe

如果这个文件不存在,就需要先构建 Moddable 的 Windows 调试器工具。工程脚本里已经做了检查,缺失时会明确提示。

最终使用方式

在 VS Code 中打开当前工程后,进入:

Terminal > Run Task...

常用流程是:

  1. 执行 环境检查,确认 MODDABLEIDF_PATHmcconfigidf.pynmake 都正常。
  2. 执行 编译,确认 JavaScript 应用和 ESP-IDF 工程可以正常构建。
  3. 连接 ESP32-S3 开发板。
  4. 执行 烧录,脚本会优先自动检测端口。
  5. 如果要进入 JavaScript 调试,确认 xsbug.exe 存在后执行 调试

小结

这次工程的重点不是点亮一个 LED 本身,而是把 Moddable + ESP-IDF + VS Code + ESP32-S3 的基础链路跑通。

当前工程已经完成了这些基础能力:

  • Moddable SDK 固定放在 D:\moddable
  • ESP-IDF v6.0 通过工程脚本自动激活。
  • main.js 使用 pins/digital 直接控制 GPIO46。
  • VS Code 提供中文任务入口:环境检查、编译、烧录、调试、清除。
  • 烧录任务支持自动端口检测,并避免误触发调试器。
  • 带空格工程路径通过临时盘符映射规避兼容问题。

后续可以在这个最小点灯工程上继续扩展按键输入、串口日志、Wi-Fi、传感器驱动和更完整的 JavaScript 调试流程。

参考资料

作者名片

Yukikaze
Yukikaze
@Yukikaze

私にできることなら何でもするから

评论区
文章作者和管理员都可以管理这里的评论。
0 条评论
登录后即可参与评论。 去登录
还没有评论,欢迎留下第一条交流内容。