# MKS SERVO42C + MKS DLC32 单轴电机整合指南（LO-TTA-03）

> **从接线到使用，一份可复现的完整手册。**
> 2026-09-19 真机验收通过 · 链路：`PC(USB/COM) → MKS DLC32 V2.2 → SERVO42C → 42 步进电机`
>
> 本文与以下文档配套（都在 `configs\` 目录）：
> - `SERVO42C-点动不转排查.md` —— 42C 那个"点动不转"故障的完整排查与结案记录
> - `LO-TTA-03-交付清单.md` —— 本次整合改了什么、成果物在哪
> - `config_LO-TTA-03.notes.md` —— `config.yaml` 的逐字段说明与上传步骤
> - `LO-TTA-03\config.yaml` —— 待上传到设备的配置文件本体

---

## 0. 一句话概览

一块 **DLC32**（ESP32 主板，跑 FluidNC）接收上位机指令，产生 **Stp/Dir 脉冲**；
一个 **SERVO42C**（驱动板，装在电机后轴）把脉冲变成电机转动。上位机通过 **USB 串口**控制 DLC32。

```
┌──────────────┐  USB(CH340)   ┌─────────────────┐  Stp/Dir/En   ┌──────────────┐
│  PC 上位机    │ ────────────▶ │ MKS DLC32 V2.2  │ ────────────▶ │ MKS SERVO42C │
│ (jiahe-agg)  │  115200 8N1   │ FluidNC (GRBL)  │  12~24V 共地   │  (开环模式)   │
└──────────────┘               └─────────────────┘               └──────┬───────┘
                                                                            │ A+/A-/B+/B-
                                                                            ▼
                                                                      ┌──────────┐
                                                                      │ 42 步进电机│
                                                                      └──────────┘
