1. 程式人生 > >Apollo學習筆記(一):canbus模組與車輛底盤之間的CAN資料傳輸過程

Apollo學習筆記(一):canbus模組與車輛底盤之間的CAN資料傳輸過程

 

Apollo學習筆記(一):canbus模組與車輛底盤之間的CAN資料傳輸過程

博主現在從車載自組網通道分配和多跳路由轉向了自動駕駛,沒啥經驗,想快些做出來個Demo還是得站在巨人的肩膀上才行,我選擇了Apollo,主要還是支援國產而且它的開發者套件有現成的底盤可以直接跑起來,但是apollo系統結構比較複雜,各種花哨的設計模式(訊息介面卡、工廠模式等)繞得人頭暈。日本那裡有個autoware是基於原生ROS的,也用Apollo開發者套件跑了下,就是普通的機器人開發那套,難度適合學生用來做專案,還是得師夷長技以制夷,後面繼續深入研究一下。

這次的學習是基於Apollo3.0的,因為3.0還是基於ROS的,後期研究autoware開發自己的系統能用得上,而且那個開發者套件也要用3.0。

canbus模組啟動過程

參考知行合一2018大佬的Apollo Planning模組原始碼分析可以知道canbus模組的主入口為modules/canbus/main.cc

APOLLO_MAIN(apollo::canbus::Canbus);

該巨集展開後為:

 1 #define APOLLO_MAIN(APP)                                       \
 2   int main(int argc, char **argv) {                            \
 3     google::InitGoogleLogging(argv[0]);                        \
 4     google::ParseCommandLineFlags(&argc, &argv, true);         \
 5     signal(SIGINT, apollo::common::apollo_app_sigint_handler); \
 6     APP apollo_app_;                                           \
 7     ros::init(argc, argv, apollo_app_.Name());                 \
 8     apollo_app_.Spin();                                        \
 9     return 0;                                                  \
10   }

這裡直接引用知行合一2018大神對於Apollo Planning模組的分析:

Main函式完成以下工作:始化Google日誌工具,使用Google命令列解析工具解析相關引數,註冊接收中止訊號“SIGINT”的處理函式:apollo::common::apollo_app_sigint_handler(該函式的功能十分簡單,就是收到中止訊號“SIGINT”後,呼叫ros::shutdown()關閉ROS),建立apollo::planning::Planning物件:apollo_app_,初始化ROS環境,呼叫apollo_app_.Spin()函式開始訊息處理迴圈。
————————————————
版權宣告:本文為CSDN博主「知行合一2018」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/davidhopper/article/details/79176505

更詳細的不再贅述,大家可以去上面連結學習。

apollo_app_.Spin();函式內會依次呼叫modules/canbus/canbus.ccInit()Start()

