# Arcs Dev Tools

> 当用户提到 ARCS/arcs-sdk、交叉编译工具链（riscv64-unknown-elf-gcc）、cskburn、烧录、/dev/ttyACM*、串口日志、JLink、JFlash、GDB 调试、JTAG/cJTAG、debug、调试、断点、单步执行等任务时使用：负责拉取仓库、环境安装、编译、烧录、运行、日志读取与 JLink 调试环境部署；不负责代码编写/理解，代码开发由 Claude Code 本身负责

- Skill: `listenai/arcs-dev-tools` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add listenai/arcs-dev-tools`
- Raw SKILL.md: https://api.skillmd.com/api/skills/listenai/arcs-dev-tools/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: listenai (https://skillmd.com/u/listenai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/listenai/arcs-dev-tools

---


# ARCS SDK 工具链

为 Claude Code 提供与 ARCS 硬件交互的工具链能力：拉取仓库、安装开发环境、编译、烧录、运行、日志读取。

**本 skill 只负责工具链操作，不负责代码编写和理解。** 代码开发、需求理解、bug 分析由 Claude Code 本身完成。

## 当前状态

本 skill 仍处于**开发阶段**。欢迎在使用过程中反馈：

- 复现稳定的 bug（最好附上命令输出/日志片段）
- 环境兼容性问题（发行版、Python 版本、串口设备类型等）
- 更好的"触发语句/使用示例"（请提交到仓库的 `docs/` 文档中，避免放进 skill 包内）

## 已验证能力（已跑通）

- **运行某个 sample**：模型自行定位示例 → 编译 → 烧录 → 读取日志 → 判定成功/失败
- **UI 显示并迭代**：模型先写一个在 UI 上显示一行字的示例 → 烧录运行 → 再修改文字内容 → 重新烧录并确认生效

## 使用示例

示例与触发语句见 `<skill_dir>/references/usage.md`（按需加载，避免主文件过长）。

### 批量模式

- 逐个串行执行 编译→烧录→验证（串口设备同一时间只能一个操作使用）
- 每个示例独立输出验证结果，最后汇总
- 某个失败不影响后续继续

## 默认配置

| 参数 | 默认值 | 说明 |
|------|--------|------|
| BOARD | `arcs_evb` | 目标开发板 |
| 串口设备 | 动态扫描 | 每次操作前扫描 `/dev/ttyACM*` `/dev/ttyUSB*` |
| 烧录波特率 | `3000000` | cskburn 波特率 |
| 串口波特率 | `921600` | 日志读取波特率 |
| 烧录起始地址 | `0x0` | Flash 烧录偏移 |
| 仓库地址 | `https://gitlab.example.com/listenai/arcs-sdk.git` | git clone 地址（需用户提供实际地址） |

## 核心原则：先确认非偶发，再处理

遇到异常现象时，**必须重复验证 2-3 次确认是否稳定复现**，再决定是否处理。
- 每次都复现 → 真实问题，返回给 Claude Code 处理
- 仅偶发 → 标记为环境因素，不作为代码问题

## 工具链操作

### 操作 1：拉取仓库

当用户的环境中还没有 arcs-sdk 仓库时执行。

```bash
# 克隆仓库（用户需提供实际的仓库地址）
git clone <仓库地址> arcs-sdk
cd arcs-sdk
git submodule update --init --recursive
```

- 如果用户未提供仓库地址，询问用户
- 如果仓库已存在，跳过此步骤，直接使用现有仓库
- 支持切换分支：`git checkout <branch>`

### 操作 2：安装开发环境

首次使用或工具链缺失时执行。**但必须先检查环境变量，用户可能已自行配置工具链路径。**

**优先检查环境变量**：

```bash
# 检查用户是否已指定工具链路径
echo "NUCLEI_TOOLCHAIN_PATH=$NUCLEI_TOOLCHAIN_PATH"
echo "LISTENAI_TOOLS_PATH=$LISTENAI_TOOLS_PATH"
```

| 环境变量 | 含义 | 设置后跳过 |
|---------|------|-----------|
| `NUCLEI_TOOLCHAIN_PATH` | 用户指定的 RISC-V GCC 工具链路径 | 跳过 `prepare_toolchain.sh` |
| `LISTENAI_TOOLS_PATH` | 用户指定的 listenai-tools 路径（cmake、ninja 等） | 跳过 `prepare_listenai_tools.sh` |

> **环境变量已设置时，不要运行对应的安装脚本**，直接使用用户指定的路径。

**仅当环境变量未设置时**，才运行安装脚本：

