Apollo control 模块源码解析

Apollo control模块接收车辆的底盘(chassis),定位(localization),行驶轨迹(planning)信息,输出车辆的控制信息(SteeringWheelAngle,acceleration,throttle)等信息.

         首先,控制模块的入口还是在modules/control/main.cc下,使用APOLLO_MAIN(Apollo::control::Control)进入整个apollo的环境中.APOLLO_MAIN()函数的定义是在modules/common/apollo_app.h中,       

#define APOLLO_MAIN(APP)                                       \
  int main(int argc, char **argv) {                            \
    google::InitGoogleLogging(argv[0]);                        \
    google::ParseCommandLineFlags(&argc, &argv, true);         \
    signal(SIGINT, apollo::common::apollo_app_sigint_handler); \
    APP apollo_app_;                                           \
    ros::init(argc, argv, apollo_app_.Name());                 \
    apollo_app_.Spin();                                        \
    return 0;                                                  \
  }

#endif  // MODULES_COMMON_APOLLO_APP_H_

 

这是主函数的真正入口,首先使用初始化Google的日志工具,使用signal(SIGINT,Apollo::common::Apollo_app_sigint_handler)接收到终止信息SIGINT(用户在linux的terminal中输出Ctrl-c后发出)后,调用ros::shutdown()关闭ROS.使用宏定义展开定义一个control的对象 Apollo::control::Controlapollo_app_ 然后初始化ros节点.功能的入口在control对象的apollo_app_.Spin()函数.最终返回0,结束程序的运行.

 

         然后我们分析下Apollo::control::Control类型,类型的声明和定义发生在modules/control/control.h和contro.cc中,在Apollo框架中,任何主体的功能模块都是继承于apollo::common::ApolloApp类型,control也不例外.所以首先执行的是ApolloApp中的Spin()函数,让我们看一下Spin()函数都做了些什么吧(在modules/common/apollo_app.cc中)

int ApolloApp::Spin() {
  std::unique_ptr spinner;
  if (callback_thread_num_ > 1) {
    spinner = std::unique_ptr(
        new ros::AsyncSpinner(callback_thread_num_));
  }
  auto status = Init();
  if (!status.ok()) {
    AERROR << Name() << " Init failed: " << status;
    return -1;
  }
  status = Start();
  if (!status.ok()) {
    AERROR << Name() << " Start failed: " << status;
    return -2;
  }
  ExportFlags();
  if (spinner) {
    spinner->start();
  } else {
    ros::spin();
  }
  ros::waitForShutdown();
  Stop();
  AINFO << Name() << " exited.";
  return 0;
}

std::unique_ptr spinner 创建一个ros多线程,用来执行模块中的回调函数,接下来依次执行Init(),Start(),ExporeFlags(),如果spinner创建成功,就开始在另一独立线程中执行回调函数.然后等待shutdown,再执行Stop()函数,最后退出返回.而关键的Init(),Start()函数是纯虚函数,需要在子类中重写定义的,也就是说,程序到这个地方,就转到control的Init()中了,然后让我们视线转移到modules/common/control/control.cc中,

Status Control::Init() {
  init_time_ = Clock::NowInSeconds();

  AINFO << "Control init, starting ...";
  CHECK(common::util::GetProtoFromFile(FLAGS_control_conf_file, &control_conf_))
      << "Unable to load control conf file: " + FLAGS_control_conf_file;

  AINFO << "Conf file: " << FLAGS_control_conf_file << " is loaded.";

  AdapterManager::Init(FLAGS_control_adapter_config_filename);

  common::monitor::MonitorLogBuffer buffer(&monitor_logger_);

  // set controller
  if (!controller_agent_.Init(&control_conf_).ok()) {
    std::string error_msg = "Control init controller failed! Stopping...";
    buffer.ERROR(error_msg);
    return Status(ErrorCode::CONTROL_INIT_ERROR, error_msg);
  }

  // lock it in case for after sub, init_vehicle not ready, but msg trigger
  // come
  CHECK(AdapterManager::GetLocalization())
      << "Localization is not initialized.";

  CHECK(AdapterManager::GetChassis()) << "Chassis is not initialized.";

  CHECK(AdapterManager::GetPlanning()) << "Planning is not initialized.";

  CHECK(AdapterManager::GetPad()) << "Pad is not initialized.";

  CHECK(AdapterManager::GetMonitor()) << "Monitor is not initialized.";

  CHECK(AdapterManager::GetControlCommand())
      << "ControlCommand publisher is not initialized.";

  AdapterManager::AddPadCallback(&Control::OnPad, this);
  AdapterManager::AddMonitorCallback(&Control::OnMonitor, this);

  return Status::OK();
}

 

