本篇给出一个 Rust 通过FFI(Foreign Function Interface)调用 DBC生成的C代码 解析传感器数据的例子

传感器 DBC

以一个简单的开放DBC的倾角传感器MTLT305D为例, DBC可以到官网下载

下载后重命名为 MTLT305D_dbc_19.1.51_20190621.dbc, 注意:

  • 可以不需要CAN分析仪, PC上直接用vcan或vxcan模拟即可
  • 可以不需要真的去买一个传感器, 下面会写一个传感器的模拟器
  • 这可能是一个非标准的DBC或者里面存在一些错误, 用 rust 开源的 dbccdbc-codegen 生成报错, python cantools 可能有一定的纠错能力, 是可以顺利生成C代码的, 这也是本篇的由来.

VXCAN

首先是PC没有can口, 用VXCAN来模拟一对 can0-vxcan0, 解析程序用can0, 模拟器用vxcan0, 脚本vxcan.sh如下

#!/bin/sh
sudo modprobe can_raw
sudo modprobe vxcan

if ip link show can0 > /dev/null 2>&1; then
    sudo ip link set dev can0 down
    sudo ip link set dev vxcan0 down
    sudo ip link delete dev can0 type vxcan
fi

sudo ip link add dev can0 type vxcan
sudo ip link set up can0
sudo ip link set dev vxcan0 up

Python Cantools 写传感器模拟器

用Python配合cantools来写传感器模拟器极为简单, 先安装必要的库

python3 -m pip install python-can
python3 -m pip install cantools

倾角传感器主要用到 3轴acc, 3轴gyro, pitch, roll, 主要涉及3帧报文, 所以模拟器里也只实现这3帧报文, 100Hz, 造假数据循环播发. 加载DBC, 填充消息即可, fake_mtlt305d.py如下

#!/usr/bin/python3

import can
import cantools
import time
from datetime import datetime
from threading import Timer

can_bus = can.interface.Bus(bustype='socketcan', channel='vxcan0', bitrate=500000)
sensor_dbc = cantools.database.load_file('MTLT305D_dbc_19.1.51_20190621.dbc')

def x8F02D80_send(acc_x, acc_y, acc_z):
    msg = sensor_dbc.get_message_by_name("Aceinna_Accel")
    data = msg.encode({
        "Aceinna_AccX": acc_x,
        "Aceinna_AccY": acc_y,
        "Aceinna_AccZ": acc_z,
        "Aceinna_LateralAcc_FigureOfMerit": 0,
        "Aceinna_LongiAcc_FigureOfMerit": 0,
        "Aceinna_VerticAcc_FigureOfMerit": 0,
        "Aceinna_Support_Rate_Acc": 0
    })
    message = can.Message(
        arbitration_id = msg.frame_id,
        data = data,
        is_extended_id = True
    )
    can_bus.send(message)

def xCF02A80_send(gyro_x, gyro_y, gyro_z):
    msg = sensor_dbc.get_message_by_name("Aceinna_AngleRate")
    data = msg.encode({
        "Aceinna_GyroX": gyro_x,
        "Aceinna_GyroY": gyro_y,
        "Aceinna_GyroZ": gyro_z,
        "Aceinna_PitchRate_Figure_OfMerit": 0,
        "Aceinna_RollRate_Figure_OfMerit": 0,
        "Aceinna_YawRate_Figure_OfMerit": 0,
        "Aceinna_AngleRate_Latency": 0
    })
    message = can.Message(
        arbitration_id = msg.frame_id,
        data = data,
        is_extended_id = True
    )
    can_bus.send(message)

def xCF02980_send(pitch, roll):
    msg = sensor_dbc.get_message_by_name("Aceinna_Angles")
    data = msg.encode({
        "Aceinna_Pitch": pitch,
        "Aceinna_Roll": roll,
        "Aceinna_Pitch_Compensation": 0,
        "Aceinna_Pitch_Figure_OfMerit": 0,
        "Aceinna_Roll_Compensation": 0,
        "Aceinna_Roll_Figure_OfMerit": 0,
        "Aceinna_PitchRoll_Latency": 0
    })
    message = can.Message(
        arbitration_id = msg.frame_id,
        data = data,
        is_extended_id = True
    )
    can_bus.send(message)

if __name__ == '__main__':
    cnt = 0
    while True:
        t = time.time()
        xCF02980_send(7+cnt,8+cnt)
        x8F02D80_send(1+cnt,2+cnt,3+cnt)
        xCF02A80_send(4+cnt,5+cnt,6+cnt)
        cnt = (cnt+1)%10
        dt = time.time() - t
        if dt < 0.01:
            time.sleep(0.01 - dt)

运行程序, 检验

$ python3 fake_mtlt305d.py

