Apollo如何添加一个新的CAN设备

how_to_add_a_new_can_device


  • 在自动机调试的领域,使用CAN接口的应用非常广泛,这一块,在Apollo中也有所体现,今天我们就来结合代码分析一下Apollo中的CAN数据交互流程,以及分享如何在Apollo中添加一个新的CAN设备。
  • 首先,普及一下相关的知识点,普通的IPC或者PC是不具有CAN口,也就是不能直接和CAN设备通信,所以他们之间需要用到转换设备,将CAN协议转换成适配IPC的协议,例如PCIe、USB等。像Kvaser PCIEcan 4×HS就是一个CAN转PCIe设备,俗称CAN卡,后面的4xHS代表其可以“一拖四”的带有四个CAN口,它需要插在主板上的PCIe接口。而kvaser leaf light HS v2是一个CAN转USB设备,其只有一个CAN口,是插在USB口即可。
  • 本文将大体分为下面两部分:
    1. 以Apollo/conti_radar为例,聊一下Apollo中CAN设备的相关流程及代码。
    2. 以Apollo/conti_radar为例,讲解Apollo中增加新的CAN设备的基本流程。

框架理解

  • 直接看代码跳来跳去很繁琐,我总结归纳了一段整体流程的伪代码,希望可以帮助你有一个基本的流程印象。
整体流程伪代码:
struct CanFrame {
  /// Message id
  uint32_t id;
  /// Message length
  uint8_t len;
  /// Message content
  uint8_t data[8];
  /// Time stamp
  struct timeval timestamp;
}

//-- 这里的AA、BB、CC分别是不同的消息msg_id,这个是根据设备的数据手册来定义的。比如0x301之类的;
//-- dosth_AA等都是收到相应的消息后的处理逻辑代称;在Apollo中,我们一般在这里调用AdapterManager::Publish_XXX_Topic来发送ROS消息。
ContiRadarMessageManager::Parse(msg_id, data, length){
    switch(msg_id){
        case AA:{ dosth_AA; } // Publish_AA_Topic
        case BB:{ dosth_BB; } // Publish_BB_Topic
        case CC:{ dosth_CC; } // Publish_CC_Topic
    }
}

Kvaser_MSG recv_frames_;
kvaserCanClient::Receive(std::vector<CanFrame> *frames,int32_t *frame_num){
    canReadWait(handler, &recv_frames_.id,&recv_frames_.data,&recv_frames_.len, &recv_frames_.flag,&recv_frames_.time,&recv_frames_.time_out);
    CanFrame cf;
    cf.id = recv_frames_.id;
    cf.len = recv_frames_.len;
    std::memcpy(cf.data, recv_frames_.data, recv_frames_.len);
    frames->push_back(cf)
}

APOLLO_MAIN(){
    // 1. init and start the can card hardwarejiekou
    //-- can_client: ESD/Kvaser
    can_client_->Start();
    // 2. start receive first then send
    //-- can_receiver_.Start();
    std::unique_ptr<std::thread> thread_;
    MessageManager<SensorType> *pt_manager_; 
    //-- 解析conti_radar_conf.pb.txt
    GetProtoFromFile(FLAGS_sensor_conf_file,&conti_radar_conf_);
    can_type_ = conti_radar_conf_.can_conf().can_card_parameter();
    can_client_ = can_factory->CreateCANClient(can_type_); //-- 这里以kvaser为例
    //-- 这是很重要的报文解析类的管理类
    ContiRadarMessageManager *pt_manager_ = new ContiRadarMessageManager();
    
    Run in std::thread{
        wile(IsRunning()){
            can_client_->Recieve(&buf);
            for (const auto &frame : buf) {
                uint8_t len = frame.len;
      			uint32_t uid = frame.id;
                //-- 报文解析类进行报文解析以及ROS的publish等操作
                pt_manager_->Parse(uid, data, len);
            }
        }
    }
}
流程图
  • Apollo中对单独CAN设备的数据读取是单独开辟的进程,在/modules/drivers/radar/conti_radar/main.cc中的APOLLO_MAIN为程序入口。接下来调用了ContiRadarCanbus::Start()来启动CAN客户端模块以及CAN数据读取模块。
  1. CAN客户端:指Apollo中对各CAN转换设备(如kvaser)的驱动层,用来从各个转换设备中读取数据,并转换成相应的格式的过程。
  2. 数据读取:指对从CAN转换设备中读取到的一串8字节的字符串解析成不同字段的数值这么一个过程。在Apollo中,将数据解析后,会转换成proto的形式,借助ROS的topic-message机制,来Publish出去。关于ROS这块在下图中有所呈现,具体也可参考How to advertise and subscribe a topic在这里插入图片描述
报文解析
  • 既然是操作CAN设备,当然最重要的就是CAN报文解析。报文解析就是将CAN传上来的8字节串解析成不同字段数值。例如:
void ClusterListStatus600::Parse(const std::uint8_t* bytes, int32_t length, ContiRadar* conti_radar) const {
	auto status = conti_radar->mutable_cluster_list_status();
    status->set_near(near(bytes, length));
    status->set_far(far(bytes, length));
    status->set_meas_counter(meas_counter(bytes, length));
    status->set_interface_version(interface_version(bytes, length));
    auto counter = status->near() + status->far();
    conti_radar->mutable_contiobs()->Reserve(counter);
}

