notes
Tools

Host↔FW レイテンシの測定手順

5 段階で切り分ける。まず candump inter-arrival と host loop jitter

要点: 5 段階で切り分ける。まず candump inter-arrival と host loop jitter を測って主犯を絞る。

測りたい分解

  • A: 総 round-trip (指令 → 効果が feedback で見える)
  • B: 片道 feedback レイテンシ (motor 事象 → host)
  • C: USB + driver の jitter
  • D: host loop の jitter

Step 1: candump inter-arrival (5分・コード変更ゼロ)

candump -tz -e can0 > /tmp/candump_baseline.log
# 10 秒くらい経ったら Ctrl+C
  • -tz: kernel timestamp (絶対時刻、µs 精度)
  • -e: エラーフレームも

集計:

import re, numpy as np
lines = open("/tmp/candump_baseline.log").readlines()
fb_stamps = [float(re.search(r"\((\d+\.\d+)\)", l).group(1))
             for l in lines if " 100 " in l]  # feedback ID を書き換え
diffs = np.diff(fb_stamps) * 1000  # ms
print(f"mean={diffs.mean():.2f}ms std={diffs.std():.2f}ms "
      f"p99={np.percentile(diffs, 99):.2f}ms max={diffs.max():.2f}ms")

期待値: FW 100Hz なら mean 10ms, std 数百µs 以下がヘルシー。max 30ms 超えるなら USB バースト配信していて主犯。

Step 2: Round-trip step response (15分・motor_test に追加)

duty step を打って velocity 立ち上がりまでの時間を測る。負荷を外して安全に。

class LatencyProbe : public rclcpp::Node {
  rclcpp::Time t_cmd_;
  bool armed_ = false;

  void tick() {
    armed_ = true;
    t_cmd_ = now();
    motor_->sendDuty(0.15);
  }
  void on_feedback(...) {
    if (armed_ && std::abs(fb.velocity) > 0.5) {
      RCLCPP_INFO(get_logger(), "round-trip: %.2f ms",
                  (now() - t_cmd_).seconds() * 1000);
      armed_ = false;
      motor_->sendDuty(0.0);
    }
  }
};

30 サイクル回して統計。

  • RTT には motor の機械応答時間が含まれる → duty を継続して一次遅れ fit すると pure delay 成分だけ抽出できる

期待値: 10-30ms。40ms 超えるなら USB か driver が主犯。

Step 3: Feedback jitter を ROS 側でも (10分)

Step 1 と同じ内容を ROS 経由で測って、差 = driver+topic の遅延

class J(Node):
    def __init__(self):
        super().__init__('jitter')
        self.stamps = []
        self.create_subscription(FeedbackMsg, 'motor0/feedback',
                                 self.cb, 100)
    def cb(self, msg):
        self.stamps.append(self.get_clock().now().nanoseconds / 1e6)
        if len(self.stamps) == 500:
            d = np.diff(self.stamps)
            print(f"mean={d.mean():.2f}ms std={d.std():.2f}ms "
                  f"p99={np.percentile(d, 99):.2f}ms max={d.max():.2f}ms")
            rclpy.shutdown()

Step 4: 正弦波で位相遅れ (10分・bag 使う)

LTI の位相遅れは pure time delayr_v = A·sin(2π·f·t) を 1/2/5/10Hz で bag 収録。

from scipy.signal import correlate
lag = np.argmax(correlate(feedback_v, cmd_v)) - len(cmd_v) + 1
delay_ms = lag * dt * 1000

複数周波数で τ 一定 = pure delay、周波数依存 = 一次遅れ含み。分離できる

Step 5: Host loop jitter (5分)

Timer callback 内で計測:

auto t = now();
static rclcpp::Time last;
if (last.nanoseconds() > 0) {
    auto dt = (t - last).seconds() * 1000;
    log_dt_.push_back(dt);
    if (log_dt_.size() == 1000) { /* stats */ }
}
last = t;

10ms 周期狙いで std 数ms なら Timer が緩い。RT 化 (SCHED_FIFO + core pinning) で <100µs に落ちる

分離表

測定得られる分離できる要素
candump 間隔jitter (下限)USB + FW 送信
RTT probe総 round-trip通信 + FW 処理 + motor
ROS jitterROS 経由 jitter+ driver + rclcpp
正弦波位相pure time delay全部込みだが LTI として綺麗
Loop dtTimer jitterhost loop 単体

推奨順

  1. Step 1 (candump) — 何もいらない、まず数字 (5分)
  2. Step 5 (loop dt) — 既存ノードに 10 行 (5分)
  3. Step 3 (ROS jitter) — Python 30 行 (10分)
  4. Step 2 (RTT probe) — motor_test にノード追加 (15分)
  5. Step 4 (正弦波) — Ruckig 差し替えて bag (15分)

Step 1 + Step 5 だけで「主犯は USB か Timer か」の切り分けは可能。数字を見てから次を絞る。

関連

On this page