notes
Tools

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

candump inter-arrival、round-trip probe、ROS jitter、正弦波位相遅れ、host loop dt の 5 段階で通信レイテンシの主因を切り分ける手順

要点: 5 段階で通信レイテンシを切り分ける。まず candump inter-arrival と host loop jitter を測って主犯を絞り、必要に応じて RTT probe や正弦波法で詳細を取る。

測定対象#

  • 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 100 Hz なら mean 10 ms、std 数百 µs 以下が健全。max が 30 ms を超える場合、USB のバースト配信が主因の可能性が高い。

Step 2: Round-trip step response (15 分、テストノード追加)#

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-30 ms。40 ms を超える場合は 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 delay と等価。 を 1/2/5/10 Hz で 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;

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

分離表#

測定得られる値分離できる要素
candump 間隔jitter (下限)USB + FW 送信
RTT probe総 round-trip通信 + FW 処理 + motor 応答
ROS jitterROS 経由 jitterdriver + 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) — テストノード追加 (15 分)
  5. Step 4 (正弦波) — 軌道差し替えて bag (15 分)

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

関連#

最終更新:

On this page