在Init()中前面的check的参数common::util::GetProtoFromFile(FLAGS_control_conf_file,&control_conf_)中,利用了util中的GetProtoFromFile()函数,目的是将第一个参数对应的文件输入到第二个参数中,以供程序使用,前面以Flag开头的参数都是google的gflag风格,对应的文件在每个模块的common/[module_name]_gflag.cc,相比control就是在modules/control/common/control_gflag.cc中可以找到.在里面,我们发现,control_con_file对应的文件为:”modules/control/conf/lincoln.pb.txt”所以这一步,我们把control的配置文件从lincoln.pb.txt中读到control_conf_中,也就是说,在调试过程中,我们如果需要更改配置参数,直接在lincoln.pb.txt中修改,然后直接重启该模块就OK了.(但是有一个问题,lincoln.pb.txt是如何来的?这其实是来自于modules/calibration/data/mkz8/calibration_table.pb.txt,当你在dreamview中选择车辆的时候,lincoln.pb.txt会继承于calibration_table.pb.txt)然后使用AdapterManager中的Init()方法对control中的adapter_config_filename初始化,,也就是说,读取control/conf/adapter.conf文件,确定该模块的订阅话题和输出话题.

 

config {
  type: LOCALIZATION
  mode: RECEIVE_ONLY
  message_history_limit: 10
}
config {
  type: PAD
  mode: RECEIVE_ONLY
  message_history_limit: 10
}
config {
  type: PLANNING_TRAJECTORY
  mode: RECEIVE_ONLY
  message_history_limit: 10
}
config {
  type: CHASSIS
  mode: RECEIVE_ONLY
  message_history_limit: 10
}
config {
  type: CONTROL_COMMAND
  mode: PUBLISH_ONLY
}
config {
  type: MONITOR
  mode: DUPLEX
  message_history_limit: 1
}
is_ros: true

在这里面,我们看到control模块订阅的话题有:localiza,pad,planning_trajectory,chassis,发布消息的话题为control_command.再返回control的Init()函数中,

 if (!controller_agent_.Init(&control_conf_).ok()) 

执行controller_agent_.Init(&control_conf_)函数,其意义是指明使用哪一个controller,并使用其中的方法计算control_command中的信息.controller_agent是apollo的一大特色,使用代理让我们很方便的更改我们使用的控制器,并自动完成初始化,检查等一系列工作.下面介绍下controller_agent的相关功能.

         controller_agent的定义存在于modules/controller.controller_agent.cc中.

Status ControllerAgent::Init(const ControlConf *control_conf) {
  RegisterControllers(control_conf);
  CHECK(InitializeConf(control_conf).ok()) << "Fail to initialize config.";
  for (auto &controller : controller_list_) {
    if (controller == NULL || !controller->Init(control_conf_).ok()) {
      if (controller != NULL) {
        AERROR << "Controller <" << controller->Name() << "> init failed!";
        return Status(ErrorCode::CONTROL_INIT_ERROR,
                      "Failed to init Controller:" + controller->Name());
      } else {
        return Status(ErrorCode::CONTROL_INIT_ERROR,
                      "Failed to init Controller");
      }
    }
    AINFO << "Controller <" << controller->Name() << "> init done!";
  }
  return Status::OK();
}

在ControllerAgent::Init(const ControlCong *control_conf)中,首先使用内部成员函数RegisterControllers(control_conf)注册控制器

void ControllerAgent::RegisterControllers(const ControlConf *control_conf) {
  AINFO << "Only support MPC controller or Lat + Lon controllers as of now";
  for (auto active_controller : control_conf->active_controllers()) {
    switch (active_controller) {
      case ControlConf::MPC_CONTROLLER:
        controller_factory_.Register(
            ControlConf::MPC_CONTROLLER,
            []() -> Controller * { return new MPCController(); });
        break;
      case ControlConf::LAT_CONTROLLER:
        controller_factory_.Register(
            ControlConf::LAT_CONTROLLER,
            []() -> Controller * { return new LatController(); });
        break;
      case ControlConf::LON_CONTROLLER:
        controller_factory_.Register(
            ControlConf::LON_CONTROLLER,
            []() -> Controller * { return new LonController(); });
        break;
      default:
        AERROR << "Unknown active controller type:" << active_controller;
    }
  }
}

 

