Apollo CANbus 模块原理及源码学习

## 引言

CAN总线接受并执行控制命令,并收集底盘状态作为给控制模块的反馈。

## 输入

* 控制命令

## 输出

* 底盘状态

* 底盘细节状态

## 实现

CAN总线模块的主要部件有:

* 包括车辆控制器和消息管理器的车辆

* (客户端可以移动到‘/modules/drivers/canbus’,因为它是被不同的使用CAN总线协议的传感器共享的)

您自己的CAN客户端可以通过继承“CanClient”类在can_client的文件夹中实现。记得在“CanClientFactory”注册你的客户机客户端。

您自己的车辆控制器和消息管理器可以通过“VehicleController”和“MessageManager”的继承在“vehicle”的文件夹中实现。记得在“VehicleFactory”注册你的车辆。

以上是阿波罗对于CAN模块的解释,接下来会从代码内容分析CAN模块的工作原理,包括节点的构建,消息的收发和处理,至于消息的校验,如CRC、COUNTROLLING等代码设计以后有时间会做出解释,因此本文适合初学者的大致了解。阅读本文和源码需要读者具备一定的汽车CAN总线知识。

首先,根据Apollo Cyber RT架构,消息中间件的应用依赖于通信网络中的节点,而节点会与组件是一一对应的,组件作为节点的底层需要提前创建并注册进Cyber。

CYBER_REGISTER_COMPONENT(CanbusComponent) //注册方法使用宏来定义

CANbus 组件的功能原理是什么样的呢?

canbus组件创建的主程序为 canbus_componet.h/.cc,接下来将依次介绍其依赖的代码。

建议源码阅读顺序:

1、"modules/canbus/vehicle/abstract_vehicle_factory.h"

2、"modules/canbus/vehicle/vehicle_controller.h"

3、 "modules/drivers/canbus/can_comm/message_manager.h

VehicleFactory

继承于工厂基类 Factory

成员函数:

1、void RegisterVehicleFactory()

注册Apollo支持的所有品牌工厂,包括LINCOLN_MKZ、GEM、LEXUS、TRANSIT、GE3、、ZHONGYUN、CH、DKIT、NEOLIX。注册后,将根据<汽车品牌,汽车工厂类>的形式保存进一个map中。

2、std::unique_ptr VehicleFactory::CreateVehicle(const VehicleParameter &vehicle_parameter)

形参:车辆参数(由配置文件 canbus_conf 提供)

功能:根据参数中的车辆品牌,在map中获取对应品牌的工厂类,在主程序中调用该类的函数生产对应的产品。

Apollo CANbus 模块原理及源码学习_第1张图片

abstract_vehicle_factory

/**
 * @class AbstractVehicleFactory
 *
 * @brief this class is the abstract factory following the AbstractFactory
 * design pattern. It can create VehicleController and MessageManager based on
 * a given VehicleParameter.   
 */
遵循工厂模式,基于整车参数,输出产品为 车辆控制器和消息管理器

此工厂类为各车型工厂的基类,其中定义了工厂的输出产品(vehicle controller、vehicle message manager)

构造函数:默认

成员函数:

virtual std::unique_ptr CreateVehicleController() = 0; //纯虚函数,无函数体
​
virtual std::unique_ptr> CreateMessageManager() = 0;
​
void AbstractVehicleFactory::SetVehicleParameter(const VehicleParameter &vehicle_parameter) {
  vehicle_parameter_ = vehicle_parameter;
}

成员变量:

VehicleParameter vehicle_parameter_;

以上,该抽象类工厂的作用为输出基类产品。产品会在下文中进行介绍。

上述工厂模式如下图:

Apollo CANbus 模块原理及源码学习_第2张图片

 

vehicle_controller

产品的基类1

构造函数:默认

成员函数:

基类中只有4个虚函数,其余均为纯虚函数。

4个定义好的虚函数,承担的主要责任是,设置车辆驾驶模式,获取该模式,根据控制指令(外部信号)更新控制器各信号指令。

纯虚函数在实际车辆控制器中是怎么实现的呢?接下来以wey品牌为例,简单解释一下vehicle_controller中其他成员函数的实现。

1、Init

获取车辆参数、创建can_sender、创建message_manager、获取 wey 车型 can 模块需要发送消息的协议内容、将需要发送的消息添加进can_sender中。

2、start

创建线程,会加入一个看门狗。检查项:can_sender 是否存在、can_sender是否正在运行、横向控制器(eps)是否响应、纵向控制器(vcu、esp)是否响应、chassis_detail 中是否存在报警

3、stop

关闭线程

4、chassis()

通过message_manager 读取 chassis_detail将 chassis_detail 消息封装给 chassis 消息

5、update

根据 command 设置驾驶模式

message_manager

产品的基类2

构造函数:默认

主要成员变量:sensor_data_ (接收到的传感器数据,由 chassis_detail 消息复制过来)

主要成员函数:

1、void MessageManager::Parse(const uint32_t message_id, const uint8_t *data, int32_t length)

形参: 消息id(即 CANbus dbc文件中的, addressId), 地址数组指针,地址数组长度

功能:根据消息ID获取对应消息的通信协议规则 protocal_data,调用该类的parse解析方法,将接收到的sensor_data_ 解析出需要的数据。

2、ErrorCode MessageManager::GetSensorData( SensorType *const sensor_data)

形参:CAN消息

功能:

1)若拿到空指针,即没有数据,错误码返回 CANBUS_ERROR

2)若不为空,则将接口数据复制给私有变量,供 vehicle controller 使用

