树莓派CAN(FD) 测试

双向压力测试

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import time
import socket
import struct
import select
import threading
import subprocess
import argparse

# ===== CAN 常量 =====
SOL_CAN_RAW = getattr(socket, "SOL_CAN_RAW", 101)
CAN_RAW_FILTER = getattr(socket, "CAN_RAW_FILTER", 1)
CAN_RAW_LOOPBACK = getattr(socket, "CAN_RAW_LOOPBACK", 3)
CAN_RAW_RECV_OWN_MSGS = getattr(socket, "CAN_RAW_RECV_OWN_MSGS", 4)

CAN_ECHO_FLAG = 0x20000000
STD_MASK = 0x7FF

CAN_ID = 0x123
PAYLOAD = bytes([0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88])

CAN_FRAME_FMT = "=IB3x8s"  # can_id, dlc, pad, data


# ===== 工具函数 =====
def run(cmd):
    subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def setup_can():
    run("sudo ifconfig can0 down")
    run("sudo ifconfig can1 down")
    run("sudo ip link set can0 up type can bitrate 1000000")
    run("sudo ip link set can1 up type can bitrate 1000000 dbitrate 1000000 restart-ms 1000 berr-reporting on fd on")
    run("sudo ifconfig can0 txqueuelen 65536")
    run("sudo ifconfig can1 txqueuelen 65536")


def open_can(iface, is_tx):
    s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
    s.bind((iface,))

    flt = struct.pack("=II", CAN_ID, STD_MASK)
    s.setsockopt(SOL_CAN_RAW, CAN_RAW_FILTER, flt)

    if is_tx:
        s.setsockopt(SOL_CAN_RAW, CAN_RAW_LOOPBACK, 0)
    else:
        s.setsockopt(SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, 0)

    return s


def pack_frame():
    return struct.pack(CAN_FRAME_FMT, CAN_ID, 8, PAYLOAD)


def fmt_frame():
    return "ID=0x123 DATA=11 22 33 44 55 66 77 88"


# ===== 发送线程 =====
def send_loop(sock, rate, end_t, stat, key, tag):
    interval = 1.0 / rate
    next_t = time.perf_counter()
    frame = pack_frame()
    printed = False

    while time.perf_counter() < end_t:
        now = time.perf_counter()
        if now < next_t:
            time.sleep(min(0.0005, next_t - now))
            continue

        try:
            sock.send(frame)
            stat[key] += 1
            if not printed:
                print(f"[{tag} TX OK] {fmt_frame()}")
                printed = True
        except OSError:
            pass

        next_t += interval


# ===== 接收线程 =====
def recv_loop(sock, end_t, stat, key, tag):
    printed = False

    while time.perf_counter() < end_t:
        r, _, _ = select.select([sock], [], [], 0.05)
        if not r:
            continue

        frame = sock.recv(16)
        can_id, dlc, data = struct.unpack(CAN_FRAME_FMT, frame)

        if can_id & CAN_ECHO_FLAG:
            continue

        if (can_id & STD_MASK) == CAN_ID and data[:8] == PAYLOAD:
            stat[key] += 1
            if not printed:
                print(f"[{tag} RX OK] {fmt_frame()}")
                printed = True


# ===== 主程序 =====
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--no-setup", action="store_true", help="跳过 can 配置")
    parser.add_argument("--duration", type=float, default=3.0, help="每档测试秒数")
    parser.add_argument("--rates", default="10,50,100,200,500,1000,2000,3000,4000,5000")
    args = parser.parse_args()

    if not args.no_setup:
        setup_can()

    tx0 = open_can("can0", True)
    tx1 = open_can("can1", True)
    rx0 = open_can("can0", False)
    rx1 = open_can("can1", False)

    rates = [int(x) for x in args.rates.split(",")]

    print("\n=== CAN0 <-> CAN1 双向收发测试 ===")
    print("ID=0x123 DATA=11 22 33 44 55 66 77 88\n")
    print("rate(Hz) | can0->can1 sent/recv drop% | can1->can0 sent/recv drop%")

    for rate in rates:
        stat = {
            "s0": 0, "r1": 0,
            "s1": 0, "r0": 0
        }

        start = time.perf_counter()
        send_end = start + args.duration
        recv_end = send_end + 0.5

        ts0 = threading.Thread(target=send_loop, args=(tx0, rate, send_end, stat, "s0", "can0"))
        ts1 = threading.Thread(target=send_loop, args=(tx1, rate, send_end, stat, "s1", "can1"))
        tr0 = threading.Thread(target=recv_loop, args=(rx0, recv_end, stat, "r0", "can0"))
        tr1 = threading.Thread(target=recv_loop, args=(rx1, recv_end, stat, "r1", "can1"))

        tr0.start(); tr1.start()
        ts0.start(); ts1.start()

        ts0.join(); ts1.join()
        tr0.join(); tr1.join()

        d01 = 0 if stat["s0"] == 0 else (stat["s0"] - stat["r1"]) / stat["s0"] * 100
        d10 = 0 if stat["s1"] == 0 else (stat["s1"] - stat["r0"]) / stat["s1"] * 100

        print(f"{rate:7d} | {stat['s0']:5d}/{stat['r1']:5d} {d01:6.2f}%"
              f" | {stat['s1']:5d}/{stat['r0']:5d} {d10:6.2f}%")

        if d01 > 1 or d10 > 1:
            print(">> 已明显开始丢包,再提速意义不大了")
            break

    print("\n=== 测试结束 ===")


if __name__ == "__main__":
    if os.geteuid() != 0:
        print("请 sudo 运行")
        exit(1)
    main()
相关推荐
tkevinjd5 小时前
MiniCode 项目详解6:原项目控制系统的10个缺陷(已修复)
python·llm·agent
星栈独行7 小时前
翻完 Pi 源码:它和 Codex、Claude Code 有何不同
开发语言·javascript·人工智能·程序人生
dNGUZ7UGj7 小时前
10分钟完成第一个Python小游戏
python·django
没有梦想的咸鱼185-1037-16637 小时前
AI-Python机器学习与深度学习技术:CNN/Transformer/扩散模型、SHAP可解释及Hermes智能体自动化
人工智能·python·深度学习·机器学习·chatgpt·cnn·transformer
qq_448011167 小时前
C语言中的变量和函数的定义与声明
android·c语言·开发语言
霸道流氓气质7 小时前
SpringBoot中通用工具类库(Utils)封装与使用实践
spring boot·后端·python
孫治AllenSun8 小时前
【DataX】生产环境搭建DataX集群案例
java·开发语言·jvm
Draina9 小时前
CBC填充预言攻击-CBC Padding Oracle Crypto Attack
python·安全·web安全·网络安全·密码学·安全性测试
c238569 小时前
把 C++ 内存分配拆透:new 与 malloc 的三层血缘
开发语言·c++·算法
Angel Q.9 小时前
特征提取 | DINO 到 DINOv3
图像处理·python