Quadrotor.cpp 是动力学模型。
quadrotor_simulator_so3.cpp 是飞行器仿真控制器。
test_dynamics.cpp 是一个测试代码。
在CMakeLists.txt里加上:
bash展开代码# Add test_dynamics executable
add_executable(test_dynamics
src/test_dynamics/test_dynamics.cpp)
target_link_libraries(test_dynamics
quadrotor_dynamics
)
就像这样:
代码里加句话:
编译:
bash展开代码catkin_make
安装几个python包:
bash展开代码python3 -m pip install --upgrade pip python3 -m pip install --upgrade seaborn pandas
执行test_dynamics,让其把数据输出出来到 utils_python/dynamics_data.csv
bash展开代码mkdir utils_python
devel/lib/so3_quadrotor_simulator/test_dynamics > utils_python/dynamics_data.csv
utils_python/dynamics_data.csv 里面的数据:
bash展开代码时间, 高度, 偏航角, 俯仰角, 滚转角, 角速度x, 角速度y, 角速度z, 电机转速
执行python绘图:
bash展开代码cd utils_python/
python visualize_dynamics.py
得到图:
cpp展开代码double dt = 0.001; // 仿真时间步长:1毫秒
QuadrotorSimulator::Quadrotor quad; // 创建四旋翼仿真器对象
cpp展开代码const double hover_rpm = std::sqrt(m*g/(4*kf)); // 计算悬停所需的电机转速
hover_rpm = √(mg/4kf)
,其中:
程序实现了一个PD控制器来控制飞行器的高度:
cpp展开代码double KP = 8.0; // 比例增益
double KD = 2.5; // 微分增益
const double z_des = 0.5; // 目标高度:0.5米
// PD控制律
thrust = m*g + KP*(z_des - state.x(2)) + KD*(0 - state.v(2));
控制律解释:
m*g
: 重力补偿项KP*(z_des - state.x(2))
: 位置误差的比例控制KD*(0 - state.v(2))
: 速度误差的微分控制cpp展开代码if( i < 3000)
quad.setExternalForce(Eigen::Vector3d(0, 0, -KP*z_des)); // 前3秒施加向下的干扰力
else
quad.setExternalForce(Eigen::Vector3d(0, 0, 0)); // 后3秒移除干扰力
cpp展开代码std::cout << i*dt << ", " << state.x(2) << ", " << euler(0) << ", " << euler(1) << ", " << euler(2) << ", " <<
state.omega(0) << ", " << state.omega(1) << ", " << state.omega(2) << ", " << state.motor_rpm(0) << std::endl;
输出格式:时间, 高度, 偏航角, 俯仰角, 滚转角, 角速度x, 角速度y, 角速度z, 电机转速
test_dynamics.cpp 这个代码是值得细看,这也可以单独调试,这是四旋翼飞行器的动力模型的一个测试文件。
200:265:/xd_ws/ego-planner/src/uav_simulator/so3_quadrotor_simulator/src/quadrotor_simulator_so3.cpp展开代码int main(int argc, char** argv) { ros::init(argc, argv, "quadrotor_simulator_so3"); ros::NodeHandle n("~"); ros::Publisher odom_pub = n.advertise<nav_msgs::Odometry>("odom", 100); ros::Publisher imu_pub = n.advertise<sensor_msgs::Imu>("imu", 10); ros::Subscriber cmd_sub = n.subscribe("cmd", 100, &cmd_callback, ros::TransportHints().tcpNoDelay()); ros::Subscriber f_sub = n.subscribe("force_disturbance", 100, &force_disturbance_callback, ros::TransportHints().tcpNoDelay()); ros::Subscriber m_sub = n.subscribe("moment_disturbance", 100, &moment_disturbance_callback, ros::TransportHints().tcpNoDelay()); QuadrotorSimulator::Quadrotor quad;
该节点通过以下接口与系统交互:
订阅的话题:
cmd
(重映射为 so3_cmd
):接收来自 so3_control
控制器的 SO3Command 控制指令force_disturbance
:外部干扰力(可选)moment_disturbance
:外部干扰力矩(可选)发布的话题:
odom
(重映射为 /visual_slam/odom
):四旋翼的里程计信息,供规划器使用imu
:IMU 传感器数据从配置文件可以看出,该节点在两种主要场景中使用:
76:86:/xd_ws/ego-planner/src/planner/plan_manage/launch/simulator.xml展开代码<node pkg="so3_quadrotor_simulator" type="quadrotor_simulator_so3" name="quadrotor_simulator_so3" output="screen"> <param name="rate/odom" value="200.0"/> <param name="simulator/init_state_x" value="$(arg init_x)"/> <param name="simulator/init_state_y" value="$(arg init_y)"/> <param name="simulator/init_state_z" value="$(arg init_z)"/> <remap from="~odom" to="/visual_slam/odom"/> <remap from="~cmd" to="so3_cmd"/> <remap from="~force_disturbance" to="force_disturbance"/> <remap from="~moment_disturbance" to="moment_disturbance"/> </node>
7:19:/xd_ws/ego-planner/src/uav_simulator/so3_quadrotor_simulator/launch/simulator_example.launch展开代码<node pkg="so3_quadrotor_simulator" type="quadrotor_simulator_so3" name="quadrotor_simulator_so3" output="screen"> <param name="rate/odom" value="100.0"/> <param name="simulator/init_state_x" value="$(arg init_x)"/> <param name="simulator/init_state_y" value="$(arg init_y)"/> <param name="simulator/init_state_z" value="$(arg init_z)"/> <remap from="~odom" to="/sim/odom"/> <remap from="~cmd" to="/so3_cmd"/> <remap from="~imu" to="/sim/imu"/> </node>
该仿真器实现了完整的四旋翼动力学模型:
13:21:/xd_ws/ego-planner/src/uav_simulator/so3_quadrotor_simulator/include/quadrotor_simulator/Quadrotor.h展开代码struct State { Eigen::Vector3d x; // 位置 Eigen::Vector3d v; // 速度 Eigen::Matrix3d R; // 旋转矩阵(SO3 姿态表示) Eigen::Vector3d omega; // 角速度 Eigen::Array4d motor_rpm; // 四个电机转速 EIGEN_MAKE_ALIGNED_OPERATOR_NEW; };
161:181:/xd_ws/ego-planner/src/uav_simulator/so3_quadrotor_simulator/src/quadrotor_simulator_so3.cpp展开代码static void cmd_callback(const quadrotor_msgs::SO3Command::ConstPtr& cmd) { command.force[0] = cmd->force.x; command.force[1] = cmd->force.y; command.force[2] = cmd->force.z; command.qx = cmd->orientation.x; command.qy = cmd->orientation.y; command.qz = cmd->orientation.z; command.qw = cmd->orientation.w; command.kR[0] = cmd->kR[0]; command.kR[1] = cmd->kR[1]; command.kR[2] = cmd->kR[2]; command.kOm[0] = cmd->kOm[0]; command.kOm[1] = cmd->kOm[1]; command.kOm[2] = cmd->kOm[2]; command.corrections[0] = cmd->aux.kf_correction; command.corrections[1] = cmd->aux.angle_corrections[0]; command.corrections[2] = cmd->aux.angle_corrections[1]; command.current_yaw = cmd->aux.current_yaw; command.use_external_yaw = cmd->aux.use_external_yaw; }
接收的控制指令包含:
启动出rviz
bash展开代码source devel/setup.bash
roslaunch ego_planner rviz.launch
启动完整仿真系统
bash展开代码# 启动完整仿真系统
source devel/setup.bash
roslaunch ego_planner run_in_sim.launch
roslaunch ego_planner run_in_sim.launch 启动后,日志会显示这些都启动:
根据参数配置,系统建立了一个仿真环境:
要让无人机开始飞行,您需要:
起始点:
我设置点后,
一旦您设置了目标点,系统将:
独立使用(仅仿真器测试)
bash展开代码# 启动仿真器示例
roslaunch so3_quadrotor_simulator simulator_example.launch
会看到一个飞机:
也看到了:
查看话题:
bash展开代码root@euler-MS-7D30:/xd_ws/ego-planner# rostopic list
/CircleNode_1/path
/clicked_point
/corrections
/goal
/initialpose
/map_generator/global_cloud
/motors
/move_base_simple/goal
/odom_visualization/cmd
/odom_visualization/covariance
/odom_visualization/covariance_velocity
/odom_visualization/height
/odom_visualization/path
/odom_visualization/pose
/odom_visualization/robot
/odom_visualization/robot_array
/odom_visualization/sensor
/odom_visualization/trajectory
/odom_visualization/velocity
/odom_visualization_1/path
/odom_visualization_1/pose
/odom_visualization_1/velocity
/odom_visualization_1/velocity_array
/position_cmd
/quadrotor_simulator_so3/force_disturbance
/quadrotor_simulator_so3/moment_disturbance
/rosout
/rosout_agg
/sim/imu
/sim/odom
/so3_cmd
/tf
/tf_static
发送一个控制命令:
bash展开代码rostopic pub -r 10 /position_cmd quadrotor_msgs/PositionCommand "
position: {x: -4.0, y: 0.0, z: 4.0}
yaw: 0.0"
bash展开代码rostopic pub -r 10 /position_cmd quadrotor_msgs/PositionCommand "
position: {x: -4.0, y: 0.0, z: 6.0}
yaw: 0.0"
bash展开代码rostopic pub -r 10 /position_cmd quadrotor_msgs/PositionCommand "
position: {x: 0.0, y: 0.0, z: 6.0}
yaw: 0.0"
bash展开代码rostopic pub -r 10 /position_cmd quadrotor_msgs/PositionCommand "
position: {x: 2.0, y: 1.0, z: 2.0}
yaw: 0.0"
给了位置,飞机就飞过去,用的什么算法。
详细解释从 /position_cmd
到飞机实际飞行的完整控制流程:
展开代码您的位置命令 → SO3控制器 → 仿真器 → 电机转速 → 飞机运动
当您发布 /position_cmd
时:
96:118:/xd_ws/ego-planner/src/uav_simulator/so3_control/src/so3_control_nodelet.cpp展开代码void SO3ControlNodelet::position_cmd_callback( const quadrotor_msgs::PositionCommand::ConstPtr& cmd) { des_pos_ = Eigen::Vector3d(cmd->position.x, cmd->position.y, cmd->position.z); des_vel_ = Eigen::Vector3d(cmd->velocity.x, cmd->velocity.y, cmd->velocity.z); des_acc_ = Eigen::Vector3d(cmd->acceleration.x, cmd->acceleration.y, cmd->acceleration.z); // 设置控制增益 kx_ = Eigen::Vector3d(cmd->kx[0], cmd->kx[1], cmd->kx[2]); kv_ = Eigen::Vector3d(cmd->kv[0], cmd->kv[1], cmd->kv[2]); des_yaw_ = cmd->yaw; position_cmd_updated_ = true; publishSO3Command(); }
SO3控制器使用几何控制理论(不是简单的PID),核心算法:
58:85:/xd_ws/ego-planner/src/uav_simulator/so3_control/src/SO3Control.cpp展开代码// 计算总误差 Eigen::Vector3d totalError(Eigen::Vector3d::Zero()); if ( flag_use_pos ) totalError.noalias() += des_pos - pos_; // 位置误差 if ( flag_use_vel ) totalError.noalias() += des_vel - vel_; // 速度误差 if ( flag_use_acc ) totalError.noalias() += des_acc - acc_; // 加速度误差 // 计算所需推力 force_ = mass_ * g_ * Eigen::Vector3d(0, 0, 1); // 重力补偿 if ( flag_use_pos ) force_.noalias() += kx.asDiagonal() * (des_pos - pos_); // 位置控制 if ( flag_use_vel ) force_.noalias() += kv.asDiagonal() * (des_vel - vel_); // 速度控制 if ( flag_use_acc ) force_.noalias() += mass_ * ka.asDiagonal() * (des_acc - acc_) + mass_ * (des_acc); // 加速度控制 // 计算期望姿态(基于推力方向) Eigen::Vector3d b1c, b2c, b3c; Eigen::Vector3d b1d(cos(des_yaw), sin(des_yaw), 0); // 期望的yaw方向 if (force_.norm() > 1e-6) b3c.noalias() = force_.normalized(); // 推力方向 else b3c.noalias() = Eigen::Vector3d(0, 0, 1); b2c.noalias() = b3c.cross(b1d).normalized(); // 构建正交坐标系 b1c.noalias() = b2c.cross(b3c).normalized(); Eigen::Matrix3d R; R << b1c, b2c, b3c; // 期望的旋转矩阵 orientation_ = Eigen::Quaterniond(R);
仿真器接收SO3Command,转换为电机转速:
40:160:/xd_ws/ego-planner/src/uav_simulator/so3_quadrotor_simulator/src/quadrotor_simulator_so3.cpp展开代码// 计算姿态误差 float eR1 = 0.5f * (R12 * Rd13 - R13 * Rd12 + R22 * Rd23 - R23 * Rd22 + R32 * Rd33 - R33 * Rd32); float eR2 = 0.5f * (R13 * Rd11 - R11 * Rd13 - R21 * Rd23 + R23 * Rd21 - R31 * Rd33 + R33 * Rd31); float eR3 = 0.5f * (R11 * Rd12 - R12 * Rd11 + R21 * Rd22 - R22 * Rd21 + R31 * Rd32 - R32 * Rd31); // 计算力矩(姿态控制) float M1 = -cmd.kR[0] * eR1 - cmd.kOm[0] * eOm1 + in1; // 滚转力矩 float M2 = -cmd.kR[1] * eR2 - cmd.kOm[1] * eOm2 + in2; // 俯仰力矩 float M3 = -cmd.kR[2] * eR3 - cmd.kOm[2] * eOm3 + in3; // 偏航力矩 // 转换为四个电机的转速平方 float w_sq[4]; w_sq[0] = force / (4 * kf) - M2 / (2 * d * kf) + M3 / (4 * km); // 电机1 w_sq[1] = force / (4 * kf) + M2 / (2 * d * kf) + M3 / (4 * km); // 电机2 w_sq[2] = force / (4 * kf) + M1 / (2 * d * kf) - M3 / (4 * km); // 电机3 w_sq[3] = force / (4 * kf) - M1 / (2 * d * kf) - M3 / (4 * km); // 电机4 // 转换为实际转速 control.rpm[i] = sqrtf(w_sq[i]);
这个系统使用的是几何控制理论(Geometric Control),比PID更高级:
kx * (des_pos - pos_)
- 位置误差反馈kv * (des_vel - vel_)
- 速度误差反馈展开代码位置控制层 → 姿态控制层 → 电机控制层
80:90:/xd_ws/ego-planner/src/uav_simulator/so3_control/src/SO3Control.cpp展开代码// 限制控制角度不超过45度 double theta = M_PI / 2; double c = cos(theta); if (Eigen::Vector3d(0, 0, 1).dot(force_ / force_.norm()) < c) { // 重新计算推力以满足角度约束 }
与PID的区别
传统PID:
展开代码误差 = 目标 - 当前 输出 = Kp*误差 + Ki*积分 + Kd*微分
几何控制:
展开代码位置误差 = 目标位置 - 当前位置 推力 = mg + kx*位置误差 + kv*速度误差 姿态 = 基于推力方向几何计算 力矩 = kR*姿态误差 + kOm*角速度误差
SO(3) 是三维旋转群,表示所有可能的3D旋转。四旋翼的姿态控制本质上就是在 SO(3) 群上的控制问题。
四旋翼动力学模型:
其中:
这个算法采用分层控制策略:
展开代码位置控制 → 推力计算 → 姿态计算 → 力矩控制
58:85:/xd_ws/ego-planner/src/uav_simulator/so3_control/src/SO3Control.cpp展开代码// 计算总误差 Eigen::Vector3d totalError(Eigen::Vector3d::Zero()); if ( flag_use_pos ) totalError.noalias() += des_pos - pos_; // 位置误差 if ( flag_use_vel ) totalError.noalias() += des_vel - vel_; // 速度误差 if ( flag_use_acc ) totalError.noalias() += des_acc - acc_; // 加速度误差 // 计算所需推力 force_ = mass_ * g_ * Eigen::Vector3d(0, 0, 1); // 重力补偿 if ( flag_use_pos ) force_.noalias() += kx.asDiagonal() * (des_pos - pos_); // 位置控制 if ( flag_use_vel ) force_.noalias() += kv.asDiagonal() * (des_vel - vel_); // 速度控制 if ( flag_use_acc ) force_.noalias() += mass_ * ka.asDiagonal() * (des_acc - acc_) + mass_ * (des_acc); // 加速度控制
数学公式:
其中:
87:98:/xd_ws/ego-planner/src/uav_simulator/so3_control/src/SO3Control.cpp展开代码// 限制控制角度到45度 double theta = M_PI / 2; double c = cos(theta); Eigen::Vector3d f; f.noalias() = force_ - mass_ * g_ * Eigen::Vector3d(0, 0, 1); if (Eigen::Vector3d(0, 0, 1).dot(force_ / force_.norm()) < c) { double nf = f.norm(); double A = c * c * nf * nf - f(2) * f(2); double B = 2 * (c * c - 1) * f(2) * mass_ * g_; double C = (c * c - 1) * mass_ * mass_ * g_ * g_; double s = (-B + sqrt(B * B - 4 * A * C)) / (2 * A); force_.noalias() = s * f + mass_ * g_ * Eigen::Vector3d(0, 0, 1); }
数学原理: 这个约束确保推力方向与垂直方向的夹角不超过45度,保证控制稳定性。
约束条件:
100:115:/xd_ws/ego-planner/src/uav_simulator/so3_control/src/SO3Control.cpp展开代码Eigen::Vector3d b1c, b2c, b3c; Eigen::Vector3d b1d(cos(des_yaw), sin(des_yaw), 0); // 期望的yaw方向 if (force_.norm() > 1e-6) b3c.noalias() = force_.normalized(); // 推力方向作为z轴 else b3c.noalias() = Eigen::Vector3d(0, 0, 1); b2c.noalias() = b3c.cross(b1d).normalized(); // 叉积得到y轴 b1c.noalias() = b2c.cross(b3c).normalized(); // 叉积得到x轴 Eigen::Matrix3d R; R << b1c, b2c, b3c; // 构建旋转矩阵 orientation_ = Eigen::Quaterniond(R);
数学原理: 这是推力方向约束的姿态计算,基于以下几何原理:
最终旋转矩阵:
从配置文件可以看到:
yaml展开代码gains:
pos: {x: 2.0, y: 2.0, z: 3.5} # 位置增益
vel: {x: 1.8, y: 1.8, z: 2.0} # 速度增益
rot: {x: 1.0, y: 1.0, z: 0.3} # 姿态增益
ang: {x: 0.07, y: 0.07, z: 0.02} # 角速度增益
增益设计原则:
本文作者:Dong
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!