先明确一下,手头的这台工控机是有着socket_can的,所以我们配置的时候就需要对对应的配置文件进行设置。这两个文件夹,modules/drivers/canbus/这个文件夹中是比较底层的canbus设置:包括了CAN卡客户端的配置…/can_client/,CAN卡通讯的通用项…/can_common/,CAN解码的通用函数…/common/,以及配置文件…/proto/。modules/canbus/中主要是针对实际使用中的车辆端进行的设置:包括了glog的相关记录文件…/common/,运行参数文件…/conf/,配置文件…/proto/,车辆配置文件…/vehicle/,其余的文件夹意义目前还不明确,所以不介绍了。
1 1) Can_client内包含can卡的基本设置可以看到文件夹内是各种can卡的型号命名的子文件夹。再次因为手上的工控机支持socket_can,所以我们选择…/socket/这个文件夹内的代码进行解析(实际上是esd太贵了买不起)。 2 3 4 2) Can_client.h 5 路径为apollo\modules\drivers\canbus\can_client\can_client.h 6 /** 7 * @class CanFrame 8 * @brief The class which defines the information to send and receive. 9 */ 10 struct CanFrame { 11 /// Message id 12 uint32_t id; 13 /// Message length 14 uint8_t len; 15 /// Message content 16 uint8_t data[8]; 17 /// Time stamp 18 struct timeval timestamp; 19 20 /** 21 * @brief Constructor 22 */ 23 CanFrame() : id(0), len(0), timestamp{0} { 24 std::memset(data, 0, sizeof(data)); 25 } 26 27 /** 28 * @brief CanFrame string including essential information about the message. 29 * @return The info string. 30 */ 31 std::string CanFrameString() const { 32 std::stringstream output_stream(""); 33 output_stream << "id:0x" << Byte::byte_to_hex(id) 34 << ",len:" << static_cast<int>(len) << ",data:"; 35 for (uint8_t i = 0; i < len; ++i) { 36 output_stream << Byte::byte_to_hex(data[i]); 37 } 38 output_stream << ","; 39 return output_stream.str(); 40 } 41 }; 42 从名字来看,这段代码定义了一个结构体,叫做CanFrame,字面翻译就是CAN报文的数据帧;从注释来看,确实如此。这个结构体中包括了CAN报文的id,长度len,内容data[8],以及一个时间戳,这个时间戳的功能想必是要用来同步各传感器之间的时间,比如最常见的就是同步GNSS和IMU之间的时间和频率,因为IMU的信号发送频率是比GNSS高的。 43 后面是一段初始化,把刚才定义的所有内容都补充为0来初始化。 44 CanFrame() : id(0), len(0), timestamp{0} { 45 std::memset(data, 0, sizeof(data)); 46 再后面是对报文的发送。报文包括了16进制的id,10进制的长度,以及2进制转成16进制的报文内容。最后把这段字符串returen给std::string CanFrameString()。 47 std::string CanFrameString() const { 48 std::stringstream output_stream(""); 49 output_stream << "id:0x" << Byte::byte_to_hex(id) 50 << ",len:" << static_cast<int>(len) << ",data:"; 51 for (uint8_t i = 0; i < len; ++i) { 52 output_stream << Byte::byte_to_hex(data[i]); 53 } 54 output_stream << ","; 55 return output_stream.str(); 56 } 57 58 const int CAN_RESULT_SUCC = 0; 59 const int CAN_ERROR_BASE = 2000; 60 const int CAN_ERROR_OPEN_DEVICE_FAILED = CAN_ERROR_BASE + 1; 61 const int CAN_ERROR_FRAME_NUM = CAN_ERROR_BASE + 2; 62 const int CAN_ERROR_SEND_FAILED = CAN_ERROR_BASE + 3; 63 const int CAN_ERROR_RECV_FAILED = CAN_ERROR_BASE + 4; 64 上面这段代码是定义了一些错误码和成功码,用来debug的。 65 66 /** 67 * @class CanClient 68 * @brief The class which defines the CAN client to send and receive message. 69 */ 70 class CanClient 71 再往下就全是CanClient的类的定义了,注释中说定义了CanClient,他的作用就i是用来收发CAN信号的。 72 public: 73 /** 74 * @brief Constructor 75 */ 76 CanClient() = default; 77 78 /** 79 * @brief Destructor 80 */ 81 virtual ~CanClient() = default; 82 第一段, 构造和析构函数。 83 /** 84 * @brief Initialize the CAN client by specified CAN card parameters. 85 * @param parameter CAN card parameters to initialize the CAN client. 86 * @return If the initialization is successful. 87 */ 88 virtual bool Init(const CANCardParameter ¶meter) = 0; 89 第二段, 这段很有用,是初始化can_client的。后面要用到,因为后面有一个can_client_factory.cc中会有这么一句话else if (!factory->Init(parameter))。 90 /** 91 * @brief Start the CAN client. 92 * @return The status of the start action which is defined by 93 * apollo::common::ErrorCode. 94 */ 95 virtual apollo::common::ErrorCode Start() = 0; 96 第三段, 久违的错误码ErrorCode他又回来了,这里把启动can_client后的错误码置为0。 97 /** 98 * @brief Stop the CAN client. 99 */ 100 virtual void Stop() = 0; 101 第四段, 进程停止函数。 102 /** 103 * @brief Send messages 104 * @param frames The messages to send. 105 * @param frame_num The amount of messages to send. 106 * @return The status of the sending action which is defined by 107 * apollo::common::ErrorCode. 108 */ 109 virtual apollo::common::ErrorCode Send(const std::vector<CanFrame> &frames, 110 int32_t *const frame_num) = 0; 111 第五段, 报文发送的错误码,检测函数需要传入一个数组std::vector<CanFrame> &frames,填充的是指向报文的指针,以及一个数字*const frame_num,表示要发送的报文数。 112 /** 113 * @brief Send a single message. 114 * @param frames A single-element vector containing only one message. 115 * @return The status of the sending single message action which is defined by 116 * apollo::common::ErrorCode. 117 */ 118 virtual apollo::common::ErrorCode SendSingleFrame( 119 const std::vector<CanFrame> &frames) { 120 CHECK_EQ(frames.size(), 1) 121 << "frames size not equal to 1, actual frame size :" << frames.size(); 122 int32_t n = 1; 123 return Send(frames, &n); 124 } 125 第六段, 这段是单发送一个报文,先检查报文的size是否正确,不正确就会输出一段错误的报告,最后会返回发送这个报文成功与否的错误码。 126 /** 127 * @brief Receive messages 128 * @param frames The messages to receive. 129 * @param frame_num The amount of messages to receive. 130 * @return The status of the receiving action which is defined by 131 * apollo::common::ErrorCode. 132 */ 133 virtual apollo::common::ErrorCode Receive(std::vector<CanFrame> *const frames, 134 int32_t *const frame_num) = 0; 135 第七段, 和前面的virtual apollo::common::ErrorCode Send类似。 136 /** 137 * @brief Get the error string. 138 * @param status The status to get the error string. 139 */ 140 virtual std::string GetErrorString(const int32_t status) = 0; 141 第八段, 暂时按照注释来理解吧,获得错误字段的一个函数。 142 protected: 143 /// The CAN client is started. 144 bool is_started_ = false; 145 第九段, 默认CAN卡是不启动的。 146 147 3) socket_can_client_raw.cc/.h 148 路径apollo\modules\drivers\canbus\can_client\socket\socket_can_client_raw.cc和apollo\modules\drivers\canbus\can_client\socket\socket_can_client_raw.h 149 先来看.h文件,这部分的代码是紧接着上一部分can_client.h来写的,从开头的#include就能看出来。 150 #include "modules/common/proto/error_code.pb.h" 151 #include "modules/drivers/canbus/proto/can_card_parameter.pb.h" 152 153 #include "gflags/gflags.h" 154 #include "modules/drivers/canbus/can_client/can_client.h" 155 #include "modules/drivers/canbus/common/canbus_consts.h" 156 157 /** 158 * @class SocketCanClientRaw 159 * @brief The class which defines an ESD CAN client which inherites CanClient. 160 */ 161 class SocketCanClientRaw : public CanClient 162 这部分代码注释也说得很明白了,SocketCanClientRaw这个子类是继承父类CanClient的,但是因为没有模板初始化实例,我们猜测下面的内容大多和can_client.h中的吻合,并且要重写override。 163 public: 164 先看公共的内容。 165 /** 166 * @brief Initialize the ESD CAN client by specified CAN card parameters. 167 * @param parameter CAN card parameters to initialize the CAN client. 168 * @return If the initialization is successful. 169 */ 170 bool Init(const CANCardParameter ¶meter) override; 171 以一个布尔类型的函数Init来标记CAN client是否初始化成功,需要override。 172 /** 173 * @brief Destructor 174 */ 175 virtual ~SocketCanClientRaw(); 176 析构函数。有个问题:为什么在子类SocketCanClientRaw中并没有像can_client.h中的构造函数virtual SocketCanClientRaw(),却有着析构函数呢?这里是什么样的作用呢? 177 /** 178 * @brief Start the ESD CAN client. 179 * @return The status of the start action which is defined by 180 * apollo::common::ErrorCode. 181 */ 182 apollo::common::ErrorCode Start() override; 183 以ErrorCode类型的函数Start来启动CAN card,并且反馈启动是否成功,此函数依旧需要override。 184 /** 185 * @brief Stop the ESD CAN client. 186 */ 187 void Stop() override; 188 一个需要override的停止CAN card函数。 189 /** 190 * @brief Send messages 191 * @param frames The messages to send. 192 * @param frame_num The amount of messages to send. 193 * @return The status of the sending action which is defined by 194 * apollo::common::ErrorCode. 195 */ 196 apollo::common::ErrorCode Send(const std::vector<CanFrame> &frames, 197 int32_t *const frame_num) override; 198 以ErrorCode类型来定义的函数Send,需要传入以CanFrame为模板的动态数组frames,frames是一个引用的关系,还需要传入帧数frame_num,最后反馈回一个ErrorCode来判断报文是否发送成功。 199 /** 200 * @brief Receive messages 201 * @param frames The messages to receive. 202 * @param frame_num The amount of messages to receive. 203 * @return The status of the receiving action which is defined by 204 * apollo::common::ErrorCode. 205 */ 206 apollo::common::ErrorCode Receive(std::vector<CanFrame> *const frames, 207 int32_t *const frame_num) override; 208 和上面类似。 209 /** 210 * @brief Get the error string. 211 * @param status The status to get the error string. 212 */ 213 std::string GetErrorString(const int32_t status) override; 214 这个和can_client.h中的定义一样,我还是不明白他get的string是什么,是有错误的那一帧吗?具体的格式是否和can_client.h中的struct CanFrame中定义的std::string CanFrameString()一样呢?我猜测大概率是一样的吧。 215 最后来看private中定义的内容。 216 private: 217 int dev_handler_ = 0; 218 CANCardParameter::CANChannelId port_; 219 CANCardParameter::CANInterface interface_; 220 can_frame send_frames_[MAX_CAN_SEND_FRAME_LEN]; 221 can_frame recv_frames_[MAX_CAN_RECV_FRAME_LEN]; 222 这些type都还是不知道是在哪里定义的,我猜测是在#include "modules/drivers/canbus/proto/can_card_parameter.pb.h"中定义的吧,但是这个文件并没有,应该是build后生成的。 223 224 接下来我们再看.cc文件。 225 bool SocketCanClientRaw::Init(const CANCardParameter ¶meter) { 226 if (!parameter.has_channel_id()) { 227 AERROR << "Init CAN failed: parameter does not have channel id. The " 228 "parameter is " 229 << parameter.DebugString(); 230 return false; 231 } 232 233 port_ = parameter.channel_id(); 234 interface_ = parameter.interface(); 235 return true; 236 } 237 上来就是一个布尔类型的初始化函数Init,和CAN信号的处理类似,用.has_channel_id()来判断是否有CAN卡,如果没有就报错,有的话就把端口和界面用parameter引用对象的子对象来赋值。 238 PS:[大知识点,如何理解const CANCardParameter ¶meter呢?这种语句在Apollo代码中并不少见,具体意思是声明了一个引用,用parameter来引用CANCardParameter,但是这是一个常值引用,也就是parameter是只读的,并不能来改变他的子对象的内容,即给parameter.channel_id()赋值是会报错的。参考博客:https://bbs.csdn.net/topics/80196973和https://blog.csdn.net/stpeace/article/details/40918915。] 239 SocketCanClientRaw::~SocketCanClientRaw() { 240 if (dev_handler_) { 241 Stop(); 242 } 243 } 244 这部分的代码是一个析构函数,里面需要dev_handler_来判断是否停止CAN card的工作,默认是不执行Stop这个函数的,因为在.h文件中的private里,定义了‘dev_handler_ = 0;’这一条。 245 246 ErrorCode SocketCanClientRaw::Start() 247 下面的代码我们以函数为划分,分开解析,首先是这个返回参数为ErrorCode的CAN card启动函数Start()。 248 if (is_started_) { 249 return ErrorCode::OK; 250 } 251 这段代码中的参数is_started_,在can_client.h中的protected中有定义,初始值为false,所以这条if是默认不执行的。 252 struct sockaddr_can addr; 253 struct ifreq ifr; 254 PS:[知识点,这个定义是说有个结构体叫做sockaddr_can,声明了一个对象叫addr,他的类型或者说格式,是sockaddr_can的。参考博客https://blog.csdn.net/u011068702/article/details/53781273。] 255 // open device 256 // guss net is the device minor number, if one card is 0,1 257 // if more than one card, when install driver u can specify the minior id 258 // int32_t ret = canOpen(net, pCtx->mode, txbufsize, rxbufsize, 0, 0, 259 // &dev_handler_); 260 这是一段函数的总体注释。 261 if (port_ > MAX_CAN_PORT || port_ < 0) { 262 AERROR << "can port number [" << port_ << "] is out of the range [0," 263 << MAX_CAN_PORT << "]"; 264 return ErrorCode::CAN_CLIENT_ERROR_BASE; 265 } 266 这里是对CAN设备端口号port_合理性的判断。 267 dev_handler_ = socket(PF_CAN, SOCK_RAW, CAN_RAW); 268 if (dev_handler_ < 0) { 269 AERROR << "open device error code [" << dev_handler_ << "]: "; 270 return ErrorCode::CAN_CLIENT_ERROR_BASE; 271 } 272 可以看出socket这个函数返回的值是ErrorCode类型的,所以dev_handler_也是ErrorCode类型的,这里从打印的AERROR可以看出来这是设备开启失败的错误码检测。 273 // init config and state 274 int ret; 275 这句话定义的ret是一个错误码ErrorCode类型的,不要忘记我们整体函数他是一个ErrorCode类型的Start函数。 276 // 1. for non virtual busses, set receive message_id filter, ie white list 277 if (interface_ != CANCardParameter::VIRTUAL) { 278 struct can_filter filter[2048]; 279 for (int i = 0; i < 2048; ++i) { 280 filter[i].can_id = 0x000 + i; 281 filter[i].can_mask = CAN_SFF_MASK; 282 } 283 284 ret = setsockopt(dev_handler_, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, 285 sizeof(filter)); 286 if (ret < 0) { 287 AERROR << "add receive msg id filter error code: " << ret; 288 return ErrorCode::CAN_CLIENT_ERROR_BASE; 289 } 290 } 291 这部分的内容有很多不懂的地方,比如setsockopt,但是好在我们大致上可以推测dev_handler_是一个什么含义的对象了,他是指启动的CAN card client是什么类型的,有VIRTUAL虚拟的busses总线,肯定就有实际的,而且可能还和CAN card brand有关。这段中ret就充当了一个错误码的容器,用它来判断是否返回错误信息。 292 // 2. enable reception of can frames. 293 int enable = 1; 294 ret = ::setsockopt(dev_handler_, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable, 295 sizeof(enable)); 296 if (ret < 0) { 297 AERROR << "enable reception of can frame error code: " << ret; 298 return ErrorCode::CAN_CLIENT_ERROR_BASE; 299 } 300 这部分不清楚这个函数setsockopt到底是什么含义,但是可以从注释知道这一段是判断CAN card client的接收能力的。 301 std::string interface_prefix; 302 if (interface_ == CANCardParameter::VIRTUAL) { 303 interface_prefix = "vcan"; 304 } else if (interface_ == CANCardParameter::SLCAN) { 305 interface_prefix = "slcan"; 306 } else { // default: CANCardParameter::NATIVE 307 interface_prefix = "can"; 308 } 309 这里是CAN busses的几种形式,有虚拟的vcan,有slcan的,还有传统的can。 310 const std::string can_name = absl::StrCat(interface_prefix, port_); 311 std::strncpy(ifr.ifr_name, can_name.c_str(), IFNAMSIZ); 312 if (ioctl(dev_handler_, SIOCGIFINDEX, &ifr) < 0) { 313 AERROR << "ioctl error"; 314 return ErrorCode::CAN_CLIENT_ERROR_BASE; 315 } 316 C语言中strcat是用来连接字符char的,这里应该也是一样的,比如说interface_prefix是"can",port_是‘0’,所以返回的can_name就是“can0”。Strcpy的作用参考后面的PS。 317 PS:[StrCat参考网址https://www.runoob.com/cprogramming/c-function-strcat.html,strncpy参考网址https://www.runoob.com/cprogramming/c-function-strncpy.html。] 318 再往后的代码就涉及socket通信协议比较多了,稍微值得提一下的是规定了CANframes必须是8bytes的,不能多也不能少。 319 320 4) can_client_factory.cc/.h 321 路径为apollo\modules\drivers\canbus\can_clien\ can_client_factory.cc和apollo\modules\drivers\canbus\can_clien\ can_client_factory.h从名字上可以看到这是一个客户端工厂的cc与h代码。 322 #include "modules/drivers/canbus/can_client/can_client_factory.h" 323 324 #include "modules/drivers/canbus/can_client/fake/fake_can_client.h" 325 #if USE_ESD_CAN == true 326 #include "modules/drivers/canbus/can_client/esd/esd_can_client.h" 327 #endif 328 329 #include "modules/drivers/canbus/can_client/socket/socket_can_client_raw.h" 330 331 #include "modules/drivers/canbus/can_client/hermes_can/hermes_can_client.h" 332 从这里可以看到,开头是引用了很多…/can_client/文件夹下的头文件,所以可以推测所有新的CAN采集设备都要被这里引用。用更专业的话来说,都要在这个factory里注册CAN卡。 333 334 CanClientFactory::CanClientFactory() {} 335 我们来分析第一个函数CanClientFactory::CanClientFactory() {}。从字面上来看,看不出什么,甚至还会觉得这是一个没有用的操作,但是当我们把视角看向.h文件的时候,就会发现下面这样一段。 336 /** 337 * @class CanClientFactory 338 * @brief CanClientFactory inherites apollo::common::util::Factory. 339 */ 340 class CanClientFactory 341 : public apollo::common::util::Factory<CANCardParameter::CANCardBrand, 342 CanClient> 343 这段的注释说的很明白,CanClientFactory这个类是继承了apollo::common::util::Factory,具体如何形成的状态空间CanClientFactory::RegisterCanClients()呢,我现在还不清楚。所以要想解决状态空间的问题,我想我需要找到一段有namespace CanClientFactory{}的代码。但是我按照Apollo代码的习惯去思考,我会认为这一步相当于一个初始化。这句话后面的<CANCardParameter::CANCardBrand, CanClient>表示的是以<>内的类为模板实例化的这个,也就是说可以认为括号里的两个都是CanClientFactory的子类,所以当我们再次声明一个叫CanClientFactory的子类的时候,实例化这个子类的方法可以使用已有的子类,比如CANCardParameter::CANCardBrand和CanClient。 344 345 void CanClientFactory::RegisterCanClients() { 346 AINFO << "CanClientFactory::RegisterCanClients"; 347 Register(CANCardParameter::FAKE_CAN, 348 []() -> CanClient* { return new can::FakeCanClient(); }); 349 #if USE_ESD_CAN == true 350 AINFO << "register can: " << CANCardParameter::ESD_CAN; 351 Register(CANCardParameter::ESD_CAN, 352 []() -> CanClient* { return new can::EsdCanClient(); }); 353 #endif 354 Register(CANCardParameter::SOCKET_CAN_RAW, 355 []() -> CanClient* { return new can::SocketCanClientRaw(); }); 356 357 Register(CANCardParameter::HERMES_CAN, 358 []() -> CanClient* { return new can::HermesCanClient(); }); 359 } 360 我们再回到.cc中,上面的函数和.h中的RegisterCanClients一致。让我们再看一下.h中是怎么定义的。 361 /** 362 * @brief Register the CAN clients of all brands. This function call the 363 * Function apollo::common::util::Factory::Register() for all of the 364 * CAN clients. 365 */ 366 void RegisterCanClients(); 367 这里也没有重写RegisterCanClients的具体内容,所以说他是完全继承了父类中的定义。注释也验证了这一点,是按照apollo::common::util::Factory::Register()来注册所有CAN clients的。 368 我们再看Register的形式,再他后面的传入量中有两个,一个是CAN卡的参数CANCardParameter::SOCKET_CAN_RAW,另一个猜测是指向客户端的指针[]() -> CanClient* { return new can::SocketCanClientRaw(); }。这里的[]()->真不知道是什么含义了,怎么会有空集[]还能作为指针来->对象呢? 369 370 std::unique_ptr<CanClient> CanClientFactory::CreateCANClient( 371 const CANCardParameter& parameter) { 372 auto factory = CreateObject(parameter.brand()); 373 if (!factory) { 374 AERROR << "Failed to create CAN client with parameter: " 375 << parameter.DebugString(); 376 } else if (!factory->Init(parameter)) { 377 AERROR << "Failed to initialize CAN card with parameter: " 378 << parameter.DebugString(); 379 } 380 return factory; 381 } 382 这段代码需要和.h中的一起看。 383 /** 384 * @brief Create a pointer to a specified brand of CAN client. The brand is 385 * set in the parameter. 386 * @param parameter The parameter to create the CAN client. 387 * @return A pointer to the created CAN client. 388 */ 389 std::unique_ptr<CanClient> CreateCANClient(const CANCardParameter ¶meter); 390 可以看到在.h中,CreateCANClient是以CanClient为模板实例化的std::unique_ptr,他的传入参数包括了CAN卡参数const CANCardParameter和指向CAN客户端的指针¶meter。在.cc中也是按照.h中的格式定义了这部分的内容。 391 再看函数内部,先用自动初始化的方法,让factory按照CreateObject(parameter.brand())的样子来初始化。后面的if…else if…的逻辑是,如果factory为空,也就是auto定义factory失败,那么就会报错。如果auto定义factory成功,但是定义后factory指向的对象中初始化参数Init(parameter)失败,也会报错。所以如果不报错就需要auto自动定义factory成功,并且用来指向CAN client的parameter初始化成功。 392 关于auto的用法的了解,可以参考这篇博客https://blog.csdn.net/lwgkzl/article/details/82110068。 393
本篇完
2020年12月16日 16:09
于宁波天尚元振狮路365号工厂二楼