```bash
# 1. 安装构建工具（仅当 LISTENAI_TOOLS_PATH 未设置时）
bash prepare_listenai_tools.sh

# 2. 安装 GCC 交叉编译工具链（仅当 NUCLEI_TOOLCHAIN_PATH 未设置时）
bash prepare_toolchain.sh

# 3. 确保 cskburn 有执行权限
chmod +x ./tools/burn/cskburn
```

**检查项**（按优先级）：
1. 若 `NUCLEI_TOOLCHAIN_PATH` 已设置 → 检查 `$NUCLEI_TOOLCHAIN_PATH/bin/riscv64-unknown-elf-gcc` 是否可执行
2. 若 `LISTENAI_TOOLS_PATH` 已设置 → 检查 `$LISTENAI_TOOLS_PATH/cmake/bin/cmake` 是否可执行
3. 若环境变量未设置 → 检查仓库内 `listenai-dev-tools/` 下的对应路径
4. `./tools/burn/cskburn` 是否可执行

如果检查通过，跳过安装。

### 操作 3：编译

**输入**：项目路径（示例目录或用户自建项目目录）
**输出**：`build/arcs.bin` 固件文件，或编译错误信息

```bash
cd <项目目录>
bash ./build.sh -C -DBOARD=arcs_evb && bash ./build.sh -r -w -DBOARD=arcs_evb
```

**build.sh 查找策略**：
- 项目目录下有 `build.sh` → 直接用
- 没有 → 用仓库根目录的 `build.sh -S <项目相对路径>`

**编译失败时**：
- 将完整错误输出返回给 Claude Code，由 Claude Code 分析并修改代码
- 查阅 `<skill_dir>/references/knowledge.md` 对应 topic 看是否有已知解决方案
- Claude Code 修改代码后，再次调用本操作重新编译

**确认编译产物**：
- 在 `build/` 下查找 `.bin` 文件
- 返回固件文件的完整路径和大小

**ARCS 编译产物命名规则**：
- `build/<name>.bin` — 固件文件（用于烧录）
- `build/<name>` — **ELF 文件（无后缀）**，用于 GDB 调试，包含调试符号
- 例如：`build/helloworld.bin` 对应的 ELF 是 `build/helloworld`
- **ELF 文件没有 `.elf` 后缀**，不要去找 `*.elf`，直接用去掉 `.bin` 后的同名无后缀文件

**ARCS 启动顺序与 build 目录结构**：

芯片启动顺序：`ROM_BOOT → AP 固件 (boot) → CP 固件 (应用)`

如果 `build/` 下存在 `boot/` 子目录：
- `build/boot/boot.bin` — AP 核心的 boot 固件
- `build/boot/boot` — AP boot 的 ELF（无后缀），debug AP 时用此文件
- `build/<name>.bin` — **已合并 boot 的完整固件**（= `boot.bin` + 应用 bin），直接烧录即可
- `build/<name>.bin.without.boot` — 不含 boot 的纯应用固件
- `build/<name>` — CP 应用的 ELF（无后缀），debug CP 时用此文件

**GDB 调试时选择正确的 ELF**：
- Debug AP (boot) → 用 `build/boot/boot`
- Debug CP (应用) → 用 `build/<name>`

### 操作 4：烧录

**输入**：固件文件路径
**输出**：烧录成功/失败

**烧录前**：
```bash
# 动态扫描串口设备
ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null
# 确认无进程占用
fuser <串口设备> 2>/dev/null
```

**执行**：
```bash
# 必须使用仓库自带的 cskburn，必须带 -C arcs 参数
./tools/burn/cskburn -C arcs -s <串口设备> -b 3000000 0x0 <固件文件路径>
```

> **重要**：必须使用 `./tools/burn/cskburn`（仓库根目录下），**不要**使用 `listenai-dev-tools/listenai-tools/cskburn/cskburn`。
> 后者是通用版本，不支持 `-C arcs` 参数，缺少烧录前自动擦除步骤，会导致写入失败。

**正常烧录输出示例**：
```
Waiting for device...
Entering update mode...
Detected flash size: 16 MB
Erasing region 0x00000000-0x00021000...
Burning partition 1/1... (0x00000000, 130.88 KB)
130.88 KB / 130.88 KB (100.00%)
Resetting...
Finished
```

**失败处理**：
- `Failed opening device` → 板子未进入烧录模式，提示用户按 BOOT+RESET 进入烧录模式
- `EBUSY` → `fuser` 找到占用进程，提示用户释放
- `ETIMEDOUT` → 等 3 秒，重新扫描设备号，重试
- 设备消失 → 等待后重新扫描 `/dev/ttyACM*`
- `Failed burning partition: 01` → 检查是否误用了 `listenai-dev-tools` 中的 cskburn，换用 `./tools/burn/cskburn`