$ candump -td -x can0
 ...
 (000.010054)  can0  TX - -  0CF02980   [8]  00 80 81 00 00 82 00 00
 (000.000059)  can0  TX - -  08F02D80   [8]  2C 7E 90 7E F4 7E 00 00
 (000.000027)  can0  TX - -  0CF02A80   [8]  00 80 80 80 00 81 00 00
 (000.010002)  can0  TX - -  0CF02980   [8]  00 00 82 00 80 82 00 00
 (000.000060)  can0  TX - -  08F02D80   [8]  90 7E F4 7E 58 7F 00 00
 (000.000026)  can0  TX - -  0CF02A80   [8]  80 80 00 81 80 81 00 00
 (000.010072)  can0  TX - -  0CF02980   [8]  00 80 82 00 00 83 00 00
 (000.000062)  can0  TX - -  08F02D80   [8]  F4 7E 58 7F BC 7F 00 00
 (000.000027)  can0  TX - -  0CF02A80   [8]  00 81 80 81 00 82 00 00

DBC 生成C代码

python3 -m cantools generate_c_source --database-name mtlt305d -e UTF-8  MTLT305D_dbc_19.1.51_20190621.dbc

把生成的 mtlt305d.cmtlt305d.h 移到src文件夹.

Rust bindgen

先来看下最终的工程目录

$ tree play_ffi_cantools/
play_ffi_cantools/
├── build.rs
├── Cargo.toml
├── fake_mtlt305d.py
├── MTLT305D_dbc_19.1.51_20190621.dbc
├── src
│   ├── can.rs
│   ├── main.rs
│   ├── mtlt305d.c
│   └── mtlt305d.h
├── vxcan.sh
└── wrapper.h

Library Usage with build.rs - The bindgen User Guide (rust-lang.github.io)

bindgen添加到Cargo.toml, 事实上还需要 rust-lang/cc-rs: Rust library for build scripts to compile C/C++ code into a Rust library (github.com) 把 C代码编译成库, dependencies里的三个库可参考上篇, 是给CAN用的:

$ cat Cargo.toml
[package]
name = "play_ffi_cantools"
version = "0.1.0"
edition = "2021"

[dependencies]
libc = "0.2.132"
ifstructs = "0.1.1"
iptool = "0.1.0"

[build-dependencies]
bindgen = "0.60.1"
cc = "1.0.73"

写一个wrapper.h, (不一定非要叫这个名字, 和build.rs对应上就行?)

#include "src/mtlt305d.h"

编写build.rs: 首先是用cc把dbc生成的c代码编译成库, 然后在编译时生成 Rust FFI 绑定

extern crate bindgen;
extern crate cc;

use std::env;
use std::path::PathBuf;

