无人驾驶差速机器人MPC代码详解

环境构建参考我的另一篇文章

Noetic差速机器人MPC控制

关于MPC与无人驾驶的理解参考文章

自动驾驶——模型预测控制(MPC)理解与实践

针对轨迹跟踪ref_trajectory_tracking_gazebo.launch及其所涉及的代码详解

1. ref_trajectory_tracking_gazebo.launch

1.1 加载机器人模型

    <!--  ************** Robot model ***************  -->
    <param name="robot_description" command="$(find xacro)/xacro $(find servingbot_description)/urdf/servingbot.urdf.xacro" if="$(eval model == 'serving_bot')"/>

    <include file="$(find gazebo_ros)/launch/empty_world.launch">
        <arg name="world_name" value="$(find gazebo_ros)/launch/empty_world.launch"/>
        <arg name="paused" value="false"/>
        <arg name="use_sim_time" value="true"/>
        <arg name="gui" value="$(arg gui)"/>
        <arg name="headless" value="false"/>
        <arg name="debug" value="false"/>
    </include>

    <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" args="-urdf -model servingbot -x $(arg x_pos) -y $(arg y_pos) -z $(arg z_pos) -R $(arg roll) -P $(arg pitch) -Y $(arg yaw) -param robot_description" />
     
    <node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher">
        <param name="publish_frequency" type="double" value="50.0" />
    </node>

1.2 生成参考轨迹

参考轨迹是由两个python文件生成的,主要任务就是根据给定的图形选项生成路径。trajectory_type为图形选项。

    <!--  ************** Reference trajectory generation **************  -->
    <node name="mpc_trajectory_generation" pkg="mpc_ros" type="mpc_trajectory_generation.py"  if="$(eval controller == 'mpc')">
        <param name="trajectory_type" value="$(arg trajectory_type)" />
    </node>
    <node name="dwa_trajectory_generation" pkg="mpc_ros" type="dwa_trajectory_generation.py"  if="$(eval controller == 'dwa')">
        <param name="trajectory_type" value="$(arg trajectory_type)" />
    </node>

1.3 MPC节点

启动MPC节点,并加载了机器人运行参数。可以看到启动的节点名称为tracking_reference_trajectory。

    <!--  ************** MPC Node **************  -->
    <node name="MPC_tracking" pkg="mpc_ros" type="tracking_reference_trajectory" output="screen" if="$(eval controller == 'mpc')" >
        <rosparam file="$(find mpc_ros)/params/mpc_local_square_params.yaml" command="load" />
        <!--rosparam file="$(find mpc_ros)/params/mpc_local_params.yaml" command="load" /-->
        <!--rosparam file="$(find mpc_ros)//params/mpc_local_epitrochoid_params.yaml" command="load" /-->
    </node>
1.3.1 控制参数

其中重要的参数有max_speed最大速度,path_length轨迹跟踪参与计算的长度(并不是把整条轨迹都参与计算了),controller_freq计算频率

# Parameters for control loop
pub_twist_cmd: true
debug_info: false
delay_mode: true
max_speed: 0.8 # unit: m/s 
waypoints_dist: -1.0 # unit: m, set < 0 means computed by node
path_length: 3.0 # unit: m
goal_radius: 0.5 # unit: m
controller_freq: 10
1.3.2 MPC参数

包括mpc_steps步长,mpc_ref_vel期望的参考速度等

# Parameter for MPC solver
mpc_steps: 20.0
mpc_ref_cte: 0.0
mpc_ref_vel: 0.5
mpc_ref_etheta: 0.0
mpc_w_cte: 2000.0
mpc_w_etheta: 1000.0
mpc_w_vel: 2000.0
mpc_w_angvel: 0.0
mpc_w_angvel_d: 10.0
mpc_w_accel: 10.0
mpc_w_accel_d: 10.0
mpc_max_angvel: 1.0
mpc_max_throttle: 0.5 # Maximal throttle accel
mpc_bound_value: 1.0e3 # Bound value for other variables

2 CmakeLists

根据1.3主要使用的节点为tracking_reference_trajectory,定位到CmakeLists。可知该节点主要是trackRefTraj.cpp和trackRefTrajNode.cpp两个文件。

ADD_EXECUTABLE( tracking_reference_trajectory src/trackRefTraj.cpp src/trackRefTrajNode.cpp )
TARGET_LINK_LIBRARIES(tracking_reference_trajectory ipopt ${catkin_LIBRARIES} )

3 C++代码

相关的c++代码都在trackRefTraj.cpp和trackRefTrajNode.cpp两个文件中。

