Compare commits

...

5 Commits

Author SHA1 Message Date
Huang Haoyu
9f7eaf02db IMU 节点调试完成
Reviewed-on: #2
2026-03-26 18:58:29 +08:00
892a3aec4d 滤波参数,四元数 2026-03-26 18:54:59 +08:00
00157d6410 无迹卡尔曼 2026-03-26 05:01:47 +08:00
57b7ce41e1 环形缓冲区 2026-03-25 06:21:43 +08:00
2cec9bb7e1 去除部分冗余代码,更新 README 2026-03-25 01:32:55 +08:00
6 changed files with 629 additions and 306 deletions

View File

@@ -14,6 +14,7 @@ find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED) find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED) find_package(geometry_msgs REQUIRED)
find_package(sensor_msgs REQUIRED) find_package(sensor_msgs REQUIRED)
find_package(Eigen3 REQUIRED)
# 添加 SDK 库路径 # 添加 SDK 库路径
set(TRANSMITTER_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/transmitter_sdk) set(TRANSMITTER_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/transmitter_sdk)
@@ -41,6 +42,7 @@ ament_target_dependencies(imu_receiver_node
std_msgs std_msgs
geometry_msgs geometry_msgs
sensor_msgs sensor_msgs
Eigen3
) )
# ==================== 安装目标 ==================== # ==================== 安装目标 ====================

View File

@@ -21,10 +21,6 @@
- MVSDK (工业相机 SDK) - MVSDK (工业相机 SDK)
- 待补充... - 待补充...
## 目标
感谢同济 SuperPower 战队!自瞄算法参考同济的 sp_vision_26 。
## 软件架构 ## 软件架构
ROS2 基本架构为节点。以每个节点的发布、解析、接收数据为例说明。 ROS2 基本架构为节点。以每个节点的发布、解析、接收数据为例说明。
@@ -49,9 +45,9 @@ ROS2 基本架构为节点。以每个节点的发布、解析、接收数据为
### IMU 收发节点 ### IMU 收发节点
- 功能IMU 与主机通信的封装节点。物理上读取 IMUDETA10数据 - 功能IMU 与主机通信的封装节点。物理上读取 IMUDETA10数据
- 连接IMU_CP210x协商 UART 通信速率 921600`/dev/ttyUSB1` - 连接IMU_CP210x协商 UART 通信速率 921600`/dev/ttyIMU`
- 接收IMU_CP210x -> 六轴加速度、陀螺仪数据 - 接收IMU_CP210x -> 六轴加速度、陀螺仪数据
- 发布IMU 数据(地磁角、六轴角加速度、加速度、陀螺仪) - 发布IMU 数据(加速度、陀螺仪)
### 中央节点 ### 中央节点
@@ -84,6 +80,14 @@ stateDiagram-v2
### 导航节点 ### 导航节点
- 功能:根据 IMU 数据计算出当前位置,根据状态决定目标位置并发出移动到该位置所需的指令 - 功能:根据 IMU 数据计算出当前位置,根据状态决定目标位置并发出移动到该位置所需的指令
- 接收:收发节点 -> IMU.地磁角, IMU.速度, IMU.加速度, IMU.六轴陀螺仪 - 接收:收发节点 -> IMU.加速度, IMU.六轴陀螺仪(AHRS)
中央节点 -> 状态 中央节点 -> 状态
- 发布:导航运动指令 - 发布:导航运动指令
考虑到 IMU 的实际情况,导航建模思路如下:
- 地图范围:一个 $8 \times 12 m$ 的矩形,机器人运动的范围。以左下角为坐标原点 $(0,0)$,地图对角为 $(12,8)$,坐标单位为米。
- 障碍区:不可跨越,不可侵入,由地图中两个矩形组成,第一个矩形的两个角点为 $(2.5,1.5) ~ (4.5,8)$,第二个为 $(7.5,0) ~ (9.5,6.5)$。
- 基地:同样为场地中角点描述的矩形,我方基地为 $(0,6) ~ (1.5,8)$。
- 增益点:同样为场地中角点描述的矩形,范围为 $(4.5,2.5) ~ (7.5,5.5)$.
- 机器人:视为一直径 $0.75 m$ 的圆,圆心位置为 IMU 安装位置。开始时,圆心位于 $(1.235,6.265)$。
- 机器人的朝向和位置计算IMU 安装于机器人圆心开始时IMU X 轴正方向(机器人朝向)与场地 Y 轴负方向相同Y 轴正方向与场地 X 轴负方向相同IMU Z 轴则朝向地面。当机器人朝向发生改变时IMU 轴系的朝向也会相应发生改变因此需要结合陀螺仪数据才能正确反向解算加速度计带来的加速度信息从而完成惯性导航。IMU 安装并不完全平整,且机器人运动有颠簸,因此需要将 Z 轴数据结合解算出在二维平面上的加速度和位移。

View File