Init()主要過程原始碼分析

  1 Status Canbus::Init() {
  2   AdapterManager::Init(FLAGS_canbus_adapter_config_filename);/*完成AdapterManager的初始化,
  3   FLAGS_canbus_adapter_config_filename對應於modules/canbus/common/canbus_gflags.cc中的
  4   DEFINE_string(canbus_adapter_config_filename, "modules/canbus/conf/adapter.conf", 
  5   "The adapter config file");
  6   adapter.conf中配置了canbus模組訂閱和釋出的topic
  7   如果改成原生ROS的話,這裡的AdapterManager配置刪掉,改成ROS的topic訂閱和釋出*/
  8   AINFO << "The adapter manager is successfully initialized.";
  9 
 10   // load conf
 11   //匯入配置檔案modules/canbus/conf/canbus_conf.pb.txt
 12   /*
 13   vehicle_parameter {
 14       brand: LINCOLN_MKZ//指定車輛,後面生成對應的vehicle_factory,進而生成對應的message_manager_
 15       max_enable_fail_attempt: 5
 16       driving_mode: COMPLETE_AUTO_DRIVE
 17     }
 18     
 19     can_card_parameter {
 20       brand: ESD_CAN//指定車輛,後面生成對應的can_client_
 21       type: PCI_CARD
 22       channel_id: CHANNEL_ID_ZERO
 23     }
 24     
 25     enable_debug_mode: false
 26     enable_receiver_log: false
 27     enable_sender_log: false
 28   */
 29   //如果改成原生ROS的話此處也可以刪除,工廠模式也可以放一邊,上面匯入的配置檔案就是用來生成具體的產品工廠
 30   //物件和產品物件,我們直接用ROS跑自己的工程一般車輛和CAN卡是固定的,可以改動後直接生成對應的產品物件
 31   if (!common::util::GetProtoFromFile(FLAGS_canbus_conf_file, &canbus_conf_)) {
 32     return OnError("Unable to load canbus conf file: " +
 33                    FLAGS_canbus_conf_file);
 34   }
 35 
 36   AINFO << "The canbus conf file is loaded: " << FLAGS_canbus_conf_file;
 37   ADEBUG << "Canbus_conf:" << canbus_conf_.ShortDebugString();
 38 
 39   // Init can client
 40   auto *can_factory = CanClientFactory::instance();
 41   can_factory->RegisterCanClients();
 42   can_client_ = can_factory->CreateCANClient(canbus_conf_.can_card_parameter());
 43   /*
 44   std::unique_ptr<CanClient> CanClientFactory::CreateCANClient(
 45     const CANCardParameter& parameter) {
 46   auto factory = CreateObject(parameter.brand());//這裡確定了新建的can client物件,
 47   //此處新建的是ESD CAN卡對應的can client物件(ESD CAN卡是開發者套件用的CAN卡),具體為
 48   //modules/drivers/canbus/can_client/esd/esd_can_client.cc的物件。
 49   //由此也可以看出modules/canbus/與modules/drivers/canbus/之間的聯絡是很緊密的,
 50   //modules/canbus/偏向上層的針對不同車輛的針對性資料解析
 51   //modules/drivers/canbus/偏向底層的一些共性功能的實現,比如
 52   //modules/drivers/canbus/can_comm/message_manager就是
 53   //modules/canbus/vehicle/lincoln/lincoln_message_manager的父類
 54   //改成原生ROS時這兩個資料夾可以合到一起
 55   if (!factory) {
 56     AERROR << "Failed to create CAN client with parameter: "
 57            << parameter.DebugString();
 58       } else if (!factory->Init(parameter)) {
 59         AERROR << "Failed to initialize CAN card with parameter: "
 60                << parameter.DebugString();
 61       }
 62       return factory;
 63     }
 64   */
 65   if (!can_client_) {
 66     return OnError("Failed to create can client.");
 67   }
 68   AINFO << "Can client is successfully created.";
 69 
 70   VehicleFactory vehicle_factory;
 71   vehicle_factory.RegisterVehicleFactory();
 72   auto vehicle_object =
 73       vehicle_factory.CreateVehicle(canbus_conf_.vehicle_parameter());//類似地生成具體的車輛物件
 74   if (!vehicle_object) {
 75     return OnError("Failed to create vehicle:");
 76   }
 77 
 78   message_manager_ = vehicle_object->CreateMessageManager();//生成對應車輛的message_manager_
 79   if (message_manager_ == nullptr) {
 80     return OnError("Failed to create message manager.");
 81   }
 82   AINFO << "Message manager is successfully created.";
 83 //初始化can_receiver_,就是將can_client_、message_manager_賦給can_receiver_的成員變數
 84   if (can_receiver_.Init(can_client_.get(), message_manager_.get(),
 85                          canbus_conf_.enable_receiver_log()) != ErrorCode::OK) {
 86     return OnError("Failed to init can receiver.");
 87   }
 88   AINFO << "The can receiver is successfully initialized.";
 89 //初始化can_sender_,就是將can_client_賦給can_sender_的成員變數
 90   if (can_sender_.Init(can_client_.get(), canbus_conf_.enable_sender_log()) !=
 91       ErrorCode::OK) {
 92     return OnError("Failed to init can sender.");
 93   }
 94   AINFO << "The can sender is successfully initialized.";
 95 
 96     //生成對應車輛的vehicle_controller_ 
 97     //至此vehicle_object生成了message_manager_和vehicle_controller_ 
 98     //message_manager_ 用來解析canbus從CAN接收到的訊息
 99     //vehicle_controller_ 用來更新canbus發往CAN的資料物件,也叫作協議型別資料
100     //(如剎車這個命令(協議)對應的物件,apollo將每種命令建了個類,比如林肯的剎車對應的類是
101     //modules/canbus/vehicle/lincoln/protocol/brake_60.h),
102     //也就是canbus接收到上層control的commond後通過vehicle_controller_ 
103     //更新協議型別資料內的具體成員變數(如剎車這個命令對應的物件內的“剎車量pedal_cmd_”這一成員變數)
104   vehicle_controller_ = vehicle_object->CreateVehicleController();
105   if (vehicle_controller_ == nullptr) {
106     return OnError("Failed to create vehicle controller.");
107   }
108   AINFO << "The vehicle controller is successfully created.";
109 
110 /*vehicle_controller_->Init(...) (實際上是
111 modules/canbus/vehicle/lincoln/lincoln_controller.cc中的LincolnController::Init)裡面
112 先生成brake_60_等協議型別資料,接著通過
113 can_sender_->AddMessage(Brake60::ID, brake_60_, false);等將brake_60_等協議型別資料
114 加入can_sender_的成員變數send_messages_(向量),後面can_sender_傳送資料時就會將send_messages_
115 中的各個協議型別資料包含的CAN幀can_frame_to_send_傳送到底盤
116 */
117   if (vehicle_controller_->Init(canbus_conf_.vehicle_parameter(), &can_sender_,
118                                 message_manager_.get()) != ErrorCode::OK) {
119     return OnError("Failed to init vehicle controller.");
120   }
121   AINFO << "The vehicle controller is successfully initialized.";
122   
123   CHECK(AdapterManager::GetControlCommand()) << "Control is not initialized.";
124   CHECK(AdapterManager::GetGuardian()) << "Guardian is not initialized.";
125   // TODO(QiL) : depreacte this
126   //類似原生ROS的添加回調函式,Apollo將topic的釋出和訂閱都整合到了AdapterManager進行統一管理
127   //Canbus::OnControlCommand就是canbus模組對上層cotrol_command的回撥函式
128   if (!FLAGS_receive_guardian) {
129     AdapterManager::AddControlCommandCallback(&Canbus::OnControlCommand, this);
130   } else {
131     AdapterManager::AddGuardianCallback(&Canbus::OnGuardianCommand, this);
132   }
133 
134   return Status::OK();
135 }