### 操作 5：运行（日志读取）

**输入**：无（烧录完成后自动执行）或独立调用
**输出**：串口日志文本

**使用 `serial_read.py`**（技能自带脚本，纯 Python stdlib，零外部依赖）：
```bash
python3 <skill_dir>/serial_read.py read <串口设备> -b 921600 -t <读取秒数>
```

> 兼容旧用法：不加 `read` 子命令也可以（自动识别为 read 模式）。

> **`<skill_dir>`** 即本技能安装目录，与 SKILL.md 同级。

**参数说明**：
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `-b, --baudrate` | `921600` | 波特率 |
| `-t, --timeout` | `5` | 读取超时（秒） |
| `--dtr` | 不传（拉低） | 传入则拉高 DTR |
| `--rts` | 不传（拉低） | 传入则拉高 RTS |

**特性**：
- 非 TTY 环境兼容（`O_NOCTTY` + `select()` 非阻塞读取）
- 默认拉低 DTR 和 RTS（通过 `ioctl TIOCMBIC`）
- 打开后自动 flush 缓冲区，避免读到残留数据
- 实时输出到 stdout

**串口问题处理**：
- **乱码** → 确保波特率设置正确（921600），或等板子完全启动后再读
- **设备断连** → 关闭连接 → 等 2-3 秒 → 重新扫描设备 → 重连
- **无输出** → 先尝试操作 7 复位芯片，仍无输出则提醒用户检查连接或重新烧录
- **权限问题** → 确保用户在 uucp/dialout 组中
- **设备占用** → 读取前检查是否有其他进程占用（fuser <串口设备>）

**日志返回给 Claude Code**，由 Claude Code 判断程序是否正常运行。

### 操作 6：检查硬件连接（串口）

独立操作，可在任意时刻调用。

```bash
ls /dev/ttyACM* /dev/ttyUSB* 2>/dev/null
```

- 找到唯一设备 → 返回设备路径
- 找到多个 → `udevadm info` 识别后询问用户
- 找不到 → `lsusb` + `dmesg | tail -20` 后告知用户

### 操作 7：复位芯片

通过烧录串口的 DTR 信号复位芯片，无需手动按 Reset 键。

```bash
python3 <skill_dir>/serial_read.py reset <串口设备>
```

**原理**：拉高 DTR → 等待 0.5 秒 → 拉低 DTR，触发芯片硬件复位。

**使用场景**：
- 烧录后需要复位芯片重新启动
- 芯片卡死需要重启
- 调试过程中需要重新运行程序

**复位后读日志的典型组合**：
```bash
# 先复位，再立即读取启动日志
python3 <skill_dir>/serial_read.py reset <串口设备> && \
python3 <skill_dir>/serial_read.py read <串口设备> -b 921600 -t 5
```

### 操作 8：JLink 环境检测与部署

当用户需要通过 JLink 调试 ARCS 芯片时执行。详细步骤见 `<skill_dir>/references/jlink-setup.md`。

**检测与部署流程**：
1. 检测 JLink 软件是否已安装（JLinkExe、JLinkGDBServerCLExe）
2. 检测并部署 JLinkDevices 配置（`~/.config/SEGGER/JLinkDevices/Listenai.xml` + `Flashloader.elf`）
3. 检测目标核心（AP/CP）并选择对应 JLink Script，**告知用户当前调试的核心**
4. 动态检测 JLink 硬件序列号
5. 执行连接测试（**JLinkExe** 纯 CLI 读取 flash 验证通畅，不要使用 JFlashExe 避免弹出 GUI）

**核心选择**：ARCS 双核（AP+CP），通过 build 目录 `.config` 中的 `CONFIG_ARCS_AP_CORE`/`CONFIG_ARCS_CP_CORE` 自动判断。Debug AP 用 `jtagscan0`，debug CP 用 `jtagscan1`，烧录/读 Flash 始终用 `jtagscan0`。

**ARCS JLink 接线**：PA01-SWDIO, PA00-SWCLK, GND, VTref(3.3V)。接口模式为 cJTAG (2-pin)。

**资源文件**：`<skill_dir>/assets/jlink/` 下包含 Flashloader.elf、arcs.jflash 模板、JLinkScript 文件。

> **严禁违反以下三条，否则必定连接失败：**
> 1. **接口必须用 `-if cJTAG`**，不能用 `-if JTAG` 或 `-if SWD`
> 2. **必须加载 JLinkScript**：`-JLinkScriptFile <skill_dir>/assets/jlink/jtagscan0.JLinkScript`（或 jtagscan1），不带 JLinkScript 会报 `Could not connect to target`
> 3. **设备名必须用 `ARCS`**，不能用 `ListenAI ARCS`

