← reports index ← reports index
机制详解 · 2026-09-09

抓夹触觉:用舵机电流判断到底抓着没有

试验结果一直只有人工打的成功/失败三态标。这一页说明怎么在不加任何传感器、不改模型、不重训的前提下,让每次试验多带一个客观测量值:合爪时夹爪舵机的电流。也说明为什么现在刻意不下判定。

一句话:STS3215 的控制表里本来就有 Present_Current(地址 69)。夹爪合在杯子上走不到目标位、持续加力,电流上去;合在空气里一路走到底,电流很低。这就是可用的触觉参照 —— 物理上是堵转电流,不是压力传感。

一、为什么不用加硬件

夹爪用的是 Feetech STS3215 串行总线舵机。它是个位置伺服:给一个目标角,它会一直加力直到走到。合爪被杯子挡住时走不到目标位,于是持续加力 —— 电流就是「有没有东西顶着」的直接证据。

这四个只读寄存器一直都在(lerobot/motors/feetech/tables.py 的 STS_SMS_SERIES_CONTROL_TABLE,sts3215 用的就是这张):

寄存器地址说明
Present_Position56位置 —— 目前唯一在用的
Present_Load60负载,符号-幅值编码(第 10 位是符号)
Present_Voltage62电压
Present_Temperature63温度
Present_Current69电流 —— 不在编码表里,读出来是原始无符号值

项目此前一个都没读过:17 维的 observation.state 全是位置和速度。

二、三个设计决定,以及各自的代价

1. 只读两次,不是每拍读

控制回路 30 Hz,send_action 每拍已经有三路 sync_read。这是一条 17 颗舵机的菊花链,每拍再加一次读等于给总线加约三分之一流量。这条链有前科 —— 2026-08-23 一次 configure 里的丢包就让整场试验作废。

所以只在两个时刻各读一次:

1
合爪那一拍 —— 接触瞬间
夹爪读数从 >30 跌到 ≤30 的那一拍触发
2
合拢 0.4 秒后 —— 稳定持握
两个数合起来才说得清是「碰了一下弹开」还是「攥住了」;单看接触瞬间会把撞一下误判成抓住

每次试验最多 3 次额外读(接触时读电流+负载,稳定时读电流),对总线是可以忽略的负担。

2. 不做成策略输入

把电流加进 observation.state 意味着改状态维度 —— 现有 checkpoint 全部作废,必须重训。所以这一版是纯运行时校验:策略照常跑,只是结果里多一个数,和所有现有模型兼容。

要让它参与决策有两条路,代价差很多:

路线代价前置条件
当策略输入state 维度变 → 现有 checkpoint 全废 → 重训—
当运行时门控不用重训先要有阈值,而阈值要从真实成功/失败样本统计出来

现在这一步正是在为第二条路攒数据。

3. 刻意不下判定

可以随手写个「电流 > 300 就算抓住」,但那是拍脑袋。阈值必须从真实成功/失败的样本里统计出来。项目里已有先例 —— --grip-gate auto 就是从数据集统计阈值而不是硬编码。

所以结果里只有测量值,没有结论:

"grip_current": {
  "arms": {"right": {
      "gripper_pos_at_close": 10.0,
      "raw_at_close": 412,   "mA_at_close": 2678.0,
      "raw_settled": 398,    "mA_settled": 2587.0,
      "load_at_close": 233,
      "settled_at_end": false
  }},
  "unit_note": "raw = 舵机原始读数; mA 按数据手册标称 6.5 mA/tick 换算,
                本机未标定 —— 定阈值请用 raw"
}
mA 那两个数只是换算给人看的。6.5 mA/tick 是数据手册标称值,没有在本机标定过。真要定阈值请直接用 raw,别经过这个系数。

三、自测抓到的一个真 bug

写完先做了不碰硬件的逻辑自测(跟着 arm_home.py --selftest、trial_keyframes.selftest 的惯例,用假舵机)。结果发现:

合爪发生在试验最后 0.4 秒内时,第二次读永远等不到,raw_settled 恒为 None。而抓握恰恰常常就在试验末尾 —— 等于最需要的那个数会系统性缺失。

修法:在 result() 里补一次。试验刚结束、爪子多半还攥着,这时读到的就是稳定持握电流。并且用 settled_at_end: true 标明这次是收尾补读而不是按时读的 —— 两者时机不同,以后统计阈值要分开看。

九个用例全过:按时读、收尾补读、只动左爪、全程没合爪、读失败记 None、关闭开关时零动作、只读对应那条总线。

四、可靠性

五、默认开

--grip-current      默认开
--no-grip-current   要关才显式给

下次上机试跑不用做任何事,数就自动记进 trials/*.json。

2026-09-09 首次真机验证 ✅ 左臂 left-sd-p0.2-b16-u20000/020000 实机 2/2 成功,两次都读到了:
试验接触 raw持握 raw倍数Present_Load帧/CLIP
13(19.5 mA)13(84.5 mA)4.3×9290 / 0.0%
27(45.5 mA)21(136.5 mA)3.0×216291 / 0.3%
两次都是持握远高于接触,正是「夹爪合到杯子上走不到目标位、持续加力」的特征; Present_Load 同向变化(92→216),两个独立寄存器互相印证。 settled_at_end 都是 None,说明第二次读是按时读到的,0.4 秒的等待生效, 收尾补读那条兜底没被用上。
第一批基线:成功抓取 = 持握 raw 13~21。还缺失败样本(抓空会更低、卡死会更高)才能定阈值。
此前的风险提示(已由上面那次验证解除) —— 逻辑自测用的是假舵机。第一次真机试跑请看一眼 raw_at_close 的量级是否合理(空爪应明显低于攥住杯子)。如果读回来是 0 或异常大,说明这颗舵机的固件版本寄存器布局不同,需要改用 Present_Load。

六、接下来

  1. 跑若干次试验,让电流数和人工标的成功/失败并排积累
  2. 数据够了之后统计阈值 —— 空爪分布和持杯分布应该分得开
  3. 阈值稳了再考虑做成运行时门控(不用重训)
代码:03-software/scripts/grip_current.py(探针)· 03-software/scripts/run_policy_trials.py(接入点:--grip-current、循环里 gcp.observe、结果字段 grip_current)
寄存器定义:lerobot/motors/feetech/tables.py 的 STS_SMS_SERIES_CONTROL_TABLE
总线归属:lerobot/robots/xlerobot/xlerobot.py :94(bus1) :129(bus2)
行号对应本机 ~/lerobot,升级 lerobot 后要重新核对。