```

**软件分四层，各管各的**（改一层不影响另一层）：

| 层 | 是什么 | 在哪 | 什么时候要动 |
|---|---|---|---|
| 设备固件 | FluidNC v4（GRBL 方言） | DLC32 板内 | 基本不动 |
| 设备配置 | `config.yaml` | DLC32 的 LittleFS | **换电机/改机械参数时** |
| 驱动层 | `fluidncdll.dll` | `Industrial_ui\device\` | 平台侧包装层，本次未改 |
| 厂商层 | `FluidNC.dll`（源码 `D:\workspace\FluidNC_API`） | 同上 | 本次**重编过**（修点动） |
| 平台配置 | `LO-TTA-03.json` | `Industrial_ui\src\json\` | 新增型号时 |
| 驱动器参数 | 42C 的屏幕菜单 | 42C 板内 | 换驱动板/复位后 |

---

## 1. 硬件清单

| 件 | 型号 / 规格 | 说明 |
|---|---|---|
| 主板 | **Makerbase MKS DLC32 V2.2** | ESP32，激光雕刻机控制板，固件 FluidNC v4.x |
| 驱动器 | **MKS SERVO42C** | 闭环步进驱动板，装在电机后轴；本次**按开环（`CR_OPEN`）使用** |
| 电机 | 42 步进（NEMA17），**1.8°** | 200 步/圈 |
| 编码器磁铁 | 径向充磁，装在电机后轴端 | 42C 套装自带；**装错/丢失会报 `Magnet Loss!`** |
| 电源 | **12~24V 直流（≥9V）** | 42C 工作范围；**别把 5V 接进 V+** |
| 绝缘材料 | **绝缘胶带 / 绝缘垫片（或加高尼龙柱）** | ★ **必需**：板子与电机外壳之间要隔离，否则短路（见 **2.1**） |
| USB 线 | Type-C 或 Micro-USB（视板子版本） | 板载 CH340，本机枚举为 **COM15** |
| 无线（可选） | 板内 WiFi AP，SSID **`FluidNC`**，`192.168.10.123` | 改设备配置用；本项目走 USB 串口 |

---

## 2. 接线

### 2.1 ★★★ 第一步：把驱动板装到电机上 —— 必须做绝缘隔离

**这是装机的第一个动作，别跳过。** 装完再接线，顺序反了要拆。

**风险（实测踩到）**：
- 42C 靠**四个垫脚（铜柱）**架在电机后端面上；
- 但**电机端面上螺丝孔的位置是凹陷的** ⇒ **垫脚高度不够**；
- 结果**电路板背面的针脚 / 焊点会顶到电机底部的金属外壳** ⇒ **短路**
  （轻则上电不启动，重则烧板）。

**做法**：
- ★ 在**板子与电机外壳之间加一层绝缘隔离**：**绝缘胶带**（Kapton / 电工胶带）、绝缘垫片、
  或换更高的尼龙柱垫高，都可以；
- ★ **别用金属垫片 / 金属柱替代** —— 那等于没做；
- 隔离层要**平整**，别挡住编码器磁铁（磁铁在轴端，见 **2.6**），也别把板子顶得离轴端太远；
- **装完用万用表蜂鸣档量一下**：`V+`、`Gnd`、相线等关键点**对外壳不应导通**；
- 四个螺丝**拧到位但别压板**（压板同样会把焊点顶到外壳上）。

### 2.2 总览

| 42C 绿色端子 | 接到 DLC32 | 必要性 |
|---|---|---|
| `V+` | `VIN`（12~24V） | **必接** |
| `Gnd` | `G` | **必接**（电源地） |
| `Com` | `Vcc` | **建议接** |
| `Stp` | `STP`（丝印可能是 `S`） | **必接** |
| `Dir` | `DIR`（丝印可能是 `D`） | **必接** |
| `En` | `EN`（丝印可能是 `E`） | 接不接都行（见 2.4） |

> ★ **DLC32 的轴端子是 4 针**（`G`/`D`/`S`/`E` 一类丝印），而 42C 要接 **6 根** ——
> 其中 `Gnd` 和 `Com` 都要落到主板的 `G` 上。**用一个端子压两根线，或用一分二端子**。
> **具体丝印顺序以你手上板子的实物为准**，按字母含义对接即可（`G`=GND、`S`=Step、`D`=Dir、`E`=Enable）。

### 2.3 电源

- `V+` 接 **12~24V 直流**（可从主板的 `VIN` 取电，也可独立电源）。
- **`Gnd` 必须与主板共地**——不共地则脉冲信号没有参考电平，表现为"发脉冲完全没反应"。
- ⚠️ 大电流（`Ma` 设到 1A 以上）时建议**独立电源供电、只共地**，避免主板供电轨过载。
- ⚠️ **适配器的 5V 不要接 `V+`**（会欠压，42C 工作下限是 9V）。

### 2.4 脉冲信号（42C ↔ DLC32）★ 最容易出错的一段

**四个坑，按顺序避开：**

**① `Com` 的极性要和主板的脉冲极性匹配**
- `Com=VCC` → 共阳 → 低电平有效
- **DLC32 的 STP/DIR 是 3.3V 高有效脉冲** → 所以**优先接 `Com=G`（共阴）**
- 接反的后果：光耦长期导通，**看不到有效的脉冲边沿** ⇒ 发脉冲不转
- 手册允许 `Com` 接 `VCC` / `G` / 悬空三种，**先试共阴**

**② `En` 引脚 —— 建议直接设 `En=Hold` 绕开它**
- GRBL 主板的 EN 脚常常**悬空或常高**，而 42C 出厂默认 `En=L`（低有效）
- 两者一撞，驱动器**一直处于"未使能"** ⇒ 现象同样是"发脉冲不转，且不报错"
- **解法**：42C 菜单里把 `En` 设为 **`Hold`（一直使能）**，此后 EN 引脚接不接都不影响

**③ `Mode` 不能是 `CR_UART`**
- `CR_UART` 模式下 **Stp/Dir 被完全忽略**，脉冲接得再对也不会转
- 本机最终用的是 **`CR_OPEN`**（详见第 3 节）

**④ DLC32 侧的 `step_pin` / `direction_pin` 要与实际接线一致**
- 本机配置（`config.yaml`）：`step_pin: i2so.1` / `direction_pin: i2so.2` / `shared_stepper_disable_pin: i2so.0`
- `i2so.*` 是 DLC32 的 **I2S 扩展 IO**（板载 74HC595 移位寄存器），**不是 ESP32 直连 GPIO**
- ⚠️ `$GD`（查 GPIO 电平）**看不到 I2SO 扩展脚**，别用它判断脉冲是否发出

### 2.5 电机相线

- 42C 端子 `A+ / A- / B+ / B-` → 电机四根相线，**按颜色/丝印对应**。
- **接错的判据**：跑 `CAL` 校准时电机**只抖动、不整圈转** ⇒ 相线线序错，改成 `A+/A-` 对调或 `B+/B-` 对调。
- 跑 `CAL` 能**正反转各一整圈**，就证明**驱动级 + 相线 + 编码器 + 磁铁整条链路都是好的**。

### 2.6 编码器（磁铁安装）

- 42C 板上是 **MT6816（14 位磁编码器）**，芯片正对**电机后轴端**。
- 电机后轴端要装 **径向充磁**的磁铁；**轴向充磁会报 `Magnet Loss!`**。
- 磁铁与芯片间距建议 **1~2mm**，且**尽量同心**。
- ⚠️ 本次按**开环（`CR_OPEN`）**使用，**运行时不依赖编码器**；
  但 `CAL` 校准仍会用到它，所以磁铁还是要装好。

### 2.7 USB 串口

- 主板 → PC，板载 CH340，本机枚举为 **COM15**，参数 **115200 8N1**。
- 该串口既是"控制通道"（平台走它），也是"调试通道"（串口助手发 `$` 命令）。
- ★ **开关串口不会复位主板**（实测重连后 `MPos` 保留）⇒ 平台可以放心重连。

### 2.8 上电前检查清单

- [ ] ★ **驱动板与电机外壳之间已做绝缘隔离**，并已用万用表确认关键点对外壳不导通（见 2.1）
- [ ] `V+` = 12~24V（**不是 5V**）
- [ ] 42C `Gnd` 与主板 `G` **共地**
- [ ] `Com` 接 `G`（共阴）
- [ ] `Stp`/`Dir` 已接到对应端子（丝印确认过）
- [ ] 电机四根相线接好
- [ ] 编码器磁铁已装、径向充磁、间距 1~2mm
- [ ] 42C 菜单 `Mode` 已确认为 **`CR_OPEN`**（不是 `CR_vFOC`，更不是 `CR_UART`）

---

## 3. 42C 驱动器配置

### 3.1 ★ 菜单项变少 = 没识别到编码器磁铁（不是 `Mode` 的差别）

> ★★ **更正（2026-09-19）**：早前记录的"某模式下菜单被锁死、只剩两项"，
> **真因是当时 42C 没有识别到编码器磁铁**，与 `Mode` 无关。
> **磁铁被识别到之后，`CR_OPEN` / `CR_vFOC` / `CR_UART` 三个模式的菜单都是齐全的。**

| 状态 | 屏幕菜单长相 |
|---|---|
| **已识别到编码器磁铁** | ★ **三个模式的菜单都齐全** —— `MotType` / `Mode` / `MStep` / `Ma` / `En` / `Dir` / `CAL` / `UartBaud` / `UartAddr` / `Protect` / `MPlyer` / `ACC` 等都在 |
| **未识别到编码器磁铁** | 菜单项**明显变少**（曾观察到只剩 `MotType` + `Mode`）⇒ **去查磁铁，别去改 `Mode`** |

**⇒ 现场判据**：菜单项少 ⇒ 先确认磁铁有没有被识别到（安装要求见 **2.6**）：

1. 屏幕是否报 `Magnet Loss!` / `Magnet Error!`；
2. 磁铁是否**径向充磁**、是否同心、与芯片间距 **1~2mm**；
3. 用串口 `e0 30` 手转电机轴，看编码器值是否在 `0~FFFF` 间**连续变化**
   （恒 0 / 不动 ⇒ 磁铁问题坐实）。

> ★ 所以**不要用"菜单项数"去判断当前 `Mode`**（它反映的是磁铁识别状态）。
> 看 `Mode` 就直接进菜单找 `Mode` 那一项。
> ★ 本次整合同样要求 `Mode` 最终落在 **`CR_OPEN`**，理由见 3.3。

### 3.2 本机参数快照（换板 / 复位后照此设回）

| 参数 | 值 | 说明 |
|---|---|---|
| **`Mode`** | **`CR_OPEN`** | ★ **本次整合最关键的一项** |
| `MotType` | **1.8°** | 由"3200 脉冲 = 1 圈"反推自洽 |
| `MStep` | **16** | 与 FluidNC 侧 `steps_per_mm = 3200` 对应 |
| `Ma` | **800~1200mA** | ★ **开环下电流恒定**，发热只由它决定 |
| `En` | **`Hold`** | 一直使能，绕开 EN 引脚电平问题（出厂默认 `L`） |
| `Protect` | `Enable` | 开环没有丢步保护，靠堵转保护兜一层 |
| `MPlyer` | `Enable`（默认） | 内部 256 细分插补，低速更静 |
| `UartBaud` | `38400`（本项目不用串口控制，可不管） | 出厂默认 `Disable` |

### 3.3 ★★★ 为什么必须用 `CR_OPEN` 而不是 `CR_vFOC`

**这是本次整合踩得最深的一个坑，结论先行：**

> **把 `Mode` 从 `CR_vFOC`（闭环）改成 `CR_OPEN`（开环），故障全部消失。**

改之前的表现（四个症状会同时出现）：

| 症状 | 说明 |
|---|---|
| 发脉冲**电机不动**，但 42C 屏幕数字在变 | 驱动器收下了脉冲，但没把它变成运动 |
| 改完 `CR_OPEN` 之前，**运动一次就锁死** | 位置环饱和，只有复位能清 |
| **明显电流声 + 电机烫手** | 闭环在"较劲"，电流顶到上限 |
| 上位机控制完全无效 | 脉冲进了驱动器，但没出力 |

**根因链**：`CR_vFOC` 的位置环失效 ⇒ 误差不收敛（屏幕 `err` 卡在 17.99 不动）
⇒ 电流灌满线圈 ⇒ 嗡响 + 发热 + 锁死。**开环不依赖反馈，脉冲直接被当作步进指令执行**，所以正常。

★ **一个极具迷惑性的判据**：`CAL` 校准**永远正常**（正反转各一圈），
这让"驱动器是好的"看起来成立。但 **`CAL` 不走位置环**（它只是强制正反转做电角度对齐），
**所以 CAL 正常证明不了位置环正常。** 详见 `SERVO42C-点动不转排查.md`。

**开环的代价（要接受的取舍）**：

| 项 | 说明 |
|---|---|
| **没有丢步保护** | 开环不看编码器，丢步不会报错 |
| **电流恒定** | `Ma` 设的就是运行电流，不会随负载自动降 |
| **没有堵转自动保护** | 除非开 `Protect` |
| ✅ 好处 | 不依赖编码器/电角度对齐，**绕开了本次故障，最简单最稳** |

> 若将来对定位精度/丢步有硬要求，再按 `SERVO42C-点动不转排查.md` 第二节去修闭环
> （顺序：`Restore` → 重新上电 → 重设参数 → 空载重跑 `CAL` → 验收）。

### 3.4 ★ 屏幕三行读数的真实含义（别读错）

42C 主界面显示三个数：

| 显示 | 真实含义 | 怎么用 |
|---|---|---|
| `角度`（如 `359.9°`） | ★ **驱动器的内部「命令位置」**，**不是电机真实转角** | 手转电机轴时它**不变** —— 所以它变了只说明"命令走完了"，**不能证明电机转了** |
| `err`（如 `17.99`） | 位置角度误差，单位 **182.444 / 度** | ★ **只有它能反映闭环好坏**：卡住不收敛 = 闭环在较劲 |
| `clk`（如 `3.2K`） | **输入累计脉冲数**，由 **`Stp` 引脚直接累加** | ★ **与电机是否出力无关**；正好 +3200 就证明脉冲通路完全正确 |

★ 实测验证方法：**上电后用手转电机轴** —— 如果 `角度` 不变而 `err` 变了，就印证了上表。
★ 另一个关键点：**`clk` 在看脉冲，`角度` 在看命令，只有 `err` 才在看在闭环**。

---

## 4. DLC32 设备侧配置（`config.yaml`）

### 4.1 完整文件（待上传的那份）

```yaml
board: MKS DLC32 (Single Axis)
name: LO-TTA-03
stepping:
  engine: I2S_STATIC
  idle_ms: 255
  pulse_us: 4
  dir_delay_us: 0
  disable_delay_us: 0
  segments: 12

