apollo3.0 canbus部分源码解析

最近一直想要调整下底层can通信部分的架构,想了一些适配的方案,细节部分还是有些模糊,想试着看看大厂的处理方法找找灵感,也学习下。

1、文件结构

apollo有关canbus的代码主要集中在modules/canbus和drivers/canbus中,其中,modules/canbus主要是关于vehicle基于canbus的底层通信和控制的实现,主要应用了工厂模式(这里感觉改名叫chassis或者类似的可能更合适);drivers/canbus才是canbus通信的主要实现部分,它主要实现了一个接受和发送数据的结构框架,任何基于canbus的设备都能够使用这一套框架去实现数据的收发;此外,drivers中还有有基于canbus通信的传感器例如conti_radar的实现。
chassis这部分的主体架构如下:
在这里插入图片描述

首先分析下drivers/canbus中各个文件夹的主要内容。

/common

byte.h/cc主要实现了一个byte的工具类,用于对8bit的单字节进行一通操作,工具函数太多,就不一一分析了,举几个例子:

static std::string byte_to_hex(const uint8_t value)
完成类似10进制转表示16进制的string,例如:10->“0A”,30->“1E”

void set_bit_1(const int32_t pos)
把8bit中任意位设置为1

uint8_t get_byte(const int32_t start_pos, const int32_t length) const
获取8bit中任意一个区间的值;例如:a=01010001,调用get_byte(1,4)返回1000即8;

void set_value(const uint8_t value, const int32_t start_pos,const int32_t length)
与get_byte对应,将8bit任意range设置指定值

其他的就不一一列举了,源码中注释也很详细

canbus_consts.h主要是一些can常用常量。

/can_client

也是应用了工厂模式,不多赘述;
can_client.h/cc
是各类can drivers的一个抽象类,实现了一些公用接口例如send和receive,通过调用init来初始化各类can设备,用户通过继承这个类并在工厂中注册就可以实现用自己的can设备进行通信,代码中有实现一些can通信方式例如socket、hermes_can文件夹中的内容。

/can_comm

protocol_data.h
用于解析can frame的一个抽象类,其核心接口是Parse和UpdateData,前者将CanFrame中8byte的数据解析到用户定义的SensorType中,主要用于数据的接收与解析;后者主要用于将需要发送的数据从各类变量例如bool、int转为CanFrame的8byte数据。用户通过继承该类实现自定义的待发送数据以及接收数据解析方法,同时将数据保存到自定义的SensorType对象中。
这里写图片描述
message_manager.h
顾名思义,用于管理所有的msg,使用前需要将所有用户自定义的ProtocolData初始化并将其分为send和receive类加入各自队列中,包含一个Parse接口会逐一调用所有receive类ProtocolData对象的Parse接口,实现将接受到的CanFrame数据转化为自定义数据类型(例如0x6b的帧中数据为车辆加速度数据,将数据解析到自定义的ChassisDetail中的lateral_acceleration、longitudinal_acceleration、vertical_acceleration变量中),底层有一个unordered_map用于根据id搜索加入的ProtocolData对象,用于将send类对象提取出来供CanSender使用。

can_receiver.h/cccan_sender.h/cc
顾名思义,两者分别负责can数据的接收与发送,显然该类需要CanClient完成数据的收发。

CanSender中额外定义了SenderMessage类完成ProtocolData与CanFrame的耦合。CanSender在使用前必须调用AddMessage接口将所有从MessageManager中提取出的发送类ProtocolData对象加入队列中,并逐一使用SenderMessage对象对其封装,当ProtocolData中自定义数据被更改需要发送时,调用CanSender的Update接口,该接口逐一调用队列中SenderMessage的Upadate接口,将ProtocolData中被更改的数据更新到CanFrame中,然后调用CanClient进行发送,每个CanSender中都有单独的发送线程。

CanReceive机制比较简单,传入自定义的MessageManager,在底层起一个接受线程,接受到CanFrame后调用MessageManager的Parse接口解析即可。

2、应用

modules/canbus这个文件夹个人认为命名不太恰当,它其实是底层can通信结构在底盘方面的一次应用,而整个系统中应用canbus的部分很多,例如所有通过canbus通信的传感器,这里来看看要应用canbus通信完成一个CanNode需要哪些步骤,以modules/canbus/lincoln为例:

1、用protobuf定义一个数据结构ChassisDetail,存储底盘部分的所有信息例如档位,速度等等,此数据结构用于将底盘数据发送给系统中其他有需求的模块例如control;

2、继承ProtocolData实现底盘部分Can协议解析,内容位于/protocol,主要包括can协议的解析类,即对ProtocolData的继承和实现,例如发送类brake_60.h/cc,用于控制车辆的刹车,主要包括:

  double pedal_cmd_ = 0.0;
  bool boo_cmd_ = false;
  bool pedal_enable_ = false;
  bool clear_driver_override_flag_ = false;
  bool ignore_driver_override_ = false;

上文提到过,该类将以上数据解析为ID0x6b的CanFrame中8byte数据,交由CanSender发送,达到控制车体的目的。
此外,还有一系列send类和receive类。

/

lincoln_message_manager.h/cc
对MessageManager的继承和实现,构造阶段通过AddRecvProtocolData和AddSendProtocolData接口将所有协议类加入队列并初始化。

lincoln_controller.h/cc
车辆工厂模式中vehicle_controller的继承和实现,主要通过封装send类ProtocolData和CanSender对象实现一些更简化的控制接口例如设置油门、刹车等。Init阶段会提取MessageManager中所有send类ProtocolData并用以初始化CanSender。

最外层的canbus.h/cc完成整个系统的初始化,流程源码中Init函数中已经很清晰了,就不多赘述了,基本详细读过上文也能推出整个初始化流程。

3、总结

apollo整个底层canbus部分的逻辑还是比较清晰的,适配性强,用户通过继承和自定义少量类就可以完成一个自己的CanNode,但初始化过程仍显得比较复杂。

  • 4
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值