3.1 main函数

main函数非常简单,主要就是启动MPCNode

int main(int argc, char **argv)
{
    //Initiate ROS
    ros::init(argc, argv, "MPC_Node");
    MPCNode mpc_node;

    ROS_INFO("Waiting for global path msgs ~");
    ros::AsyncSpinner spinner(mpc_node.get_thread_numbers()); // Use multi threads
    spinner.start();
    ros::waitForShutdown();
    return 0;
}

3.2 订阅话题及回调函数

odomCB,接收外部的odom数据
pathCB,为空,没有任何执行

desiredPathCB,将接收到的参考轨迹转化到odom坐标系下(重要)

goalCB,接收到终点回调函数,接收到终点后才会进行计算
amclCB,定位回调函数
    _sub_odom   = _nh.subscribe("/odom", 1, &MPCNode::odomCB, this);
    _sub_path   = _nh.subscribe( _globalPath_topic, 1, &MPCNode::pathCB, this);
    _sub_gen_path   = _nh.subscribe( "desired_path", 1, &MPCNode::desiredPathCB, this);
    _sub_goal   = _nh.subscribe( _goal_topic, 1, &MPCNode::goalCB, this);
    _sub_amcl   = _nh.subscribe("/amcl_pose", 5, &MPCNode::amclCB, this);

_controller_freq,控制函数,周期调用(重要)

//Timer
    _timer1 = _nh.createTimer(ros::Duration((1.0)/_controller_freq), &MPCNode::controlLoopCB, this); // 10Hz //*****mpc

3.3 desiredPathCB

这一函数有几个关键点

3.3.1 设置轨迹离当前点最近处为起点,编号为min_idx
for(int i = min_idx; i < N; i++) 
        {
            dx = totalPathMsg->poses[i].pose.position.x - px;
            dy = totalPathMsg->poses[i].pose.position.y - py;
                    
            tf::Quaternion q(
                totalPathMsg->poses[i].pose.orientation.x,
                totalPathMsg->poses[i].pose.orientation.y,
                totalPathMsg->poses[i].pose.orientation.z,
                totalPathMsg->poses[i].pose.orientation.w);
            tf::Matrix3x3 m(q);
            m.getRPY(roll, pitch, yaw);

            if(abs(pre_yaw - yaw) > 5)
            {
                cout << "abs(pre_yaw - yaw)" << abs(pre_yaw - yaw) << endl;
                pre_yaw = yaw;
            }
       
            if(min_val > sqrt(dx*dx + dy*dy) && abs((int)(i - min_idx)) < 50)
            {
                min_val = sqrt(dx*dx + dy*dy);
                min_idx = i;
            }
        }
3.3.2 参考轨迹的长度不超过最大设定,设定值为1.3.1中的path_length。
for(int i = min_idx; i < N ; i++)
        {
            if(total_length > _pathLength)
                break;
            
            _tf_listener.transformPose(_odom_frame, ros::Time(0) , 
                                            totalPathMsg->poses[i], _odom_frame, tempPose);                     
            mpc_path.poses.push_back(tempPose);                          
            total_length = total_length + _waypointsDist;           
        }   

这里_waypointsDist为固定值,默认相邻两点间距都是一样的

if(_waypointsDist <= 0.0)
        {        
            double gap_x = totalPathMsg->poses[1].pose.position.x - totalPathMsg->poses[0].pose.position.x;
            double gap_y = totalPathMsg->poses[1].pose.position.y - totalPathMsg->poses[0].pose.position.y;
            _waypointsDist = sqrt(gap_x*gap_x + gap_y*gap_y);             
        }  

3.4 _controller_freq

3.4.1 曲线拟合

将odom坐标系下的路径拟合为多项式

        for(int i = 0; i < N; i++) 
        {
            const double dx = odom_path.poses[i].pose.position.x - px;
            const double dy = odom_path.poses[i].pose.position.y - py;
            x_veh[i] = dx * costheta + dy * sintheta;
            y_veh[i] = dy * costheta - dx * sintheta;
        }
        
        // Fit waypoints
        auto coeffs = polyfit(x_veh, y_veh, 3); 
3.4.2 延迟