## 调试策略：JLink GDB 优先

当用户提到"debug"、"调试"、"断点"、"单步"、"崩溃"、"卡死"、"排查问题"等关键词时，**优先使用 JLink GDB 调试**，而非串口日志。

### 调试方法优先级

1. **JLink GDB 调试**（首选）— 可设断点、单步执行、查看变量和寄存器、精确定位问题
2. **串口日志读取**（备选）— 仅当 JLink 环境不可用时使用

### 选择逻辑

```
用户提到 debug/调试/排查问题
  → 检查 JLink 环境是否就绪（操作 8）
    → 就绪 → 使用 JLink GDB 调试（流水线 C）
    → 未就绪 → 尝试部署 JLink 环境（操作 8）
      → 部署成功 → 使用 JLink GDB 调试
      → 无法部署（无 JLink 硬件等）→ 降级为串口日志（流水线 A/B）
        → 告知用户："当前使用串口日志排查，建议连接 JLink 以获得更精确的调试能力"
```

## 常用流水线

### 流水线 A：运行现有示例

Claude Code 调用顺序：
1. 检查硬件连接
2. 定位示例目录，阅读 README 和源码（Claude Code 自己做）
3. **编译**（操作 3）
4. **烧录**（操作 4）
5. **日志读取**（操作 5）
6. Claude Code 对比日志和预期输出，判断结果

### 流水线 B：编写新代码并验证

Claude Code 调用顺序：
1. 检查硬件连接
2. Claude Code 参考仓库中的示例和 API，编写代码
3. **编译**（操作 3）→ 失败则 Claude Code 改代码 → 重新编译（循环）
4. **烧录**（操作 4）
5. **日志读取**（操作 5）
6. Claude Code 判断是否符合预期 → 不符合则改代码 → 回到步骤 3（循环）

### 流水线 C：JLink GDB 调试

当用户需要 debug/调试时，**优先使用此流水线**：

Claude Code 调用顺序：
1. **JLink 环境检测**（操作 8）→ 未就绪则自动部署
2. **编译**（操作 3）→ 定位 ELF 文件：`build/<name>`（**无后缀**，不是 `.elf`），确认带调试信息（`with debug_info, not stripped`）
3. **检测目标核心**（AP/CP）→ 选择对应 JLink Script
4. **启动 JLinkGDBServer**：
   > **⚠️ `-JLinkScriptFile` 是必填参数，缺少会导致 `CPU-TAP not found in JTAG chain`**
   ```bash
   JLinkGDBServerCLExe -device ARCS -if cJTAG -speed 4000 -port 2331 \
       -USB <serial_number> -nogui -noir -nologtofile \
       -JLinkScriptFile <jtagscan_script> &
   ```
   > **设备名必须用 `ARCS`**，不能用 `ListenAI ARCS`（会卡死）
5. **GDB 连接并调试**：
   ```bash
   <riscv-gdb> -batch -x <gdb_commands_file> <elf_file>
   ```
   GDB 命令文件示例：
   ```
   set pagination off
   set confirm off
   target remote localhost:2331
   monitor reset
   monitor halt
   load
   break main
   continue
   ```
6. 根据调试目标设置断点、单步执行、查看变量
7. 调试完成后关闭 GDBServer 进程

## 经验知识库

文件：`<skill_dir>/references/knowledge.md`

按 6 个 topic 组织：**仓库管理 / 环境安装 / 编译 / 烧录 / 串口 / JLink**。遇到问题时只读对应 topic。

> 本文件由维护者手动维护，**模型不要修改**。

### 自优化 SKILL.md

发现应沉淀到 skill 的通用性问题时，**不要直接修改 SKILL.md**，告知用户，**用户同意后**再更新。

## 注意事项

1. **本 skill 只做工具链操作**：代码编写、需求理解、bug 分析由 Claude Code 本身完成
2. **串口独占**：烧录和日志读取不能同时使用同一串口
3. **设备号动态扫描**：每次操作前都扫描，不硬编码
4. **异常先确认再处理**：重复验证 2-3 次，确认非偶发后再处理
5. **安全优先**：不执行 `rm -rf` 等危险操作
6. **避免使用的方法**：
   - **picocom/minicom**: 在非交互式环境中无法正常工作（需要 TTY）
   - **pyserial**: 需要额外安装 Python 包，在受限环境中可能不可用
   - **stty + cat**: 在非 TTY 环境下会回显波特率，且无法控制 DTR/RTS 信号线


