树莓派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()
相关推荐
爱吃大芒果7 小时前
Flutter 列表优化:ListView 性能调优与复杂列表实现
开发语言·hive·hadoop·flutter·华为
yaoh.wang7 小时前
力扣(LeetCode) 67: 二进制求和 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
啃火龙果的兔子7 小时前
Java 学习路线及学习周期
java·开发语言·学习
电饭叔7 小时前
自定义重载运算符--《python语言程序设计》2018版--第8章20题使用Rational类求和数列之一
开发语言·python
济南壹软网络科技有限公司7 小时前
高并发电商实战:基于Java生态的多元化盲盒系统技术实现方案
java·开发语言·开源·盲盒源码·盲盒定制开发
Evan芙7 小时前
基于Nginx和Python的动态站点安装配置
数据库·python·nginx
獭.獭.7 小时前
C++ -- 位图与布隆过滤器
开发语言·c++
工具人55557 小时前
python 环境问题
开发语言·python
小年糕是糕手7 小时前
【C++】string类(二)
开发语言·数据结构·c++·程序人生·算法·leetcode·数字货币