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

杯子碰到桌面了吗:用堵转检测代替写死的高度

队友提的:「电机在往下推但角度不变,显然是撞到桌子了」。这一页记录把它做成可用判据的全过程 —— 包括第一版判据在真机上失败的原因,以及改完之后从第 13 步提前到第 3 步。

结论:可行,判据是 「窗口内跟随率 < 35% 且 |Load| ≥ 40」,实测多压 1.05° 就能判出。 不能只看瞬时速度 —— 实测自由运动时也有 67% 的帧速度读数为 0。

一、想法从哪来

放杯子的位姿现在是写死的关节角(place_pose.json,从 10 集示范算的中位数)。桌面高度一变就不准,而且压过头会挤坏杯子或顶伤手臂。队友的建议是让手臂自己知道「碰到了」。

二、信号确实存在

第一次采集(手臂端着杯子、杯底已触桌,继续往下压):

步  目标°    实际°   Load  Vel
 1  32.03   32.00    36    0
 2  31.53   32.00    60    0     ← 目标一直降,实际位置纹丝不动
 3  31.03   32.00    84    0
 4  30.53   32.00   104    0
 5  30.03   32.00   128    0     ← Load 单调爬升

换一个完全不同的姿态再测一次(起点 52° 而不是 32°),曲线几乎重合:

步第一次(32° 起点)第二次(52° 起点,杯子在桌上)
1Load 36Load 36
26060
38484
4104104
5128124

两次在不同姿态下 Load 的爬升几乎一致 —— 信号稳定,不是偶然。

三、★ 第一版判据在真机上失败了 ★

第一版写的是「连续 4 帧 |Velocity| ≈ 0 且 |Load| > 阈值」。它在已经顶死的场景下工作正常(多压 0.13°),但一旦从悬空处压下来就反复失效。

真机数据(先抬 6° 再下压):

步3-4:   Vel=0     堵转 1/4 → 2/4
步5:     Vel=-50   → 计数清零        ← 手臂又挪了一格
步7-8:   Vel=0     堵转 1/4 → 2/4
步9:     Vel=-50   → 计数清零        ← 又挪了
步10-13: Vel=0     堵转 1/4 → 4/4    ← 到第 13 步才判出

原因:手臂不是一次卡死,而是走走停停地下降(52→55→54→53)。舵机每次积累够力矩就挪一格,然后又停住。「连续 N 帧速度为零」被这个间歇运动反复打断,结果多压了 3.0°。

更根本的问题:速度本身没有判别力。把两次采集的 40 帧数据分开统计:
状态帧数Velocity = 0 的比例
抬升(自由运动)2467%
下压(受阻)1662%
自由运动时速度读数为 0 的比例和受阻时几乎一样。用它当判据从一开始就站不住。

四、改成跟随率

判据改成看窗口内实际走了多少,而不是瞬时速度:

跟随率 = 最近 4 步的实际位移 / 命令位移

判定撞到东西:  跟随率 < 35%   且   |Load| ≥ 40
                连续满足 2 次

走走停停照样算「跟不上」,因为总位移远小于命令位移。

先用已有数据回放验证

  步    目标    实际  Load  Vel   跟随率   判定
  1   55.50     55    23  -50      -
  2   55.00     55    35  -50      -
  3   54.50     55    64    0      -
  4   54.00     55    84    0     0%   堵转1
  5   53.50     55    87  -50     0%   堵转2  ★判定★

注意第 5 步:Vel=-50(手臂在动), 旧判据会清零,新判据照样判出 —— 因为窗口内实际位移是 0,而命令走了 2 度。

再上真机验证

  步    目标    实际  Load  Vel   跟随率   判定
  1   56.55     56   -21  -50     0%   堵转0
  2   56.05     56    40    0     0%   堵转1
  3   55.55     56    64    0     0%   堵转2  ★判定★
判出的步数多压了多少
旧判据(连续 4 帧 Vel≈0)第 13 步3.00°
新判据(跟随率)第 3 步1.05°

五、意外收获:Load 的符号指示方向

状态Load 范围中位符号一致性
抬升(对抗重力向上)-148 ~ -19-8524/24 为负
下压(受桌面阻挡)-21 ~ 196+8615/16 为正

Present_Load 是符号-幅值编码的,符号直接指示受力方向。这比预期干净,可以用来区分「在往上顶」和「在往下压」。

六、和夹爪电流的关系 —— 两个不同的量

同一天早些时候做的 抓夹触觉 读的是夹爪电机的负载,那是「爪子夹多紧」,由夹爪目标位置决定,和杯子多重无关 —— 实测加水杯(Load 262/430)和空杯(中位 370、最大 500)分不开。

真正扛重量的是 shoulder_lift。要测重量或接触,必须读它,不是读夹爪。

七、一个必须知道的限制

静止且已到位时,Load 恒为 0。舵机在「目标 = 当前位置」时不出力。当天两次尝试都读到全 0,直到给出一个略偏的目标位置让它顶住,才读到 -96。

所以这套检测只在运动过程中有效,不能用来做「静态称重」。

八、接下来

  1. 把判据接进 place_cup.py:放杯改成「压到碰桌为止」,不再依赖写死的关节角,桌面高度变了也能自适应
  2. 同一个信号也能回答「杯子有没有落地」—— 松爪后重量转移到桌子,Load 会阶跃下降
  3. 阈值(跟随率 35% / Load 40)目前只在左臂、一种桌面高度下验证过。换右臂或换桌面要重测
代码:03-software/scripts/table_contact_probe.py(采集与判据)· place_cup.py --no-release(走到放置位不松爪,给采样用)
原始数据:两次抬升+下压共 40 帧,含 Load / Current / Velocity / Position / Temperature
相关:抓夹触觉(读的是夹爪,测的是另一件事)