Init()主要過程流程圖(為方便整合不嚴格按照程式碼裡的順序)

Start()主要過程原始碼分析

 1 /*Init()生成的物件有can_client_ , vehicle_object , message_manager_,
 2  vehicle_controller_ , can_receiver_ 和 can_sender_,
 3  Start()裡面就是呼叫can_client_, can_receiver_, can_sender_和vehicle_controller_的Start()將
 4  它們的功能各自啟動起來,比如can_client_ 的Start() (實際上是
 5  modules/drivers/canbus/can_client/esd/esd_can_client.cc的Start())
 6  就是呼叫third_party/can_card_library/esd_can/include/ntcan.h的內建函式canOpen()等設定埠等
 7  以啟動CAN卡,ntcan.h是買CAN卡的時候附帶光盤裡的檔案,買了開發者套件裝好CAN卡以後要把這個檔案拷到
 8  third_party/can_card_library/esd_can/include/下使用。因為ntcan.h是花錢買的所以就不把ntcan.h
 9  的程式碼放上來了。
10  
11  最後啟動定時器迴圈執行Canbus::OnTimer,OnTimer這個函式就是釋出底盤資訊用的,釋出以後
12  訂閱底盤topic的上層模組就能接收到底盤資訊。
13 */
14 Status Canbus::Start() {
15   // 1. init and start the can card hardware
16   if (can_client_->Start() != ErrorCode::OK) {
17     return OnError("Failed to start can client");
18   }
19   AINFO << "Can client is started.";
20 
21   // 2. start receive first then send
22   if (can_receiver_.Start() != ErrorCode::OK) {
23     return OnError("Failed to start can receiver.");
24   }
25   AINFO << "Can receiver is started.";
26 
27   // 3. start send
28   if (can_sender_.Start() != ErrorCode::OK) {
29     return OnError("Failed to start can sender.");
30   }
31 
32   // 4. start controller
33   if (vehicle_controller_->Start() == false) {
34     return OnError("Failed to start vehicle controller.");
35   }
36 
37   // 5. set timer to triger publish info periodly
38   const double duration = 1.0 / FLAGS_chassis_freq;
39   timer_ = AdapterManager::CreateTimer(ros::Duration(duration),
40                                        &Canbus::OnTimer, this);
41 
42   // last step: publish monitor messages
43   apollo::common::monitor::MonitorLogBuffer buffer(&monitor_logger_);
44   buffer.INFO("Canbus is started.");
45 
46   return Status::OK();
47 }