这里是conti_radar下对msg_id为600的报文的解析。其中bytes是CAN收到的8字节,64位的报文消息。我们将其解析并给ContiRadar* conti_radar中的对应成员赋值。这里的ContiRadar是一种proto消息格式,可以理解为Apollo中的ROS消息格式,这在下文的proto部分有详细解释。

  • 因为CAN报文的种类较多,Apollo抽象了一个协议解析的基类,放在/modules/drivers/canbus/can_comm/protocol_data.h内的class ProtocolData。我们在继承这个基类后,重写其virtual void Parse()用来做报文解析操作即可。一般,每个类型的CAN报文都新建一个对应的解析类,一般放在/modules/metoak_stereo/protocol/下,如下图就是conti_radar的相关解析类:
    在这里插入图片描述
  • 因为解析类太多,肯定还需要一个类来进行管理。Apollo也抽象了一个基类class MessageManager,放在/modules/drivers/canbus/can_comm/message_manager.h内。像conti_radar就实现了class ContiRadarMessageManager : public MessageManager<ContiRadar>来继承它。如果你想添加对某一种报文的解析,需要在/protocol/下实现了对应的解析类后,在ContiRadarMessageManager中注册对应的解析类,这个操作,我们一般放在其构造函数内,参考conti_radar:
ContiRadarMessageManager::ContiRadarMessageManager() {
    AddRecvProtocolData<RadarState201, true>();
    AddRecvProtocolData<ClusterListStatus600, true>();
    AddRecvProtocolData<ClusterGeneralInfo701, true>();
    AddRecvProtocolData<ClusterQualityInfo702, true>();
    AddRecvProtocolData<ObjectExtendedInfo60D, true>();
    AddRecvProtocolData<ObjectGeneralInfo60B, true>();
    AddRecvProtocolData<ObjectListStatus60A, true>();
    AddRecvProtocolData<ObjectQualityInfo60C, true>();
}

这里的AddRecvProtocolData就是一个解析类的注册过程,本质上将解析类push_back到一个vector,在vector的上层用map来根据msg_id做一个映射的管理,在解析的时候,根据msg_id,来find对应的解析类,然后调用其parse()函数进行解析。这一块是在ContiRadarMessageManager::GetMutableProtocolDataById()中做的。

整个的报文解析这一套可以理解为一个简单工厂模式。

  • 上述就是对整个CAN设备相关模块的一个代码解析,包括整体流程分析和解析类的分析。下面且看如何添加一个新的CAN设备。

添加设备

  • 对整体框架有了一个基本的了解后,接下来试试实操,看看如何在Apollo中添加一个新的CAN设备。在这里,我们以元橡(metoak)的双目模块为例,具体的讲一讲。
工作目录
  • 要添加一个新的CAN设备,一般我们在/modules/drivers/下对应的目录添加文件夹,就在/modules/drivers下添加一个/stereo/metoak_stereo/的目录,这个就是我们的工作目录了。
  • 这个目录下,你需要酌情新建一些如/conf/,/protocol/等目录,conf是存放配置文件,protocol用来存放对CAN报文解析的相关类,这里的protocol目录下的代码是我们的主要工作量所在。这里可以参考conti_radar的相关文件结构。
程序入口
  • 在/metoak_stereo/下新建一个main.cc,程序入口当然是APOLLO_MAIN(MetoakStereoCanbus);
整体框架
  • 上面的MetoakStereoCanbus我们放在metoak_stereo_canbus.h中声明,基本如下:
    class MetoakStereoCanbus : public apollo::common::ApolloApp {
    	apollo::common::Status Init() override;
    	apollo::common::Status Start() override;
      	void Stop() override;
    };
    
  • 在这里,最重要的是重写上述三个函数,这也是整个模块的控制开关。
    在上述的Init()中,是根据/conf/下的配置文件配置我们的can_client以及can_receiver。这里的具体写法可参考ContiRadarCanbus::Init()。对can_client和can_receiver我们都不需要修改其代码,只需要仿照现有代码调用接口即可,顺着Apollo的框架逻辑来。
  • 这里需要注意,在初始化can_receiver时,传入的sensor_message_manager是一个很重要的部分——CAN报文解析。这一块也是我们的主要工作量所在,详见下文。
报文
  • 我们最重要的工作将放在如下两点:
    • 根据设备的CAN报文数据手册定义我们的proto。
    • 根据数据手册,实现CAN报文的解析代码。
  • proto
    • 在这里,我们可参考conti_radar的proto,在*/modules/drivers/proto/conti_radar.proto*。这个proto是Apollo修改ROS后的消息格式,类同ROS中的*.msg*文件,这里需要我们根据设备的CAN数据手册,浓缩出一份proto格式。对proto的相关理解可参考Google Protocol Buffer 的使用和原理
  • 报文解析
    • 对CAN报文的解析代码,我们放在*/metoak_stereo/protocol/下,参考/modules/drivers/radar/conti_radar/protocol/下的文件,其文件名后面的数字就是当前解析类对应的报文帧ID,也就是msg_id。每一个文件对应一个解析类,每一个解析类解析一种报文。在前文我们讲到,解析类继承class ProtocolData,具体就不赘述了,参考conti_radar*吧。
发布
多个设备
  • 如果我们有需要接入多个同一个类型的设备,例如多个大陆毫米波雷达,这时候有两种方式:
  1. 在同一个CAN口接入不同设备号的多个设备
    • 例如conti_radar,如果要同一个CAN口接入多个conti_radar,可以提前将多个conti_radar刷成不同的设备号,然后在ContiRadarMessageManager::Parse()中,根据设备号进入不同的解析分支。
  2. 一个CAN口仅接入一个设备,也就是用多个CAN口来接入多个设备。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值