以一个简单的开放DBC的倾角传感器MTLT305D为例, DBC可以到官网下载
Aceinna MTLT305D_dbc_19.1.51_20190621.dbc
Aceinna MTLT305D_dbc_19.1.51_20190620.dbc
下载后重命名为 MTLT305D_dbc_19.1.51_20190621.dbc
, 注意:
dbcc
或 dbc-codegen
生成报错, python cantools 可能有一定的纠错能力, 是可以顺利生成C代码的, 这也是本篇的由来.首先是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来写传感器模拟器极为简单, 先安装必要的库
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
python3 -m cantools generate_c_source --database-name mtlt305d -e UTF-8 MTLT305D_dbc_19.1.51_20190621.dbc
把生成的 mtlt305d.c
和 mtlt305d.h
移到src文件夹.
先来看下最终的工程目录
$ 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 }
可以看到解析结果正如模拟器所发…
rust_note/playground/play_ffi_cantools at main · weifengdq/rust_note (github.com)