Start()主要過程流程圖


下面分canbus模組向底盤傳送資料和canbus模組從底盤接收資料兩部分進行深入分析。

canbus模組向底盤傳送資料

主要過程

canbus模組向底盤傳送資料的開端在canbus模組接收到上層control_command,因此Canbus::OnControlCommand是傳送的開端。

 1 void Canbus::OnControlCommand(const ControlCommand &control_command) {
 2   int64_t current_timestamp =
 3       apollo::common::time::AsInt64<common::time::micros>(Clock::Now());
 4   // if command coming too soon, just ignore it.
 5   if (current_timestamp - last_timestamp_ < FLAGS_min_cmd_interval * 1000) {
 6     ADEBUG << "Control command comes too soon. Ignore.\n Required "
 7               "FLAGS_min_cmd_interval["
 8            << FLAGS_min_cmd_interval << "], actual time interval["
 9            << current_timestamp - last_timestamp_ << "].";
10     return;
11   }
12 
13   last_timestamp_ = current_timestamp;
14   ADEBUG << "Control_sequence_number:"
15          << control_command.header().sequence_num() << ", Time_of_delay:"
16          << current_timestamp - control_command.header().timestamp_sec();
17 
18 /*從此處開始的vehicle_controller_->Update(control_command)和can_sender_.Update()是關鍵
19 正如之前所提到的,vehicle_controller_->Update(control_command)用來更新協議型別資料的
20 成員變數,接著can_sender_.Update()把更新之後的資料通過can_frame_to_update_
21 賦值給can_frame_to_send_,can_sender_在Start()是開啟的傳送執行緒將can_frame_to_send_
22 通過CAN卡附帶驅動內的canWrite()函式注入底盤CAN網路
23 
24 這兩個Update()涉及的函式比較多,中間挺多跳轉,就不放原始碼了,跳轉的過程在後面流程圖中給出,具體程式碼的
25 實現大家還是需要自己去看程式碼
26 */
27   if (vehicle_controller_->Update(control_command) != ErrorCode::OK) {
28     AERROR << "Failed to process callback function OnControlCommand because "
29               "vehicle_controller_->Update error.";
30     return;
31   }
32   can_sender_.Update();
33 }

vehicle_controller_->Update流程圖(以剎車對應的協議型別資料為例)

can_sender_.Update()流程圖(以剎車對應的協議型別資料為例)

canbus模組從底盤接收資料

主要過程