px_act, py_act, theta_act三项为MPC的起点约束,约束设为dt时间后即考虑延迟时间dt

        if(_delay_mode)
        {
            // Kinematic model is used to predict vehicle state at the actual moment of control (current time + delay dt)
            const double px_act = v * dt;
            const double py_act = 0;
            const double theta_act = w * dt; //(steering) theta_act = v * steering * dt / Lf;
            const double v_act = v + throttle * dt; //v = v + a * dt
            
            const double cte_act = cte + v * sin(etheta) * dt;
            const double etheta_act = etheta - theta_act;  
            
            state << px_act, py_act, theta_act, v_act, cte_act, etheta_act;
        }
        else
        {
            state << 0, 0, 0, v, cte, etheta;
        }
3.4.3 MPC求解
// Solve MPC Problem
        vector<double> mpc_results = _mpc.Solve(state, coeffs);
设定IPOPT迭代的初始值
    vars[_x_start] = x;
    vars[_y_start] = y;
    vars[_theta_start] = theta;
    vars[_v_start] = v;
    vars[_cte_start] = cte;
    vars[_etheta_start] = etheta;
设定等式约束
    for (int i = 0; i < n_constraints; i++)
    {
        constraints_lowerbound[i] = 0;
        constraints_upperbound[i] = 0;
    }
    constraints_lowerbound[_x_start] = x;
    constraints_lowerbound[_y_start] = y;
    constraints_lowerbound[_theta_start] = theta;
    constraints_lowerbound[_v_start] = v;
    constraints_lowerbound[_cte_start] = cte;
    constraints_lowerbound[_etheta_start] = etheta;
    constraints_upperbound[_x_start] = x;
    constraints_upperbound[_y_start] = y;
    constraints_upperbound[_theta_start] = theta;
    constraints_upperbound[_v_start] = v;
    constraints_upperbound[_cte_start] = cte;
    constraints_upperbound[_etheta_start] = etheta;
设定不等式约束
    for (int i = 0; i < _angvel_start; i++) 
    {
        vars_lowerbound[i] = -_bound_value;
        vars_upperbound[i] = _bound_value;
    }
    // The upper and lower limits of angvel are set to -25 and 25
    // degrees (values in radians).
    for (int i = _angvel_start; i < _a_start; i++) 
    {
        vars_lowerbound[i] = -_max_angvel;
        vars_upperbound[i] = _max_angvel;
    }
    // Acceleration/decceleration upper and lower limits
    for (int i = _a_start; i < n_vars; i++)  
    {
        vars_lowerbound[i] = -_max_throttle;
        vars_upperbound[i] = _max_throttle;
    }
