外观
TMR3109 磁编码器全链路:协议、驱动、标定,以及一个"装错磁铁"的坑
TMR3109 是一颗 23-bit 磁旋转编码器,SPI 读绝对角度,装在夹爪手指转轴上测开合。 从驱动、数据采集到标定,全链路已上线:MCU 50Hz 采集 → AP → 网页九点分段标定。 最大的教训不在软件:读数异常的头号根因不是装配偏心,而是装错了磁铁的充磁方向。
成果速览
换掉错误磁铁后,量程从"个位数~二十几度、台间差 10 倍"恢复到 ~40°:
| 能力 | 现状 |
|---|---|
| 协议解析 | SPI Mode 1,28-bit 帧(23-bit 角度 + CRC + error)✅ |
| 采集 | MCU 50Hz 采集 → 共享内存 → AP → 网页/标定 ✅ |
| 标定 | 两点线性 + 九点分段标定共存(治具 0/5/…/40°)✅ |
| 读数 | 芯片角度 / 标定角度 / 开合百分比 / 量程,四种口径 ✅ |
| 失败模式 | 磁铁类型、传感器掉线、无磁钢、量程退化——全部有判据 ✅ |
传感器与安装要求
TMR3109(多维科技)基于隧道磁阻:芯片测的是正上方磁场矢量的方向,不是零件角度。 磁钢随手指转轴旋转,面内场矢量跟随,芯片读出绝对角度。
原厂对安装的硬性要求(产品规格书 §3.6):
| 项 | 规格 |
|---|---|
| 磁钢 | Ø9 × 2.5 mm,钕铁硼 N35SH,径向对半充磁 |
| 安装距离(气隙) | 1 ~ 3 ~ 10 mm |
| 芯片表面场强 | 100 ~ 1000 Gs |
| 中心偏差 | ≤ 0.5 mm |
| 中心倾斜 | ≤ ±3° |
⚠️ 场强范围两处写法不一致:规格书 §3.6 写 100~1000 Gs,官网产品页写 200~800 Gs,建议向原厂确认。
⚠️ 头号前提:磁钢的充磁方向(本篇主角)
"径向对半充磁"是字面意思:圆盘的 N、S 沿直径左右分布。 本批结构装的是轴向充磁(一个圆面是 N、一个圆面是 S)——这就是灾难的起点:

驱动与数据链路
SPI 协议与位提取
读角度指令返回 28-bit 数据(4 字节 SPI 帧,MSB 优先):
| 位 | 内容 |
|---|---|
| d[27:5] | 23-bit 角度码 |
| d[4:1] | 4-bit CRC(x⁴+x³+x²+1,初值 0011) |
| d[0] | error 位 |
c
// 换算:θ = raw / 8388608 × 360°(raw 为 23-bit 角度码)
rt_uint8_t tx[4] = {0x60, 0x00, 0x00, 0x00}, rx[4] = {0};
spi_xfer(s, tx, rx, 4);
uint32_t v = (rx[0]<<24) | (rx[1]<<16) | (rx[2]<<8) | rx[3];
uint32_t raw = (v >> 5) & 0x7FFFFF; // d[27:5]
float angle = raw / 8388608.0f * 360.0f; // 0~360°坑:datasheet 时序图没写,但实测 MISO 有 4 个时钟的流水线延迟—— 按手册位对齐提取会把读数钳在 337.5°~360°。上代码已按实测修正(
>>5取 d[27:5])。
数据链路
- 读数经
raw/8388608×360换算成角度,四条路径(MCU 串口 / 共享内存 / HTTP / 网页)数值一致 - 采集完整性已验证:60s 3012 帧无丢帧、无毛刺、无位错误特征
遇到的问题:同型机量程差 10 倍
现象:同一结构、同一 80° 开合行程(预期传感器量程 ~40°), 本批 9 台实测读数量程 2.6°~25.1°,且同一侧机器出现正/反两种读数方向。
| 机号 | 量程 | 机号 | 量程 | 机号 | 量程 |
|---|---|---|---|---|---|
| 机1 | 25.1° | 机4 | 11.2° | 机7 | 4.5° |
| 机2 | 13.9° | 机5 | 11.0° | 机8 | 3.8° |
| 机3 | 11.7° | 机6 | 8.0° | 机9 | 2.6° |
当时的分析走了两条线:
教训:"横向偏心"模型在数学上完全自洽,也拟合了观测,但拟合到的原因不一定是真因。 最终是结构+原厂确认才定案。排查顺序应当是:先验磁铁充磁方向,再谈装配公差。
定案:装错了磁铁
轴向充磁磁钢在芯片轴线上产生的面内旋转分量 ≈ 0(磁场沿轴向走), 读数只剩磁钢偏心/晃动带来的小残余分量——所以量程小、台间差大、方向随机翻转。 这与观测完全吻合;换正确磁铁(径向对半充磁)后,面内旋转分量占主导,量程立即恢复 ~40°。
快速鉴别两种充磁(来料/装配自检):
| 方法 | 径向对半充磁(正确) | 轴向充磁(错误) |
|---|---|---|
| 铁片靠近圆盘边缘 | 两侧(N、S 各一半)都吸(侧面是磁极面) | 边缘一圈吸力弱且均匀 |
| 铁片靠近上下圆面 | 不吸 / 弱 | 一面 N、一面 S 都吸(圆面就是磁极) |
| 装转轴上转一周测读数 | 量程正常(~40°) | 量程很小、不稳定、方向随机 |
怎么救残余非线性:两点 + 九点分段标定
两点标定(全闭/全开)只能做线性映射,修正不了中段非线性(读数被压扁的曲线不是直线)。 所以在应用层加了九点分段标定:治具给出 9 个(治具角度, 原始值)点对,分段线性插值把非线性掰回来。

