获得了里程计数据后,下一步就是将里程计通过MicroROS话题发布到ROS 2 系统中。
一、了解接口
在 ROS 2 已有的消息接口中:
nav_msgs/msg/Odometry
用于表示里程计数据,该接口内容如下:
ros2 interface show nav_msgs/msg/Odometry --- # This represents an estimate of a position and velocity in free space. # The pose in this message should be specified in the coordinate frame given by header.frame_id # The twist in this message should be specified in the coordinate frame given by the child_frame_id # Includes the frame id of the pose parent. std_msgs/Header header builtin_interfaces/Time stamp int32 sec uint32 nanosec string frame_id # Frame id the pose points to. The twist is in this coordinate frame. string child_frame_id # Estimated pose that is typically relative to a fixed world frame. geometry_msgs/PoseWithCovariance pose Pose pose Point position float64 x float64 y float64 z Quaternion orientation float64 x 0 float64 y 0 float64 z 0 float64 w 1 float64[36] covariance # Estimated linear and angular velocity relative to child_frame_id. geometry_msgs/TwistWithCovariance twist Twist twist Vector3 linear float64 x float64 y float64 z Vector3 angular float64 x float64 y float64 z float64[36] covariance
注意看,除了表示位置的 pose 和表示速度的 twist ,还有 child_frame_id 这一参数,它表示里程计子坐标系名称,根据ROS 导航堆栈定义,一般用 base_link 或者 base_footprint 。
接着我们来编写代码。
二、编写代码
如何发布话题在前面的章节中我们已经学习过了,现在我们来编写代码,因为是直接在原来的代码基础上修改的,所以下面展示的代码前如果是+表示新增行,-表示删除行,没有符号表示没有修改。
Kinematics.h
首先是/lib/Kinematics/Kinematics.h,增加四元数定义和欧拉角转四元数函数,这是因为ROS中姿态的表示使用的是四元数。
+typedef struct +{ + float w; + float x; + float y; + float z; +} quaternion_t; + /** * @brief 里程计相关信息,根据轮子速度信息和运动模型推算而来 * @@ -41,6 +49,7 @@ typedef struct float x; // 坐标x float y; // 坐标y float yaw; // yaw + quaternion_t quaternion; // 姿态四元数 float linear_speed; // 线速度 float angular_speed; // 角速度 } odom_t; @@ -56,6 +65,7 @@ private: public: Kinematics(/* args */) = default; ~Kinematics() = default; + static void Euler2Quaternion(float roll, float pitch, float yaw, quaternion_t &q); static void TransAngleInPI(float angle,float& out_angle);
Kinematics.cpp
接着是:lib/Kinematics/Kinematics.cpp,增加 Euler2Quaternion 函数实现,在 odom 函数中增加对 Euler2Quaternion 函数的调用。
#include "Kinematics.h" +// 用于将欧拉角转换为四元数。 +void Kinematics::Euler2Quaternion(float roll, float pitch, float yaw, quaternion_t &q) +{ + // 传入机器人的欧拉角 roll、pitch 和 yaw。 + // 计算欧拉角的 sin 和 cos 值,分别保存在 cr、sr、cy、sy、cp、sp 六个变量中 + // https://blog.csdn.net/xiaoma_bk/article/details/79082629 + double cr = cos(roll * 0.5); + double sr = sin(roll * 0.5); + double cy = cos(yaw * 0.5); + double sy = sin(yaw * 0.5); + double cp = cos(pitch * 0.5); + double sp = sin(pitch * 0.5); + // 计算出四元数的四个分量 q.w、q.x、q.y、q.z + q.w = cy * cp * cr + sy * sp * sr; + q.x = cy * cp * sr - sy * sp * cr; + q.y = sy * cp * sr + cy * sp * cr; + q.z = sy * cp * cr - cy * sp * sr; +} odom_t &Kinematics::odom() { + // 调用 Euler2Quaternion 函数,将机器人的欧拉角 yaw 转换为四元数 quaternion。 + Kinematics::Euler2Quaternion(0, 0, odom_.yaw, odom_.quaternion); return odom_; }
main.cpp
接着修改了 /src/main.cpp ,主要添加了一个发布者,接着对时间进行同步,方便发布里程计话题时使用当前的时间。
然后对数据的各项进行设置,最后添加了里程计数据的发布,间隔 50ms 进行发布。
#include <Esp32McpwmMotor.h> // 包含使用 ESP32 的 MCPWM 硬件模块控制 DC 电机的 ESP32 MCPWM 电机库 #include <PidController.h> // 包含 PID 控制器库,用于实现 PID 控制 #include <Kinematics.h> // 运动学相关实现 +#include <nav_msgs/msg/odometry.h> +#include <micro_ros_utilities/string_utilities.h> +rcl_publisher_t odom_publisher; // 用于发布机器人的里程计信息(Odom) +nav_msgs__msg__Odometry odom_msg; // 机器人的里程计信息 Esp32PcntEncoder encoders[2]; // 创建一个长度为 2 的 ESP32 PCNT 编码器数组 rclc_executor_t executor; // 创建一个 RCLC 执行程序对象,用于处理订阅和发布 void microros_task(void *param) { + // 使用 micro_ros_string_utilities_set 函数设置到 odom_msg.header.frame_id 中 + odom_msg.header.frame_id = micro_ros_string_utilities_set(odom_msg.header.frame_id, "odom"); + odom_msg.child_frame_id = micro_ros_string_utilities_set(odom_msg.child_frame_id, "base_link"); // 等待 2 秒,以便网络连接得到建立。 delay(2000); + rclc_publisher_init_best_effort( + &odom_publisher, + &node, + ROSIDL_GET_MSG_TYPE_SUPPORT(nav_msgs, msg, Odometry), + "odom"); // 设置 micro-ROS 执行器,并将订阅添加到其中。 rclc_executor_init(&executor, &support.context, 1, &allocator); rclc_executor_add_subscription(&executor, &subscriber, &sub_msg, &twist_callback, ON_NEW_DATA); // 循环运行 micro-ROS 执行器以处理传入的消息。 while (true) { + if (!rmw_uros_epoch_synchronized()) + { + rmw_uros_sync_session(1000); + // 如果时间同步成功,则将当前时间设置为MicroROS代理的时间,并输出调试信息。 + delay(10); + } delay(100); rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100)); } } unsigned long previousMillis = 0; // 上一次打印的时间 -unsigned long interval = 1000; // 间隔时间,单位为毫秒 +unsigned long interval = 50; // 间隔时间,单位为毫秒 void loop() { previousMillis = currentMillis; // 记录上一次打印的时间 float linear_speed, angle_speed; kinematics.kinematic_forward(kinematics.motor_speed(0), kinematics.motor_speed(1), linear_speed, angle_speed); - Serial.printf("[%ld] linear:%f angle:%f\n", currentMillis, linear_speed, angle_speed); // 打印当前时间 - Serial.printf("[%ld] x:%f y:%f yaml:%f\n", currentMillis,kinematics.odom().x, kinematics.odom().y, kinematics.odom().yaw); // 打印当前时间 + // Serial.printf("[%ld] linear:%f angle:%f\n", currentMillis, linear_speed, angle_speed); // 打印当前时间 + // Serial.printf("[%ld] x:%f y:%f yaml:%f\n", currentMillis,kinematics.odom().x, kinematics.odom().y, kinematics.odom().yaw); // 打印当前时间 + // 用于获取当前的时间戳,并将其存储在消息的头部中 + int64_t stamp = rmw_uros_epoch_millis(); + // 获取机器人的位置和速度信息,并将其存储在一个ROS消息(odom_msg)中 + odom_t odom = kinematics.odom(); + odom_msg.header.stamp.sec = static_cast<int32_t>(stamp / 1000); // 秒部分 + odom_msg.header.stamp.nanosec = static_cast<uint32_t>((stamp % 1000) * 1e6); // 纳秒部分 + odom_msg.pose.pose.position.x = odom.x; + odom_msg.pose.pose.position.y = odom.y; + odom_msg.pose.pose.orientation.w = odom.quaternion.w; + odom_msg.pose.pose.orientation.x = odom.quaternion.x; + odom_msg.pose.pose.orientation.y = odom.quaternion.y; + odom_msg.pose.pose.orientation.z = odom.quaternion.z; + + odom_msg.twist.twist.angular.z = odom.angular_speed; + odom_msg.twist.twist.linear.x = odom.linear_speed; + + rcl_publish(&odom_publisher, &odom_msg, NULL); }
这三个文件修改好,接着就可以下载代码进行测试了。
三、下载测试
下载代码,运行agent,点击RST按键。
sudo docker run -it --rm -v /dev:/dev -v /dev/shm:/dev/shm --privileged --net=host microros/micro-ros-agent:$ROS_DISTRO udp4 --port 8888 -v6
看到连接建立表示通信成功,接着用ros2 topic list
ros2 topic list --- /cmd_vel /odom /parameter_events /rosout
接着我们可以查看里程计数据或其发布频率。
ros2 topic echo /odom --once # 查看数据 --- header: stamp: sec: 2093 nanosec: 40 frame_id: odom child_frame_id: base_link pose: pose: position: x: 0.0 y: 0.0 z: 0.0 orientation: x: 0.0 y: 0.0 z: 0.0 w: 1.0 covariance: - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 twist: twist: linear: x: 0.0 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: 0.0 covariance: - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 - 0.0 ---
查看数据频率
ros2 topic hz /odom --- average rate: 19.376 min: 0.047s max: 0.063s std dev: 0.00326s window: 21 average rate: 19.558 min: 0.039s max: 0.063s std dev: 0.00338s window: 41 average rate: 19.527 min: 0.039s max: 0.063s std dev: 0.00307s window: 61 average rate: 19.533 min: 0.039s max: 0.063s std dev: 0.00301s window: 81
查看数据带宽
ros2 topic bw /odom --- Subscribed to [/odom] 14.78 KB/s from 20 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB 14.26 KB/s from 39 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB 14.34 KB/s from 59 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB 14.19 KB/s from 78 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB 14.25 KB/s from 98 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB 14.18 KB/s from 100 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB 14.25 KB/s from 100 messages Message size mean: 0.72 KB min: 0.72 KB max: 0.72 KB
四、总结
有了控制话题和里程计话题,底盘部分就完成的差不多了,但是对于一个合格的底盘来说,其实还有很多待完善的地方,下一节我们就来说说可以怎么完善,以及完善的后的代码。