@@ -0,0 +1,35 @@
# MSG_IMU | FDISYSTEMS支持中心
1. 机体坐标系及坐标系方向如图所示。在FDI系列产品中模块表面都会标注准确的机体坐标系方向。具体以实际产品为准。
2. 对于角速度,加速度的标定解释:
- (1) 转台标定:测量并补偿了陀螺仪和加速度计的零偏、尺度误差及三轴不垂直度。
- (2) 温箱标定:在宽温范围(如-40℃~85℃测量了传感器误差随温度的变化规律并生成了补偿模型。对陀螺和加表的输出数据进行了补偿。
3. 对于加速度中未分离重力加速度解释:
加速度计测量的是载体运动与地球重力的"混合"结果。简单来说当模块静止时加速度读数并不为零而会显示出一个重力值例如平放时Z轴约为+9.8 m/s²
## 基本信息
| 属性 | 值 |
|------|-----|
| **Packet ID** | 0x40 |
| **Length** | 56 |
| **Read / Write** | Read |
## 数据字段
| Offset | Size | Format | Field | Unit | Description |
|--------|------|--------|-------|------|-------------|
| 0 | 4 | float32_t | Gyroscope_X | rad/s | 机体系X轴角速度 |
| 4 | 4 | float32_t | Gyroscope_Y | rad/s | 机体系Y轴角速度 |
| 8 | 4 | float32_t | Gyroscope_Z | rad/s | 机体系Z轴角速度 |
| 12 | 4 | float32_t | Accelerometer_X | m/s² | 机体系X轴加速度(未分离重力加速度) |
| 16 | 4 | float32_t | Accelerometer_Y | m/s² | 机体系Y轴加速度(未分离重力加速度) |
| 20 | 4 | float32_t | Accelerometer_Z | m/s² | 机体系Z轴加速度(未分离重力加速度) |
| 24 | 4 | float32_t | Magnetometer_X | mG | 机体系X轴磁感应强度 |
| 28 | 4 | float32_t | Magnetometer_Y | mG | 机体系Y轴磁感应强度 |
| 32 | 4 | float32_t | Magnetometer_Z | mG | 机体系Z轴磁感应强度 |
| 36 | 4 | float32_t | IMU_Temperature | deg C | 如果IMU数据由多个传感器组成则该值为这些传感器的平均温度 |
| 40 | 4 | float32_t | Pressure | Pa | 气压值如果没装气压计默认为一个标准大气压101325Pa |
| 48 | 8 | int64_t | Timestamp | us | 数据的时间戳从上电开始启动的微秒数。时钟源为MCU外部晶振。 |

View File

@@ -1,102 +0,0 @@
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
# 声明启动参数
serial_port_arg = DeclareLaunchArgument(
'serial_port',
default_value='/dev/ttyCH340',
description='CH340 串口设备路径'
)
baudrate_arg = DeclareLaunchArgument(
'baudrate',
default_value='115200',
description='串口波特率'
)
send_frequency_arg = DeclareLaunchArgument(
'send_frequency',
default_value='10.0',
description='发送频率 (Hz)'
)
# 控制参数
x_move_arg = DeclareLaunchArgument(
'x_move',
default_value='0',
description='平动左右 [-660, 660]'
)
y_move_arg = DeclareLaunchArgument(
'y_move',
default_value='0',
description='平动前后 [-660, 660]'
)
yaw_arg = DeclareLaunchArgument(
'yaw',
default_value='0',
description='云台偏航 [-660, 660]'
)
pitch_arg = DeclareLaunchArgument(
'pitch',
default_value='0',
description='云台俯仰 [-660, 660]'
)
feed_arg = DeclareLaunchArgument(
'feed',
default_value='0',
description='拨弹轮 [-660, 660]'
)
left_switch_arg = DeclareLaunchArgument(
'left_switch',
default_value='3',
description='左拨杆 [1, 3]'
)
right_switch_arg = DeclareLaunchArgument(
'right_switch',
default_value='3',
description='右拨杆 [1, 3]'
)
# 创建节点
uart_transmitter_node = Node(
package='amadeus_26',
executable='uart_transmitter_node',
name='uart_transmitter_node',
output='screen',
parameters=[{
'serial_port': LaunchConfiguration('serial_port'),
'baudrate': LaunchConfiguration('baudrate'),
'send_frequency': LaunchConfiguration('send_frequency'),
'x_move': LaunchConfiguration('x_move'),
'y_move': LaunchConfiguration('y_move'),
'yaw': LaunchConfiguration('yaw'),
'pitch': LaunchConfiguration('pitch'),
'feed': LaunchConfiguration('feed'),
'left_switch': LaunchConfiguration('left_switch'),
'right_switch': LaunchConfiguration('right_switch'),
}],
)
return LaunchDescription([
serial_port_arg,
baudrate_arg,
send_frequency_arg,
x_move_arg,
y_move_arg,
yaw_arg,
pitch_arg,
feed_arg,
left_switch_arg,
right_switch_arg,
uart_transmitter_node,
])

View File

@@ -1,23 +1,34 @@
/** /**
* @file imu_receiver_node.cpp * @file imu_receiver_node.cpp
* @brief IMU 数据接收节点 (FDI DETA10) - 高实时性版本 * @brief IMU 数据接收节点 (FDI DETA10) - 环形缓冲区版本 + UKF
* *
* 通过串口读取 FDI DETA10 IMU 模块的数据 * 通过串口读取 FDI DETA10 IMU 模块的数据
* 波特率921600 * 波特率921600
* 协议FDILink * 协议FDILink
* *
* 转发数据包: * 转发数据包:
* - MSG_AHRS (0x41): 航姿参考系统数据 * - MSG_BODY_ACCELERATION (0x62): 滤波修正后的机体系加速度(不包括重力)
* - MSG_BODY_ACCELERATION (0x62): 机体系加速度数据 * - MSG_AHRS (0x41): 航姿参考系统数据(四元数、角速度)
*
* 特性:
* - 使用环形缓冲区避免数据堆积爆发
* - 使用 UKF无迹卡尔曼滤波器对加速度数据进行滤波
* - 融合 AHRS 姿态数据和 UKF 滤波后的加速度
*/ */
#include <atomic> #include <atomic>
#include <cmath>
#include <condition_variable>
#include <cstring> #include <cstring>
#include <eigen3/Eigen/Dense>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <geometry_msgs/msg/quaternion.hpp> #include <geometry_msgs/msg/quaternion.hpp>
#include <geometry_msgs/msg/twist.hpp> #include <geometry_msgs/msg/twist.hpp>
#include <geometry_msgs/msg/vector3.hpp> #include <geometry_msgs/msg/vector3.hpp>
#include <mutex>
#include <queue>
#include <rclcpp/logging.hpp>
#include <rclcpp/rclcpp.hpp> #include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/imu.hpp> #include <sensor_msgs/msg/imu.hpp>
#include <std_msgs/msg/bool.hpp> #include <std_msgs/msg/bool.hpp>
@@ -33,8 +44,8 @@ constexpr uint8_t FRAME_START = 0xFC;
constexpr uint8_t FRAME_END = 0xFD; constexpr uint8_t FRAME_END = 0xFD;
// 数据包 ID // 数据包 ID
constexpr uint8_t MSG_BODY_ACCELERATION = 0x62; // 滤波修正后的机体系加速度
constexpr uint8_t MSG_AHRS = 0x41; // 航姿参考系统数据 constexpr uint8_t MSG_AHRS = 0x41; // 航姿参考系统数据
constexpr uint8_t MSG_BODY_ACCELERATION = 0x62; // 机体系加速度数据
// 默认串口设备 // 默认串口设备
constexpr const char *DEFAULT_SERIAL_PORT = "/dev/ttyIMU"; constexpr const char *DEFAULT_SERIAL_PORT = "/dev/ttyIMU";
@@ -44,6 +55,20 @@ constexpr int DEFAULT_BAUDRATE = 921600;
constexpr size_t SERIAL_READ_BUF_SIZE = 4096; constexpr size_t SERIAL_READ_BUF_SIZE = 4096;
constexpr size_t MAX_FRAME_SIZE = 256; constexpr size_t MAX_FRAME_SIZE = 256;
// 环形缓冲区配置
constexpr size_t RING_BUFFER_SIZE = 16; // 环形缓冲区最大帧数
constexpr size_t MAX_PUBLISH_RATE_HZ = 100; // 最大发布频率 100Hz
constexpr auto PUBLISH_PERIOD =
std::chrono::microseconds(1000000 / MAX_PUBLISH_RATE_HZ); // 10ms
// UKF 参数
constexpr int UKF_STATE_DIM = 2; // 状态维度 [位置, 速度]
constexpr int UKF_MEAS_DIM = 1; // 测量维度 [位置]
constexpr int UKF_SIGMA_POINTS = 2 * UKF_STATE_DIM + 1; // 2n+1 个 Sigma 点
// 数据同步超时 (ms)
constexpr int DATA_SYNC_TIMEOUT_MS = 10;
// CRC8 查找表 // CRC8 查找表
static const uint8_t CRC8Table[] = { static const uint8_t CRC8Table[] = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31,
@@ -97,34 +122,378 @@ static const uint16_t CRC16Table[256] = {
0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
/**
* @brief 无迹卡尔曼滤波器 (UKF) 用于加速度滤波
*
* 状态向量: [位置, 速度]^T
* 测量: 位置
*
* 使用 Sigma 点变换处理非线性
*/
class UKF {
public:
UKF()
: x_(Eigen::Vector2d::Zero()), P_(Eigen::Matrix2d::Identity()),
Q_(Eigen::Matrix2d::Identity() * 0.001),
R_(Eigen::Matrix<double, 1, 1>::Identity() * 0.03), dt_(0.01),
initialized_(false) {
// 初始化权重
computeWeights();
}
/**
* @brief 设置过程噪声
*/
void setProcessNoise(double q) {
Q_ = Eigen::Matrix2d::Identity() * q;
Q_(1, 1) = q * 10; // 速度噪声更大一些
}
/**
* @brief 设置测量噪声
*/
void setMeasurementNoise(double r) { R_(0, 0) = r; }
/**
* @brief 更新滤波器 - 简单的一维位置滤波
* @param measurement 测量值 (加速度/位置)
* @return 滤波后的值
*/
double update(double measurement) {
if (!initialized_) {
x_(0) = measurement;
x_(1) = 0; // 初始速度为0
initialized_ = true;
return measurement;
}
// === 预测步骤 ===
// 生成 Sigma 点
Eigen::Matrix<double, UKF_STATE_DIM, UKF_SIGMA_POINTS> sigma_points;
generateSigmaPoints(sigma_points);
// 传播 Sigma 点通过状态转移方程
Eigen::Matrix<double, UKF_STATE_DIM, UKF_SIGMA_POINTS> sigma_points_pred;
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
// 状态转移: 恒定速度模型
// x_k+1 = x_k + v_k * dt
// v_k+1 = v_k
sigma_points_pred(0, i) = sigma_points(0, i) + sigma_points(1, i) * dt_;
sigma_points_pred(1, i) = sigma_points(1, i);
}
// 计算预测均值
x_pred_.setZero();
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
x_pred_ += weights_m_[i] * sigma_points_pred.col(i);
}
// 计算预测协方差
P_pred_.setZero();
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
Eigen::Vector2d diff = sigma_points_pred.col(i) - x_pred_;
P_pred_ += weights_c_[i] * diff * diff.transpose();
}
P_pred_ += Q_;
// === 更新步骤 ===
// 传播 Sigma 点到测量空间
Eigen::Matrix<double, UKF_MEAS_DIM, UKF_SIGMA_POINTS> Z_sigma;
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
// 测量模型: 只测量位置
Z_sigma(0, i) = sigma_points_pred(0, i);
}
// 计算预测测量均值
z_pred_.setZero();
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
z_pred_ += weights_m_[i] * Z_sigma.col(i);
}
// 计算预测测量协方差
Eigen::Matrix<double, UKF_MEAS_DIM, UKF_MEAS_DIM> S;
S.setZero();
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
Eigen::Matrix<double, UKF_MEAS_DIM, 1> diff = Z_sigma.col(i) - z_pred_;
S += weights_c_[i] * diff * diff.transpose();
}
S += R_;
// 计算互协方差
Eigen::Matrix<double, UKF_STATE_DIM, UKF_MEAS_DIM> T;
T.setZero();
for (int i = 0; i < UKF_SIGMA_POINTS; ++i) {
Eigen::Vector2d diff_x = sigma_points_pred.col(i) - x_pred_;
Eigen::Matrix<double, UKF_MEAS_DIM, 1> diff_z = Z_sigma.col(i) - z_pred_;
T += weights_c_[i] * diff_x * diff_z.transpose();
}
// 计算卡尔曼增益
Eigen::Matrix<double, UKF_STATE_DIM, UKF_MEAS_DIM> K = T * S.inverse();
// 更新状态
Eigen::Matrix<double, UKF_MEAS_DIM, 1> z;
z(0) = measurement;
x_ = x_pred_ + K * (z - z_pred_);
// 更新协方差
P_ = P_pred_ - K * S * K.transpose();
return x_(0);
}
/**
* @brief 设置采样时间
*/
void setDt(double dt) { dt_ = dt; }
/**
* @brief 获取当前估计值
*/
double getEstimate() const { return x_(0); }
/**
* @brief 获取当前速度估计
*/
double getVelocity() const { return x_(1); }
private:
/**
* @brief 计算 Sigma 点权重
*/
void computeWeights() {
// 使用标准 UKF 参数
// alpha = 1e-3, beta = 2, kappa = 0
double alpha = 1e-3;
double beta = 2.0;
double kappa = 0.0;
lambda_ = alpha * alpha * (UKF_STATE_DIM + kappa) - UKF_STATE_DIM;
weights_m_.resize(UKF_SIGMA_POINTS);
weights_c_.resize(UKF_SIGMA_POINTS);
weights_m_[0] = lambda_ / (UKF_STATE_DIM + lambda_);
weights_c_[0] = weights_m_[0] + (1 - alpha * alpha + beta);
for (int i = 1; i < UKF_SIGMA_POINTS; ++i) {
weights_m_[i] = 0.5 / (UKF_STATE_DIM + lambda_);
weights_c_[i] = weights_m_[i];
}
}
/**
* @brief 生成 Sigma 点
*/
void generateSigmaPoints(
Eigen::Matrix<double, UKF_STATE_DIM, UKF_SIGMA_POINTS> &sigma_points) {
// 计算矩阵平方根
Eigen::Matrix2d sqrt_P = ((UKF_STATE_DIM + lambda_) * P_).llt().matrixL();
sigma_points.col(0) = x_;
for (int i = 0; i < UKF_STATE_DIM; ++i) {
sigma_points.col(i + 1) = x_ + sqrt_P.col(i);
sigma_points.col(i + 1 + UKF_STATE_DIM) = x_ - sqrt_P.col(i);
}
}
// 状态向量 [位置, 速度]
Eigen::Vector2d x_;
Eigen::Vector2d x_pred_;
// 协方差矩阵
Eigen::Matrix2d P_;
Eigen::Matrix2d P_pred_;
// 过程噪声和测量噪声
Eigen::Matrix2d Q_;
Eigen::Matrix<double, UKF_MEAS_DIM, UKF_MEAS_DIM> R_;
// 预测测量
Eigen::Matrix<double, UKF_MEAS_DIM, 1> z_pred_;
// UKF 参数
double lambda_;
std::vector<double> weights_m_;
std::vector<double> weights_c_;
// 采样时间
double dt_;
// 初始化标志
bool initialized_;
};
// AHRS 数据结构
struct AhrsData {
bool valid = false;
// 角速度 (rad/s)
float roll_speed = 0;
float pitch_speed = 0;
float heading_speed = 0;
// 欧拉角 (rad)
float roll = 0;
float pitch = 0;
float heading = 0;
// 四元数
float q1 = 0, q2 = 0, q3 = 0, q4 = 0;
// 时间戳 (us)
int64_t timestamp_us = 0;
// 接收时间
std::chrono::steady_clock::time_point receive_time;
};
// 加速度数据结构
struct AccelData {
bool valid = false;
// 滤波修正后的机体系加速度 (m/s²) - 不包括重力
float acc_x = 0, acc_y = 0, acc_z = 0;
// 当地重力加速度 (m/s²)
float g_force = 0;
// 接收时间
std::chrono::steady_clock::time_point receive_time;
};
// 融合的 IMU 数据结构
struct ImuDataFrame {
bool valid = false;
// 加速度 (UKF 滤波后)
float acc_x = 0, acc_y = 0, acc_z = 0;
// 角速度 (来自 AHRS)
float gyro_x = 0, gyro_y = 0, gyro_z = 0;
// 四元数 (来自 AHRS)
float q1 = 0, q2 = 0, q3 = 0, q4 = 0;
// 时间戳
rclcpp::Time timestamp;
};
// 线程安全的环形缓冲区
template <typename T> class RingBuffer {
public:
explicit RingBuffer(size_t size) : max_size_(size) {}
// 推入数据,如果满则丢弃最旧的数据
void push(const T &item) {
std::lock_guard<std::mutex> lock(mutex_);
if (buffer_.size() >= max_size_) {
buffer_.pop(); // 丢弃最旧的数据
}
buffer_.push(item);
cond_var_.notify_one();
}
// 非阻塞弹出
bool try_pop(T &item) {
std::lock_guard<std::mutex> lock(mutex_);
if (buffer_.empty()) {
return false;
}
item = buffer_.front();
buffer_.pop();
return true;
}
// 获取最新数据(不清除)
bool peek_latest(T &item) {
std::lock_guard<std::mutex> lock(mutex_);
if (buffer_.empty()) {
return false;
}
item = buffer_.back(); // 返回最新的
return true;
}
// 清空缓冲区
void clear() {
std::lock_guard<std::mutex> lock(mutex_);
while (!buffer_.empty())
buffer_.pop();
}
size_t size() {
std::lock_guard<std::mutex> lock(mutex_);
return buffer_.size();
}
bool empty() {
std::lock_guard<std::mutex> lock(mutex_);
return buffer_.empty();
}
private:
std::queue<T> buffer_;
size_t max_size_;
std::mutex mutex_;
std::condition_variable cond_var_;
};
class ImuReceiverNode : public rclcpp::Node { class ImuReceiverNode : public rclcpp::Node {
public: public:
ImuReceiverNode() : Node("imu_receiver_node") { ImuReceiverNode()
: Node("imu_receiver_node"), accel_ring_buffer_(RING_BUFFER_SIZE),
ahrs_ring_buffer_(RING_BUFFER_SIZE),
output_ring_buffer_(RING_BUFFER_SIZE) {
// 声明参数 // 声明参数
this->declare_parameter("serial_port", DEFAULT_SERIAL_PORT); this->declare_parameter("serial_port", DEFAULT_SERIAL_PORT);
this->declare_parameter("baudrate", DEFAULT_BAUDRATE); this->declare_parameter("baudrate", DEFAULT_BAUDRATE);
this->declare_parameter("verbose", true); // 是否输出详细日志 this->declare_parameter("verbose", true); // 是否输出详细日志
this->declare_parameter("enable_crc", false); // 是否启用CRC校验默认关闭 this->declare_parameter("enable_crc", false); // 是否启用CRC校验默认关闭
// UKF 参数
this->declare_parameter("ukf_process_noise", 0.001); // 过程噪声
this->declare_parameter("ukf_measurement_noise", 0.03); // 测量噪声
this->declare_parameter("ukf_dt", 0.01); // 采样时间
// 获取参数 // 获取参数
serial_port_ = this->get_parameter("serial_port").as_string(); serial_port_ = this->get_parameter("serial_port").as_string();
baudrate_ = this->get_parameter("baudrate").as_int(); baudrate_ = this->get_parameter("baudrate").as_int();
enable_crc_ = this->get_parameter("enable_crc").as_bool(); enable_crc_ = this->get_parameter("enable_crc").as_bool();
RCLCPP_INFO(this->get_logger(), "================================="); // 获取 UKF 参数并设置
RCLCPP_INFO(this->get_logger(), "IMU 数据接收节点启动 (高实时性版本)"); double process_noise = this->get_parameter("ukf_process_noise").as_double();
double measurement_noise =
this->get_parameter("ukf_measurement_noise").as_double();
double dt = this->get_parameter("ukf_dt").as_double();
// 设置 UKF 参数
ukf_x_.setProcessNoise(process_noise);
ukf_x_.setMeasurementNoise(measurement_noise);
ukf_x_.setDt(dt);
ukf_y_.setProcessNoise(process_noise);
ukf_y_.setMeasurementNoise(measurement_noise);
ukf_y_.setDt(dt);
ukf_z_.setProcessNoise(process_noise);
ukf_z_.setMeasurementNoise(measurement_noise);
ukf_z_.setDt(dt);
RCLCPP_INFO(this->get_logger(), "---------------------------------");
RCLCPP_INFO(this->get_logger(), "IMU 数据接收节点 (UKF + AHRS 融合)");
RCLCPP_INFO(this->get_logger(), "串口: %s", serial_port_.c_str()); RCLCPP_INFO(this->get_logger(), "串口: %s", serial_port_.c_str());
RCLCPP_INFO(this->get_logger(), "波特率: %d", baudrate_); RCLCPP_INFO(this->get_logger(), "波特率: %d", baudrate_);
RCLCPP_INFO(this->get_logger(), "CRC校验: %s", RCLCPP_INFO(this->get_logger(), "CRC校验: %s",
enable_crc_ ? "启用" : "禁用"); enable_crc_ ? "启用" : "禁用");
RCLCPP_INFO(this->get_logger(), "================================="); RCLCPP_INFO(this->get_logger(),
"UKF 参数 - 过程噪声: %.4f, 测量噪声: %.4f, dt: %.4f",
process_noise, measurement_noise, dt);
RCLCPP_INFO(this->get_logger(), "---------------------------------");
// 创建发布者 // 创建发布者
ahrs_pub_ = this->create_publisher<sensor_msgs::msg::Imu>("imu/ahrs", 10); imu_pub_ = this->create_publisher<sensor_msgs::msg::Imu>("imu/data", 10);
body_acc_pub_ = this->create_publisher<geometry_msgs::msg::Vector3>( accel_pub_ = this->create_publisher<geometry_msgs::msg::Vector3>(
"imu/body_acceleration", 10); "imu/body_acceleration", 10);
g_force_pub_ =
this->create_publisher<std_msgs::msg::Float32>("imu/g_force", 10);
connection_status_pub_ = this->create_publisher<std_msgs::msg::Bool>( connection_status_pub_ = this->create_publisher<std_msgs::msg::Bool>(
"imu/connection_status", 10); "imu/connection_status", 10);
@@ -138,12 +507,20 @@ public:
// 创建接收线程 // 创建接收线程
receive_thread_ = std::thread(&ImuReceiverNode::receiveLoop, this); receive_thread_ = std::thread(&ImuReceiverNode::receiveLoop, this);
// 创建融合线程
fusion_thread_ = std::thread(&ImuReceiverNode::fusionLoop, this);
// 创建处理线程(固定频率处理数据)
process_thread_ = std::thread(&ImuReceiverNode::processLoop, this);
// 创建状态发布定时器 // 创建状态发布定时器
status_timer_ = this->create_wall_timer( status_timer_ = this->create_wall_timer(
std::chrono::seconds(1), std::chrono::seconds(1),
std::bind(&ImuReceiverNode::publishStatus, this)); std::bind(&ImuReceiverNode::publishStatus, this));
RCLCPP_INFO(this->get_logger(), "IMU 节点初始化完成"); RCLCPP_INFO(this->get_logger(), "IMU 节点初始化完成");
RCLCPP_INFO(this->get_logger(), "环形缓冲区大小: %zu, 最大发布频率: %zu Hz",
RING_BUFFER_SIZE, MAX_PUBLISH_RATE_HZ);
} }
~ImuReceiverNode() { ~ImuReceiverNode() {
@@ -151,6 +528,12 @@ public:
if (receive_thread_.joinable()) { if (receive_thread_.joinable()) {
receive_thread_.join(); receive_thread_.join();
} }
if (fusion_thread_.joinable()) {
fusion_thread_.join();
}
if (process_thread_.joinable()) {
process_thread_.join();
}
if (serial_fd_ >= 0) { if (serial_fd_ >= 0) {
close(serial_fd_); close(serial_fd_);
} }
@@ -251,45 +634,33 @@ private:
return crc16; return crc16;
} }
// 高实时性接收循环 - 批量读取,无阻塞 // 接收循环 - 只负责解析数据并存入环形缓冲区
void receiveLoop() { void receiveLoop() {
// 使用双缓冲区策略:一个用于接收,一个用于处理
std::vector<uint8_t> rx_buffer; std::vector<uint8_t> rx_buffer;
rx_buffer.reserve(SERIAL_READ_BUF_SIZE * 2); rx_buffer.reserve(SERIAL_READ_BUF_SIZE * 2);
// 临时读取缓冲区
uint8_t read_buf[SERIAL_READ_BUF_SIZE]; uint8_t read_buf[SERIAL_READ_BUF_SIZE];
// 帧解析状态
bool in_frame = false; bool in_frame = false;
std::vector<uint8_t> current_frame; std::vector<uint8_t> current_frame;
current_frame.reserve(MAX_FRAME_SIZE); current_frame.reserve(MAX_FRAME_SIZE);
while (running_ && rclcpp::ok()) { while (running_ && rclcpp::ok()) {
// 批量读取串口数据 - 非阻塞
ssize_t n = read(serial_fd_, read_buf, sizeof(read_buf)); ssize_t n = read(serial_fd_, read_buf, sizeof(read_buf));
if (n > 0) { if (n > 0) {
// 将新数据追加到缓冲区
rx_buffer.insert(rx_buffer.end(), read_buf, read_buf + n); rx_buffer.insert(rx_buffer.end(), read_buf, read_buf + n);
// 处理缓冲区中的数据
size_t i = 0; size_t i = 0;
while (i < rx_buffer.size()) { while (i < rx_buffer.size()) {
uint8_t byte = rx_buffer[i]; uint8_t byte = rx_buffer[i];
if (!in_frame) { if (!in_frame) {
// 查找帧头
if (byte == FRAME_START) { if (byte == FRAME_START) {
// 检查是否有足够的字节来读取帧头信息
if (i + 5 <= rx_buffer.size()) { if (i + 5 <= rx_buffer.size()) {
// 读取帧头信息
uint8_t msg_id = rx_buffer[i + 1]; uint8_t msg_id = rx_buffer[i + 1];
uint8_t data_len = rx_buffer[i + 2]; uint8_t data_len = rx_buffer[i + 2];
uint8_t seq = rx_buffer[i + 3]; uint8_t seq = rx_buffer[i + 3];
uint8_t header_crc = rx_buffer[i + 4]; uint8_t header_crc = rx_buffer[i + 4];
// 可选的CRC校验
bool header_valid = true; bool header_valid = true;
if (enable_crc_) { if (enable_crc_) {
uint8_t header[4] = {FRAME_START, msg_id, data_len, seq}; uint8_t header[4] = {FRAME_START, msg_id, data_len, seq};
@@ -297,7 +668,6 @@ private:
} }
if (header_valid) { if (header_valid) {
// 开始收集帧数据
in_frame = true; in_frame = true;
current_frame.clear(); current_frame.clear();
current_frame.push_back(FRAME_START); current_frame.push_back(FRAME_START);
@@ -306,32 +676,24 @@ private:
current_frame.push_back(seq); current_frame.push_back(seq);
current_frame.push_back(header_crc); current_frame.push_back(header_crc);
// 计算还需要读取多少字节 size_t total_frame_len = 5 + 2 + data_len + 1;
size_t total_frame_len = size_t remaining = total_frame_len - 5;
5 + 2 + data_len + 1; // 头+CRC16+数据+尾
size_t remaining = total_frame_len - 5; // 已经读取了5字节
// 检查缓冲区中是否有足够的数据
if (i + 5 + remaining <= rx_buffer.size()) { if (i + 5 + remaining <= rx_buffer.size()) {
// 数据足够,直接复制剩余部分
current_frame.insert(current_frame.end(), current_frame.insert(current_frame.end(),
rx_buffer.begin() + i + 5, rx_buffer.begin() + i + 5,
rx_buffer.begin() + i + 5 + remaining); rx_buffer.begin() + i + 5 + remaining);
// 检查帧尾
if (current_frame.back() == FRAME_END) { if (current_frame.back() == FRAME_END) {
// 解析帧 // 解析帧
processFrame(current_frame); parseFrame(current_frame);
} }
// 无论是否有帧尾,都结束当前帧处理
in_frame = false; in_frame = false;
i += total_frame_len; i += total_frame_len;
continue; continue;
} else { } else {
// 数据不够,需要等待更多数据
// 删除已处理的部分,保留当前帧的后续数据
rx_buffer.erase(rx_buffer.begin(), rx_buffer.begin() + i); rx_buffer.erase(rx_buffer.begin(), rx_buffer.begin() + i);
i = 5; // 保留当前帧头 i = 5;
break; break;
} }
} }
@@ -339,17 +701,12 @@ private:
} }
i++; i++;
} else { } else {
// 已经在帧中,继续收集数据
current_frame.push_back(byte); current_frame.push_back(byte);
// 检查是否到达帧尾
if (byte == FRAME_END) { if (byte == FRAME_END) {
// 解析帧 parseFrame(current_frame);
processFrame(current_frame);
in_frame = false; in_frame = false;
i++; i++;
} else if (current_frame.size() >= MAX_FRAME_SIZE) { } else if (current_frame.size() >= MAX_FRAME_SIZE) {
// 帧太长,丢弃
in_frame = false; in_frame = false;
i++; i++;
} else { } else {
@@ -358,40 +715,30 @@ private:
} }
} }
// 如果不在帧中且缓冲区太大,清理旧数据
if (!in_frame && rx_buffer.size() > SERIAL_READ_BUF_SIZE) { if (!in_frame && rx_buffer.size() > SERIAL_READ_BUF_SIZE) {
// 保留最后一部分数据,可能包含不完整的帧头
if (rx_buffer.size() > 100) { if (rx_buffer.size() > 100) {
rx_buffer.erase(rx_buffer.begin(), rx_buffer.end() - 100); rx_buffer.erase(rx_buffer.begin(), rx_buffer.end() - 100);
} }
} }
} else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { } else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
// 读取错误
RCLCPP_ERROR(this->get_logger(), "串口读取错误: %s", strerror(errno)); RCLCPP_ERROR(this->get_logger(), "串口读取错误: %s", strerror(errno));
} }
// n == 0 或 EAGAIN: 没有数据可读继续循环无sleep保证实时性
} }
} }
// 处理完整的一帧数据 // 解析帧 - 处理 MSG_BODY_ACCELERATION 和 MSG_AHRS
void processFrame(const std::vector<uint8_t> &frame) { void parseFrame(const std::vector<uint8_t> &frame) {
// 最小帧长度检查:头(5) + CRC16(2) + 尾(1) = 8
if (frame.size() < 8) if (frame.size() < 8)
return; return;
// 检查帧尾
if (frame.back() != FRAME_END) if (frame.back() != FRAME_END)
return; return;
uint8_t msg_id = frame[1]; uint8_t msg_id = frame[1];
uint8_t data_len = frame[2]; uint8_t data_len = frame[2];
// 验证数据长度
size_t expected_len = 5 + 2 + data_len + 1; size_t expected_len = 5 + 2 + data_len + 1;
if (frame.size() != expected_len) if (frame.size() != expected_len)
return; return;
// 可选的数据CRC校验
if (enable_crc_) { if (enable_crc_) {
uint16_t data_crc = (frame[5] << 8) | frame[6]; uint16_t data_crc = (frame[5] << 8) | frame[6];
uint16_t calc_crc = calculateCRC16(frame.data() + 7, data_len); uint16_t calc_crc = calculateCRC16(frame.data() + 7, data_len);
@@ -399,144 +746,177 @@ private:
return; return;
} }
// 解析数据包 const uint8_t *payload = frame.data() + 7;
parsePacket(msg_id, frame.data() + 7, data_len);
}
void parsePacket(uint8_t msg_id, const uint8_t *data, uint8_t len) { // 处理 MSG_BODY_ACCELERATION 数据包 (0x62)
switch (msg_id) { if (msg_id == MSG_BODY_ACCELERATION) {
case MSG_AHRS: if (data_len >= 16) {
ahrs_count_++; AccelData data;
parseAHRS(data, len); data.valid = true;
break;
case MSG_BODY_ACCELERATION: // 滤波修正后的机体系加速度 (m/s²) - 不包括重力
body_acc_count_++; data.acc_x = *reinterpret_cast<const float *>(payload);
parseBodyAcceleration(data, len); data.acc_y = *reinterpret_cast<const float *>(payload + 4);
break; data.acc_z = *reinterpret_cast<const float *>(payload + 8);
default:
break; // 当地重力加速度 (m/s²)
data.g_force = *reinterpret_cast<const float *>(payload + 12);
data.receive_time = std::chrono::steady_clock::now();
// 推入加速度环形缓冲区
accel_ring_buffer_.push(data);
}
}
// 处理 MSG_AHRS 数据包 (0x41)
else if (msg_id == MSG_AHRS) {
if (data_len >= 48) {
AhrsData data;
data.valid = true;
// 角速度 (rad/s)
data.roll_speed = *reinterpret_cast<const float *>(payload);
data.pitch_speed = *reinterpret_cast<const float *>(payload + 4);
data.heading_speed = *reinterpret_cast<const float *>(payload + 8);
// 欧拉角 (rad)
data.roll = *reinterpret_cast<const float *>(payload + 12);
data.pitch = *reinterpret_cast<const float *>(payload + 16);
data.heading = *reinterpret_cast<const float *>(payload + 20);
// 四元数
data.q1 = *reinterpret_cast<const float *>(payload + 24);
data.q2 = *reinterpret_cast<const float *>(payload + 28);
data.q3 = *reinterpret_cast<const float *>(payload + 32);
data.q4 = *reinterpret_cast<const float *>(payload + 36);
// 时间戳 (us)
data.timestamp_us = *reinterpret_cast<const int64_t *>(payload + 40);
data.receive_time = std::chrono::steady_clock::now();
// 推入 AHRS 环形缓冲区
ahrs_ring_buffer_.push(data);
}
} }
} }
// 解析 AHRS 数据 (0x41) // 融合线程 - 同步加速度数据和 AHRS 数据
void parseAHRS(const uint8_t *data, uint8_t len) { void fusionLoop() {
if (len < 48) while (running_ && rclcpp::ok()) {
return; // 尝试获取最新的加速度和 AHRS 数据
AccelData accel_data;
AhrsData ahrs_data;
// 解析角速度 (rad/s) bool has_accel = accel_ring_buffer_.peek_latest(accel_data);
float roll_speed = *reinterpret_cast<const float *>(data); bool has_ahrs = ahrs_ring_buffer_.peek_latest(ahrs_data);
float pitch_speed = *reinterpret_cast<const float *>(data + 4);
float heading_speed = *reinterpret_cast<const float *>(data + 8);
// 解析欧拉角 (rad) if (has_accel && has_ahrs) {
float roll = *reinterpret_cast<const float *>(data + 12); // 检查时间同步(简单策略:都取最新的)
float pitch = *reinterpret_cast<const float *>(data + 16); // 使用 UKF 对加速度进行滤波
float heading = *reinterpret_cast<const float *>(data + 20); float filtered_acc_x = ukf_x_.update(accel_data.acc_x);
float filtered_acc_y = ukf_y_.update(accel_data.acc_y);
float filtered_acc_z = ukf_z_.update(accel_data.acc_z);
// 解析四元数 // 创建融合后的数据
float q1 = *reinterpret_cast<const float *>(data + 24); ImuDataFrame fused_data;
float q2 = *reinterpret_cast<const float *>(data + 28); fused_data.valid = true;
float q3 = *reinterpret_cast<const float *>(data + 32);
float q4 = *reinterpret_cast<const float *>(data + 36);
// 解析时间戳 (int64_t, us) // UKF 滤波后的加速度
int64_t timestamp_us = *reinterpret_cast<const int64_t *>(data + 40); fused_data.acc_x = filtered_acc_x;
fused_data.acc_y = filtered_acc_y;
fused_data.acc_z = filtered_acc_z;
// 将 IMU 时间戳转换为 ROS 时间 // AHRS 提供的角速度 (注意AHRS中的RollSpeed对应X轴角速度)
// 使用当前 ROS 时间作为基准,计算相对时间 fused_data.gyro_x = ahrs_data.roll_speed;
rclcpp::Time current_time = this->now(); fused_data.gyro_y = ahrs_data.pitch_speed;
rclcpp::Time imu_time = fused_data.gyro_z = ahrs_data.heading_speed;
imu_base_time_ + rclcpp::Duration(0, timestamp_us * 1000);
// 如果是第一个数据包,初始化基准时间 // AHRS 提供的四元数 (q1是实部ROS使用q4作为实部注意转换)
if (!imu_time_initialized_) { // FDI: [q1, q2, q3, q4] where q1 is scalar
imu_base_time_ = current_time - rclcpp::Duration(0, timestamp_us * 1000); // ROS: [x, y, z, w] where w is scalar
imu_time = current_time; fused_data.q1 = ahrs_data.q2; // x
imu_time_initialized_ = true; fused_data.q2 = ahrs_data.q3; // y
fused_data.q3 = ahrs_data.q4; // z
fused_data.q4 = ahrs_data.q1; // w (实部)
fused_data.timestamp = this->now();
// 推入输出缓冲区
output_ring_buffer_.push(fused_data);
} }
// 保存最新的 IMU 时间戳,供 BodyAcceleration 使用 // 短暂休眠
latest_imu_timestamp_ = imu_time; std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
// 处理循环 - 固定频率从环形缓冲区读取并发布
void processLoop() {
ImuDataFrame data;
auto next_publish_time = std::chrono::steady_clock::now();
while (running_ && rclcpp::ok()) {
// 等待到下一次发布时间
std::this_thread::sleep_until(next_publish_time);
next_publish_time += PUBLISH_PERIOD;
// 获取最新的数据(如果有)
// 策略:只取最新的一个数据,丢弃中间堆积的数据
bool has_data = false;
ImuDataFrame latest_data;
// 清空缓冲区,只保留最新的
while (output_ring_buffer_.try_pop(data)) {
latest_data = data;
has_data = true;
}
if (has_data) {
publishData(latest_data);
}
}
}
// 发布数据
void publishData(const ImuDataFrame &data) {
// 发布加速度消息 (UKF 滤波后的数据)
geometry_msgs::msg::Vector3 accel_msg;
accel_msg.x = data.acc_x;
accel_msg.y = data.acc_y;
accel_msg.z = data.acc_z;
accel_pub_->publish(accel_msg);
// 发布 IMU 消息 // 发布 IMU 消息
sensor_msgs::msg::Imu imu_msg; sensor_msgs::msg::Imu imu_msg;
imu_msg.header.stamp = imu_time; imu_msg.header.stamp = data.timestamp;
imu_msg.header.frame_id = "imu_link"; imu_msg.header.frame_id = "imu_link";
// 角速度 (转换为 ROS 坐标系: x=roll, y=pitch, z=heading) // 线性加速度 - 使用 UKF 滤波后的值
imu_msg.angular_velocity.x = roll_speed; imu_msg.linear_acceleration.x = data.acc_x;
imu_msg.angular_velocity.y = pitch_speed; imu_msg.linear_acceleration.y = data.acc_y;
imu_msg.angular_velocity.z = heading_speed; imu_msg.linear_acceleration.z = data.acc_z;
// 四元数 (FDI: Q1=w, Q2=x, Q3=y, Q4=z -> ROS: x,y,z,w) // 角速度 - 使用 AHRS 提供的角速度
imu_msg.orientation.x = q2; imu_msg.angular_velocity.x = data.gyro_x;
imu_msg.orientation.y = q3; imu_msg.angular_velocity.y = data.gyro_y;
imu_msg.orientation.z = q4; imu_msg.angular_velocity.z = data.gyro_z;
imu_msg.orientation.w = q1;
// 线性加速度暂时设为0从 MSG_BODY_ACCELERATION 获取) // 姿态 - 使用 AHRS 提供的四元数
imu_msg.linear_acceleration.x = 0; imu_msg.orientation.x = data.q1;
imu_msg.linear_acceleration.y = 0; imu_msg.orientation.y = data.q2;
imu_msg.linear_acceleration.z = 0; imu_msg.orientation.z = data.q3;
imu_msg.orientation.w = data.q4;
ahrs_pub_->publish(imu_msg); imu_pub_->publish(imu_msg);
// 根据 verbose 参数输出日志 // 日志
bool verbose = this->get_parameter("verbose").as_bool();
if (verbose) {
RCLCPP_INFO(
this->get_logger(),
"AHRS - Roll: %.3f, Pitch: %.3f, Heading: %.3f rad, Time: %ld us",
roll, pitch, heading, timestamp_us);
} else {
RCLCPP_DEBUG(this->get_logger(),
"AHRS - Roll: %.3f, Pitch: %.3f, Heading: %.3f rad", roll,
pitch, heading);
}
}
// 解析机体系加速度数据 (0x62)
void parseBodyAcceleration(const uint8_t *data, uint8_t len) {
if (len < 16)
return;
// 解析机体系加速度 (m/s²)
float acc_x = *reinterpret_cast<const float *>(data);
float acc_y = *reinterpret_cast<const float *>(data + 4);
float acc_z = *reinterpret_cast<const float *>(data + 8);
// 解析重力加速度
float g_force = *reinterpret_cast<const float *>(data + 12);
// 使用与 AHRS 相同的时间戳(如果可用),否则使用当前时间
rclcpp::Time msg_time = latest_imu_timestamp_;
if (msg_time.nanoseconds() == 0) {
msg_time = this->now();
}
// 发布机体系加速度
geometry_msgs::msg::Vector3 acc_msg;
acc_msg.x = acc_x;
acc_msg.y = acc_y;
acc_msg.z = acc_z;
body_acc_pub_->publish(acc_msg);
// 发布重力加速度
std_msgs::msg::Float32 g_msg;
g_msg.data = g_force;
g_force_pub_->publish(g_msg);
// 根据 verbose 参数输出日志
bool verbose = this->get_parameter("verbose").as_bool();
if (verbose) {
RCLCPP_INFO(this->get_logger(), RCLCPP_INFO(this->get_logger(),
"BodyAcc - X: %.3f, Y: %.3f, Z: %.3f m/s2, G: %.3f", acc_x, "Accel(UKF): [%.3f, %.3f, %.3f] m/s² | "
acc_y, acc_z, g_force); "Gyro: [%.3f, %.3f, %.3f] rad/s | "
} else { "Quat: [%.3f, %.3f, %.3f, %.3f]",
RCLCPP_DEBUG(this->get_logger(), data.acc_x, data.acc_y, data.acc_z, data.gyro_x, data.gyro_y,
"BodyAcc - X: %.3f, Y: %.3f, Z: %.3f m/s2, G: %.3f", acc_x, data.gyro_z, data.q1, data.q2, data.q3, data.q4);
acc_y, acc_z, g_force);
}
} }
void publishStatus() { void publishStatus() {
@@ -544,10 +924,8 @@ private:
msg.data = is_connected_ && (serial_fd_ >= 0); msg.data = is_connected_ && (serial_fd_ >= 0);
connection_status_pub_->publish(msg); connection_status_pub_->publish(msg);
if (is_connected_) { if (!is_connected_) {
RCLCPP_INFO_THROTTLE(this->get_logger(), *this->get_clock(), 5000, RCLCPP_WARN(this->get_logger(), "IMU 接收不正常");
"IMU 接收正常 - AHRS: %zu, BodyAcc: %zu",
ahrs_count_.load(), body_acc_count_.load());
} }
} }
@@ -560,19 +938,21 @@ private:
bool is_connected_ = false; bool is_connected_ = false;
std::atomic<bool> running_{true}; std::atomic<bool> running_{true};
std::thread receive_thread_; std::thread receive_thread_;
std::thread fusion_thread_;
// 统计信息 // 线程
std::atomic<size_t> ahrs_count_; std::thread process_thread_;
std::atomic<size_t> body_acc_count_; RingBuffer<AccelData> accel_ring_buffer_;
RingBuffer<AhrsData> ahrs_ring_buffer_;
RingBuffer<ImuDataFrame> output_ring_buffer_;
// IMU 时间戳同步 // UKF 滤波器(用于加速度滤波)
bool imu_time_initialized_ = false; UKF ukf_x_;
rclcpp::Time imu_base_time_; UKF ukf_y_;
rclcpp::Time latest_imu_timestamp_; UKF ukf_z_;
rclcpp::Publisher<sensor_msgs::msg::Imu>::SharedPtr ahrs_pub_; rclcpp::Publisher<sensor_msgs::msg::Imu>::SharedPtr imu_pub_;
rclcpp::Publisher<geometry_msgs::msg::Vector3>::SharedPtr body_acc_pub_; rclcpp::Publisher<geometry_msgs::msg::Vector3>::SharedPtr accel_pub_;
rclcpp::Publisher<std_msgs::msg::Float32>::SharedPtr g_force_pub_;
rclcpp::Publisher<std_msgs::msg::Bool>::SharedPtr connection_status_pub_; rclcpp::Publisher<std_msgs::msg::Bool>::SharedPtr connection_status_pub_;
rclcpp::TimerBase::SharedPtr status_timer_; rclcpp::TimerBase::SharedPtr status_timer_;
}; };

View File

@@ -38,13 +38,16 @@ public:
this->declare_parameter("send_frequency", 50.0); // Hz this->declare_parameter("send_frequency", 50.0); // Hz
// 控制参数 // 控制参数
this->declare_parameter("x_move", 0); // 平动左右 [-660, 660] this->declare_parameter("x_move", 0); // 平动左右 [-660, 660] 负为向左
this->declare_parameter("y_move", 0); // 平动前后 [-660, 660] this->declare_parameter("y_move", 0); // 平动前后 [-660, 660] 负为向后
this->declare_parameter("yaw", 0); // 云台偏航 [-660, 660] this->declare_parameter("yaw", 0); // 云台偏航 [-660, 660] 负为向左
this->declare_parameter("pitch", 0); // 云台俯仰 [-660, 660] this->declare_parameter("pitch", 0); // 云台俯仰 [-660, 660] 负为向下
this->declare_parameter("feed", 0); // 拨弹轮 [-660, 660] this->declare_parameter("feed",
this->declare_parameter("left_switch", 0); // 左拨杆 [1, 3] 0); // 拨弹轮 [-660, 660] 660 开火0 无动作
this->declare_parameter("right_switch", 0); // 右拨杆 [1, 3] this->declare_parameter("left_switch",
3); // 左拨杆 [1, 3] 2 摩擦轮打开 3 关闭
this->declare_parameter("right_switch",
3); // 右拨杆 [1, 3] 2 小陀螺 3 关闭
// 获取参数 // 获取参数
serial_port_ = this->get_parameter("serial_port").as_string(); serial_port_ = this->get_parameter("serial_port").as_string();
@@ -178,7 +181,7 @@ private:
case 921600: case 921600:
return B921600; return B921600;
default: default:
RCLCPP_WARN(this->get_logger(), "不支持的波特率 %d使用 115200", RCLCPP_WARN(this->get_logger(), "不支持的波特率 %d, 使用 115200",
baudrate); baudrate);
return B115200; return B115200;
} }
@@ -240,9 +243,10 @@ private:
// CRC8 (除帧头和帧尾外的所有数据不包括CRC本身) // CRC8 (除帧头和帧尾外的所有数据不包括CRC本身)
// 当前idx=13, 数据长度=11 (从frame[2]到frame[12]) // 当前idx=13, 数据长度=11 (从frame[2]到frame[12])
// uint8_t crc_value = calculateCRC8(frame + 2, 11); // uint8_t crc_value = calculateCRC8(frame + 2, 11);
frame[idx++] = 0xCC; // crc_value // 帧尾 1接收没有写 CRC8因此固定为 CC
frame[idx++] = 0xCC;
// 帧尾 // 帧尾 2
frame[idx++] = FRAME_TAIL; frame[idx++] = FRAME_TAIL;
// 调试输出:打印完整帧内容 // 调试输出:打印完整帧内容