设定约束矩阵并求解,fg[0]为优化函数
    CppAD::ipopt::solve<Dvector, FG_eval>(
      options, vars, vars_lowerbound, vars_upperbound, constraints_lowerbound,
      constraints_upperbound, fg_eval, solution);
 void operator()(ADvector& fg, const ADvector& vars) 
        {
            // fg[0] for cost function
            fg[0] = 0;
            cost_cte =  0;
            cost_etheta = 0;
            cost_vel = 0;

            /*
            for (int i = 0; i < _mpc_steps; i++) 
            {
                cout << i << endl;
                cout << "_x_start" << vars[_x_start + i] <<endl;
                cout << "_y_start" << vars[_y_start + i] <<endl;
                cout << "_theta_start" << vars[_theta_start + i] <<endl;
                cout << "_v_start" << vars[_v_start + i] <<endl;
                cout << "_cte_start" << vars[_cte_start + i] <<endl;
                cout << "_etheta_start" << vars[_etheta_start + i] <<endl;
            }*/

            for (int i = 0; i < _mpc_steps; i++) 
            {
              fg[0] += _w_cte * CppAD::pow(vars[_cte_start + i] - _ref_cte, 2); // cross deviation error
              fg[0] += _w_etheta * CppAD::pow(vars[_etheta_start + i] - _ref_etheta, 2); // heading error
              fg[0] += _w_vel * CppAD::pow(vars[_v_start + i] - _ref_vel, 2); // speed error

              cost_cte +=  _w_cte * CppAD::pow(vars[_cte_start + i] - _ref_cte, 2);
              cost_etheta +=  (_w_etheta * CppAD::pow(vars[_etheta_start + i] - _ref_etheta, 2)); 
              cost_vel +=  (_w_vel * CppAD::pow(vars[_v_start + i] - _ref_vel, 2)); 
            }
            cout << "-----------------------------------------------" <<endl;
            cout << "cost_cte, etheta, velocity: " << cost_cte << ", " << cost_etheta  << ", " << cost_vel << endl;
            

            // Minimize the use of actuators.
            for (int i = 0; i < _mpc_steps - 1; i++) {
              fg[0] += _w_angvel * CppAD::pow(vars[_angvel_start + i], 2);
              fg[0] += _w_accel * CppAD::pow(vars[_a_start + i], 2);
            }
            cout << "cost of actuators: " << fg[0] << endl; 

            // Minimize the value gap between sequential actuations.
            for (int i = 0; i < _mpc_steps - 2; i++) {
              fg[0] += _w_angvel_d * CppAD::pow(vars[_angvel_start + i + 1] - vars[_angvel_start + i], 2);
              fg[0] += _w_accel_d * CppAD::pow(vars[_a_start + i + 1] - vars[_a_start + i], 2);
            }
            cout << "cost of gap: " << fg[0] << endl; 
            

            // fg[x] for constraints
            // Initial constraints
            fg[1 + _x_start] = vars[_x_start];
            fg[1 + _y_start] = vars[_y_start];
            fg[1 + _theta_start] = vars[_theta_start];
            fg[1 + _v_start] = vars[_v_start];
            fg[1 + _cte_start] = vars[_cte_start];
            fg[1 + _etheta_start] = vars[_etheta_start];

            // Add system dynamic model constraint
            for (int i = 0; i < _mpc_steps - 1; i++)
            {
                // The state at time t+1 .
                AD<double> x1 = vars[_x_start + i + 1];
                AD<double> y1 = vars[_y_start + i + 1];
                AD<double> theta1 = vars[_theta_start + i + 1];
                AD<double> v1 = vars[_v_start + i + 1];
                AD<double> cte1 = vars[_cte_start + i + 1];
                AD<double> etheta1 = vars[_etheta_start + i + 1];

                // The state at time t.
                AD<double> x0 = vars[_x_start + i];
                AD<double> y0 = vars[_y_start + i];
                AD<double> theta0 = vars[_theta_start + i];
                AD<double> v0 = vars[_v_start + i];
                AD<double> cte0 = vars[_cte_start + i];
                AD<double> etheta0 = vars[_etheta_start + i];

                // Only consider the actuation at time t.
                //AD<double> angvel0 = vars[_angvel_start + i];
                AD<double> w0 = vars[_angvel_start + i];
                AD<double> a0 = vars[_a_start + i];


                //AD<double> f0 = coeffs[0] + coeffs[1] * x0 + coeffs[2] * CppAD::pow(x0, 2) + coeffs[3] * CppAD::pow(x0, 3);
                AD<double> f0 = 0.0;
                for (int i = 0; i < coeffs.size(); i++) 
                {
                    f0 += coeffs[i] * CppAD::pow(x0, i); //f(0) = y
                }

                //AD<double> trj_grad0 = CppAD::atan(coeffs[1] + 2 * coeffs[2] * x0 + 3 * coeffs[3] * CppAD::pow(x0, 2));
                AD<double> trj_grad0 = 0.0;
                for (int i = 1; i < coeffs.size(); i++) 
                {
                    trj_grad0 += i*coeffs[i] * CppAD::pow(x0, i-1); // f'(x0) = f(1)/1
                }
                trj_grad0 = CppAD::atan(trj_grad0);


                // Here's `x` to get you started.
                // The idea here is to constraint this value to be 0.
                //
                // NOTE: The use of `AD<double>` and use of `CppAD`!
                // This is also CppAD can compute derivatives and pass
                // these to the solver.
                // TODO: Setup the rest of the model constraints
                fg[2 + _x_start + i] = x1 - (x0 + v0 * CppAD::cos(theta0) * _dt);
                fg[2 + _y_start + i] = y1 - (y0 + v0 * CppAD::sin(theta0) * _dt);
                fg[2 + _theta_start + i] = theta1 - (theta0 +  w0 * _dt);
                fg[2 + _v_start + i] = v1 - (v0 + a0 * _dt);
                
                fg[2 + _cte_start + i] = cte1 - ((f0 - y0) + (v0 * CppAD::sin(etheta0) * _dt));
                fg[2 + _etheta_start + i] = etheta1 - ((theta0 - trj_grad0) + w0 * _dt);//theta0-trj_grad0)->etheta : it can have more curvature prediction, but its gradient can be only adjust positive plan.   
                //fg[2 + _etheta_start + i] = etheta1 - (etheta0 + w0 * _dt);
            }
        }
  • 22
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 四轮差速小车是一种常用的移动机器人,由四个驱动轮组成,每个驱动轮都可以独立地控制转动速度。为了建立四轮差速小车的运动学模型,我们可以采用基于约束的模型预测控制(Model Predictive Control,MPC)方法。 首先,我们需要定义小车的状态和输入。小车的状态可以用位姿(位置和朝向)以及线速度和角速度表示。输入是驱动轮的转动速度。 接下来,我们可以根据小车的几何特征和运动学关系来建立运动学模型。对于每个驱动轮,我们可以根据其位置和转动速度计算其的线速度和角速度。由于四轮差速小车是非完整约束系统,即存在轮子之间的约束条件,我们还需要考虑两组轮子之间的转动速度差。 然后,我们可以采用离散化的方式建立MPC模型。首先,我们将连续时间离散化为离散时间,通过选择合适的采样周期。然后,在每个离散时间步长内,我们根据当前状态和输入计算出下一个状态和输出,并更新控制器的输出。最后,我们可以使用优化算法(如二次规划)来求解最优控制输入,以使得系统向着期望的目标状态运动。 最后,我们需要设置适当的目标和约束条件。目标可以是使小车达到某个指定位置或者遵循某个规划轨迹。约束条件可以包括小车的物理限制、转动速度的范围限制等。 通过建立四轮差速小车的MPC运动学模型,我们可以在每个离散时间步长内计算出最优的转动速度,从而实现小车的准确控制和轨迹跟踪。这种模型可以广泛应用于自动驾驶、移动机器人导航等领域。 ### 回答2: 四轮差速小车是一种常见的机器人底盘结构,具有较好的机动性能和灵活性。为了进行运动规划和控制,需要建立其运动学模型。 四轮差速小车的运动学模型可以分为整体运动学和轮子运动学两个部分。 整体运动学部分描述了小车整体的运动关系。设小车的中心坐标为(x, y),角度为θ,通过计算可以得到小车的位置和姿态的变化关系。具体地,位置的变化可以通过机器人底盘的速度信息计算得到,即: dx = v * cos(θ) * dt dy = v * sin(θ) * dt 其中,v为小车的线速度,θ为小车的转角,dt为时间间隔。角度的变化可以根据小车两侧各自的转速ωL和ωR计算得到,即: dθ = (ωR - ωL) * l / w * dt 其中,l为小车两轮之间的距离,w为小车两个轮子的距离。 轮子运动学部分描述了轮子的运动关系。对于差速小车,它的轮子由两侧各一个,可以计算出每个轮子的转速ωL和ωR。具体地,ωL和ωR与小车的线速度v和角速度dθ的关系可以通过小车运动学模型计算得到,即: v = (ωL + ωR) * r / 2 dθ = (ωR - ωL) * r / w 其中,r为轮子的半径。 通过整体运动学和轮子运动学的计算,我们就可以建立四轮差速小车的MPC运动学模型。这个模型可以用来进行轨迹规划、路径跟踪、避障等运动控制任务。在实际应用中,还可以通过实时测量的数据不断更新模型,以提高控制的准确性和鲁棒性。 ### 回答3: 四轮差速小车是一种基于差速驱动的移动平台,它具有四个轮子,两个靠近一侧的轮子可以独立地驱动。为了建立四轮差速小车的运动学模型,我们需要考虑车辆的转向和运动。 首先,我们定义车辆的坐标系。假设车辆的中心点为原点O,x轴与车辆前进方向平行,y轴与车辆左侧平行。车辆的朝向角度为θ,角度为正表示顺时针旋转。 其次,我们定义车辆的速度和转向控制输入。车辆的线速度为v,表示车辆前进的速度;车辆的角速度控制为ω,表示车辆的转向速度。 根据差速驱动的性质,我们可以将车辆的速度和转向控制输入与车辆各个轮子的线速度和角速度联系起来。假设左右两个靠近一侧的轮子的线速度分别为v1和v2,则左右两个轮子的角速度分别为ω1 = v1/R 和 ω2= v2/R,其中R为车轮的半径。 根据四轮差速小车的运动学模型,我们可以得到车辆的运动方程: v = (v1 + v2) / 2 ω = (v2 - v1) / (2L) 其中L为轴距,表示两个靠近一侧轮子之间的距离。 通过以上方程,我们可以根据给定的线速度和角速度控制输入,计算出左右两个靠近一侧轮子的线速度v1和v2。然后根据v1和v2,我们可以得到车辆的整体线速度v和角速度ω。 最后,根据车辆的线速度和角速度,我们可以通过积分的方法得到车辆在特定时间段内的位置和朝向。 四轮差速小车的mpc运动学模型建立包括了车辆的坐标系定义、速度和转向控制输入、轮子线速度和角速度的关系以及车辆的运动方程等。通过这个模型,我们可以控制车辆的运动,实现特定的路径跟踪和轨迹规划。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值