型号
MDC300F, 刷的新的101版本, 操作系统是Ubuntu18.
有12路CAN可供使用, 本篇当标准CAN来用, 500Kbit/s, 用透传的方式(2ms上传一次)收数据, Event或者Method的方式下发数据.
CAN端口分配
参考 产品文档 -> 接口说明 -> MDC300F接口说明书 -> 数据透传 -> 通路配置说明 -> 通路与硬件线缆配套关系
12路CAN, 程序中的channelID范围[0,11], 对应的官方提供的线束的标签如下:
- 0, CANAB1
- 1, RADAR5
- 2, ECUCAN2
- 3, RADAR1
- 4, RADAR2
- 5, RADAR3
- 6, GPS1
- 7, ECUCAN0
- 8, RADAR4
- 9, RADAR6
- 10, ECUCAN1
- 11, CANAB2
除了反直觉的 CANAB1, CANAB2, GPS1 是绿H黄L, 其余都是正常的黄H绿L.
除了 ECUCAN0/1/2 没有接终端电阻, 其余均接有终端电阻.
SOME/IP定义
mdc_can_abstract_application.arxml
中定义的 udp 端口是 :
- 上行 54870~54881 (12个端口对应12路CAN, 这是host的, mcu的为51301~51312).
- 下行 54882~54893 (12个端口对应12路CAN, 这是host的, mcu的为51321~51332).
can_rx_service_interface.arxml
中定义了SOME/IP的Rx Event
- EventID:0x8001 (32769)
- ServiceID:0xd0 (208)
- InstanceID:1~12, 对应12路CAN
- EventGroupID:0x10
can_rx_service_interface.arxml
还定义了 Tx Method
- MethodID:0x0001
- ServiceID:0xd0
- InstanceID:1~12
can_tx_service_interface.arxml
定义了 Tx Event
- EventID:0x8001 (32769)
- ServiceID:0xd1 (209)
- InstanceID:1~12, 对应12路CAN
- EventGroupID:0x10
发送参数配置
CAN配置文件的路径 /opt/platform/mdc_platform/runtime_service/devmc/conf/mcu_canbus_config.json
CAN通路配置参数定义 可以参考下CANFD的: 产品文档 -> 接口说明 -> 数据透传 -> 通路配置说明 -> CANFD通路配置参数定义, 或者参考之前版本的产品文档
由于CAN接收是透传(2ms一次), 不用配置接收, 只配置发送即可.
假设 我拿导远的INS570D(标签CAN1)挂到MDC的CAN01(标签RADAR5) :
- 通信速率500Kbit/s
- INS570D输出INS信息给MDC
- MDC需要周期发送(这里用Event方式)四轮轮速和档位信息(可以从车辆CAN获得)给INS570D
由于MMC现在只能配CANFD, 不能配CAN, 所以还是拿模版 mcu_canbus_config_ars408.json
来改. 大概6500多行, 手撕json也不要怂, 有辅助工具. 下面这个就比较好用
https://jsoneditoronline.org/
先在右侧删除所有的 RxIdList
, 这个不需要
12路 RxIdList 都删光以后, 发现只剩了3400多行, 少了将近一半, 接下来再移除除CAN01外所有的TxIdList(如果其它路又需要, 可以从这里拷贝)
发现只剩1100多行了, 修改CAN01的TxIdList(小于0x800就是标准帧, 否则为扩展帧), 删除多余的ID, 只留下3个坑位, 一共3个标准帧ID, 用来给INS570D发送四轮轮速和档位, 这里输入的CANID是十进制的:
再确认下通信速率, 确实为500Kbit/s:
至此, 1051行的 json文件, 就是我们用的, 保存下来上传为 /home/sftpuser/uploads/mcu_canbus_config.json
接下来下发配置
# 切换到root用户
$ su
# 切换到配置路径
$ cd /opt/platform/mdc_platform/runtime_service/devmc/conf
# 备份 mcu_canbus_config.json
$ cp mcu_canbus_config.json mcu_canbus_config.json.bak
# 拷贝配置好的json文件
$ cp /home/sftpuser/uploads/mcu_canbus_config.json mcu_canbus_config.json
# 删除canfd配置
$ mdc-tool mcu delete canfd
# 下发can配置
$ mdc-tool devm set-dev-cfg 1
# 复位mcu让配置生效, 或者断电重启
$ mdc-tool mcu reset mcu
这时应该能在设备上收到 周期性发送的3帧ID, 数据默认是0, 下面是用Xavier充当RADAR5的接收设备, 运行 python3 -m can.viewer -c can1 -i socketcan
得到的结果
数据的值要在下面的程序用Event的方式修改
新建CAN工程
参考GPIO篇, 用MDS新建tk_can工程, 导入官方的 can_sample
. 文件树为
$ tree
.
├── cmake
│ └── toolchain.cmake
├── CMakeLists.txt
├── manifest
│ ├── application_sample
│ │ └── can_sample
│ │ └── mdc_can_abstract_application.arxml
│ └── common
│ ├── data_type
│ │ ├── mdc_data_type.arxml
│ │ └── mdc_mode_declaration.arxml
│ ├── machine
│ │ ├── dmini0_machine.arxml
│ │ ├── dmini1_machine.arxml
│ │ ├── dmini2_machine.arxml
│ │ ├── dmini3_machine.arxml
│ │ └── host_machine.arxml
│ └── service_interface
│ └── canfd_can_uart_gpio_sample
│ ├── can_rx_service_interface.arxml
│ └── can_tx_service_interface.arxml
└── modules
├── can_sample
│ ├── CMakeLists.txt
│ └── src
│ ├── can.cpp
│ ├── can.h
│ └── main.cpp
└── CMakeLists.txt
12 directories, 17 files
依次点击 Generate, Reload Cmake, Build 按钮, 点齿轮图标配置如下
点击Launch按钮运行
cantools dbc 数据解析
先不接ins570d, 用xavier给mdc发送数据, 如 cansend can1 233#22.33
发现MDS收到了数据
接上ins570d, 发现车辆的指示灯已经亮了(表示已经收到轮速和档位的CAN消息), 但是MDS里面这个数据乱蹦, 没啥观赏性, 这里直接给解析出来.
使用cantools通过dbc文件生成c代码:
python3 -m cantools generate_c_source --database-name ins570d -e CP850 .\ins570d.dbc
会生成 ins570d.h
和 ins570d.c
这两个文件, 加入到工程中, 这个是可以c/c++混编的. 有了这个dbc生成的c文件, 信号解析起来就和抄作业一样简单, 举例如下:
回到工程中, 屏蔽掉can.cpp
中的打印 std::cout<<"success send the value, reply value is: "<< static_cast<int>(value.result) <<std::endl;
改造下main.cpp
的接收(请无视直接拿过来的printf):
#include "ins570d.h"
void ins570d_print(unsigned char channelId, const mdc::mdccan::CanBusDataParam &canData)
{
for (unsigned int i = 0; i < canData.elementList.size(); i++) {
if(canData.elementList[i].canId == INS570D_INS_ACC_FRAME_ID) { //0x500
printf("\n");
g_logger.LogInfo();
}
if(canData.elementList[i].canId < 0x800) {
printf("can%d %03X [%d] ", channelId, canData.elementList[i].canId, canData.elementList[i].validLen);
} else {
printf("can%d %08X [%d] ", channelId, canData.elementList[i].canId, canData.elementList[i].validLen);
}
for (unsigned int j = 0; j < canData.elementList[i].validLen; j++) {
printf(" %02X", canData.elementList[i].data[j]);
}
if(canData.elementList[i].canId == INS570D_INS_ACC_FRAME_ID) { //0x500
struct ins570d_ins_acc_t msg;
ins570d_ins_acc_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_ACC_LENGTH);
double acc_x = ins570d_ins_acc_acc_x_decode(msg.acc_x);
double acc_y = ins570d_ins_acc_acc_y_decode(msg.acc_y);
double acc_z = ins570d_ins_acc_acc_z_decode(msg.acc_z);
printf(" :: acc_x: %.3f g, acc_y: %.3f g, acc_z: %.3f g", acc_x, acc_y, acc_z);
} else if(canData.elementList[i].canId == INS570D_INS_GYRO_FRAME_ID) { //0x501
struct ins570d_ins_gyro_t msg;
ins570d_ins_gyro_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_GYRO_LENGTH);
double gyro_x = ins570d_ins_gyro_gyro_x_decode(msg.gyro_x);
double gyro_y = ins570d_ins_gyro_gyro_y_decode(msg.gyro_y);
double gyro_z = ins570d_ins_gyro_gyro_z_decode(msg.gyro_z);
printf(" :: gyro_x: %.3f deg/s, gyro_y: %.3f deg/s, gyro_z: %.3f deg/s", gyro_x, gyro_y, gyro_z);
} else if(canData.elementList[i].canId == INS570D_INS_HEADING_PITCH_ROLL_FRAME_ID) { //0x502
struct ins570d_ins_heading_pitch_roll_t msg;
ins570d_ins_heading_pitch_roll_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_HEADING_PITCH_ROLL_LENGTH);
double ins_pitch_angle = ins570d_ins_heading_pitch_roll_ins_pitch_angle_decode(msg.ins_pitch_angle);
double ins_roll_angle = ins570d_ins_heading_pitch_roll_ins_roll_angle_decode(msg.ins_roll_angle);
double ins_heading_angle = ins570d_ins_heading_pitch_roll_ins_heading_angle_decode(msg.ins_heading_angle);
printf(" :: ins_pitch_angle: %.3f deg, ins_roll_angle: %.3f deg, ins_heading_angle: %.3f deg", ins_pitch_angle, ins_roll_angle, ins_heading_angle);
} else if(canData.elementList[i].canId == INS570D_INS_HEIGHT_AND_TIME_FRAME_ID) { //0x503
struct ins570d_ins_height_and_time_t msg;
ins570d_ins_height_and_time_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_HEIGHT_AND_TIME_LENGTH);
double ins_locat_heigh = ins570d_ins_height_and_time_ins_locat_heigh_decode(msg.ins_locat_heigh);
double ins_time = ins570d_ins_height_and_time_ins_time_decode(msg.ins_time);
printf(" :: ins_locat_heigh: %.3f m, ins_time: %.3f ms", ins_locat_heigh, ins_time);
} else if(canData.elementList[i].canId == INS570D_INS_LATITUDE_LONGITUDE_FRAME_ID) { //0x504
struct ins570d_ins_latitude_longitude_t msg;
ins570d_ins_latitude_longitude_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_LATITUDE_LONGITUDE_LENGTH);
double ins_latitude = ins570d_ins_latitude_longitude_ins_latitude_decode(msg.ins_latitude);
double ins_longitude = ins570d_ins_latitude_longitude_ins_longitude_decode(msg.ins_longitude);
printf(" :: ins_latitude: %.3f deg, ins_longitude: %.3f deg", ins_latitude, ins_longitude);
} else if(canData.elementList[i].canId == INS570D_INS_SPEED_FRAME_ID) { //0x505
struct ins570d_ins_speed_t msg;
ins570d_ins_speed_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_SPEED_LENGTH);
double ins_north_spd = ins570d_ins_speed_ins_north_spd_decode(msg.ins_north_spd);
double ins_east_spd = ins570d_ins_speed_ins_east_spd_decode(msg.ins_east_spd);
double ins_to_ground_spd = ins570d_ins_speed_ins_to_ground_spd_decode(msg.ins_to_ground_spd);
printf(" :: ins_north_spd: %.3f m/s, ins_east_spd: %.3f m/s, ins_to_ground_spd: %.3f m/s", ins_north_spd, ins_east_spd, ins_to_ground_spd);
} else if(canData.elementList[i].canId == INS570D_INS_DATA_INFO_FRAME_ID) { //0x506
struct ins570d_ins_data_info_t msg;
ins570d_ins_data_info_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_DATA_INFO_LENGTH);
double ins_gps_flag_pos = ins570d_ins_data_info_ins_gps_flag_pos_decode(msg.ins_gps_flag_pos);
double ins_num_sv = ins570d_ins_data_info_ins_num_sv_decode(msg.ins_num_sv);
double ins_gps_flag_heading = ins570d_ins_data_info_ins_gps_flag_heading_decode(msg.ins_gps_flag_heading);
double ins_gps_age = ins570d_ins_data_info_ins_gps_age_decode(msg.ins_gps_age);
double ins_car_status = ins570d_ins_data_info_ins_car_status_decode(msg.ins_car_status);
double ins_status = ins570d_ins_data_info_ins_status_decode(msg.ins_status);
printf(" :: ins_gps_flag_pos: %.3f, ins_num_sv: %.3f, ins_gps_flag_heading: %.3f, ins_gps_age: %.3f s, ins_car_status: %.3f, ins_status: %.3f",
ins_gps_flag_pos, ins_num_sv, ins_gps_flag_heading, ins_gps_age, ins_car_status, ins_status);
} else if(canData.elementList[i].canId == INS570D_INS_STD_FRAME_ID) { //0x507
struct ins570d_ins_std_t msg;
ins570d_ins_std_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_STD_LENGTH);
double ins_std_lat = ins570d_ins_std_ins_std_lat_decode(msg.ins_std_lat);
double ins_std_lon = ins570d_ins_std_ins_std_lon_decode(msg.ins_std_lon);
double ins_std_locat_height = ins570d_ins_std_ins_std_locat_height_decode(msg.ins_std_locat_height);
double ins_std_heading = ins570d_ins_std_ins_std_heading_decode(msg.ins_std_heading);
printf(" :: ins_std_lat: %.3f, ins_std_lon: %.3f, ins_std_locat_height: %.3f, ins_std_heading: %.3f", ins_std_lat, ins_std_lon, ins_std_locat_height, ins_std_heading);
} else if(canData.elementList[i].canId == INS570D_INS_UTC_FRAME_ID) { //0x508
struct ins570d_ins_utc_t msg;
ins570d_ins_utc_unpack(&msg, (uint8_t *)(&canData.elementList[i].data[0]), INS570D_INS_UTC_LENGTH);
double year = ins570d_ins_utc_utc_year_decode(msg.utc_year);
double month = ins570d_ins_utc_utc_month_decode(msg.utc_month);
double day = ins570d_ins_utc_utc_day_decode(msg.utc_day);
double hour = ins570d_ins_utc_utc_hour_decode(msg.utc_hour);
double min = ins570d_ins_utc_utc_min_decode(msg.utc_min);
double sec = ins570d_ins_utc_utc_sec_decode(msg.utc_sec);
double msec = ins570d_ins_utc_utc_msec_decode(msg.utc_msec);
printf("%d-%d-%d %d:%d:%d.%d", (int)year, (int)month, (int)day, (int)hour, (int)min, (int)sec, (int)msec);
}
printf("\n");
}
}
void OnCanRawDataRecieved(unsigned char channelId, const mdc::mdccan::CanBusDataParam &canData)
{
// 入参判断
if (channelId >= CAN_NUM) {
return;
}
if(channelId == 1) {
ins570d_print(channelId, canData);
}
}
重新编译运行, 发现原始数据和解析后的数据都出来了:
Event下发数据
接收基本没问题了, 开始改造Event发送.
//can.h
const int SLEEP_TIME = 10000;
const int CHANNEL_ID = 1;
//发送和组包函数增加 canid, data, len 参数
void CanEventSend(unsigned char channelId, uint32_t canid, uint8_t data[], uint8_t len);
void PackageEventCanData(mdc::mdccan::CanBusDataParam &canDataParm, uint32_t canid, uint8_t data[], uint8_t len);
//can.cpp
void McuApInterface::CanEventSend(unsigned char channelId, uint32_t canid, uint8_t data[], uint8_t len)
{
...
// Event组包
CanBusDataParam canDataParm;
PackageEventCanData(canDataParm, canid, data, len);
...
}
void McuApInterface::PackageEventCanData(CanBusDataParam &canDataParm, uint32_t canid, uint8_t data[], uint8_t len)
{
// vector中的其中一帧canRawdata
struct Element canRawdata;
canRawdata.timeStamp.second = 0xFFFFFFFF;
canRawdata.timeStamp.nsecond = 0xFFFFFFFF;
// 下发的CAN帧,需要在canbus_config.json中配置,比如CanId、DataLength
canRawdata.canId = canid;
canRawdata.validLen = len;
// Event下发方式,每包(vector)包含1帧
for (int i = 0; i < len; i++) {
canRawdata.data.push_back(data[i]);
}
canDataParm.seq = 1;
canDataParm.elementList.push_back(canRawdata);
}
//main.cpp
int main(int argc, char *argv[])
{
...
int32_t i = -1;
//这个数据从车辆CAN上更新, 这里造假数据
//导远给每个厂家的手册可能不一样, 这里用111,222,333代替
uint8_t data_0x111[8] = {0};
uint8_t data_0x222[8] = {0};
uint8_t data_0x333[8] = {0};
while (1) {
i = (i + 1) % 100;
if(i%2==0) { //20ms
data_0x111[7]++; //最后一个字节累加
data_0x222[7]++;
mcuApInterfaceNode.CanEventSend(CHANNEL_ID, 0x111, data_0x111, 8);
mcuApInterfaceNode.CanEventSend(CHANNEL_ID, 0x222, data_0x222, 8);
}
if(i%20==0) { //200ms
data_0x333[7]++;
mcuApInterfaceNode.CanEventSend(CHANNEL_ID, 0x333, data_0x333, 8);
}
usleep(SLEEP_TIME); //10ms
}
return 0
}
运行, 就可以看到不断变化的can值
当然, 也可以仿照接收, 对信号值进行encode, 对消息pack, 再发送, 参考ins570d.c
Method方式下发
例子中用 Method 下发了0x410 的帧, 但是没有收到, 因为mcu_canbus_config.json
中没有配, 在原来3个ID的基础上加上这第4个ID(网页上直接复制).
sftp上传为2.json, 重新配置
cd /opt/platform/mdc_platform/runtime_service/devmc/conf
cp /home/sftpuser/uploads/2.json mcu_canbus_config.json
mdc-tool devm set-dev-cfg 1
mdc-tool mcu reset mcu
在Xavier上就收到了MDC通过Method方式发出的周期为400ms的CAN数据
$ candump -td -x can1,410:7FF
(000.000000) can1 RX - - 410 [8] 01 01 01 01 01 01 01 01
(000.402229) can1 RX - - 410 [8] 02 02 02 02 02 02 02 02
(000.401927) can1 RX - - 410 [8] 03 03 03 03 03 03 03 03
(000.402004) can1 RX - - 410 [8] 04 04 04 04 04 04 04 04
(000.401919) can1 RX - - 410 [8] 05 05 05 05 05 05 05 05
多路CAN
如CAN00-底盘, CAN01-惯导, CAN02-EyeQ3, CAN03-毫米波ARS430… 想揉到一个工程里面(不太清楚这样合不合适), 这样来回转发车速轮速之类的信息也方便, 那就使劲造node, 举个栗子
int main(int argc, char *argv[])
{
ara::log::InitLogging("CAN", "can abstract example", LogLevel::kVerbose, (LogMode::kConsole | LogMode::kRemote));
McuApInterface can0_node;
can0_node.Init(0); //物理连接为CANAB1
g_logger.LogInfo() << "can0_node init success";
can0_node.RegisterRxEventCallBackFunc(std::bind(&can0_rx_callback, std::placeholders::_1, std::placeholders::_2)); // 注册CAN帧处理回调函数
McuApInterface can1_node;
can1_node.Init(1); //物理连接为RADAR5
g_logger.LogInfo() << "can1_node init success";
can1_node.RegisterRxEventCallBackFunc(std::bind(&can1_rx_callback, std::placeholders::_1, std::placeholders::_2)); // 注册CAN帧处理回调函数
...
}
一般工作流程大致为:
- 确定传感器和MDC连接关系
- 改 mcu_canbus_config.json 文件, 周期性下发报文
- 向供应商索取或者自己编写DBC文件, 使用 python cantools 生成c文件, 加到工程中
- 工程中 解析 / 转发 数据
- AP to ROS
结语: 由于导远给各个厂家的协议有差异, DBC文件请自己获取, 这里不提供