Files
huoyi_deploy/src/invicta_hardware
caowangrenbo 7ba2bba450 feat(safety): 错误位上报 1002/3;IMU 断流判定;移除硬件假 IMU
- MotionState 增加 error_code,新增 invicta_msgs/error_code.h 共用位定义;SafeController 写入、remote 展开成协议 ErrorList(component 暂为 0)
- InvictaInterface 增加 IMU 断流/恢复判定(imu_timeout_s / imu_recover_s),covariance=-1 视为不可用;RL 丢失 IMU 走正常趴下
- invicta_hardware 不再发布硬编码单位四元数 IMU(含错误的 covariance[0]=0)
- 更新 README 与 docs/TODO.md:记录已完成项、反馈帧 CANID 1~4 已确认、[joint_limits] 重复维护入待办
2026-10-23 23:32:57 +08:00
..

invicta_hardware

关节数据源与关节指令执行端。已接 SocketCAN + 智身关节模组 CANFD 协议(协议见 docs/zhishen_joint_module_canfd.md)。

它负责什么

以 publish_rate_hz(默认 200 Hz)为一个周期,循环做三件事:

  1. ReadHardware() — 非阻塞收各路总线上的 12 字节反馈帧,更新关节位置/速度/力矩、温度、状态码、电压
  2. PublishState() — 发 sensor_msgs/JointState
  3. WriteHardware() — 使能后每周期发 MIT 运行帧(p / v / kp / kd / tau)

上游指令断流时(command_timeout_s,默认 100 ms)自动转阻尼帧(kp=0、速度/力矩 0、只留 timeout_*_kd), 避免 deploy 挂掉/卡住后本节点一直重发最后一帧。指令恢复后自动退出阻尼。

另按 diagnostics_rate_hz(默认 10 Hz)发 invicta_msgs/JointDiagnostics(温度/状态/电压)。

反馈不需要主动查询:模组对每条指令都回一帧状态(手册 §3.2),所以"发了指令就有反馈",读回和写入天然同频。

话题 方向 消息
/joint_states 发布 sensor_msgs/JointState
/joint_diagnostics 发布 invicta_msgs/JointDiagnostics(默认 10 Hz)
/joint_commands 订阅 invicta_msgs/JointCommandArray

控制字 → CAN 帧

control_word 实际动作
kDisable(1) 每关节块 0xFF×9 + 0xFD,线圈断开,之后不再发运行帧
kEnable(2) 0xFF×9 + 0xFC,按 enable_frames/enable_interval_s 连续刷几帧
kMotorControl(3) 发运行块;未使能时拒发(手册 §9.1 要求先使能再运行)
kErrorReset(4) 本协议没有独立清错指令(手册 §6:故障后靠重新使能/重启),按"重发使能"处理
kGetStatus(5) 不需要发帧:状态随每条指令自动返回
kSetZero(6) 0xFF×9 + 0xEF:把当前机械位置写成模组零位。只作用于指令 name 选中的关节,且只发摆臂(轮毂不支持,手册 §4.4)。之后该位姿反馈角≈0,请把对应 offset_deg 置 0

关节选择(部分接入 / 单腿调试)

JointCommandArray 的 name 列表就是本次要控制的关节子集:

  • name 为空 → 全部已配置关节(deploy 一直填全部,行为不变)。
  • name 是子集 → 只驱动/置零这些关节;不在子集里的关节会被写入一个零力矩「自由」块(kp=kd=0、tau=0),既不驱动也不保持,也不参与置零。
    • ⚠ 这里不能简单地把未选中的块留成全 0:按协议 (0,0,0,0,0) 会解码成 P_MIN/V_MIN/T_MIN,其中 tau=T_MIN(摆臂 −100 N·m)是危险的前馈力矩。所以显式写零力矩块。
  • 数组长度(position/velocity/effort/kp/kd)仍必须是全部关节数,只有 name 决定选择范围。
  • kEnable/kDisable/kErrorReset 仍作用于全部已配置关节(总线级动作);只有运行帧与 kSetZero 按 name 选择。
  • 控制字只在变化时落地:要对另一个子集重新置零,先把控制字切到别的值(如 kGetStatus)再切回 kSetZero。

这样台架上只接一条腿也能单独控制、单独置零,不必把电机全接上。

同一路总线上最多 4 个关节(CANID 1~4),合成一帧下发:Byte0-1 是 MSGID 0x0100,每个 CANID 占 10 字节,块起点 2 + 10×(CANID−1),长度按手册裁剪表(本机 4 个 ID → 48 字节)。