axes:
  shared_stepper_disable_pin: i2so.0
  shared_stepper_reset_pin: NO_PIN
  x:
    steps_per_mm: 3200
    max_rate_mm_per_min: 60.0
    acceleration_mm_per_sec2: 3.0
    max_travel_mm: 1000.0
    soft_limits: false
    motor0:
      stepstick:
        step_pin: i2so.1
        direction_pin: i2so.2
start:
  must_home: false
  check_limits: true
arc_tolerance_mm: 0.002
junction_deviation_mm: 0.01
verbose_errors: true
report_inches: false
use_line_numbers: false
planner_blocks: 16

i2so:
  bck_pin: gpio.16
  data_pin: gpio.21
  ws_pin: gpio.17
```

### 4.2 关键字段

| 字段 | 值 | 怎么定 |
|---|---|---|
| `name` | `LO-TTA-03` | 机器名，`$I` 会回显 `[MSG:Machine: LO-TTA-03]` |
| **`steps_per_mm`** | **`3200`** | `= 一圈脉冲数 ÷ 导程(mm) × 传动比 = (200×16) ÷ 1 × 1` |
| `max_rate_mm_per_min` | `60.0` | = 1mm/s。★ **jog 的 F 会被它钳位**（实测 `SPAxisSpeed` 恒为 1.000） |
| `acceleration_mm_per_sec2` | `3.0` | 越大越干脆；太大会丢步。本机 3 意味着加速到 1mm/s 要 0.33s |
| `max_travel_mm` | `1000.0` | ★ **软限位关闭时它只是声明值，拦不住运动** |
| `soft_limits` | `false` | 见第 9 节"已知局限" |
| `start.must_home` | `false` | 本机**没有限位开关、也不回零** |

★ **`steps_per_mm = 3200` 的推导**：42C 实测 `MStep = 16`，1.8° 电机
⇒ 一圈脉冲 = `200 × 16 = 3200`；当前裸电机、导程按 1mm、传动比 1 ⇒ `3200 ÷ 1 × 1 = 3200`。

★ 由此得到一个**不用卡尺的验证法**：3200 步/mm 且 3200 脉冲/圈 ⇒ **1mm = 电机转 1 圈**。
所以发"走 10mm"，**电机应该正好转 10 圈**，数圈数即可。

### 4.3 ★★★ 上传方法（唯一持久路径）

> **核心事实：串口 `$` 写入不持久，只有改文件才作数。**

实测证据（`$ESP444=RESTART` 重启验证）：

| 写入方式 | 立即生效 | **重启后** |
|---|---|---|
| `$axes/X/steps_per_mm=3200` | ✅ 回读 3200 | ❌ **回到 6400** |
| `$Name=LO-TTA-03` | ✅ `$I` 立刻报新名 | ❌ **回到旧名** |
| `$110=3200`（Grbl 编号） | ❌ | 报 `error:162 Read-only setting`，**是只读视图** |
| `$Config/Save` 等 6 个候选 | ❌ | 全部 `error:3 Invalid $ statement`，**根本没有"保存"命令** |
| **改 `config.yaml` 文件 + 重启** | ✅ | ✅ **唯一持久路径** |

**上传步骤（WiFi WebUI）**：

1. 电脑连上这块 DLC32 的热点：SSID **`FluidNC`**（设备 IP `192.168.10.123`）
2. 浏览器打开 **`http://192.168.10.123/`**
3. 顶部标签切到 **`FluidNC`**（面板标题 *FluidNC Settings*）
4. 工具栏点 **`Manage local files`** → 弹窗 **`FluidNC Local Filesystem`**
5. ★ 上传 `configs\LO-TTA-03\config.yaml` 到 `/`
   —— **文件名必须就叫 `config.yaml`**！
   WebUI 上传是用**本地文件名**当设备端路径的，传 `config_LO-TTA-03.yaml`
   只会多出一个新文件，**不会覆盖开机读的那份**，你会以为"改了没生效"
