# MKS SERVO42C「点动不转 / 转一下就锁死 + 电流声 + 烫手」——已解决记录

> 2026-09-19 · **结论：`Mode` 从 `CR_vFOC`（闭环）改为 `CR_OPEN`（开环）后，一切正常。**
> 上位机控制好使、电机正常运动、**电流声消失**。
> 本文记录：已验证配置、根因链、以及"以后想用闭环该怎么查"。

---

## 一、✅ 已验证解（结论先行）

**把 42C 的 `Mode` 从 `CR_vFOC` 改成 `CR_OPEN`。**

改完现象全部消失：

| 症状 | 改 `CR_OPEN` 后 |
|---|---|
| 发脉冲电机不动 | ✅ 正常运动 |
| 上位机控制无效 | ✅ 好使 |
| **电流声** | ✅ **消失** |
| 运动一次后就锁死 | ✅ 不再出现 |
| 电机烫手 | ✅ 随之解决（电流声没了 = 不再持续灌大电流） |

**⇒ 根因不在 `config.yaml`、不在接线、不在 FluidNC 侧，而在 42C 的 `CR_vFOC` 闭环路径本身。**

---

## 二、★ 为什么 `CR_OPEN` 能解、`CR_vFOC` 不能 —— 整条因果链

### 三个模式的区别（手册原文）

| Mode | 含义 | 是否需要编码器 |
|---|---|---|
| **`CR_OPEN`** | 开环控制模式 | **不需要** ← 本次可用 |
| `CR_vFOC` | 矢量**闭环**控制模式，有编码器反馈防丢步 | 需要（用电角度对齐） |
| `CR_UART` | 串口直接控制 | Stp/Dir **完全失效** |

### 现象 → 机制对照

| 观察 | 机制 |
|---|---|
| `clk` = **正好 3200**（1mm × 3200 步/mm，分毫不差） | ✅ 脉冲**数量正确**，Stp/Dir 接线 + Com 共地正确 |
| `MPos` 0 → 1.000，2.39s 停稳 | ✅ FluidNC 侧命令链 / 速度 / 限位全正常 |
| **屏幕"角度"是内部命令位置**（手转轴时**角度不变**、`err` 会变 → 实测） | `角度 359.9°` **只说明命令走了一圈**，不能证明电机转了 |
| `err` 卡在 **17.99**（≈10 个整步）不收敛 | ⚠️ **闭环在较劲**：位置环追不上 ⇒ 电流顶到上限 |
| **电流声 + 烫手** | ⚠️ 电流顶满的结果 |
| **第一次运动能转一圈、之后再也不响应** | ⚠️ 位置环**饱和 → 锁死**，只有复位能清 |
| **CAL 后电流声消失、屏幕归零** | ★ **`CAL` 不走位置环**（它只是强制正反转做电角度对齐）⇒ CAL 永远正常，**证明不了位置环正常** |

### ⇒ 根因
**`CR_vFOC` 闭环路径失效**：驱动器收下脉冲（内部命令位置走了一圈），
但**位置环无法把转子驱动到位** ⇒ 误差不收敛 ⇒ 电流灌满线圈 ⇒ 嗡响 + 发热 + 锁死。
**改成开环后不再依赖反馈，脉冲直接被当作步进指令执行 ⇒ 一切正常。**

### ⇒ 闭环为什么会失效（后续想修再看，不影响当前使用）

按可能性排序：

| # | 原因 | 怎么查 |
|---|---|---|
| 1 | **`MotType` 与电机不匹配**（0.9° vs 1.8°） | 菜单 `MotType` 应 = **1.8°**；改过必须重跑 CAL |
| 2 | **闭环 PID 被改坏** `Kp`/`Ki`/`Kd` | ⚠️ **没有菜单项**！只能在串口 `e0 a1/a2/a3` 看；恢复只有 `Restore`（`e0 3f 1f`） |
| 3 | **`ACC` / `MaxT` 被改坏** | `ACC` 有菜单项；`MaxT`（`e0 a5`）没有 |
| 4 | **编码器磁铁**（轴向充磁 / 偏心 / 间距不在 1~2mm） | 屏幕报 `Magnet Loss!` / `Magnet Error!`；或用 `e0 30` 手转轴看编码器值是否连续变化 |