在canbus Start()的時候,can_receiver_.Start()啟動了從底盤接收資料的執行緒,執行緒內執行CanReceiver::RecvThreadFunc()

 1 // 2. start receive first then send
 2   if (can_receiver_.Start() != ErrorCode::OK) {
 3     return OnError("Failed to start can receiver.");
 4   }
 5   AINFO << "Can receiver is started.";
 6 
 7 template <typename SensorType>
 8 ::apollo::common::ErrorCode CanReceiver<SensorType>::Start() {
 9   if (is_init_ == false) {
10     return ::apollo::common::ErrorCode::CANBUS_ERROR;
11   }
12   is_running_ = true;
13 
14   thread_.reset(new std::thread([this] { RecvThreadFunc(); }));
15   if (thread_ == nullptr) {
16     AERROR << "Unable to create can client receiver thread.";
17     return ::apollo::common::ErrorCode::CANBUS_ERROR;
18   }
19   return ::apollo::common::ErrorCode::OK;
20 }

RecvThreadFunc()內迴圈執行can_client_->Receive(&buf, &frame_num)

 1 template <typename SensorType>
 2 void CanReceiver<SensorType>::RecvThreadFunc() {
 3   AINFO << "Can client receiver thread starts.";
 4   CHECK_NOTNULL(can_client_);
 5   CHECK_NOTNULL(pt_manager_);
 6 
 7   int32_t receive_error_count = 0;
 8   int32_t receive_none_count = 0;
 9   const int32_t ERROR_COUNT_MAX = 10;
10   std::chrono::duration<double, std::micro> default_period{10 * 1000};
11 
12   while (IsRunning()) {
13     std::vector<CanFrame> buf;
14     int32_t frame_num = MAX_CAN_RECV_FRAME_LEN;
15     if (can_client_->Receive(&buf, &frame_num) !=
16         ::apollo::common::ErrorCode::OK) {
17 /*
18 can_client_為modules/drivers/canbus/can_client/esd/esd_can_client.cc的例項化物件,
19 其Receive()函式呼叫了third_party/can_card_library/esd_can/include/ntcan.h的canRead函式從
20 CAN網路中讀取資料並存入buf,
21 const int32_t ret = canRead(dev_handler_, recv_frames_, frame_num, nullptr);
22 buf的定義是std::vector<CanFrame> buf;
23 CanFrame在之前can_sender_.Update()流程圖內有分析。
24 */
25       LOG_IF_EVERY_N(ERROR, receive_error_count++ > ERROR_COUNT_MAX,
26                      ERROR_COUNT_MAX)
27           << "Received " << receive_error_count << " error messages.";
28       std::this_thread::sleep_for(default_period);
29       continue;
30     }
31     receive_error_count = 0;
32 
33     if (buf.size() != static_cast<size_t>(frame_num)) {
34       AERROR_EVERY(100) << "Receiver buf size [" << buf.size()
35                         << "] does not match can_client returned length["
36                         << frame_num << "].";
37     }
38 
39     if (frame_num == 0) {
40       LOG_IF_EVERY_N(ERROR, receive_none_count++ > ERROR_COUNT_MAX,
41                      ERROR_COUNT_MAX)
42           << "Received " << receive_none_count << " empty messages.";
43       std::this_thread::sleep_for(default_period);
44       continue;
45     }
46     receive_none_count = 0;
47 
48     for (const auto &frame : buf) {
49       uint8_t len = frame.len;
50       uint32_t uid = frame.id;
51       const uint8_t *data = frame.data;
52       pt_manager_->Parse(uid, data, len);
53 /*
54 pt_manager_在Init時被賦值為message_manager_(modules/canbus/vehicle/lincoln
55 /lincoln_message_manager.cc的例項化物件),message_manager_用來解析從CAN網路獲取的CanFrame
56 */
57       if (enable_log_) {
58         ADEBUG << "recv_can_frame#" << frame.CanFrameString();
59       }
60     }
61     std::this_thread::yield();
62   }
63   AINFO << "Can client receiver thread stopped.";
64 }

接收CAN資料流程圖

至於chassis_datail找不到的問題大家可以從https://blog.csdn.net/M_Alan_walker/article/details/88823610的最後面獲得解答。

後記

博主主要是從https://blog.csdn.net/M_Alan_walker/article/details/88823610學習的,加入一些自己的理解,有些分析得不完善的地方大家在上面的網址可以得到更詳細的補