两种标定方式共存
两套数据都保留,mode 字段决定当前生效方式(哪个后做用哪个):
| 你做的操作 | 生效映射 |
|---|---|
| 九点「确认标定」 | 分段线性(points) |
| 「标定全闭/全开」 | 两点线性(two-point) |
| 「启用九点标定」按钮 | 显式切回分段 |
操作(网页「传感器」页)
- 手指放到治具某卡位(如 20°)→ 点对应按键(采 2 秒均值)→ 重复点即覆盖
- 九点齐全(
9/9 已采集)→ 点「确认标定」(校验:九点齐备 + 读数随角度单调) - 生效后实时显示 开合百分比(给算法的口径)、标定角度、量程
接口:/api/v1/angle/calib(GET/POST)、/calib/{point,commit,reset,mode}、/angle/regress。
读数口径(容易混)
| 显示 | 含义 |
|---|---|
| 实时 | 芯片原始角度(0~360°)+ raw,与标定无关 |
| 标定角度 | raw 经当前标定换算的治具角度(0~40°),超程显示 ⚠超程 |
| 开合 | raw 经当前标定换算的百分比(给算法) |
| 量程 | 当前标定的量程 = span_raw / span_deg(芯片角度) |
口径坑:端点字段(
closed_deg/open_deg/span_deg)一律是芯片角度,不是治具角度—— 之前九点确认曾把治具角度写进端点字段,导致"量程"显示成 40°,已统一修正。
经验与坑档案
| 现象 | 根因 | 判据 / 解决 |
|---|---|---|
| 量程个体差异巨大、方向随机 | 磁钢装错(轴向充磁) | 换径向对半充磁;来料/装配按规格书 §8 验充磁方向 |
读数恒为 0x7FFFFF(359.99996°) | 传感器未响应(未供电/未连接/掉线),MISO 上拉读全 1 | 真值在 360° 附近会抖 ±几 LSB,钉死 = 假值;检查供电与 J57 连接;驱动应补 CRC/error 位校验 |
| 读数满量程随机跳(几 ms ±300°) | 无磁钢或磁钢不在位(裸板) | 这不是"噪声大",是没信号;装磁钢 |
| 标定后百分比是天文数字 | 两次采在同一位置,端点量程退化 | 已加 2000 raw 保护;在两个真实位置重新标定 |
| 两点面板「量程」显示 40° | 治具角度被写进端点字段 | 端点字段统一为芯片角度 |
MSH 单跑 tmr3109 触发断言 | SPI 未单独 init | 先 angle_init |
| 部署后功能没变 | 构建后漏了 docker cp 产物,推的是旧二进制 | 用 md5 比对板上二进制 vs 产物,必做 |
| 静止噪声 | 健康整机 std ≈ 0.001°(~18 LSB) | >0.5° 查机械晃动或磁场;测噪声要直读共享内存按时间戳去重,HTTP 轮询会重复计数低估噪声 |
复现与出处
| 项 | 位置 |
|---|---|
| 驱动 | MCU:rtos/components/angle_sensor/angle_sensor.c(SPI Mode 1,位提取含 4clk 延迟修正) |
| 采集 | MCU:applications/gripper/sensor_acq.c(50Hz → 共享内存) |
| 标定 | AP:app/gripper/v2/cmd/gripper-app/angle_calib.go、angle_calib_http.go、web/index.html |
| 数据手册 | 多维科技 TMR3109 产品规格书 v1.0.1(§3.6 磁场规格、§4.1 SPI、§8 机械角度方向) |
| 知识库 | 团队 skill:magnetic-angle-sensor、rv1126b-gripper-angle-calib |
写于 2026-09 · 基于夹爪项目实测 · 全部数据来自真机