**若以后要回到闭环，按这个顺序做一遍：**
`Restore` → **重新上电** → 重设 `MotType=1.8°` / `MStep=16` / `Ma=1200` / `En=Hold` / `Mode=CR_vFOC`
→ **空载重跑 `CAL`** → 点动 1mm，验收标准见第四节。

---

## 三、★ 当前可用的 42C 参数快照（换板/复位后照着设回来）

| 参数 | 值 | 说明 |
|---|---|---|
| **`Mode`** | **`CR_OPEN`** | ★ **本次的关键项** |
| `MotType` | 1.8° | 待最终确认 |
| `MStep` | **16** | 与 FluidNC 侧 `steps_per_mm = 3200` 对应 |
| `Ma` | 待确认（建议 800~1200mA） | ★ **开环下电流是恒定的**，不受负载自动调节 ⇒ 发热只由 `Ma` 决定 |
| `En` | `Hold` 建议 | 一直使能，绕开 EN 引脚电平问题（出厂默认是 `L`） |
| `Protect` | 建议 `Enable` | 开环没有丢步保护，靠堵转保护兜一层（触发后屏幕 `Wrong Protect Enter..`，Enter 解除） |
| `MPlyer` | Enable（默认） | 内部 256 细分插补，低速更静 |

### ⚠️ 两件要确认的事

1. **做一次断电重启验证**：断电 → 重新上电 → 点动一次，确认 `Mode` 仍是 `CR_OPEN` 且能转
   （42C 参数应掉电保存，但值得实测一次，避免下次上电又变回去）。
2. **跟一次温度**：开环恒流，若跑一会儿还烫手 ⇒ **把 `Ma` 调低**（800mA 起）。

---

## 四、开环（`CR_OPEN`）的代价 —— 要接受的取舍

| 项 | 说明 |
|---|---|
| **没有丢步保护** | 开环不看编码器，丢步不会报错。本机负载轻（纯电机 / 后续接位移台），一般可接受 |
| **电流恒定** | 手册原文：`Ma` 设的是**开环模式的运行电流档位**；闭环才会随负载自动调电流 |
| **没有堵转自动保护** | 除非开 `Protect` |
| **好处** | 不依赖编码器/电角度对齐 ⇒ **绕开了本次的闭环故障**，最简单最稳 |

> 若后续对**定位精度/丢步**有硬要求，再按第二节末尾的清单去修闭环。

---

## 五、验收标准（本机，随时可复测）

点动 **1mm**：

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

---

## 六、已实测排除的环节（留档，别再回头查）

| 环节 | 实测 | 结论 |
|---|---|---|
| `config.yaml` | 脉冲数量 + 速度 + 接线三项全对 | ✅ **不是 config 的问题** |
| FluidNC 命令链 | `$J=G91 X1 F60` → `MPos 0.000 → 1.000`；同方向连点两次各 +1.000mm | ✅ 正常 |
| 脉冲数量 | 42 板 `clk` = **正好 3200** | ✅ Stp/Dir 接线 + Com 共地正确 |
| `steps_per_mm` | `$/axes/X/steps_per_mm` = 3200.000 | ✅ 生效 |
| 脉冲速率 | 3.2kHz，手册上限 60KHz | ✅ 不存在"太快跟不上" |
| 转速量级 | 60mm/min = 1 圈/s = 60RPM，上限 1200RPM | ✅ 不存在"转速过高" |
| Stp 噪声 / 接反 | 不点动时 `clk` 不变；每次点动正好 +3200 | ✅ 均排除 |
| `CR_UART` | 菜单能跑到 CAL；当时"`CR_UART` 下菜单只剩 2 项"⇒ **该项判据已作废**，见下方更正 | ✅ 排除 |
| 驱动器使能 | 手拧轴有阻力；改为 `CR_OPEN` 后能正常驱动 | ✅ 排除 |
| 驱动级 + 相线 + 编码器 + 磁铁 | CAL 正反转各一圈正常 | ✅ 好 |
| **`CR_vFOC` 闭环** | 误差不收敛、电流顶满、锁死、烫手 | ❌ **坏 —— 本次根因** |