6. 工具栏点 **`Restart FluidNC`** 重启

> **不想用 WiFi**：固件也有整套文件 API，串口就能做：
> `$LocalFS/List` / `$LocalFS/Show=/config.yaml` / `$LocalFS/Backup =path` / `$Xmodem/Send`

**复核（三者应一致，且再重启一次确认不回退）**：
```
$I                              → 看 [MSG:Machine: LO-TTA-03]
$LocalFS/Show=/config.yaml      → 看磁盘文件原文（权威值）
$axes/X/steps_per_mm            → 看生效值（应为 3200.000）
```

---

## 5. 平台侧配置

### 5.1 `LO-TTA-03.json`

位置：`D:\workspace\Industrial_ui\src\json\LO-TTA-03.json`

以 `LO-TTA-01.json` 为底稿，**实测只有 10 处差异**：

| # | 位置 | 旧值 → 新值 |
|---|---|---|
| **1** | `queryJson.SPQuery` | `lobll75` → **`fluidnc`** ★★★ 最关键 |
| 2–8 | `checkJson[*].model` ×7 | `LO-ALN100-2` → `LO-TTA-03` |
| 9 | `attribute[1].value`（开环步数） | `4000` → **`3200`** |
| 10 | `attribute[6].value`（细分） | `"20"` → **`"16"`** |