注:关于消息id,Apollo 源码中对不同车型所需的CAN信号均做了适配。不同的消息有不同的声明和实现文件,存放在了在/vehicle/车型/protocal中。除此之外,protocal还赋值了CAN信号中所包含的数据,id和收发周期为直接赋值,其他数据定义了赋值方法,而数据的种类和名称在message文件中声明(.proto),如 Adseps113消息,包含EPS模式、目标转角两种数据,消息id为0x113,工作周期为20ms,EPS mode 占据0-1bit,目标转角占据2-15bit。

message manager的主要作用可以总结为以下三点

1、消息管理器提供 添加接收信号和添加发送信号的接口,将消息ID和消息类型存放在一个map中,故针对一个车型来说,它的消息管理器中全局保存了 所有需要处理的CAN信号。

// Control Messages
  AddSendProtocolData();
  AddSendProtocolData();
  AddSendProtocolData();
  AddSendProtocolData();
  AddSendProtocolData();
​
  // Report Messages
  AddRecvProtocolData();
  AddRecvProtocolData();
  AddRecvProtocolData();
  AddRecvProtocolData();
  AddRecvProtocolData();

2、获取传感器数据

3、根据协议内容,解析数据(protocol_data->Parse(data, length, &sensor_data_)

消息管理器的作用已经做出了说明,但消息的接收以及发送则需要CAN_receiver 和 CAN_sender 来支持,下面将做出简单讲解。具体可以参考源码

1、modules/drivers/canbus/can_comm/can_receiver.h

2、modules/drivers/canbus/can_comm/can_sender.h

class CanReceiver

主要成员变量:客户端、消息管理器、异步返回结果

主要成员函数:

1、apollo::common::ErrorCode Start();

该函数调用了函数2(异步调用)来获取并解析消息。获取是使用CAN客户端,解析则使用消息管理器。

2、void CanReceiver::RecvThreadFunc()

该函数为私有函数,不允许在外界暴露,只能通过函数1来调用。

功能:利用CAN客户端采集消息,放进 receiver 中,再利用消息管理器解析采集到的消息。

注:CAN receiver并不实际接收和处理信号,而是创建线程,调用客户端和消息管理器。

class SenderMessage

SenderMessage类主要作用是定义要发送的消息

主要成员变量:消息id、协议内容、发送周期、要发送的CanFrame、要更新的CanFrame

首先需要说明一下CanFrame这个数据结构,如下所示。其中包含 id(消息id)、len(消息长度)、data[8](数据场的数据)、时间戳。

struct CanFrame {
  /// Message id
  uint32_t id;
  /// Message length
  uint8_t len;
  /// Message content
  uint8_t data[8];
  /// Time stamp
  struct timeval timestamp;

SenderMessage 的主要任务其实就是将消息封装起来,将其添加进CanSender中,去为can消息创建一个异步线程任务,并传递给can_client发送出去。

class CanSender

主要成员变量:can客户端、send_messages_(SenderMessage类的数组)、线程

成员函数:

1、common::ErrorCode CanSender::Init(CanClient *can_client, bool enable_log)

为 CanSender 初始化分配can客户端。

2、void CanSender::AddMessage(uint32_t message_id, ProtocolData *protocol_data, bool init_with_one)

直接调用 SenderMessage 生成 CanFrame,添加进 send_messages_ 中。

3、void CanSender::PowerSendThreadFunc()

为CanSender创建一个异步线程任务,利用can客户端,周期性发送CAN信号。

class can_client

写了一个发送函数send,该函数为纯虚函数,类中没有对其实现;另外还写了一个单信号发送函数SendSingleFrame,该函数封装了send。需要注意的是不同类型的can卡有自己的客户端,send函数在不同的客户端中做了实现。

除此之外还写了一个接收函数receive,同样是一个纯虚函数,在不同类型can卡中各自定义。

主文件:canbus_componet.h/.cc

这是 CAN 模块的主干类,包含了以上介绍的各类小部件,用来收取CAN消息,并处理指令将 更新后的CAN 信号发送给 CAN 卡。

CanbusComponet 继承自 TimerComponent,是一个定时执行模块。

创建CanbusComponet 的同时,为监控日志设置了缓存空间。

主要成员函数

1、bool CanbusComponent::Init()

初始化组件:

1)加载CAN配置文件;

2)根据配置文件中的CAN卡参数初始化某类型的CAN客户端(apollo默认为 Hermes_CAN,type 是 PCI_Card,通道号为0);

3)注册汽车工厂,根据配置文件中的车辆参数生产某类型车辆(默认为林肯MKZ,驾驶模式为全自动驾驶);

4)为车辆创建消息管理器;

5)初始化receiver 和 can_sender;创建车辆控制器;

6)为组件的节点创建 reader(具体内容可参考apollo Cyber RT框架中的内容),对指令的判断和操作 作为回调函数注册进reader中,这类 reader 可以分为 接收guardian cmd 和接收 control cmd两种;

7)为组件的节点创建 writer ,该writer 负责写入chassis topic 的消息;

8)初始化并启动CAN客户端、先启动canReceiver 再启动canSender、启动控制器。

2、void CanbusComponent::PublishChassis()

writer 写 chassis 消息。

3、void CanbusComponent::PublishChassisDetail()

writer 写 chassisDetail 消息。

4、bool CanbusComponent::Proc()

组件的执行函数,调用函数2和函数3。

最后来一张CAN组件内消息流转图,如下图所示:

Apollo CANbus 模块原理及源码学习_第3张图片

 

 

你可能感兴趣的