> ★★ **更正（2026-09-19）**：当时"`CR_UART` 下菜单只剩 2 项、此模式菜单被锁死"是**误判** ——
> 菜单项变少的真因是**当时 42C 没有识别到编码器磁铁**，**与 `Mode` 无关**。
> 磁铁被识别到后，**`CR_OPEN` / `CR_vFOC` / `CR_UART` 三个模式的菜单都是齐全的**。
> ⇒ 因此也**不能用"菜单项数"/"菜单里能不能跑到 CAL"来判断 `Mode`**（原判据作废）；
> 判断 `Mode` 直接进菜单看 `Mode` 项。详见 `LO-TTA-03-整合指南.md` §3.1。

---

## 七、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 3d` | 解除堵转状态 | |
| `e0 80 00 60` | 校准编码器（＝菜单 CAL），**必须空载** | 成功 `e0 01 e1` |
| `e0 81` | **电机类型 MotType** | `00`=0.9° `01`=1.8°（**改后必须重校准**） |
| **`e0 82`** | **工作模式 CtrMode** | `00`**CR_OPEN** `01`CR_vFOC `02`CR_UART |
| `e0 83` | **电流档 Ma** | 00=0mA, 01=200mA, … 06=1200mA, 0F=3000mA |
| `e0 84` | 细分 MStep | 1~256（本机 = 16） |
| `e0 85` | **En 有效电平** | `00`L（出厂默认） `01`H `02`**Hold** |
| `e0 86` | 旋转正方向 Dir | `00`CW `01`CCW |
| `e0 88` | 堵转保护 Protect | 触发后屏幕 `Wrong Protect Enter..` |
| `e0 89` | 内部 256 细分插补 MPlyer | 默认 Enable |
| `e0 3f 1f` | 恢复默认参数（需重新上电） | |
| `e0 a1` / `a2` / `a3` | 位置环 **Kp / Ki / Kd**（**无菜单项**） | 默认 0x650 / 1 / 0x650 |
| `e0 a4` / `a5` | 加速度 **ACC** / 最大扭矩 **MaxT**（MaxT 无菜单项） | 默认 0x11E / 0x4B0 |

屏幕报错对照：`Not Cal`=未校准 / `Phase Line Error!`=电机线序错 /
`Magnet Loss!`=编码器磁铁缺失或**轴向充磁**（需径向充磁）/ `Wrong Protect Enter..`=堵转保护。

---

## 八、FluidNC 侧命令速查（本机 COM15 @115200）

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

---

## 九、相关脚本（`D:\workspace\jiahe-agg\fluidncdll\`）

| 脚本 | 用途 |
|---|---|
| `test_enable.py` | 4 步现场判定：① 判使能（`$MD`/`$ME` 交替、看**松手弹不弹回**） ② 点动看 `clk` 增量 ③ 同方向再点（判 Stp/Dir 接反） ④ 看角度/err |
| `probe_ports.py` | 枚举串口 + 逐个探测（判断设备是否还在线）★怀疑掉线先跑这个 |
| `diag_motor.py` | 抓 `$I` / `$CMD` / `$GD` / 运动配置段 |
| `diag_jog_me.py` | 点动验证 MPos + 试 `$MD`/`$ME` |
| `axis_params.py` | 轴参数在线读写 + 卡尺标定 |

> ⚠ **工具坑（已修）**：`check_jogcancel.py` 的 `Raw` 原先**没设 `COMMTIMEOUTS`**，
> 未设超时的串口 `ReadFile` 会**一直阻塞到有 1 字节到达**，`read_for(ms)` 的时限形同虚设。
> 设备一旦不回话脚本就卡死被 SIGTERM，而且 stdout 是空的（缓冲没 flush）——
> 我因此两次把 `$GD` 的"读不出来"误判成命令问题。
> 现已在 `SetCommState` 后补 `SetCommTimeouts`（`ReadIntervalTimeout=10ms` /
> `ReadTotalTimeoutConstant=25ms`，读缓冲 1→4096 字节）。
> 另注：蓝牙调制解调器口（`\Device\BthModem*`）**打开就卡住**，探测时跳过；
> `Raw.read_for()` 返回的已是 `str`，**不要再 `.decode()`**。
