
1.Controller ManagerController Manager/ \/ \joint_trajectory_controller InexbotHardware↓ ↓产生目标关节角 调用 servoJ / 读真实角度是一个大管家统管很多控制器2.joint_trajectory_controller是ros2里面专门执行关节轨迹的标准控制器joint_trajectory_controller接收轨迹管理 time_from_start插值输出目标关节角判断轨迹执行状态它接收的是control_msgs/action/FollowJointTrajectory这个 action 里面装着一条trajectory_msgs/JointTrajectory大概结构是这样control_msgs::action::FollowJointTrajectory::Goal goal; goal.trajectory.joint_names { Joint_1, Joint_2, Joint_3, Joint_4, Joint_5, Joint_6 }; goal.trajectory.points[0].positions {0.0, 0.2, -0.5, 0.1, 0.0, 0.0}; goal.trajectory.points[0].time_from_start rclcpp::Duration::from_seconds(0.0); goal.trajectory.points[1].positions {0.1, 0.3, -0.4, 0.2, 0.0, 0.1}; goal.trajectory.points[1].time_from_start rclcpp::Duration::from_seconds(1.0); goal.trajectory.points[2].positions {0.2, 0.4, -0.3, 0.3, 0.1, 0.2}; goal.trajectory.points[2].time_from_start rclcpp::Duration::from_seconds(2.0);这条轨迹的意思是第 0.0 秒6 个关节在 positions[0] 第 1.0 秒6 个关节到 positions[1] 第 2.0 秒6 个关节到 positions[2]joint_trajectory_controller的核心工作就是严格按着这些time_from_start来执行轨迹但是它不负责执行只负责下发它怎么执行 time_from_start假设 MoveIt 发来这样一条轨迹t 0.0s q [0, 0, 0, 0, 0, 0] t 1.0s q [10, 5, -8, 3, 0, 2] t 2.0s q [20, 8, -12, 5, 1, 4]如果控制器频率是 100Hz也就是每 0.01 秒运行一次那么joint_trajectory_controller会不断计算当前时间 elapsed 0.37s 应该处在第 0 秒和第 1 秒之间 所以算出当前目标关节角 q_command例如简单理解double ratio (elapsed - t0) / (t1 - t0); q_command[j] q0[j] ratio * (q1[j] - q0[j]);当然真实的joint_trajectory_controller内部比这个更完整会处理速度、加速度、容差、停止、取消等情况。但你入门阶段可以先这样理解它按照时间在轨迹点之间插值周期性产生目标关节角。joint_trajectory_controller不会直接调用set_servoJ_pos(...)它只会把每个周期算出来的目标关节角写到 ros2_control 的 command interface 里。如果你配置的是command_interfaces:- position意思就是joint_trajectory_controller 每个周期输出“目标关节位置”完整闭环是joint_trajectory_controller↓ command positionhardware_interface::write()↓ servoJ真实机械臂↓ get_current_positionhardware_interface::read()↓ state positionjoint_trajectory_controller / joint_state_broadcaster3.hardware_interface把 ros2_control 的标准关节命令转换成 Inexbot SDK / servoJ 能理解的命令同时把 Inexbot 真实反馈关节角转换成 ROS2 能理解的关节状态FollowJointTrajectory action → joint_trajectory_controller 轨迹时间执行和插值 → joint_trajectory_controller 控制循环 read → update → write → Controller Manager 调用 set_servoJ_pos() → hardware_interface::write() 调用 get_current_position() → hardware_interface::read() ros_rad 和 ctrl_deg 转换 → hardware_interface 连接机器人 IP / 端口 → hardware_interface::on_configure() 或 on_activate() servoJ 初始化 → hardware_interface::on_activate()完整周期是这样的1. Controller Manager 调用 hardware_interface::read() 2. read() 从 Inexbot 读取真实角度 填入 state_positions_ 3. joint_trajectory_controller 读取 state_positions_ 知道当前机械臂在哪里 4. joint_trajectory_controller 根据轨迹 time_from_start 算出当前目标角 5. joint_trajectory_controller 写入 command_positions_ 6. Controller Manager 调用 hardware_interface::write() 7. write() 把 command_positions_ 转成 ctrl_deg 调用 set_servoJ_pos()4.joint_state_broadcaster是ros2_controller里面负责发布关节状态的标准组件把 hardware_interface::read() 读到的关节状态发布成 ROS2 标准话题 /joint_states。一条是“命令方向”MoveIt ↓ joint_trajectory_controller ↓ hardware_interface::write() ↓ servoJ ↓ 真实机械臂另一条是“状态方向”真实机械臂 ↓ hardware_interface::read() ↓ joint_state_broadcaster ↓ /joint_states ↓ MoveIt / RViz在hardware_interface中你的read()以后大概会这样hardware_interface::return_type InexbotHardware::read( const rclcpp::Time time, const rclcpp::Duration period) { std::vectordouble now_ctrl_deg; readCtrlJointsDeg(now_ctrl_deg); for (size_t i 0; i 6; i) { double ros_deg (now_ctrl_deg[i] - OFF[i]) / SGN[i]; state_positions_[i] ros_deg * M_PI / 180.0; } return hardware_interface::return_type::OK; }这里的重点是state_positions_[i] ...state_positions_里保存的是当前真实机械臂的关节角单位一般是rad。但是read()本身不会自动发布/joint_states。它只是把真实状态写进 ros2_control 的状态接口里。然后joint_state_broadcaster会读取这些状态接口再发布出去。其中state_interface可以理解为hardware_interface 暴露给 ros2_control 的状态插口比如你的硬件接口声明std::vectorhardware_interface::StateInterface InexbotHardware::export_state_interfaces() { std::vectorhardware_interface::StateInterface interfaces; for (size_t i 0; i 6; i) { interfaces.emplace_back( joint_names_[i], hardware_interface::HW_IF_POSITION, state_positions_[i]); } return interfaces; }这表示你的硬件提供了这些状态Joint_1/position Joint_2/position Joint_3/position Joint_4/position Joint_5/position Joint_6/position每个状态背后对应一个变量state_positions_[0] state_positions_[1] state_positions_[2] ...joint_state_broadcaster就是从这些 state interface 里取数据然后发布/joint_states。moveit规划出来的轨迹是一串离散的关节目标点不是连续曲线它大概长这样trajectory: joint_names: - Joint_1 - Joint_2 - Joint_3 - Joint_4 - Joint_5 - Joint_6 points: 第 0 个点 time_from_start 0.0s positions [0.00, 0.10, -0.30, 0.20, 0.00, 0.00] 第 1 个点 time_from_start 0.8s positions [0.15, 0.20, -0.25, 0.25, 0.03, 0.02] 第 2 个点 time_from_start 1.6s positions [0.30, 0.25, -0.15, 0.35, 0.08, 0.05] 第 3 个点 time_from_start 2.5s positions [0.45, 0.30, -0.05, 0.40, 0.10, 0.08]假设机械臂的频率是100hz那就是一毫秒发一个点但是moveit一般只给出几个关键点位所以就需要进行插值MoveIt 规划出来的点里有什么一个JointTrajectoryPoint通常可能包含trajectory_msgs::msg::JointTrajectoryPoint point; point.positions; point.velocities; point.accelerations; point.effort; point.time_from_start;也就是positions这个时间点的关节位置 velocities这个时间点的关节速度 accelerations这个时间点的关节加速度 time_from_start从轨迹开始到这个点的时间