起因

前两篇把买的东西定了:关节模组、编码器、倾角计。这篇回到自己动手最多的那层——让这十四个轴转起来的软件。立项的时候给它起过名字,叫薄执行器:1kHz 的伺服循环加 EtherCAT 主站加轨迹插值,再没有别的。为什么强调薄,黑灯车间里的软件,每多一层就多一层会坏的东西,部署面越小越好养。这篇把这一层拆开写:1kHz 是什么意思,主站放哪,一个核怎么要出来,一毫秒里到底干了什么,轨迹从哪来。

先把 1kHz 说准确

一说 1kHz 伺服,容易跟电流环混在一起,先掰开。电流环在模组的驱动板里,几十微秒一个周期,那是模组厂写好的,我们碰不着也不用碰;第四篇定的 CSP 模式下,模组内部闭环位置,主站的活是每毫秒喂一个位置设定点过去。所以我们的 1kHz 是喂轨迹的频率,不是驱动电流的频率,两个数差着一个量级,各归各。

1kHz 喂轨迹够不够,算一下就知道:腕部最快每秒 250 度,一毫秒走 0.25 度,插值粒度上的速度误差可以忽略——前提是轨迹本身平滑,S 曲线把加速度的变化率都限住了,毫秒粒度上没有台阶。再往上拉到 2k、4k 也不是不行,EtherCAT 的带宽立项篇算过,跑到 10kHz 都富余,但 CPU 那边占用翻倍,节拍一点没变快,收益是零。频率不是越大越好,是够用就好,1kHz 定死。

主站放用户态还是内核态

EtherCAT 主站在 Linux 上两条路:SOEM 跑在用户态,IgH 跑在内核态。内核态时延更稳,用户态代码好写好调,崩了就是崩一个进程,不带走内核。

决定是先用 SOEM。理由很实际:这一层的合同——周期、过程映像、DC 时钟——两个主站是一样的,先拿简单的把整条链路跑通,实测抖动压不住再换 IgH,上面一行不用改。买主站框架不像买模组,换实现没有切换成本,那就先挑省事的。

PREEMPT_RT 打上,实时线程 SCHED_FIFO 优先级拉满,mlockall 把内存锁死不让换页。这三样是用户态实时的底线,缺一个,抖动就不是微秒级的事了。

一个核的专属

RK3588 八个核,四个 A76 四个 A55;RK3576 少一些。这一层最重要的一件事,是从 Linux 手里要一个核出来,专门伺候伺服循环:内核启动参数里把这个核从调度器手里隔离掉,别的线程一律不许落上去,实时线程绑死在上面,调频 governor 锁在定频。要的是独占,不是快。

这里吃过一个亏,照实记。最早没绑核,循环跑得好好的,偶尔周期翻倍还抖。查下来是调度器把线程从 A76 挪到了 A55——大小核的负载均衡在它看来是好意,对实时线程就是灾难,核一换,缓存全冷,频率还不一样。绑核之后这个世界清静了。所以绑核不是优化项,是正确性的前提;调频锁死同理,频率一漂,一段计算的时间就漂。

顺带把立项时的一个承诺兑现了:一份逻辑,两颗芯片。实时循环不碰 NPU,不依赖哪个核多快,它要的只有独占和定频——这两样 RK3576 和 RK3588 都给得起。A72 的核上,一毫秒预算里的活几十微秒就干完了,慢核跑实时一点不勉强。两档芯片分档分的从来不是控制,是感知。

一毫秒里干什么

循环体每毫秒跑一遍,内容是固定的四样:收上一周期的反馈,算这一周期的设定点,发下去,顺手做监控。EtherCAT 是一帧出去、带着十四个轴的命令,一帧回来、带着十四个轴的状态,三百字节上下,线上一个来回百微秒级。插值和前馈计算,十四个轴加起来不到十微秒。监控那一遍——跟随误差、软限位、力矩限幅——又是几微秒。算下来一毫秒的预算用了不到三分之一,剩下七成是空转,空转是故意的,那是留给异常处理的余量。

步骤 耗时量级
EtherCAT 一帧往返 约 100 微秒
插值加前馈,十四轴 10 微秒以内
监控检查 10 微秒以内
剩余(余量) 700 微秒以上

时钟的事交给分布式时钟:第一个模组当参考钟,其余从站跟它对齐,主站自己的时钟偏移另算。两级对下来,两条臂的命令起点差在微秒以下——立项篇说双臂一条抓一条放的配合靠的就是这个,不是靠软件里留半秒余量保平安。

抖动要实测,不能靠感觉。办法是把 DC 参考的同步脉冲引出来,示波器上看一毫秒一格的晃动,连续看一天。PREEMPT_RT 加绑核加锁频之后,用户态 SOEM 在这个平台上能做到微秒到几十微秒的抖动,对 1kHz 绰绰有余。这个数进验收,不达标就换 IgH,再不达标就要回头查是不是隔离没做干净。

轨迹从哪来

执行器对上只有一个接口:关节位置的时间序列,进一个环形缓冲,循环每毫秒从里面取一点。序列从哪来它不管——离线的,MoveIt 在开发机上规划仿真完,输出时间参数化的轨迹文件,拷到设备上;在线的,散堆件每件的位姿现场才知道,设备上跑轻量解算,数值逆解加简单避障,解出来同样进环缓冲;还有示教的,人拉着臂走一遍录下来平滑处理,还是进同一个缓冲。三个来源一个入口,上层将来怎么换脑,这一层一行不改——薄就薄在这里。

环缓冲两边各管各的:规划那边连续吐段,执行这边连续吃点,段与段之间不停顿——立项篇说的节拍三大隐形杀手里那个"段间停顿",解法就落在这一处。缓冲深度按 100 毫秒设计:短了规划一卡就断粮,长了改轨迹不灵。断粮是宁可停也不瞎走:缓冲见底,循环保持最后位置等补给,同时报缺料。

双臂协调不在这层做。1kHz 里只有两件事跟"双"有关:两条臂各自的跟踪,和一个碰撞检查(关节空间的包络距离,查表,微秒级)。协调是上一层的活——100 赫兹的规划层把两条臂的轨迹在时间上对齐,DC 时钟保证两边同拍起步。真正要两个关节较劲的共抬,是阻抗控制的事,留到那篇再写。

这层怎么养

部署面小到什么程度:一个二进制加一份配置,systemd 一个服务拉起来,没有容器,没有节点图,没有消息中间件。升级就是换文件重启,黑灯车间里一次重启的代价是分钟级,可以排进任意一次换班的间隙。

断电的处理跟零点那篇连着:零点表在参数区双副本里,丢不了;轨迹执行进度也每次过段边界记一笔,重新上电从最近的段边界重放——码了一半的托盘不用推倒重来。日志走 journald 落盘环形,够用,不自己造存储。

接下来

到这篇,机器的骨架、关节、自检、执行层都齐了,是个聋哑的壮劳力:让干什么干什么,看不见东西。下一篇给它加眼睛:视觉链怎么搭,拍照、推理、坐标怎么流回给运动层,NPU 在哪一帧上出力——也是 RK3576 和 RK3588 真正分档的地方。

参考链接