DEEP 109 · 第十辑 · duck-control 逐函数精读 · LEARN NOTES

收进学习手册 · 2026-09-11 · duck.whatled.com

解读 109 · hl2915.rs(二):Hl2915RobotIo 实现

解读对象:duck-control/src/hl2915.rs(620 行)
需要的前置:解读 108(帧层与寄存器)、解读 106(trait 语义)

导览见解读 69,帧层见解读 108,本篇走 Hl2915RobotIo(:200-481):实现 RobotIo 全部八个方法,是 15 只 HL-2915 加原 IMU 板的完整后端。

1. 一个后端、两种协议

结构体(hl2915.rs:200-210)最要紧的是两个协议处理器并排:v1: DynamixelProtocolHandler(飞特磁编码走协议 v1)与 v2:(IMU 板走协议 v2)。文档(:194-199)说清边界:"两个协议处理器共享一个串口,但从不共享一个包——每笔事务结束后才发另一种协议";并自称"从台架探针到 robotd 的最小安全桥",XL330 仍是默认后端,直到 HL 总线通过双舵机与 IMU 验收门。open(:213-254)在碰串口之前连过四道检查:名单合法(:220)、恰好 15 个 ID(:221-227,"数量不对"复用 ShortRead)、名单不含 IMU ID(:228-232)、校准表通过 validate(:233-235);然后才开串口,读超时由调用方传入(:236-242,robotd 传 30 ms)。

2. trait 之外的三个帮手

check_devices(:257-275)在控制循环启动前逐个 ping:15 只 HL-2915 用 v1(:259-265),IMU 用 v2(:267-274),任何一台不应答都是带 ID 的明确错误。present_positions(:277-303)是只读位置的轻量版:按 56 地址读 2 字节(:280),逐关节校验块长后过 raw_to_radians(:298-300)。interpolate_to(:305-325)是阻塞式斜率:起点取当前实测,按 duration/step 向上取整分步线性插值,每步 write 后 std::thread::sleep(step)(:322)——它不属于 trait,是 robotd init 一次性送机器人到 home 位用的。私有 write_register(:327-331)把 v1.sync_write 的错误包上带地址的 IoError::Bus,所有写路径共用。

3. read:两笔事务拼一拍

read(:335-404)先 IMU 后舵机。第一笔 v2.sync_read(&[self.imu_id], IMU_ADDR, IMU_BLOCK_LEN)(:336-344)取回 12 字节块,块数与块长各一道 ShortRead(:345-356);陈旧检测就地内联(hl2915.rs:359-364):

if self.last_imu_block.replace(raw_imu) == Some(raw_imu) {
    self.stale_imu.total = self.stale_imu.total.saturating_add(1);
    self.stale_imu.run = self.stale_imu.run.saturating_add(1);

replace 存入新值并返回旧值,相等即连续第二次见到同一块——与 bus.rs 的 StaleImuTracker(解读 99)同一逻辑,这里没有独立类型。第二笔 v1.sync_read(&self.ids, PRESENT_STATE_ADDR, PRESENT_STATE_EXTENDED_LEN)(:366-374)按 56 地址、15 字节取 15 块;Sensors 先用解码出的 IMU 填充、其余走默认(:383-386),循环里逐关节换算(:387-402):位置字节 0..2 过校准(:399),速度 2..4 过 speed_to_rad_per_second(:400),电流取 13..15 并 & 0x7fff 丢符号位、乘 6.5 mA(:397-401)——与 Sensors::currents_ma "只要幅值"的约定一致(解读 106)。

4. 写、增益桥与慢速

write(:406-416)逐关节 radians_to_raw,失败时错误带关节号(:412),2 字节小端打包后按 42 地址广播。set_torque(:418-421)一行 vec![on as u8] 写 40 地址。reboot(:423-430)沿用 bus.rs 语义:状态包是"舵机复位前未必来得及给的礼节",只有发不出去才算错。set_gain(:432-441)是解读 69 点过名的那处映射:trait 的 kp: u16 对上飞特一字节寄存器,代码显式钳制 kp.min(255) as u8(:436),同时把 D、I 增益清零(:437-440)——注释自称"刻意保守的桥",更好的映射要等 HL-2915 的调参 pass。slow_sensors(:443-472)按 62 地址读 2 字节:电压每计数 0.1 V 取平均(:465、469),温度逐关节取第 1 字节(:466);最后 imu_stale/imu_ready(:474-480)把计数器与 SflpDecoder::ready() 交给 trait——诊断语义与 XL330 后端逐字对齐,上层无需关心脚下是谁。

你带走的收获

延伸

什么鸭 WHATDUCK · 复刻学习笔记 ← 全部手册 · 首页