fn main() {
    cc::Build::new()
        .file("src/mtlt305d.c")
        .compile("mtlt305d");

    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

给库写rust接口或者直接填充src/main.rs解析:

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

mod can;

#[derive(Debug)]
struct mtlt305d_t {
    acc_x: f64,
    acc_y: f64,
    acc_z: f64,
    gyro_x: f64,
    gyro_y: f64,
    gyro_z: f64,
    pitch: f64,
    roll: f64,
}

impl mtlt305d_t {
    fn new() -> mtlt305d_t {
        mtlt305d_t {
            acc_x: 0.0,
            acc_y: 0.0,
            acc_z: 0.0,
            gyro_x: 0.0,
            gyro_y: 0.0,
            gyro_z: 0.0,
            pitch: 0.0,
            roll: 0.0,
        }
    }
}

fn mtlt305d_parser(frame: &can::CanFrame, mtlt305d: &mut mtlt305d_t) -> i32 {
    let id = frame.can_id & 0x1FFFFFFF;
    let ret = match id {
        MTLT305D_ACEINNA_ANGLES_FRAME_ID => unsafe {
            let mut msg = std::mem::zeroed();
            mtlt305d_aceinna_angles_unpack(
                &mut msg,
                &frame.data[0],
                MTLT305D_ACEINNA_ANGLES_LENGTH.into(),
            );
            mtlt305d.pitch = mtlt305d_aceinna_angles_aceinna_pitch_decode(msg.aceinna_pitch);
            mtlt305d.roll = mtlt305d_aceinna_angles_aceinna_roll_decode(msg.aceinna_roll);
            1
        },
        MTLT305D_ACEINNA_ACCEL_FRAME_ID => unsafe {
            let mut msg = std::mem::zeroed();
            mtlt305d_aceinna_accel_unpack(
                &mut msg,
                &frame.data[0],
                MTLT305D_ACEINNA_ACCEL_LENGTH.into(),
            );
            mtlt305d.acc_x = mtlt305d_aceinna_accel_aceinna_acc_x_decode(msg.aceinna_acc_x);
            mtlt305d.acc_y = mtlt305d_aceinna_accel_aceinna_acc_y_decode(msg.aceinna_acc_y);
            mtlt305d.acc_z = mtlt305d_aceinna_accel_aceinna_acc_z_decode(msg.aceinna_acc_z);
            2
        },
        MTLT305D_ACEINNA_ANGLE_RATE_FRAME_ID => unsafe {
            let mut msg = std::mem::zeroed();
            mtlt305d_aceinna_angle_rate_unpack(
                &mut msg,
                &frame.data[0],
                MTLT305D_ACEINNA_ANGLE_RATE_LENGTH.into(),
            );
            mtlt305d.gyro_x = mtlt305d_aceinna_angle_rate_aceinna_gyro_x_decode(msg.aceinna_gyro_x);
            mtlt305d.gyro_y = mtlt305d_aceinna_angle_rate_aceinna_gyro_y_decode(msg.aceinna_gyro_y);
            mtlt305d.gyro_z = mtlt305d_aceinna_angle_rate_aceinna_gyro_z_decode(msg.aceinna_gyro_z);
            3
        },
        _ => {
            println!("{}", id);
            0
        },
    }
    .into();
    ret
}

fn main() {
    let fd = can::can_open("can0");
    if fd < 0 {
        println!("can_open failed");
        return;
    }
    let mut mtlt305d = mtlt305d_t::new();
    let mut frame = can::CanFrame::new();
    loop {
        can::can_read(fd, &mut frame);
        let ret = mtlt305d_parser(&frame, &mut mtlt305d);
        if ret == 3 {
            println!("{:?}", mtlt305d);
        }
    }
}

编译运行

$ cargo build

# cargo run -p play_ffi_cantools
# cargo run --bin play_ffi_cantools
# ./target/debug/play_ffi_cantools
$ cargo run -p play_ffi_cantools
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/play_ffi_cantools`
mtlt305d_t { acc_x: 1.0, acc_y: 2.0, acc_z: 3.0, gyro_x: 4.0, gyro_y: 5.0, gyro_z: 6.0, pitch: 7.0, roll: 8.0 }
mtlt305d_t { acc_x: 2.0, acc_y: 3.0, acc_z: 4.0, gyro_x: 5.0, gyro_y: 6.0, gyro_z: 7.0, pitch: 8.0, roll: 9.0 }
mtlt305d_t { acc_x: 3.0, acc_y: 4.0, acc_z: 5.0, gyro_x: 6.0, gyro_y: 7.0, gyro_z: 8.0, pitch: 9.0, roll: 10.0 }
mtlt305d_t { acc_x: 4.0, acc_y: 5.0, acc_z: 6.0, gyro_x: 7.0, gyro_y: 8.0, gyro_z: 9.0, pitch: 10.0, roll: 11.0 }
mtlt305d_t { acc_x: 5.0, acc_y: 6.0, acc_z: 7.0, gyro_x: 8.0, gyro_y: 9.0, gyro_z: 10.0, pitch: 11.0, roll: 12.0 }
mtlt305d_t { acc_x: 6.0, acc_y: 7.0, acc_z: 8.0, gyro_x: 9.0, gyro_y: 10.0, gyro_z: 11.0, pitch: 12.0, roll: 13.0 }
mtlt305d_t { acc_x: 7.0, acc_y: 8.0, acc_z: 9.0, gyro_x: 10.0, gyro_y: 11.0, gyro_z: 12.0, pitch: 13.0, roll: 14.0 }
mtlt305d_t { acc_x: 8.0, acc_y: 9.0, acc_z: 10.0, gyro_x: 11.0, gyro_y: 12.0, gyro_z: 13.0, pitch: 14.0, roll: 15.0 }
mtlt305d_t { acc_x: 9.0, acc_y: 10.0, acc_z: 11.0, gyro_x: 12.0, gyro_y: 13.0, gyro_z: 14.0, pitch: 15.0, roll: 16.0 }
mtlt305d_t { acc_x: 10.0, acc_y: 11.0, acc_z: 12.0, gyro_x: 13.0, gyro_y: 14.0, gyro_z: 15.0, pitch: 16.0, roll: 17.0 }
mtlt305d_t { acc_x: 1.0, acc_y: 2.0, acc_z: 3.0, gyro_x: 4.0, gyro_y: 5.0, gyro_z: 6.0, pitch: 7.0, roll: 8.0 }
mtlt305d_t { acc_x: 2.0, acc_y: 3.0, acc_z: 4.0, gyro_x: 5.0, gyro_y: 6.0, gyro_z: 7.0, pitch: 8.0, roll: 9.0 }

可以看到解析结果正如模拟器所发…

Github

rust_note/playground/play_ffi_cantools at main · weifengdq/rust_note (github.com)

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