## 引言
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
继承于工厂基类 Factory
成员函数:
注册Apollo支持的所有品牌工厂,包括LINCOLN_MKZ、GEM、LEXUS、TRANSIT、GE3、、ZHONGYUN、CH、DKIT、NEOLIX。注册后,将根据<汽车品牌,汽车工厂类>的形式保存进一个map中。
形参:车辆参数(由配置文件 canbus_conf 提供)
功能:根据参数中的车辆品牌,在map中获取对应品牌的工厂类,在主程序中调用该类的函数生产对应的产品。
/**
* @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_;
以上,该抽象类工厂的作用为输出基类产品。产品会在下文中进行介绍。
上述工厂模式如下图:
产品的基类1
构造函数:默认
成员函数:
基类中只有4个虚函数,其余均为纯虚函数。
4个定义好的虚函数,承担的主要责任是,设置车辆驾驶模式,获取该模式,根据控制指令(外部信号)更新控制器各信号指令。
纯虚函数在实际车辆控制器中是怎么实现的呢?接下来以wey品牌为例,简单解释一下vehicle_controller中其他成员函数的实现。
获取车辆参数、创建can_sender、创建message_manager、获取 wey 车型 can 模块需要发送消息的协议内容、将需要发送的消息添加进can_sender中。
创建线程,会加入一个看门狗。检查项:can_sender 是否存在、can_sender是否正在运行、横向控制器(eps)是否响应、纵向控制器(vcu、esp)是否响应、chassis_detail 中是否存在报警
关闭线程
通过message_manager 读取 chassis_detail将 chassis_detail 消息封装给 chassis 消息
根据 command 设置驾驶模式
产品的基类2
构造函数:默认
主要成员变量:sensor_data_ (接收到的传感器数据,由 chassis_detail 消息复制过来)
主要成员函数:
形参: 消息id(即 CANbus dbc文件中的, addressId), 地址数组指针,地址数组长度
功能:根据消息ID获取对应消息的通信协议规则 protocal_data,调用该类的parse解析方法,将接收到的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
主要成员变量:客户端、消息管理器、异步返回结果
主要成员函数:
该函数调用了函数2(异步调用)来获取并解析消息。获取是使用CAN客户端,解析则使用消息管理器。
该函数为私有函数,不允许在外界暴露,只能通过函数1来调用。
功能:利用CAN客户端采集消息,放进 receiver 中,再利用消息管理器解析采集到的消息。
注:CAN receiver并不实际接收和处理信号,而是创建线程,调用客户端和消息管理器。
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发送出去。
主要成员变量:can客户端、send_messages_(SenderMessage类的数组)、线程
成员函数:
为 CanSender 初始化分配can客户端。
直接调用 SenderMessage 生成 CanFrame,添加进 send_messages_ 中。
为CanSender创建一个异步线程任务,利用can客户端,周期性发送CAN信号。
写了一个发送函数send,该函数为纯虚函数,类中没有对其实现;另外还写了一个单信号发送函数SendSingleFrame,该函数封装了send。需要注意的是不同类型的can卡有自己的客户端,send函数在不同的客户端中做了实现。
除此之外还写了一个接收函数receive,同样是一个纯虚函数,在不同类型can卡中各自定义。
这是 CAN 模块的主干类,包含了以上介绍的各类小部件,用来收取CAN消息,并处理指令将 更新后的CAN 信号发送给 CAN 卡。
CanbusComponet 继承自 TimerComponent,是一个定时执行模块。
创建CanbusComponet 的同时,为监控日志设置了缓存空间。
主要成员函数
初始化组件:
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、启动控制器。
writer 写 chassis 消息。
writer 写 chassisDetail 消息。
组件的执行函数,调用函数2和函数3。
最后来一张CAN组件内消息流转图,如下图所示: