当前位置:   article > 正文

LQR轨迹跟踪——基于ROS系统和全向车实验平台_(vector waypoints, int index)

(vector waypoints, int index)

前言

 

        上一篇博客用纯C++程序写完了LQR仿真过程,效果还行,然后这一篇博客用来把这个程序部署到实验平台的ROS系统上进行仿真及实验验证。

建议简单看一下上一篇的一个程序思路:Visual studio C++:LQR轨迹跟踪仿真

思路

ee0bd848ffaf474f8a809c00c1cd19b9.jpeg

        其实思路非常简单,就是path_planning向LQR_node发送目标点就可以了,主要看红圈里面的就行,其他的都是可视化要用的。

        path_planning生成路径通过话题/path传给LQR_node中,LQR_node进行跟踪。

代码部署

一、自定义库函数(LQR、tool、trajectory)

         基于上一篇博客,已经写了自定义的相关库函数,有LQR、tool、trajectory、matplot,但是在ROS里面可以不用matplot,可以在rviz里面看,所以丢弃。

1.Tool优化

        为了让路径生成过程更加条理清晰,对库函数进行了一些优化,Tool里面路径点参数只设置x、y、yaw,而曲率K选择在控制器里面针对获取的路径去算。添加了一个计算曲率K的函数。

Tool.h

  1. #pragma once
  2. #include <iostream>
  3. #include <math.h>
  4. #include <vector>
  5. using namespace std;
  6. #define pi acos(-1)
  7. //定义路径点
  8. typedef struct waypoint {
  9. int ID;
  10. double x, y, yaw;//x,y
  11. }waypoint;
  12. //定义小车状态
  13. typedef struct vehicleState {
  14. double x, y, yaw, v, kesi;//x,y,yaw,前轮偏角kesi
  15. }vehicleState;
  16. //定义控制量
  17. typedef struct U {
  18. double v;
  19. double kesi;//速度v,前轮偏角kesi
  20. }U;
  21. double cal_K(vector<waypoint> waypoints, int index);//计算曲率K

Tool.cpp

  1. #include<iostream>
  2. #include<LQR_track/Tool.h>
  3. double cal_K(vector<waypoint> waypoints, int index){
  4. double res;
  5. //差分法求一阶导和二阶导
  6. double dx, dy, ddx, ddy;
  7. if (index == 0) {
  8. dx = waypoints[1].x - waypoints[0].x;
  9. dy = waypoints[1].y - waypoints[0].y;
  10. ddx = waypoints[2].x + waypoints[0].x - 2 * waypoints[1].x;
  11. ddy = waypoints[2].y + waypoints[0].y - 2 * waypoints[1].y;
  12. }
  13. else if (index == (waypoints.size() - 1)) {
  14. dx = waypoints[index].x - waypoints[index - 1].x;
  15. dy = waypoints[index].y - waypoints[index - 1].y;
  16. ddx = waypoints[index].x + waypoints[index - 2].x - 2 * waypoints[index].x;
  17. ddy = waypoints[index].y + waypoints[index - 2].y - 2 * waypoints[index].y;
  18. }
  19. else {
  20. dx = waypoints[index + 1].x - waypoints[index].x;
  21. dy = waypoints[index + 1].y - waypoints[index].y;
  22. ddx = waypoints[index + 1].x + waypoints[index - 1].x - 2 * waypoints[index].x;
  23. ddy = waypoints[index + 1].y + waypoints[index - 1].y - 2 * waypoints[index].y;
  24. }
  25. //res.yaw = atan2(dy, dx);//yaw
  26. //计算曲率:设曲线r(t) =(x(t),y(t)),则曲率k=(x'y" - x"y')/((x')^2 + (y')^2)^(3/2).
  27. res = (ddy * dx - ddx * dy) / (sqrt(pow((pow(dx, 2) + pow(dy, 2)), 3)));
  28. return res;
  29. }

2.trajectory优化

        设置了可以从launch文件导入的参数接口,用于设计轨迹起始点,长度限制等信息,添加了用户自定义轨迹custom_path,按照trajectory.h里面的备注慢慢操作就好了。

trajectory.h

  1. #include <iostream>
  2. #include <vector>
  3. #include "LQR_track/Tool.h"
  4. #include <string>
  5. using namespace std;
  6. class trajectory {
  7. private:
  8. vector<waypoint> waypoints;
  9. double x_start,y_start,limit_x,limit_y;
  10. string trajectory_type;
  11. public:
  12. trajectory(double initial_x_,double initial_y_,string type_,double limit_x_,double limit_y_):
  13. x_start(initial_x_),y_start(initial_y_),trajectory_type(type_),limit_x(limit_x_),limit_y(limit_y_){};
  14. //set reference trajectory
  15. void refer_path();
  16. vector<waypoint> get_path();
  17. //If you need to add a custom path:1.please add the function; 2.Overwrite trajectory.cpp synchronously
  18. //void custom_path();
  19. void line();
  20. void wave1();
  21. void wave2();
  22. };

trajectory.cpp

  1. #include <iostream>
  2. #include <vector>
  3. #include "LQR_track/trajectory.h"
  4. #include <math.h>
  5. using namespace std;
  6. double dt = 0.01;//轨迹计算频率
  7. void trajectory::line(){
  8. waypoint PP;
  9. for (int i = 0; i < limit_x/dt; i++)
  10. {
  11. PP.ID = i;
  12. PP.x = x_start + dt * i;//x:10米
  13. PP.y = y_start ;//y
  14. waypoints.push_back(PP);
  15. }
  16. }
  17. void trajectory::wave1(){
  18. waypoint PP;
  19. for (int i = 0; i < limit_x/dt; i++)
  20. {
  21. PP.ID = i;
  22. PP.x = x_start + dt * i;//x
  23. PP.y = y_start + 1.0 * sin(dt*i / 1.5) + 0.5 * cos(dt*i / 1.0);//y
  24. waypoints.push_back(PP);
  25. }
  26. }
  27. void trajectory::wave2(){
  28. waypoint PP;
  29. for (int i = 0; i < limit_x/dt; i++)
  30. {
  31. PP.ID = i;
  32. PP.x = x_start + dt * i;//x
  33. PP.y = y_start - 0.2 * sin(dt*i / 0.4);//y
  34. waypoints.push_back(PP);
  35. }
  36. }
  37. //write the path you design
  38. /*void trajectory::custom_path(){
  39. waypoint PP;
  40. for (int i = 0; i < limit_x/dt; i++)
  41. {
  42. PP.ID = i;
  43. PP.x = ...;//x
  44. PP.y = ...;//y
  45. waypoints.push_back(PP);
  46. }
  47. }*/
  48. void trajectory::refer_path() {
  49. if(trajectory_type == "wave1")wave1();
  50. else if(trajectory_type == "line")line();
  51. else if(trajectory_type == "wave2")wave2();
  52. //else if(trajectory_type == "custom_path")custom_path();//set the index
  53. //计算切线方向并储存
  54. for (int j=0;j<waypoints.size();j++){
  55. double dx, dy, yaw;
  56. if (j == 0) {
  57. dx = waypoints[1].x - waypoints[0].x;
  58. dy = waypoints[1].y - waypoints[0].y;
  59. }
  60. else if (j == (waypoints.size() - 1)) {
  61. dx = waypoints[j].x - waypoints[j - 1].x;
  62. dy = waypoints[j].y - waypoints[j - 1].y;
  63. }
  64. else {
  65. dx = waypoints[j + 1].x - waypoints[j].x;
  66. dy = waypoints[j + 1].y - waypoints[j].y;
  67. }
  68. yaw = atan2(dy, dx);//yaw
  69. waypoints[j].yaw = yaw;
  70. }
  71. }
  72. vector<waypoint> trajectory::get_path() {
  73. return waypoints;
  74. }

3.LQR优化

这个好像没做优化,跟原来的一样吧,还是附上代码:

LQR.h

  1. #include <iostream>
  2. #include <Eigen/Dense>
  3. #include "LQR_track/Tool.h"
  4. using namespace std;
  5. typedef Eigen::Matrix<double, 3, 3> Matrix3x3;
  6. typedef Eigen::Matrix<double, 3, 1> Matrix3x1;
  7. typedef Eigen::Matrix<double, 2, 1> Matrix2x1;
  8. typedef Eigen::Matrix<double, 2, 2> Matrix2x2;
  9. typedef Eigen::Matrix<double, 3, 2> Matrix3x2;
  10. typedef Eigen::Matrix<double, 2, 3> Matrix2x3;
  11. //状态方程变量: X = [x_e y_e yaw_e]^T
  12. //状态方程控制输入: U = [v_e kesi_e]^T
  13. class LQR
  14. {
  15. private:
  16. Matrix3x3 A_d;
  17. Matrix3x2 B_d;
  18. Matrix3x3 Q;
  19. Matrix2x2 R;
  20. Matrix3x1 X_e;
  21. Matrix2x1 U_e;
  22. double L;//车辆轴距
  23. double T;//采样间隔
  24. double x_car, y_car, yaw_car, x_d, y_d, yaw_d;//车辆位姿和目标点位姿
  25. double v_d, kesi_d;//期望速度和前轮偏角
  26. double Q3[3];//Q权重,三项
  27. double R2[2];//R权重,两项
  28. int temp = 0;
  29. public:
  30. void initial(double L_, double T_, vehicleState car, waypoint waypoint, U U_r, double* Q_, double* R_);//初始化
  31. void param_struct();//构造状态方程参数
  32. Matrix2x3 cal_Riccati();//黎卡提方程求解
  33. U cal_vel();//LQR控制器计算速度
  34. void test();
  35. };

LQR.cpp

  1. #include <iostream>
  2. #include "LQR_track/LQR.h"
  3. using namespace std;
  4. void LQR::initial(double L_, double T_, vehicleState car, waypoint waypoint, U U_r, double *Q_, double *R_) {
  5. L = L_;
  6. T = T_;
  7. x_car = car.x; y_car = car.y; yaw_car = car.yaw;
  8. x_d = waypoint.x; y_d = waypoint.y; yaw_d = waypoint.yaw;
  9. v_d = U_r.v;kesi_d = U_r.kesi;
  10. for (int i = 0; i < 3; i++) {
  11. Q3[i] = Q_[i];
  12. }
  13. for (int j = 0; j < 2; j++) {
  14. R2[j] = R_[j];
  15. }
  16. }
  17. void LQR::param_struct() {
  18. Q << Q3[0], 0.0, 0.0,
  19. 0.0, Q3[1], 0.0,
  20. 0.0, 0.0, Q3[2];
  21. //cout << "Q矩阵为:\n" << Q << endl;
  22. R << R2[0], 0.0,
  23. 0.0, R2[1];
  24. //cout << "R矩阵为:\n" << R << endl;
  25. A_d << 1.0, 0.0, -v_d * T * sin(yaw_d),
  26. 0.0, 1.0, v_d* T* cos(yaw_d),
  27. 0.0, 0.0, 1.0;
  28. //cout << "A_d矩阵为:\n" << A_d << endl;
  29. B_d << T * cos(yaw_d), 0.0,
  30. T* sin(yaw_d), 0.0,
  31. T* tan(kesi_d), v_d* T / (L * cos(kesi_d) * cos(kesi_d));
  32. //cout << "B_d矩阵为:\n" << B_d << endl;
  33. X_e << x_car - x_d, y_car - y_d, yaw_car - yaw_d;
  34. //cout << "X_e矩阵为:\n" << X_e << endl;
  35. }
  36. Matrix2x3 LQR::cal_Riccati() {
  37. int N = 150;//迭代终止次数
  38. double err = 100;//误差值
  39. double err_tolerance = 0.01;//误差收敛阈值
  40. Matrix3x3 Qf = Q;
  41. Matrix3x3 P = Qf;//迭代初始值
  42. //cout << "P初始矩阵为\n" << P << endl;
  43. Matrix3x3 Pn;//计算的最新P矩阵
  44. for (int iter_num = 0; iter_num < N; iter_num++) {
  45. Pn = Q + A_d.transpose() * P * A_d - A_d.transpose() * P * B_d * (R + B_d.transpose() * P * B_d).inverse() * B_d.transpose() * P * A_d;//迭代公式
  46. //cout << "收敛误差为" << (Pn - P).array().abs().maxCoeff() << endl;
  47. //err = (Pn - P).array().abs().maxCoeff();//
  48. err = (Pn - P).lpNorm<Eigen::Infinity>();
  49. if(err < err_tolerance)//
  50. {
  51. P = Pn;
  52. //cout << "迭代次数" << iter_num << endl;
  53. break;
  54. }
  55. P = Pn;
  56. }
  57. //cout << "P矩阵为\n" << P << endl;
  58. //P = Q;
  59. Matrix2x3 K = -(R + B_d.transpose() * P * B_d).inverse() * B_d.transpose() * P * A_d;//反馈率K
  60. return K;
  61. }
  62. U LQR::cal_vel() {
  63. U output;
  64. param_struct();
  65. Matrix2x3 K = cal_Riccati();
  66. Matrix2x1 U = K * X_e;
  67. //cout << "反馈增益K为:\n" << K << endl;
  68. //cout << "控制输入U为:\n" << U << endl;
  69. output.v = U[0] + v_d;
  70. output.kesi = U[1] + kesi_d;
  71. return output;
  72. }
  73. void LQR::test() //控制器效果测试
  74. {
  75. /*param_struct();
  76. while (temp < 1000) {
  77. Matrix2x3 K = cal_Riccati();
  78. Matrix2x1 U = K * X_e;
  79. //cout <<"state variable is:\n" <<X_e << endl;
  80. //cout <<"control input is:\n"<< U << endl;
  81. Matrix3x1 X_e_ = A_d * X_e + B_d * U;
  82. X_e = X_e_;
  83. temp++;
  84. }*/
  85. Matrix3x3 C,D,F;
  86. C << 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0;
  87. F << 1.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 7.0;
  88. D = (C - F);
  89. double BBBB = D.lpNorm<Eigen::Infinity>();
  90. cout << BBBB << endl;
  91. }

4.ROS中自定义库的编译生成

        可以参考一下这一篇:5.ROS学习之自定义头文件和源文件的调用,如果不想看的话可以就按照我这个过程就好了。

        首先参考之前的博客:ROS C++调用osqp-eigen库的具体操作步骤,完成:添加ROS自带的Eigen模块

        然后在Build下面写上如下代码:

        注意!!!!:头文件和源文件的绝对路径一定要写成自己的!!

  1. ###########
  2. ## Build ##
  3. ###########
  4. include_directories(
  5. include
  6. ${catkin_INCLUDE_DIRS}
  7. ${Eigen_INCLUDE_DIRS}
  8. )
  9. ####以下绝对路径一定要写成自己的!!!
  10. add_library(LQR_LIBRARIES
  11. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/include/LQR_track/Tool.h
  12. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/include/LQR_track/trajectory.h
  13. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/include/LQR_track/LQR.h
  14. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/src/Tool.cpp
  15. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/src/trajectory.cpp
  16. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/src/LQR.cpp
  17. )
  18. target_link_libraries(LQR_LIBRARIES ${catkin_LIBRARIES})

 这样就好了。编译一下可以生成lqr_track功能包的名为LQR_LIBRARIES自定义库。

二、调用库函数完成轨迹生成工作(path_planning.cpp)

        基于ROS的发布者机制去写一个路径发布程序,路径数据类型为nav_msgs::Path。直接调用trajectory就可以实现路径的获取,然后转换一下数据类型(从vector<waypoint>转换为nav_msgs::Path)就可以发布出去啦。

        同时从launch文件传入参数流程是:launch→path_planning→trajectory.h

path_planning.cpp

  1. #include <ros/ros.h>
  2. #include <nav_msgs/Path.h>
  3. #include <geometry_msgs/PoseStamped.h>
  4. #include <iostream>
  5. #include <vector>
  6. #include "LQR_track/trajectory.h"
  7. #include "ros/init.h"
  8. #include "ros/node_handle.h"
  9. #include "ros/publisher.h"
  10. #include "ros/rate.h"
  11. #include "ros/time.h"
  12. #include <math.h>
  13. #include <tf/tf.h>
  14. double x_start,y_start,limit_x,limit_y;
  15. string trajectory_type;
  16. nav_msgs::Path produce_path(){
  17. trajectory* trajec = new trajectory(x_start,y_start,trajectory_type,limit_x,limit_y);//实例化trajectory类;
  18. vector<waypoint> waypoint_vec;//定义vector类用于接收由trajec生成的路径,得到若干组[ID,x,y]
  19. nav_msgs::Path waypoints;//定义nav_msgs::Path类用于构造ROS的Path消息
  20. //获取路径
  21. trajec->refer_path();
  22. waypoint_vec = trajec->get_path();
  23. //构造适用于ROS的Path消息
  24. waypoints.header.stamp = ros::Time::now();
  25. waypoints.header.frame_id = "map";
  26. for(int i =0;i<waypoint_vec.size();i++){
  27. geometry_msgs::PoseStamped this_pose;
  28. this_pose.header.seq = i;
  29. //ROS_INFO("path_id is %d", this_pose.header.seq);
  30. this_pose.header.frame_id = "map";
  31. this_pose.pose.position.x = waypoint_vec[i].x;
  32. this_pose.pose.position.y = waypoint_vec[i].y;
  33. this_pose.pose.position.z = 0;
  34. geometry_msgs::Quaternion goal_quat = tf::createQuaternionMsgFromYaw(waypoint_vec[i].yaw);
  35. //ROS_INFO("the yaw is %f",waypoint_vec[i].yaw);
  36. this_pose.pose.orientation.x = goal_quat.x;
  37. this_pose.pose.orientation.y = goal_quat.y;
  38. this_pose.pose.orientation.z = goal_quat.z;
  39. this_pose.pose.orientation.w = goal_quat.w;
  40. waypoints.poses.push_back(this_pose);
  41. }
  42. return waypoints;//返回适用于ROS的Path消息
  43. }
  44. int main(int argc,char **argv){
  45. ros::init(argc, argv, "path_produce");
  46. ros::NodeHandle n;
  47. ros::NodeHandle n_prv("~");
  48. n_prv.param<double>("x_start",x_start,0.0);
  49. n_prv.param<double>("y_start",y_start,0.0);
  50. n_prv.param<string>("trajectory_type",trajectory_type,"line");
  51. n_prv.param<double>("limit_x",limit_x,10);
  52. n_prv.param<double>("limit_y",limit_y,0.0);
  53. ros::Publisher path_pub = n.advertise<nav_msgs::Path>("path",10);
  54. ros::Rate loop_rate(1);
  55. while(ros::ok()){
  56. nav_msgs::Path path = produce_path();
  57. ROS_INFO("the trajectory size is: %ld",path.poses.size());
  58. path_pub.publish(path);
  59. loop_rate.sleep();
  60. }
  61. }

三、调用库函数完成LQR控制器的轨迹跟踪工作

1.仿真程序(LQR_node.cpp)

        这个文件中定义了两个类,一个是对订阅到的路径进行处理的类(Class Path),一个是负责轨迹跟踪的类(Class LQR_node),其跟踪功能的实现步骤如下:

    跟踪功能实现步骤:
        1.获取路径点,在while(ros::ok())函数下面ros::spinOnce()阶段订阅回调函数addpointcallback时完成
        2.搜索路径点
        3.获取路径点信息,构造期望控制量
        4.先进行一次减速判断,确定纵向期望速度v_r,和机器人当前动作:tracking或reached the goal
        5.构造权重矩阵,从launch文件的参数Q_set,R_set导入
        6.使用LQR控制器,计算速度和前轮转角,其中有限幅过程
        7.发布话题,其中有角速度的计算

        8.用运动学模型计算小车下一时刻位姿
        8.判断是否需要关闭控制器,如果到达终点就关闭
        9.loop_rate.sleep(),结束一次循环,准备开始下一次个跟踪工作

直接放代码了:

LQR_node.cpp:

  1. #include <ros/ros.h>
  2. #include <iostream>
  3. #include "LQR_track/LQR.h"
  4. #include <vector>
  5. #include "ros/node_handle.h"
  6. #include "ros/publisher.h"
  7. #include "tf/LinearMath/Quaternion.h"
  8. #include "tf/transform_broadcaster.h"
  9. #include "visualization_msgs/Marker.h"
  10. #include <nav_msgs/Path.h>
  11. #include <geometry_msgs/Twist.h>
  12. #include <geometry_msgs/Pose2D.h>
  13. #include <tf/tf.h>
  14. #include <tf/transform_broadcaster.h>
  15. using namespace std;
  16. double freq,L,V_DESIRED;//采样频率,车辆轴距,期望速度
  17. double v_max;//最大速度
  18. bool limit_v_and_kesi;//是否限幅(对于阿卡曼转向车辆需要限幅,全向车倒还好)
  19. double initial_x,initial_y,initial_yaw,initial_v,initial_kesi;//初始化车辆位姿,速度和前轮转角
  20. std::vector<double> Q_set;
  21. std::vector<double> R_set;
  22. double slow_LEVE1_DISTANCE,slow_LEVE2_DISTANCE,slow_LEVE1_V,slow_LEVE2_V,goal_tolerance_DISTANCE;//定义二级减速距离和速度
  23. #define pi acos(-1)
  24. #define T 1/freq //采样时间
  25. vehicleState update_state(U control, vehicleState car) {
  26. car.v = control.v;
  27. car.kesi = control.kesi;
  28. car.x += car.v * cos(car.yaw) * T;
  29. car.y += car.v * sin(car.yaw) * T;
  30. car.yaw += car.v / L * tan(car.kesi) * T;
  31. return car;
  32. }
  33. class Path {
  34. private:
  35. vector<waypoint> path;
  36. public:
  37. //添加新的路径点
  38. void Add_new_point(waypoint& p)
  39. {
  40. path.push_back(p);
  41. }
  42. void Add_new_point(vector<waypoint>& p)
  43. {
  44. path = p;
  45. }
  46. //路径点个数
  47. unsigned int Size()
  48. {
  49. return path.size();
  50. }
  51. //获取路径点
  52. waypoint Get_waypoint(int index)
  53. {
  54. waypoint p;
  55. p.ID = path[index].ID;
  56. p.x = path[index].x;
  57. p.y = path[index].y;
  58. p.yaw = path[index].yaw;
  59. return p;
  60. }
  61. vector<waypoint> Get_waypoints(){
  62. return path;
  63. }
  64. // 搜索路径点, 将小车到起始点的距离与小车到每一个点的距离对比,找出最近的目标点索引值
  65. int Find_target_index(vehicleState state)
  66. {
  67. double min = abs(sqrt(pow(state.x - path[0].x, 2) + pow(state.y - path[0].y, 2)));
  68. int index = 0;
  69. for (int i = 0; i < path.size(); i++)
  70. {
  71. double d = abs(sqrt(pow(state.x - path[i].x, 2) + pow(state.y - path[i].y, 2)));
  72. if (d < min)
  73. {
  74. min = d;
  75. index = i;
  76. }
  77. }
  78. //索引到终点前,当(机器人与下一个目标点的距离Lf)小于(当前目标点到下一个目标点距离L)时,索引下一个目标点
  79. if ((index + 1) < path.size())
  80. {
  81. double current_x = path[index].x; double current_y = path[index].y;
  82. double next_x = path[index + 1].x; double next_y = path[index + 1].y;
  83. double L_ = abs(sqrt(pow(next_x - current_x, 2) + pow(next_y - current_y, 2)));
  84. double L_1 = abs(sqrt(pow(state.x - next_x, 2) + pow(state.y - next_y, 2)));
  85. //ROS_INFO("L is %f,Lf is %f",L,Lf);
  86. if (L_1 < L_)
  87. {
  88. index += 1;
  89. }
  90. }
  91. return index;
  92. }
  93. };
  94. class LQR_node {
  95. private:
  96. //car
  97. vehicleState car;//小车状态
  98. U control;//小车控制量[v,kesi]
  99. double Q[3];
  100. double R[2];
  101. int lastIndex;//最后一个点索引值
  102. waypoint lastPoint;//最后一个点信息
  103. string action;//小车目前动作:跟踪或跟踪完成(tracking or reach goal!)
  104. //ROS
  105. ros::Subscriber path_sub;//订阅路径,消息类型为nav_msgs::Path
  106. ros::Publisher vel_pub;//发布速度信息,消息类型为geometry_msgs::Twist
  107. ros::Publisher actual_state_pub;//发布小车实际位姿,消息类型为geometry_msgs::Pose2D
  108. ros::Publisher visual_state_pub;//向rviz发布小车虚拟轨迹,消息类型为visualization_msgs::Marker
  109. geometry_msgs::Point visual_state_pose;
  110. visualization_msgs::Marker visual_state_trajectory;
  111. geometry_msgs::Pose2D actual_pose;
  112. geometry_msgs::Twist vel_msg;
  113. int temp;//计数,达到终点时,用于判断控制器是否需要关闭
  114. public:
  115. LQR* controller;
  116. Path* path;
  117. LQR_node(ros::NodeHandle& nh)//初始化中添加轨迹、小车初始位姿
  118. {
  119. controller = new LQR();
  120. path = new Path();
  121. //ROS:
  122. path_sub = nh.subscribe("path",10,&LQR_node::addpointcallback,this);
  123. vel_pub = nh.advertise<geometry_msgs::Twist>("cmd_vel",10);
  124. visual_state_pub = nh.advertise<visualization_msgs::Marker>("visualization_pose",10);
  125. actual_state_pub = nh.advertise<geometry_msgs::Pose2D>("LQR_pose",10);
  126. //robot state initialize:
  127. car.x = initial_x;car.y = initial_y;car.yaw = initial_yaw;car.v = initial_v;car.kesi = initial_kesi;
  128. action = "the car is tracking!!";
  129. }
  130. ~LQR_node() {
  131. delete(controller);
  132. delete(path);
  133. }
  134. void addpointcallback(const nav_msgs::Path::ConstPtr& msg){
  135. vector<waypoint> waypoints;
  136. for(int i=0;i<msg->poses.size();i++){
  137. waypoint waypoint;
  138. //ROS_INFO("THE PATH[%d]'s ID is %d",i,msg->poses[i].header.seq);
  139. waypoint.ID = msg->poses[i].header.seq;
  140. waypoint.x = msg->poses[i].pose.position.x;
  141. waypoint.y = msg->poses[i].pose.position.y;
  142. //获取角度
  143. double roll,pitch,yaw;
  144. tf::Quaternion quat;
  145. tf::quaternionMsgToTF(msg->poses[i].pose.orientation, quat);
  146. tf::Matrix3x3(quat).getRPY(roll, pitch, yaw);
  147. waypoint.yaw = yaw;
  148. waypoints.push_back(waypoint);
  149. }
  150. path->Add_new_point(waypoints);//路径点vector数组传到path类中
  151. lastIndex = path->Size() - 1;
  152. lastPoint = path->Get_waypoint(lastIndex);
  153. }
  154. double slow_judge(double distance) {
  155. if (distance>=slow_LEVE2_DISTANCE&&distance <= slow_LEVE1_DISTANCE) {
  156. return slow_LEVE1_V;
  157. }
  158. else if (distance>=goal_tolerance_DISTANCE&&distance < slow_LEVE2_DISTANCE) {
  159. return slow_LEVE2_V;
  160. }
  161. else if (distance < goal_tolerance_DISTANCE) {
  162. action = "the car has reached the goal!";
  163. return 0.0;
  164. }
  165. else
  166. {
  167. return V_DESIRED;
  168. }
  169. }
  170. //控制器流程
  171. void LQR_track() {
  172. U U_r;
  173. waypoint Point;
  174. double K;
  175. //ROS_INFO("path size is: %d",path->Size());
  176. if(path->Size()!=0){
  177. //搜索路径点
  178. int target_index = path->Find_target_index(car);
  179. //ROS_INFO("target index is : %d", target_index);
  180. //获取路径点信息,构造期望控制量
  181. Point = path->Get_waypoint(target_index);//获取x,y
  182. //ROS_INFO("waypoint information is x:%f,y:%f,yaw:%f", Point.x, Point.y, Point.yaw);
  183. K = cal_K(path->Get_waypoints(),target_index);//计算曲率
  184. //减速判断
  185. double kesi = atan2(L * K, 1);
  186. double v_distance = abs(sqrt(pow(car.x - lastPoint.x, 2) + pow(car.y - lastPoint.y, 2)));
  187. //ROS_INFO("the distance is %f\n", v_distance);
  188. U_r.v = slow_judge(v_distance);U_r.kesi = kesi;
  189. ROS_INFO("%s",action.c_str());//机器人动作
  190. //ROS_INFO("the desired v is: %f,the desired kesi is: %f", U_r.v,U_r.kesi);
  191. //权重矩阵
  192. for(int q =0;q<Q_set.size();q++)Q[q] = Q_set[q];
  193. for(int r =0;r<R_set.size();r++)R[r] = R_set[r];
  194. if(Q[0]>=R[0]||Q[1]>=R[1])
  195. ROS_WARN("Q >= R, the calculation result may be abnormal,please set R < Q ");
  196. //使用LQR控制器,
  197. controller->initial(L, T, car, Point, U_r, Q, R);//初始化控制器
  198. control = controller->cal_vel();//计算输入[v, kesi]
  199. if(U_r.v==0)control.v = 0;//判断,期望速度为0,则机器人停下
  200. if(limit_v_and_kesi)control = v_and_kesi_limit(control);//速度和前轮转角限幅
  201. ROS_INFO("the speed is: %f,the kesi is: %f", control.v, control.kesi);
  202. //ROS_INFO("the car position is x: %f, y: %f", car.x, car.y);
  203. //话题发布
  204. PUB();
  205. //小车位姿状态更新
  206. car = update_state(control, car);
  207. //控制器关闭判断
  208. shutdown_controller();
  209. ROS_INFO("--------------------------------------");
  210. }
  211. }
  212. //控制启停函数
  213. void node_control() {
  214. ros::Rate loop_rate(freq);
  215. Marker_set();//设置Marker属性
  216. //设置tf坐标转换
  217. tf::TransformBroadcaster br;
  218. tf::Transform transform;
  219. tf::Quaternion q;
  220. while (ros::ok()) {
  221. transform.setOrigin(tf::Vector3(car.x, car.y, 0));
  222. q.setRPY(0, 0, car.yaw);
  223. transform.setRotation(q);
  224. br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "map", "car"));
  225. ros::spinOnce();
  226. LQR_track();
  227. loop_rate.sleep();
  228. }
  229. }
  230. void PUB(){
  231. visual_state_pose.x = car.x; visual_state_pose.y = car.y;
  232. actual_pose.x = car.x; actual_pose.y = car.y; actual_pose.theta = car.yaw;
  233. vel_msg.linear.x = control.v; vel_msg.angular.z = control.v*tan(control.kesi)/L;//横摆角速度为w = v*tan(kesi)/L
  234. visual_state_trajectory.points.push_back(visual_state_pose);//visualization_msgs::Marker为一个容器,所以现需要向里面push_back结构体,再发布
  235. visual_state_pub.publish(visual_state_trajectory);//发布虚拟轨迹
  236. vel_pub.publish(vel_msg);//发布速度
  237. actual_state_pub.publish(actual_pose);//发布位姿
  238. }
  239. void shutdown_controller(){
  240. if(action == "the car has reached the goal!"){
  241. temp+=1;
  242. if(temp ==50){
  243. ROS_WARN("shutdown the LQR controller!");
  244. temp = 0;
  245. ros::shutdown();
  246. }
  247. }
  248. }
  249. void Marker_set(){
  250. //设置消息类型参数
  251. visual_state_trajectory.header.frame_id = "map";
  252. visual_state_trajectory.header.stamp = ros::Time::now();
  253. visual_state_trajectory.action = visualization_msgs::Marker::ADD;
  254. visual_state_trajectory.ns = "LQR";
  255. //设置点的属性
  256. visual_state_trajectory.id = 0;
  257. visual_state_trajectory.type = visualization_msgs::Marker::POINTS;
  258. visual_state_trajectory.scale.x = 0.02;
  259. visual_state_trajectory.scale.y = 0.02;
  260. visual_state_trajectory.color.r = 1.0;
  261. visual_state_trajectory.color.a = 1.0;
  262. }
  263. U v_and_kesi_limit(U control_value){
  264. if(control_value.v>=v_max)//速度限幅
  265. {
  266. control_value.v = v_max;
  267. ROS_WARN("The calculated value may be inaccurate ");
  268. }
  269. else if(control_value.v<=-v_max){
  270. control_value.v = -v_max;
  271. ROS_WARN("The calculated value may be inaccurate ");
  272. }
  273. if(control_value.kesi>=pi/2)//前轮转角限幅
  274. {
  275. control_value.kesi = pi/2;
  276. ROS_WARN("The calculated value may be inaccurate ");
  277. }
  278. else if(control_value.kesi<=-pi/2){
  279. control_value.kesi = -pi/2;
  280. ROS_WARN("The calculated value may be inaccurate ");
  281. }
  282. return control_value;
  283. }
  284. };
  285. int main(int argc, char** argv)
  286. {
  287. ros::init(argc, argv, "LQR_node");
  288. ros::NodeHandle n;
  289. ros::NodeHandle n_prv("~");
  290. n_prv.param<double>("freq",freq,20);
  291. n_prv.param<double>("L",L,0.2);
  292. n_prv.param<double>("V_DESIRED",V_DESIRED,0.5);
  293. n_prv.param<double>("v_max",v_max,1.0);
  294. n_prv.param<double>("initial_x",initial_x,0.0);
  295. n_prv.param<double>("initial_y",initial_y,2.0);
  296. n_prv.param<double>("initial_yaw",initial_yaw,0.0);
  297. n_prv.param<double>("initial_v",initial_v,0.0);
  298. n_prv.param<double>("initial_kesi",initial_kesi,0.1);
  299. n_prv.param<double>("slow_LEVE1_DISTANCE",slow_LEVE1_DISTANCE,5.0);
  300. n_prv.param<double>("slow_LEVE2_DISTANCE",slow_LEVE2_DISTANCE,2.0);
  301. n_prv.param<double>("goal_tolerance_DISTANCE",goal_tolerance_DISTANCE,0.1);
  302. n_prv.param<double>("slow_LEVE1_V",slow_LEVE1_V,0.35);
  303. n_prv.param<double>("slow_LEVE2_V",slow_LEVE2_V,0.15);
  304. n_prv.param<bool>("limit_v_and_kesi",limit_v_and_kesi,false);
  305. n_prv.param("Q_set",Q_set,Q_set);
  306. n_prv.param("R_set",R_set,R_set);
  307. LQR_node* node = new LQR_node(n);
  308. node->node_control();
  309. return (0);
  310. }

2.实验程序(LQR_node_world.cpp)

        这个实验程序跟仿真程序很类似,只不过不再用运动学模型计算小车位姿,而是采样得到小车位姿,所以增加了一个订阅接口(odom_sub)

跟踪功能实现步骤:
        1.获取小车当前位姿和路径点,在while(ros::ok())函数下面ros::spinOnce()阶段订阅回调函数odomcallback和addpointcallback时完成
        2.搜索路径点
        3.获取路径点信息,构造期望控制量
        4.先进行一次减速判断,确定纵向期望速度v_r,和机器人当前动作:tracking或reached the goal
        5.构造权重矩阵,从launch文件的参数Q=[],R=[]导入
        6.使用LQR控制器,计算速度和前轮转角,其中有限幅过程
        7.发布话题,其中有角速度的计算
        8.判断是否需要关闭控制器,如果到达终点就关闭
        9.loop_rate.sleep(),结束一次循环,准备开始下一次跟踪工作

 然后也去除了程序中发布map到小车的tf,定位之后rviz里面会有tf的,所以这里不再需要。

LQR_node_world.cpp

  1. #include <ros/ros.h>
  2. #include <iostream>
  3. #include "LQR_track/LQR.h"
  4. #include <vector>
  5. #include "ros/node_handle.h"
  6. #include "ros/publisher.h"
  7. #include "tf/LinearMath/Quaternion.h"
  8. #include "tf/transform_broadcaster.h"
  9. #include "visualization_msgs/Marker.h"
  10. #include <nav_msgs/Path.h>
  11. #include <geometry_msgs/Twist.h>
  12. #include <geometry_msgs/Pose2D.h>
  13. #include <nav_msgs/Odometry.h>
  14. #include <tf/tf.h>
  15. #include <tf/transform_broadcaster.h>
  16. using namespace std;
  17. double freq,L,V_DESIRED;//采样频率,车辆轴距,期望速度
  18. double v_max;//最大速度
  19. bool limit_v_and_kesi;//是否限幅(对于阿卡曼转向车辆需要限幅,全向车随意)
  20. std::vector<double> Q_set;//Q矩阵
  21. std::vector<double> R_set;//R矩阵
  22. double slow_LEVE1_DISTANCE,slow_LEVE2_DISTANCE,slow_LEVE1_V,slow_LEVE2_V,goal_tolerance_DISTANCE;//定义二级减速距离和速度
  23. #define pi acos(-1)
  24. #define T 1/freq //采样时间
  25. class Path {
  26. private:
  27. vector<waypoint> path;
  28. public:
  29. //添加新的路径点
  30. void Add_new_point(waypoint& p)
  31. {
  32. path.push_back(p);
  33. }
  34. void Add_new_point(vector<waypoint>& p)
  35. {
  36. path = p;
  37. }
  38. //路径点个数
  39. unsigned int Size()
  40. {
  41. return path.size();
  42. }
  43. //获取路径点
  44. waypoint Get_waypoint(int index)
  45. {
  46. waypoint p;
  47. p.ID = path[index].ID;
  48. p.x = path[index].x;
  49. p.y = path[index].y;
  50. p.yaw = path[index].yaw;
  51. return p;
  52. }
  53. vector<waypoint> Get_waypoints(){
  54. return path;
  55. }
  56. // 搜索路径点, 将小车到起始点的距离与小车到每一个点的距离对比,找出最近的目标点索引值
  57. int Find_target_index(vehicleState state)
  58. {
  59. double min = abs(sqrt(pow(state.x - path[0].x, 2) + pow(state.y - path[0].y, 2)));
  60. int index = 0;
  61. for (int i = 0; i < path.size(); i++)
  62. {
  63. double d = abs(sqrt(pow(state.x - path[i].x, 2) + pow(state.y - path[i].y, 2)));
  64. if (d < min)
  65. {
  66. min = d;
  67. index = i;
  68. }
  69. }
  70. //索引到终点前,当(机器人与下一个目标点的距离L_1)小于(当前目标点到下一个目标点距离L_)时,索引下一个目标点
  71. if ((index + 1) < path.size())
  72. {
  73. double current_x = path[index].x; double current_y = path[index].y;
  74. double next_x = path[index + 1].x; double next_y = path[index + 1].y;
  75. double L_ = abs(sqrt(pow(next_x - current_x, 2) + pow(next_y - current_y, 2)));
  76. double L_1 = abs(sqrt(pow(state.x - next_x, 2) + pow(state.y - next_y, 2)));
  77. //ROS_INFO("L is %f,Lf is %f",L,Lf);
  78. if (L_1 < L_)
  79. {
  80. index += 1;
  81. }
  82. }
  83. return index;
  84. }
  85. };
  86. class LQR_node {
  87. private:
  88. //car
  89. vehicleState car;//小车状态
  90. U control;//小车控制量[v,kesi]
  91. double Q[3];
  92. double R[2];
  93. int lastIndex;//最后一个点索引值
  94. waypoint lastPoint;//最后一个点信息
  95. string action;//小车目前动作:跟踪或跟踪完成(tracking or reach goal!)
  96. //ROS
  97. ros::Subscriber path_sub;//订阅路径,消息类型为nav_msgs::Path
  98. ros::Subscriber odom_sub;//订阅小车当前位姿信息,消息类型为nav_msgs::Odometry
  99. ros::Publisher vel_pub;//发布速度信息,消息类型为geometry_msgs::Twist
  100. ros::Publisher actual_state_pub;//发布小车实际位姿,消息类型为geometry_msgs::Pose2D
  101. ros::Publisher visual_state_pub;//向rviz发布小车虚拟轨迹,消息类型为visualization_msgs::Marker
  102. geometry_msgs::Point visual_state_pose;
  103. visualization_msgs::Marker visual_state_trajectory;
  104. geometry_msgs::Pose2D actual_pose;
  105. geometry_msgs::Twist vel_msg;
  106. geometry_msgs::Pose2D pose2d_robot;
  107. int temp;//计数,达到终点时,用于判断控制器是否需要关闭
  108. public:
  109. LQR* controller;
  110. Path* path;
  111. LQR_node(ros::NodeHandle& nh)//初始化中添加轨迹、小车初始位姿
  112. {
  113. controller = new LQR();
  114. path = new Path();
  115. //ROS:
  116. path_sub = nh.subscribe("path",10,&LQR_node::addpointcallback,this);
  117. vel_pub = nh.advertise<geometry_msgs::Twist>("cmd_vel",10);
  118. visual_state_pub = nh.advertise<visualization_msgs::Marker>("visualization_pose",10);
  119. actual_state_pub = nh.advertise<geometry_msgs::Pose2D>("LQR_pose",10);
  120. odom_sub = nh.subscribe<nav_msgs::Odometry>("odom_tf", 1, &LQR_node::odomcallback, this);
  121. //robot state initialize:
  122. //car.x = car.y = car.yaw = car.v = car.kesi = 0;
  123. action = "the car is tracking!!";
  124. }
  125. ~LQR_node() {
  126. delete(controller);
  127. delete(path);
  128. }
  129. void odomcallback(const nav_msgs::Odometry::ConstPtr& odom_value)
  130. {
  131. pose2d_robot.x = odom_value->pose.pose.position.x;
  132. pose2d_robot.y = odom_value->pose.pose.position.y;
  133. pose2d_robot.theta = tf::getYaw(odom_value->pose.pose.orientation);
  134. car.x = pose2d_robot.x;
  135. car.y = pose2d_robot.y;
  136. car.yaw = pose2d_robot.theta;
  137. }
  138. void addpointcallback(const nav_msgs::Path::ConstPtr& msg){
  139. vector<waypoint> waypoints;
  140. for(int i=0;i<msg->poses.size();i++){
  141. waypoint waypoint;
  142. //ROS_INFO("THE PATH[%d]'s ID is %d",i,msg->poses[i].header.seq);
  143. waypoint.ID = msg->poses[i].header.seq;
  144. waypoint.x = msg->poses[i].pose.position.x;
  145. waypoint.y = msg->poses[i].pose.position.y;
  146. //获取角度
  147. double roll,pitch,yaw;
  148. tf::Quaternion quat;
  149. tf::quaternionMsgToTF(msg->poses[i].pose.orientation, quat);
  150. tf::Matrix3x3(quat).getRPY(roll, pitch, yaw);
  151. waypoint.yaw = yaw;
  152. waypoints.push_back(waypoint);
  153. }
  154. path->Add_new_point(waypoints);//路径点vector数组传到path类中
  155. lastIndex = path->Size() - 1;
  156. lastPoint = path->Get_waypoint(lastIndex);
  157. }
  158. double slow_judge(double distance) {
  159. if (distance>=slow_LEVE2_DISTANCE&&distance <= slow_LEVE1_DISTANCE) {
  160. return slow_LEVE1_V;
  161. }
  162. else if (distance>=goal_tolerance_DISTANCE&&distance < slow_LEVE2_DISTANCE) {
  163. return slow_LEVE2_V;
  164. }
  165. else if (distance < goal_tolerance_DISTANCE) {
  166. action = "the car has reached the goal!";
  167. return 0.0;
  168. }
  169. else
  170. {
  171. return V_DESIRED;
  172. }
  173. }
  174. //控制器流程
  175. /*步骤:
  176. 1.获取小车当前位姿和路径点,在while(ros::ok())函数下面ros::spinOnce()阶段订阅回调函数odomcallback和addpointcallback时完成
  177. 2.搜索路径点
  178. 3.获取路径点信息,构造期望控制量
  179. 4.先进行一次减速判断,确定纵向期望速度v_r,和机器人当前动作:tracking或reached the goal
  180. 5.构造权重矩阵,从launch文件的参数Q=[],R=[]导入
  181. 6.使用LQR控制器,计算速度和前轮转角,其中有限幅过程
  182. 7.发布话题,其中有角速度的计算
  183. 8.判断是否需要关闭控制器,如果到达终点就关闭
  184. 9.loop_rate.sleep(),结束一次循环,准备开始下一次。
  185. */
  186. void LQR_track() {
  187. U U_r;
  188. waypoint Point;
  189. double K;
  190. //ROS_INFO("path size is: %d",path->Size());
  191. if(path->Size()!=0){
  192. //搜索路径点
  193. int target_index = path->Find_target_index(car);
  194. //ROS_INFO("target index is : %d", target_index);
  195. //获取路径点信息,构造期望控制量
  196. Point = path->Get_waypoint(target_index);//获取x,y
  197. //ROS_INFO("waypoint information is x:%f,y:%f,yaw:%f", Point.x, Point.y, Point.yaw);
  198. K = cal_K(path->Get_waypoints(),target_index);//计算曲率
  199. //减速判断
  200. double kesi = atan2(L * K, 1);
  201. double v_distance = abs(sqrt(pow(car.x - lastPoint.x, 2) + pow(car.y - lastPoint.y, 2)));
  202. //ROS_INFO("the distance is %f\n", v_distance);
  203. U_r.v = slow_judge(v_distance);U_r.kesi = kesi;
  204. ROS_INFO("%s",action.c_str());//机器人动作
  205. //ROS_INFO("the desired v is: %f,the desired kesi is: %f", U_r.v,U_r.kesi);
  206. //权重矩阵
  207. for(int q =0;q<Q_set.size();q++)Q[q] = Q_set[q];
  208. for(int r =0;r<R_set.size();r++)R[r] = R_set[r];
  209. if(Q[0]>=R[0]||Q[1]>=R[1])
  210. ROS_WARN("Q >= R, the calculation result may be abnormal,please set R < Q ");
  211. //使用LQR控制器,
  212. controller->initial(L, T, car, Point, U_r, Q, R);//初始化控制器
  213. control = controller->cal_vel();//计算输入[v, kesi]
  214. if(U_r.v==0)control.v = 0;//判断,期望速度为0,则机器人停下
  215. if(limit_v_and_kesi)control = v_and_kesi_limit(control);//速度和前轮转角限幅
  216. ROS_INFO("the speed is: %f,the kesi is: %f", control.v, control.kesi);
  217. //ROS_INFO("the car position is x: %f, y: %f", car.x, car.y);
  218. //话题发布
  219. PUB();
  220. //控制器关闭判断
  221. shutdown_controller();
  222. ROS_INFO("--------------------------------------");
  223. }
  224. }
  225. //控制启停函数
  226. void node_control() {
  227. ros::Rate loop_rate(freq);
  228. Marker_set();//设置Marker属性
  229. while (ros::ok()) {
  230. ros::spinOnce();
  231. LQR_track();
  232. loop_rate.sleep();
  233. }
  234. }
  235. void PUB(){
  236. visual_state_pose.x = car.x; visual_state_pose.y = car.y;
  237. actual_pose.x = car.x; actual_pose.y = car.y; actual_pose.theta = car.yaw;
  238. vel_msg.linear.x = control.v; vel_msg.angular.z = control.v*tan(control.kesi)/L;//横摆角速度为w = v*tan(kesi)/L
  239. visual_state_trajectory.points.push_back(visual_state_pose);//visualization_msgs::Marker为一个容器,所以现需要向里面push_back结构体,再发布
  240. visual_state_pub.publish(visual_state_trajectory);//发布虚拟轨迹
  241. vel_pub.publish(vel_msg);//发布速度
  242. actual_state_pub.publish(actual_pose);//发布位姿
  243. }
  244. void shutdown_controller(){
  245. if(action == "the car has reached the goal!"){
  246. temp+=1;
  247. if(temp ==50){
  248. ROS_WARN("shutdown the LQR controller!");
  249. temp = 0;
  250. ros::shutdown();
  251. }
  252. }
  253. }
  254. void Marker_set(){
  255. //设置消息类型参数
  256. visual_state_trajectory.header.frame_id = "map";
  257. visual_state_trajectory.header.stamp = ros::Time::now();
  258. visual_state_trajectory.action = visualization_msgs::Marker::ADD;
  259. visual_state_trajectory.ns = "LQR";
  260. //设置点的属性
  261. visual_state_trajectory.id = 0;
  262. visual_state_trajectory.type = visualization_msgs::Marker::POINTS;
  263. visual_state_trajectory.scale.x = 0.02;
  264. visual_state_trajectory.scale.y = 0.02;
  265. visual_state_trajectory.color.r = 1.0;
  266. visual_state_trajectory.color.a = 1.0;
  267. }
  268. U v_and_kesi_limit(U control_value){
  269. if(control_value.v>=v_max)//速度限幅
  270. {
  271. control_value.v = v_max;
  272. ROS_WARN("The calculated value may be inaccurate ");
  273. }
  274. else if(control_value.v<=-v_max){
  275. control_value.v = -v_max;
  276. ROS_WARN("The calculated value may be inaccurate ");
  277. }
  278. if(control_value.kesi>=pi/2)//前轮转角限幅
  279. {
  280. control_value.kesi = pi/2;
  281. ROS_WARN("The calculated value may be inaccurate ");
  282. }
  283. else if(control_value.kesi<=-pi/2){
  284. control_value.kesi = -pi/2;
  285. ROS_WARN("The calculated value may be inaccurate ");
  286. }
  287. return control_value;
  288. }
  289. };
  290. int main(int argc, char** argv)
  291. {
  292. ros::init(argc, argv, "LQR_node");
  293. ros::NodeHandle n;
  294. ros::NodeHandle n_prv("~");
  295. n_prv.param<double>("freq",freq,20);
  296. n_prv.param<double>("L",L,0.2);
  297. n_prv.param<double>("V_DESIRED",V_DESIRED,0.5);
  298. n_prv.param<double>("v_max",v_max,1.0);
  299. n_prv.param<double>("slow_LEVE1_DISTANCE",slow_LEVE1_DISTANCE,5.0);
  300. n_prv.param<double>("slow_LEVE2_DISTANCE",slow_LEVE2_DISTANCE,2.0);
  301. n_prv.param<double>("goal_tolerance_DISTANCE",goal_tolerance_DISTANCE,0.1);
  302. n_prv.param<double>("slow_LEVE1_V",slow_LEVE1_V,0.35);
  303. n_prv.param<double>("slow_LEVE2_V",slow_LEVE2_V,0.15);
  304. n_prv.param<bool>("limit_v_and_kesi",limit_v_and_kesi,false);
  305. n_prv.param("Q_set",Q_set,Q_set);
  306. n_prv.param("R_set",R_set,R_set);
  307. LQR_node* node = new LQR_node(n);
  308. node->node_control();
  309. return (0);
  310. }

3.实验中小车的位姿获取(odom_listen_from_tf.cpp)

        可能每个人的实验平台上定位话题都不一致,我这里干脆写一个位姿获取程序吧,就是监听map到base_link或者base_footprint的坐标转换关系输出就好了,输出话题为odom_tf,消息类型为nav_msgs::Odometry,这个话题就是LQR_node_world.cpp中订阅得那个小车位姿

odom_listen_from_tf.cpp

  1. #include <ros/ros.h>
  2. #include <tf2/LinearMath/Vector3.h>
  3. #include <tf2/LinearMath/Quaternion.h>
  4. #include <tf2/LinearMath/Matrix3x3.h>
  5. #include <tf2_ros/transform_listener.h>
  6. #include <geometry_msgs/TransformStamped.h>
  7. #include <nav_msgs/Odometry.h>
  8. std::string base_frame;
  9. int main(int argc, char** argv)
  10. {
  11. ros::init(argc, argv, "odom_listener");
  12. ros::NodeHandle n;
  13. ros::NodeHandle nh("~");
  14. tf2_ros::Buffer tfBuffer;
  15. tf2_ros::TransformListener odomListener(tfBuffer);
  16. nh.param<std::string>("base_frame", base_frame, "base_link");
  17. ros::Publisher odom_pub = n.advertise<nav_msgs::Odometry>("odom_tf",10);
  18. ros::Rate r(20);
  19. while(ros::ok()){
  20. geometry_msgs::TransformStamped transform;
  21. try{
  22. transform = tfBuffer.lookupTransform("map", base_frame, ros::Time(0));
  23. }
  24. catch (tf2::TransformException &ex){
  25. ROS_ERROR("%s", ex.what());
  26. ros::Duration(1.0).sleep();
  27. continue;
  28. }
  29. tf2::Vector3 translation;
  30. translation.setValue(transform.transform.translation.x,
  31. transform.transform.translation.y,
  32. transform.transform.translation.z);
  33. tf2::Quaternion rotation;
  34. rotation.setValue(transform.transform.rotation.x,
  35. transform.transform.rotation.y,
  36. transform.transform.rotation.z,
  37. transform.transform.rotation.w);
  38. tf2::Matrix3x3 m(rotation);
  39. double roll, pitch, yaw;
  40. m.getRPY(roll, pitch, yaw);
  41. /*ROS_INFO("\n=== Got Transform ===\n"
  42. " Translation\n"
  43. " x : %f\n y : %f\n z : %f\n"
  44. " Quaternion\n"
  45. " x : %f\n y : %f\n z : %f\n w : %f\n"
  46. " RPY\n"
  47. " R : %f\n P : %f\n Y : %f",
  48. translation.x(), translation.y(), translation.z(),
  49. rotation.x(), rotation.y(), rotation.z(), rotation.w(),
  50. roll, pitch, yaw);*/
  51. nav_msgs::Odometry odom;
  52. odom.header.stamp = ros::Time::now();
  53. odom.header.frame_id = "odom";
  54. odom.pose.pose.position.x = translation.x();
  55. odom.pose.pose.position.y = translation.y();
  56. odom.pose.pose.position.z = translation.z();
  57. odom.pose.pose.orientation.x = rotation.x();
  58. odom.pose.pose.orientation.y = rotation.y();
  59. odom.pose.pose.orientation.z = rotation.z();
  60. odom.pose.pose.orientation.w = rotation.w();
  61. odom.child_frame_id = base_frame;
  62. odom.twist.twist.linear.x = 0;
  63. odom.twist.twist.linear.y = 0;
  64. odom.twist.twist.linear.z = 0;
  65. odom_pub.publish(odom);
  66. ros::spinOnce();
  67. r.sleep();
  68. }
  69. return 0;
  70. }

四、编译全部的文件

        别忘了在target_link_libraries里面加上之前生成的库名字LQR_LIBRARIES:

  1. ###########
  2. ## Build ##
  3. ###########
  4. include_directories(
  5. include
  6. ${catkin_INCLUDE_DIRS}
  7. ${Eigen_INCLUDE_DIRS}
  8. )
  9. add_library(LQR_LIBRARIES
  10. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/include/LQR_track/Tool.h
  11. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/include/LQR_track/trajectory.h
  12. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/include/LQR_track/LQR.h
  13. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/src/Tool.cpp
  14. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/src/trajectory.cpp
  15. /home/dlrobot/dlrobot_robot/src/617_robot/lqr_track/src/LQR.cpp
  16. )
  17. target_link_libraries(LQR_LIBRARIES ${catkin_LIBRARIES})
  18. link_directories(
  19. ${catkin_LIB_DIRS}
  20. ${Eigen_LIB_DIRS}
  21. )
  22. add_executable(LQR_node src/LQR_node.cpp)
  23. add_dependencies(LQR_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
  24. target_link_libraries(LQR_node ${catkin_LIBRARIES} LQR_LIBRARIES)
  25. add_executable(path_planning src/path_planning.cpp)
  26. add_dependencies(path_planning ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
  27. target_link_libraries(path_planning ${catkin_LIBRARIES} LQR_LIBRARIES)
  28. add_executable(lqr_odom_listen src/odom_listen_from_tf.cpp)
  29. add_dependencies(lqr_odom_listen ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
  30. target_link_libraries(lqr_odom_listen ${catkin_LIBRARIES} LQR_LIBRARIES)
  31. add_executable(LQR_node_world src/LQR_node_world.cpp)
  32. add_dependencies(LQR_node_world ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
  33. target_link_libraries(LQR_node_world ${catkin_LIBRARIES} LQR_LIBRARIES)

五、配置rviz文件

rviz中需要显示tf坐标变换、marker(用于显示小车实际轨迹)、path(用于显示参考路径)、map(用于显示地图,仿真中不需要)。

在最后的完整程序链接中,launch文件已经包含了rviz的启动,可以开启launch去看一下rviz中配置了啥。

六、配置launch文件

        打开底盘雷达功能和定位功能可以不从该功能包launch文件中启动,但必须从功能包中开启的有三个:

path_planning.launch

        trajectory_type参数是根据trajectory.cpp里面写的三种轨迹名字决定的,默认写了三种分别是line、wave1、wave2。

  1. <launch>
  2. <node pkg="lqr_track" type="path_planning" name="path_planning" output="screen">
  3. <param name="x_start" value="0.0"/>
  4. <param name="y_start" value="-0.25"/>
  5. <param name="trajectory_type" value="line"/><!--choose the trajectory type default:line,wave1,wave2-->
  6. <!--If you need to add a custom path, please see trajectory.h-->
  7. <param name="limit_x" value="4.0"/>
  8. <param name="limit_y" value="0.0"/>
  9. </node>
  10. <!--rviz-->
  11. <!--node name="path_rviz" pkg="rviz" type="rviz" required="true"
  12. args="-d $(find lqr_track)/rviz/path_show.rviz">
  13. </node-->
  14. </launch>

LQR_track.launch

里面有车辆信息、初始化位姿、控制器参数配置,注释也写了相关的解释或者注意事项

  1. <launch>
  2. <node pkg="lqr_track" type="LQR_node" name="LQR_node" output="screen">
  3. <!--Basic vehicle information--><!--kesi is the Vehicle front wheel deflection angle-->
  4. <param name="L" value="0.2"/><!--Vehicle wheelbase-->
  5. <param name="V_DESIRED" value="0.5"/>
  6. <param name="v_max" value="1.0"/>
  7. <!--car initial state-->
  8. <param name="initial_x" value="-0.127"/>
  9. <param name="initial_y" value="-0.1474"/>
  10. <param name="initial_yaw" value="0.0138"/>
  11. <param name="initial_kesi" value="0.0"/>
  12. <!--controller information-->
  13. <param name="freq" value="20"/><!--control freq-->
  14. <param name="slow_LEVE1_DISTANCE" value="4.0"/><!--First stage deceleration distance-->
  15. <param name="slow_LEVE2_DISTANCE" value="1.0"/><!--Secondary deceleration distance-->
  16. <param name="goal_tolerance_DISTANCE" value="0.1"/><!--Tracking stop distance-->
  17. <param name="slow_LEVE1_V" value="0.5"/>
  18. <param name="slow_LEVE2_V" value="0.15"/>
  19. <param name="limit_v_and_kesi" value="true"/><!--If it is an akaman steering car, it must be limited. If it is an omni-directional car, it is optional-->
  20. <rosparam param="Q_set">[1.0,1.0,1.0]</rosparam><!--State weight matrix Q = diag{Q1,Q2,Q3},please set Q<R-->
  21. <rosparam param="R_set">[5.0,5.0]</rosparam><!--Control input weight matrix R = diag{R1,R2},please set Q<R-->
  22. </node>
  23. <!--rviz-->
  24. <node name="lqr_track_rviz" pkg="rviz" type="rviz" required="true" args="-d $(find lqr_track)/rviz/track.rviz"/>
  25. </launch>

LQR_track_world.launch

        在这个launch里面,为了方便采用通用接口获取小车位姿信息,写了一个lqr_odom_listen节点监听map->base_link(或base_footprint)的坐标转换,至于是base_link还是base_footprint需要自己去看一下小车模型,并在launch中修改一下。

  1. <launch>
  2. <node pkg="lqr_track" type="LQR_node_world" name="LQR_node_world" output="screen">
  3. <!--Basic vehicle information--><!--kesi is the Vehicle front wheel deflection angle-->
  4. <param name="L" value="0.24"/><!--Vehicle wheelbase-->
  5. <param name="V_DESIRED" value="0.5"/>
  6. <param name="v_max" value="1.0"/>
  7. <!--controller information-->
  8. <param name="freq" value="20"/><!--control freq-->
  9. <param name="slow_LEVE1_DISTANCE" value="4.0"/><!--First stage deceleration distance-->
  10. <param name="slow_LEVE2_DISTANCE" value="1.0"/><!--Secondary deceleration distance-->
  11. <param name="goal_tolerance_DISTANCE" value="0.2"/><!--Tracking stop distance-->
  12. <param name="slow_LEVE1_V" value="0.5"/>
  13. <param name="slow_LEVE2_V" value="0.15"/>
  14. <param name="limit_v_and_kesi" value="true"/><!--If it is an akaman steering car, it must be limited. If it is an omni-directional car, it is optional-->
  15. <rosparam param="Q_set">[1.0,1.0,1.0]</rosparam><!--State weight matrix Q = diag{Q1,Q2,Q3},please set Q<R-->
  16. <rosparam param="R_set">[4.0,4.0]</rosparam><!--Control input weight matrix R = diag{R1,R2},please set Q<R-->
  17. </node>
  18. <node pkg="lqr_track" type="lqr_odom_listen" name="lqr_odom_listen" output="screen">
  19. <parma name="base_frame" value="base_link"/> <!--这个地方需要根据自己小车的底盘坐标系名称进行修改-->
  20. </node>
  21. </launch>

仿真结果

本项目一共进行了三次仿真:

一、直线仿真  trajectory_type = line

1ad41acedd244747abce7c54685b2a87.png

LQR_line_simulation

二、波浪线1仿真  trajectory_type = wave1

85c1a8ae0eb043479e8e8acc49afe546.png

LQR_wave1_simulation

三、波浪线2仿真 trajectory_type = wave2

382be02ebb7d410c80ef0689aa89c3dd.png

LQR_wave2_simulation

实验结果

实验流程:

1.打开机器人底盘、雷达,保证能接收到雷达数据
       可以配置功能包中的launch/turn_on_base_and_ladiar.launch文件来同时启动地盘雷达

2.开启机器人定位功能,保证有map到base_link(或base_footprint)的tf转换,
       如果有cartographer的话,可以运行如下命令:roslaunch lqr_track carto_robot_localization.launch

3.进行机器人定位工作,并前往路径起始点附近,别放在超过路径起始点一米以外,跟踪效果会变差,与控制器参数有关,如Q、P矩阵选取

4.roslaunch lqr_track LQR_track_world.launch

5.roslaunch lqr_track path_planning.launch

bade462c26cc41fc946ebe5156fba9b5.png

        其中绿色为期望轨迹,红色为跟踪过程的实际轨迹,跟踪误差可能与车辆参数(如:前后轴距)或控制器参数有关。

具体实验情况如下:

line_track_experiment

完整程序

完整程序请见:Bitbucket

git clone到工作空间下,按照功能包中的README.md操作就好了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/510292
推荐阅读
相关标签
  

闽ICP备14008679号