约定(最重要的部分)

  • 关节角 [rad]、角速度 [rad/s]、力矩 [Nm],按 joint_names 的固定顺序。
  • 边界和限幅是两件事:[motor.*] 的 p/v/t 是归一化边界——只决定 CAN 帧里 0~65535 映射到多少物理量(编码/解码必须用同一套,手册 §5/§9.6),不是运行限幅;[joint_limits] 才是唯一实际生效的限幅。下发顺序是「先按限幅夹紧 → 再归一化编码」。加载时会校验限幅不超过边界,超了直接报错,不靠编码饱和兜着。
  • kp/kd 不是策略算的,来自 deploy 的 [policy].kp/kd 与 [leg.gains](用户配置)。越界时本节点会夹到 [0, kp_max] / [0, kd_max] 并打警告——那种日志说明配置写错了,不要靠饱和兜着。
  • 前馈力矩按 [joint_limits].torque_limit(默认取厂家峰值扭矩)夹紧,超过也告警。
  • 本节点是 raw↔model 的唯一边界:话题上收发的都是模型帧(模型坐标、rad)。 [[legs]] 的 dir/offset_deg 由本节点施加(q_model = q_motor·dir + offset,反向 q_motor = (q_model − offset)·dir;速度/力矩只乘 dir,不含 offset)。 invicta_deploy 侧不再做任何标定,只处理模型量。这样 [joint_limits](模型帧限幅)与被限幅的指令同帧,不会错位;任何看 /joint_states 的消费者拿到的也是有物理意义的角度。
  • IMU 不由本节点发布。之前这里发的是硬编码的单位四元数 + 零角速度/零加速度(假数据),还把 orientation_covariance[0] 标成 0(有效),会让下游把「永远水平静止」当真值——已删除。姿态/角速度由独立的 IMU 节点发布到 imu/data(话题名见 deploy/remote 的 [topics].imu),契约见 invicta_deploy/README.md。
  • 温度取自反馈帧 Byte8:T = raw − 40(0255 ↔ −40215 ℃);电压 Byte10 直接是伏。
  • 状态码 Byte9:bit0 使能,bit1~6 故障(过压/过流/过温/过速/编码器/欠压)。故障位出现时本节点会打 ERROR 日志,并原样放进 /joint_diagnostics。

总线前置条件

位率由系统配置,本节点只负责 socket 收发:

sudo ip link set can0 up type can bitrate 1000000 dbitrate 5000000 fd on
sudo ip link set can1 up type can bitrate 1000000 dbitrate 5000000 fd on

[motor.can].bitrate/data_bitrate 只用于出错时的提示,不能透过 SocketCAN 设置。

无硬件调试开关

位置 默认 作用
[hardware].require_can true CAN 打不开就退出,避免"以为在控车其实没连上"
roslaunch ... require_can:=false — 无 CAN 也启动:照常发零位状态、缓存指令,但不下发电机指令

src/invicta_deploy/scripts/smoke_test.sh 用的就是 require_can:=false,因此容器/podman 里没有 CAN 也能跑通。

参数(本节点自己的 config/invicta_hardware.toml)

TOML 段 内容
[robot].joint_names 关节名与顺序(8 个:fl/fr/hl/hr × swing/wheel)
[joint_limits] 人为限幅(模型帧):位置 [rad]、速度 [rad/s]、前馈力矩 [N·m](下发前夹紧)
[[legs]] ×4 每腿 swing/wheel 的 dir、offset_deg(硬件标定,本节点施加)、bus、canid
[motor.joint] / [motor.wheel] 归一化边界(0~65535 ↔ 物理量),编码/解码必须一致(P65MAX-S / W190-S)
[motor.can] 总线名、仲裁 ID、1M/5M、上电使能帧数与间隔
[motor.gains] bench_* 仅作参考(实际 kp/kd 由指令给);timeout_*_kd 是看门狗阻尼
[topics] [hardware] 本节点话题、200 Hz 状态频率、10 Hz 诊断频率、require_can、command_timeout_s

运行

roslaunch invicta_hardware invicta_hardware.launch                  # 真机
roslaunch invicta_hardware invicta_hardware.launch require_can:=false  # 无 CAN 调试

自查:rostopic echo /joint_diagnostics 看温度/状态;candump can0 看反馈帧的仲裁 ID(当前按 "反馈使用关节自身 CANID" 实现,若模组回帧 ID 不同需改 ReadHardware 的匹配)。