★ **第 1 条是本质区别**：`SPQuery` 是"用哪个设备驱动"的键，与 DLL 目录名一一对应：

| `SPQuery` | 实际加载 |
|---|---|
| **`fluidnc`** | **`fluidncdll.dll`**（+ 厂商层 `FluidNC.dll`） |
| `lobll75` | `Lobll75dll.dll` |
| `mcc` | `mccdll.dll` |
| `cam` | `camdll.dll` |
| `weightcell` | `WeightCellDll.dll` |

⇒ **`LO-TTA-01` 走 `Lobll75dll`；`LO-TTA-03` 改走 `fluidncdll`**。
这就是"本次没有新增 DLL 模块、复用 `fluidncdll`"的物证。

### 5.2 让型号出现在前端下拉列表

前端下拉读的是**登录用户目录**下 `info.json` 的 `configData[]`。
新型号要能被选中，需把它登记进去（脚本：`Industrial_ui\register_tta03.py`，幂等、自动备份）。

> ⚠️ 服务端同步资源时可能覆盖 `info.json`，届时重跑该脚本。
> ⚠️ 前端需**重启应用**才能看到新下拉项。

### 5.3 DLL 层：版本与位置（四处，务必对齐）

| 位置 | 作用 | 应是什么 |
|---|---|---|
| `D:\workspace\FluidNC_API\bin\x64\Release\FluidNC.dll` | 编译产物（源头） | `9f9575a4`（202752B） |
| `D:\workspace\Industrial_ui\device\` | **平台运行目录** | `FluidNC.dll`=`9f9575a4`，`fluidncdll.dll`=`5b10aee3` |
| `D:\workspace\Industrial_ui\needCopyDependence\device\` | **★ 打包源**（`vue.config.js` 里 `from: "needCopyDependence/"`） | 同上，**发版前必须同步** |
| `D:\workspace\Industrial_ui\dist_electron\win-unpacked\device\` | 上次打包输出 | 重新打包会自动覆盖，不用手工维护 |

★ **这两个 DLL 必须成对更新**：`fluidncdll.dll` 是平台按 `SPQuery` 加载的入口，
它**隐式链接** `FluidNC.dll`（协议层）。只换一个会让入口指向不匹配的依赖。

★ 换**运行目录**里的 DLL 时：**必须先停平台**（DLL 被进程锁定，否则复制会失败）。
打包源目录可以随时改。

★ **发布链路的关键**：`vue.config.js` 会把 `needCopyDependence\` 整目录复制进安装包。
**本机跑得再好，这个目录是旧的，客户装完就是坏的**——现场极难查。

---

## 6. 使用

### 6.1 开机顺序

1. 上电（42C 和 DLC32 通常同一路电源，一起上电）
2. 42C 屏幕显示 `0` / `0.0` / `0` 三行初值，无报错
3. PC 上启动平台（`lier-ws-proveder.exe`）
4. 平台里打开设备

### 6.2 平台侧打开设备（测试脚本路线）

```bash
cd D:\workspace\jiahe-agg\fluidncdll
python test_fluidnc.py --refresh     # SPRefrensh=1，热重载 device/ 目录
python test_fluidnc.py --open        # SPOpen "com15-fluidnc"
python test_fluidnc.py --probe       # 打印原始广播帧，核对字段
python test_fluidnc.py               # 只读监控
```
> ★ **顺序很重要：先 `--refresh` 再 `--open`。** 直接 `SPOpen` 曾出现"成功但几秒后平台自行关闭设备"，
> 补一次 `SPRefrensh` 后广播稳定。
>
> ★ 平台进程要用**后台常驻**方式启动；`&` 挂在前台 shell 里会随 shell 退出被杀。
> 停进程：`MSYS_NO_PATHCONV=1 taskkill /F /IM lier-ws-proveder.exe`

其他可用动作：
```bash
python test_fluidnc.py --rel 2        # 相对定位 +2mm
python test_fluidnc.py --abs 0.5      # 绝对定位到 0.5mm
python test_fluidnc.py --stop         # 缓停
python test_fluidnc.py --estop        # 急停
python test_fluidnc.py --setpos 0     # 置位
python verify_jog_fix.py + 6 1        # 点动 6s @1mm/s（按帧记录，判速率/连续性）
python verify_stop_ab.py 2.5 1        # 缓停 vs 急停 A/B 对照
```

### 6.3 指令映射表（平台积木块 → 底层动作）

| 平台动作 | `normal_data.name` | 落到 GRBL |
|---|---|---|
| 急停 | `SingleEmergencyStop` | `velocityStop`(0x85) + `$ES` |
| 缓停 | `SingleStop` | `velocityStop`(0x85) + `!` |
| 调整位置（置位） | `Reset` | `$MPos=x<值>` |
| **点动 / 定速** | `SingleToSpeed` | **长 `$J` + 0x85 停止**（见第 9 节） |
| 相对定位 | `SingleToPosition` + `ModeType=1` | `$J=G91 X<d> F<feed>` |
| 绝对定位 | `SingleToPosition` + `ModeType=2` | `$J=G90 X<t> F<feed>` |
| 回零 | `ToHome` | `$H` |

单位约定：JSON 的 `Speed` 是 **mm/s**（DLL 里 ×60 变成 GRBL 的 F/mm-min）；`Distance` 是 mm。

★ **点动积木块里虽然带 `Distance` 字段，但底层不读它** ——
点动是"按住走、松开停"，靠同一积木块的 `stop.actions[0] = SingleStop` 收尾。
**不要试图用 `Distance` 限制点动行程，它没有作用。**

---

## 7. 验收标准

### 7.1 三步验收（本机，随时可复测）

**① 单点动 1mm**

| 项目 | 期望 |
|---|---|
| 电机 | **肉眼可见转 1 圈**（1mm = 1 圈） |
| 42C 屏幕 `clk` | **+3200** |
| `MPos` | `0.000 → 1.000`，约 2.4s 停稳 |
| 声音 / 温度 | **无电流声；不烫手** |

**② 连走 10mm**：`$J=G91 X10 F60` → `MPos` 增量 **+10.000mm**，用时约 10.7s（≈1.02mm/s，与 F60 吻合）。

**③ 平台层点动**：`verify_jog_fix.py + 6 1` → 位移 **+6.100mm** 左右，逐帧位移连续单调、无反向跳变。

### 7.2 两个不用卡尺的判据

- **数圈数**：设 3200 步/mm 时 1mm = 1 圈 ⇒ 走 10mm 电机转 10 圈
- **看 `clk`**：走 1mm 必然 +3200，**分毫不差**就说明脉冲通路完全正确

★ ⚠️ **`MPos` 读数自洽不能证明物理位移**：`MPos = steps ÷ steps_per_mm` 用同一个常数反算，
"相对定位 +2.000 精确"是恒等式，**不是验证**。要证明物理位移只能用卡尺或数圈数。

---

## 8. 故障排查速查

| 症状 | 最可能的原因 | 怎么处置 |
|---|---|---|
| **发脉冲电机不动**，但 42C 屏幕数字在变 | ① `Mode` 是 `CR_vFOC`（闭环坏）<br>② `En` 未使能<br>③ `Mode` 是 `CR_UART` | 改 `CR_OPEN`（优先）；`En` 设 `Hold`；确认不是 `CR_UART` |
| 屏幕数字**根本不变** | 脉冲没进来：Stp/Dir 接错、**没共地**、`Com` 极性反 | 查接线；`Com` 接 `G`（共阴） |
| 电机**只抖动不转** | 相线线序错 | `A+/A-` 或 `B+/B-` 对调 |
| 屏幕报 `Magnet Loss!` | 编码器磁铁缺失 / **轴向充磁** | 换径向充磁磁铁，间距 1~2mm |
| 屏幕报 `Phase Line Error!` | 电机线序错 | 检查相线 |
| 屏幕报 `Not Cal` | 未校准 | 空载跑 `CAL` |
| 屏幕报 `Wrong Protect Enter..` | 堵转保护触发 | 按 Enter 解除；检查是否卡住 |
| **电流声大 + 烫手** | 闭环在较劲（`err` 不收敛） | 改 `CR_OPEN`；开环下若还烫 ⇒ **降 `Ma`** |
| **转一次就锁死** | 位置环饱和 | 改 `CR_OPEN`；或复位 |
| 改的配置**重启后失效** | 只用了串口 `$` 写入（只改 RAM） | **必须改 `config.yaml` 文件并重启** |
| 平台报 `-2147483387` / `-3000453`，广播 `axis_stutus: []` | 平台已判定设备断开并关闭 | 重跑 `--refresh` + `--open` 恢复 |
| 串口发 `$` 命令**毫无响应** | 串口被占用 / 设备离线 | 先跑 `probe_ports.py` 确认在线 |

★ 怀疑设备是否在线，**先跑 `probe_ports.py`**（枚举注册表串口并逐个探测）。
★ 蓝牙调制解调器口（`\Device\BthModem*`）**打开就会卡住**，探测时要跳过。

---

## 9. 已知局限与取舍（用之前要知道）

### 9.1 长按点动超过 20 秒，会有一个"接缝"

修好后的点动方案是**发一条长 `$J`（≈20 秒行程）+ 实时字节 `0x85` 停止**，
跑完一段会续接下一段。**段与段之间必然有个过渡**：

| 指标 | 实测（45 秒点动） |
|---|---|
| 总位移 | +44.250mm（期望 45.000，**缺口 0.75mm ≈ 2 次接缝**） |
| 接缝处速度 | 从 1.000 掉到 **0.450**（45%） |
| 接缝持续 | **≈1.0 秒** |

**成因**：① 上一段 `$J` 自然减速跑完 → ② 平台状态缓存（≤100ms 滞后）报 IDLE
→ ③ 新段 `$J` 重新加速（`accel=3mm/s²`，到 1mm/s 要 0.33s）。

- **短按（< 20 秒）完全碰不到**，日常"按住找位置"无影响
- ⚠️ **相对/绝对定位不走这条路**，是单条 `$J`，**没有接缝问题**
- 若确实需要长时间连续恒速且不能有速度波动，可考虑把
  `FluidNC_API\src\VelocityController.cpp` 里的 `HOLD_SEC`（现 20s）调大后重编
  —— 但它是**安全上限**（防上位机崩了无界走），调大要配合限位保护

### 9.2 开环没有丢步保护

`CR_OPEN` 不看编码器，丢步不会报错。负载轻（纯电机/后续接位移台）一般可接受；
要硬保证就开 `Protect` 堵转保护，或回去修闭环。

### 9.3 软限位关闭 = 没有软件保护

本机 `soft_limits: false`，且 **`must_home: false`、没有限位开关、不回零**
⇒ `MPos` 只是"上电以来"的相对坐标，**`soft_limits: true` 也不是可靠保护**（`MPos` 本身可能是任意值）。

实际可行的保护手段：
- 小行程轴把 `MAX_CHUNK_MM`（现 3000）调小
- `max_travel_mm` 填真实行程
- **操作上留足余量**

### 9.4 速度被 `max_rate` 钳位

`max_rate_mm_per_min = 60` ⇒ 无论指令给多少 mm/s，**实测速率上限就是 1mm/s**
（`SPAxisSpeed` 恒为 1.000）。判速率要拿 `SPAxisSpeed` 实测值当基准，
**别拿指令速度当期望值**，否则会把设备限速误判成驱动故障。

### 9.5 平台广播的位置有约 0.9s 滞后

把"下发停止指令那一刻读到的位置"当基准去量过冲，会把滞后算成过冲。
**过冲/拖尾只用"起始 → 停稳的总位移"来量。**

---

## 10. 附：命令与参数速查

### 10.1 FluidNC 串口命令（COM15 @115200）

```
?                            查状态（★轮询判停要取缓冲区最后一条状态行）
$I                           固件 / 板型 / 机器名
$GD                          所有 GPIO 电平（★看不到 I2SO 扩展脚）
$MD / $ME                    禁用 / 使能所有电机
$CMD                         列出固件支持的全部 $ 命令（105 条）
$Config/Dump                 当前生效配置（≠ 磁盘文件）
$LocalFS/Show=/config.yaml   磁盘上真正的 config.yaml（★权威值）
$axes/X/steps_per_mm         读单项设置
$J=G91 X1 F60                相对点动 1mm @F60（★本机 1mm = 电机 1 圈 = 3200 脉冲）
$J=G91 X10 F60               走 10mm（电机转 10 圈）
$H                           归零（本机无回零配置）
$ESP444=RESTART              重启（★串口 $ 写入全部丢失，只认 config.yaml 文件）
```

### 10.2 SERVO42C 串口功能码

| 功能码 | 作用 | 取值 |
|---|---|---|
| `e0 30` | 读编码器值 | 0~FFFF |
| `e0 33` | 读输入累计脉冲数（＝屏幕 `clk`） | int32 |
| `e0 36` | 读位置（＝屏幕 `角度`，**实测为内部命令位置**） | int32 |
| `e0 39` | 读位置角度误差（＝屏幕 `err`） | int16，**182.444 / 度** |
| `e0 3a` | 读 En 引脚状态 | `01`使能 `02`未使能 `00`错误 |
| `e0 3e` | 读堵转标志 | `01`堵转 `02`正常 |
| `e0 80 00 60` | 校准编码器（＝菜单 CAL），**必须空载** | |
| `e0 81` | 电机类型 MotType | `00`=0.9° `01`=1.8°（**改后必须重校准**） |
| **`e0 82`** | **工作模式 CtrMode** | `00`**CR_OPEN** `01`CR_vFOC `02`CR_UART |
| `e0 83` | 电流档 Ma | `06`=1200mA `0F`=3000mA |
| `e0 84` | 细分 MStep | 1~256（本机 = 16） |
| `e0 85` | En 有效电平 | `00`L `01`H `02`**Hold** |
| `e0 88` | 堵转保护 Protect | |
| `e0 3f 1f` | 恢复默认参数（**需重新上电**） | |
| `e0 a1/a2/a3` | 位置环 Kp / Ki / Kd（**无菜单项**） | |
| `e0 a4/a5` | 加速度 ACC / 最大扭矩 MaxT（MaxT 无菜单项） | |

★ 42C 串口是板上 **4pin `3V3/G/T/R`**（不是右边绿色端子），接线要**交叉 + 共地**：
`TXD→R`、`RXD→T`、`GND→G`、`3V3 不接`。8N1，关流控。
★ 出厂默认 `UartBaud=Disable` ⇒ 板子对串口**全程静默**，必须先设波特率。

### 10.3 常用测试脚本（`D:\workspace\jiahe-agg\fluidncdll\`）

| 脚本 | 用途 |
|---|---|
| `test_fluidnc.py` | 平台 WS 客户端：`--refresh` / `--open` / `--probe` / `--rel` / `--abs` / `--stop` / `--estop` / `--setpos` / `--jog` |
| `verify_jog_fix.py` | ★ 按广播帧采样，判点动速率/连续性/是否掉线 |
| `verify_stop_ab.py` | ★ 缓停 vs 急停 A/B 对照 |
| `probe_ports.py` | ★ 枚举串口并逐个探测（**怀疑掉线先跑这个**） |
| `test_enable.py` | 4 步现场判定：判使能 → `clk` 增量 → 判 Stp/Dir 接反 → 看角度/err |
| `axis_params.py` | 轴参数在线读写 + 卡尺标定（`calib 10 <实测mm> --apply`） |
| `check_jogcancel.py` | 裸串口校验「长 `$J` + 0x85」方案（不占平台） |
| `dump_config.py COM15` | 抓 `$Config/Dump` + 关键 `$$` |

★ 裸串口脚本一律复用 `check_jogcancel.py` 里的 `Raw` 类（ctypes 直调 kernel32，**不需要 pyserial**）。
★ **`Raw` 必须设 `COMMTIMEOUTS`**，否则 `ReadFile` 在没有数据时会**无限阻塞**，
`read_for(ms)` 的时限形同虚设（已修：`ReadIntervalTimeout=10ms` + `ReadTotalTimeoutConstant=25ms`）。
★ `Raw.read_for()` **直接返回 `str`**，不要再 `.decode()`。

---

## 变更记录

| 日期 | 内容 |
|---|---|
| 2026-09-19 | 首版。42C 改 `CR_OPEN` 结案；FluidNC.dll 点动修复（长 `$J` + 0x85）；`LO-TTA-03.json` 新增；`config.yaml` 就绪待上传；打包源 DLL 同步完成 |
| 2026-09-19 | **修订**：3.1 重写 —— 菜单项变少的真因是**未识别到编码器磁铁**（与 `Mode` 无关；识别到后三个模式菜单都齐全）；新增 **2.1 驱动板安装绝缘隔离**（原 2.1~2.7 顺延为 2.2~2.8，同步修正交叉引用与检查清单） |