这RegisterControllers中,从control_conf中读取到配置文件中我们激活的controller(也就是说,我们需要使用什么类型的控制器,我们需要在modules/control/conf/lincoln.pb.txt中修改

 

active_controllers: LAT_CONTROLLER
active_controllers: LON_CONTROLLER

,依次使用apollo的工厂模式注册返回一个controller.完成注册后再次检测是否初始化成功,里面有很多日志文件的输出,这样设计,特别规范,能够很方便我们在日志文件中找到程序运行失败的原因.

         当代理注册完成之后,返回到control的Init()函数中,依次检查下control模块所需要的localization,chassis,planning,pad,monitor是否能够得到信息,以便确保输入信息的正确性.截止到此,我们已经完成了Spin()中的Init()函数,然后执行的将是Start()函数,让我们再次把视线转移到modules/control/control.cc中

Status Control::Start() {
  // set initial vehicle state by cmd
  // need to sleep, because advertised channel is not ready immediately
  // simple test shows a short delay of 80 ms or so
  AINFO << "Control resetting vehicle state, sleeping for 1000 ms ...";
  std::this_thread::sleep_for(std::chrono::milliseconds(1000));

  // should init_vehicle first, let car enter work status, then use status msg
  // trigger control

  AINFO << "Control default driving action is "
        << DrivingAction_Name(control_conf_.action());
  pad_msg_.set_action(control_conf_.action());

  timer_ = AdapterManager::CreateTimer(
      ros::Duration(control_conf_.control_period()), &Control::OnTimer, this);

  AINFO << "Control init done!";

  common::monitor::MonitorLogBuffer buffer(&monitor_logger_);
  buffer.INFO("control started");

  return Status::OK();
}

         在Start函数中,最重要的就是timer_= AdapterManager::CreateTimer(ros::Duration(control_conf_.control_period()), &Control::OnTimer, this)函数,这个函数定义了回调函数的入口,和回调函数的执行周期,也就是说OnTimer()函数会周期性的执行,具体执行周期,在配置文件lincoln.pb.txt中有定义:control_period:0.01.也就是说control模块会以100hz的频率向外输出control_command信息.

         然后让我们看下Ontimer()中执行了哪些动作:

void Control::OnTimer(const ros::TimerEvent &) {
  double start_timestamp = Clock::NowInSeconds();

  if (FLAGS_is_control_test_mode && FLAGS_control_test_duration > 0 &&
      (start_timestamp - init_time_) > FLAGS_control_test_duration) {
    AERROR << "Control finished testing. exit";
    ros::shutdown();
  }

  ControlCommand control_command;

  Status status = ProduceControlCommand(&control_command);
  AERROR_IF(!status.ok()) << "Failed to produce control command:"
                          << status.error_message();

  double end_timestamp = Clock::NowInSeconds();

  if (pad_received_) {
    control_command.mutable_pad_msg()->CopyFrom(pad_msg_);
    pad_received_ = false;
  }

  const double time_diff_ms = (end_timestamp - start_timestamp) * 1000;
  control_command.mutable_latency_stats()->set_total_time_ms(time_diff_ms);
  control_command.mutable_latency_stats()->set_total_time_exceeded(
      time_diff_ms < control_conf_.control_period());
  ADEBUG << "control cycle time is: " << time_diff_ms << " ms.";
  status.Save(control_command.mutable_header()->mutable_status());

  SendCmd(&control_command);
}

除去检查信息的,最重要的一点就是ProduceControlCommand(&control_command)函数.这个成员函数中,最重要的一点函数就是controller_agent_.ComputeControlCommand(...)函数,这个函数将使用我们注册过的controller,调用其ComputeControlCommand()函数计算control_command信息.然后返回到Ontimer中其最后调用内部成员函数SendCmd(&control_command)函数将控制信息在话题/apollo/control中.

         到此为止,我们知道了control的模块的工作流程了.下面着重介绍下Apollo目前在使用的LQR控制器的策略,其源文件位于modules/control/controller/lat_controller.h和lat_controller.cc中.研究其工作流程,可以从其ComputeControlCommand()作为入口,依次看各个程序运行的关系即可

         下面讨论下preview_window_= 0(默认为0,目前还没有测试,下期更新)的控制策略

横向控制的重点在于ComputeControlCommand(

const localization::LocalizationEstimate *localization,

const canbus::Chassis *chassis,

const planning:ADCTrajectory *planning_published_trajectory,

ControlCommand *cmd)

首先,使用函数UpdateStateAnalyticalMatching(debug)计算了车辆距轨迹点(planning给出的)的横向位置(使用ComputeLateralErrors()),然后将latral_error()、latral_error_rate()、heading_error()、heading_error_rate()分别放在了matrix_state_的第1,2,3,4行。即:

Apollo control 模块源码解析_第1张图片

 

然后,使用UpdateMatrix(),对matrix_a_进行计算,matrix_a_= matrix_A_coeff_()/v,即

 

 

 

经过matrix_a_ = matrix_A_coeff_()/v后,

新建一个单位矩阵matrix_i;

又经历

对状态矩阵进行了离散化,具体参考资料

https://en.wikipedia.org/wiki/Discretization#Approximations

 

 

最后经历UpdateMatrixCompound(),把带有previewmatrix添加到matrix_adc_,这里没有用到preview matrix,暂时不用管,只是这个函数完成了把matrix_ad_的值赋给matrix_adc_,把matrix_bd_的值赋给matrix_bdc_.

 

最后使用LQR求解器对k矩阵进行求解,在此求解器中,把matrix_adc_当做是LQR的状态矩阵A,把matrix_bdc_当做是输入控制矩阵B.Apollo中LQR求解器总共有7个参数,依次分别为:

①A:车辆状态矩阵(复合矩阵),4*4,当preview_window_= 0 时;

②B:输入控制矩阵(复合矩阵),4*4,当preview_window_= 0 时;

③Q:系统状态的常矩阵,也是LQR中的Q,状态权重矩阵;

④R:控制输出常矩阵,也是LQR中的R

⑤tolerance:LQR计算阈值,求解AlgebraicRiccati方程的数值公差,是一个double类型,默认为0.01

⑥max_num_iteration:计算LQR的最大迭代次数

⑦ptr_k:反馈控制函数,也是LQR计算的结果

 

下面描述一下apollo中的LQR控制器的工作流程:

(详细见modules/common/math/linear_quadratic_regulator.c)

 

计算P的值:

          

反复迭代,直到迭代次数超过max_num_iteration(默认为150)的数大或者前后两个P值内部差值小于tolerance(默认为lqr_eps_=0.01).结束循环.

         最后计算K矩阵:

          

Apollo中前轮转角使用了前馈+反馈的机制进行横向控制

反馈的转角为:

         steer_angle_feedback= -(matrix_k*matrix_state)(0,0)

前馈的前轮转角为:

         先计算

          

          

附录:apollo中control中lat_controller.h中的成员变量:

ts_:离散时长 默认值为0.01

cf_:前轮侧偏刚度

cr_:后轮侧偏刚度

wheelbase_:轴距 

mass_:车重

lf_:前轮到质心的距离

lr_:后轮到质心的距离

iz_:转动惯量

steer_tranmission_ratio_:转向比

steer_single_direction_max_degree_:最大方向盘转角

max_lat_aa_:横向加速度限值(理论值) 

preview_window_:向前预测的控制周期数(预测控制器) 默认为0

basic_state_size_: 基本状态的大小,默认为4,包括横向误差,横向误差速率,航向角误差,航向角误差速率

matrix_a_ 车辆状态矩阵默认为4*4

matrix_ad_ 车辆状态矩阵(离散) 默认为4*4

matrix_adc_ 车辆状态矩阵(复合矩阵)  在preview_window_=0时为4*4

matrix_b_ 控制矩阵 4*1

matrix_bd_ 控制矩阵(离散) 4*1

matrix_bdc_ 控制矩阵(复合) 在preview_window_=0时为4*1

matrix_k_ 增益矩阵 1*4 在preview_window_ = 0 时为1*4

matrix_r_ 控制权重矩阵默认为1*1且为单位阵

matrix_q_ 状态权重矩阵在preview_window_ = 0 时为4*4

matrix_q_updated_ 状态权重更新矩阵

matrix_a_coeff_ 状态矩阵系数 在preview_window_ = 0 时为4*4

matrix_state_ 四合一矩阵,状态矩阵 在preview_window_ = 0 时为4*1

 

previous_heading_error_: 最后控制周期的航向偏差

previous_lateral_error_: 最后控制周期内与参考轨迹的横向距离

lqr_max_iteration_: LQR循环次数默认为150

lqr_eps_:  LQR计算阈值 默认数值为0.01

你可能感兴趣的:(Apollo)