Skip to content

Commit fcf028f

Browse files
committed
ekf to fuse imu and wheel odom
for some reason the fuse_imu param is not read correctly from the vesc.lua file, but setting it to true in the header is fine for now
1 parent 9bd2fbe commit fcf028f

7 files changed

Lines changed: 453 additions & 63 deletions

File tree

CMakeLists.txt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ IF(CMAKE_VERSION VERSION_LESS "3.7.0")
2626
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
2727
ENDIF()
2828

29-
SET(CMAKE_CXX_FLAGS "-std=c++11 -march=native -Werror -Wall -g")
29+
SET(CMAKE_CXX_FLAGS "-std=c++11 -march=native -Werror -Wall -g -Wno-error=class-memaccess")
3030

3131
IF(${CMAKE_BUILD_TYPE} MATCHES "Release")
3232
MESSAGE(STATUS "Additional Flags for Release mode")
@@ -57,6 +57,7 @@ find_package(cv_bridge REQUIRED)
5757
find_package(OpenCV REQUIRED)
5858
find_package(rosidl_default_generators REQUIRED)
5959
find_package(rosidl_default_runtime REQUIRED)
60+
find_package(Eigen3 REQUIRED)
6061

6162
FIND_PACKAGE(Qt5 COMPONENTS Core Widgets Gui WebSockets OpenGL REQUIRED)
6263
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -116,11 +117,14 @@ IF(${CMAKE_BUILD_MODE} MATCHES "Hardware")
116117
src/vesc_driver/vesc_driver.cpp
117118
src/vesc_driver/vesc_interface.cpp
118119
src/vesc_driver/vesc_packet.cpp
119-
src/vesc_driver/vesc_packet_factory.cpp)
120-
ament_target_dependencies(vesc_driver rclcpp std_msgs geometry_msgs nav_msgs sensor_msgs amrl_msgs rosidl_default_runtime tf2_ros)
120+
src/vesc_driver/vesc_packet_factory.cpp
121+
src/vesc_driver/ekf_fusion.cpp
122+
${CMAKE_CURRENT_SOURCE_DIR}/../mpu6050driver/src/mpu6050sensor.cpp)
123+
ament_target_dependencies(vesc_driver rclcpp std_msgs geometry_msgs nav_msgs sensor_msgs amrl_msgs rosidl_default_runtime tf2_ros Eigen3)
121124
target_include_directories(vesc_driver PRIVATE
122-
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_cpp>)
123-
target_link_libraries(vesc_driver ${libs} ${UT_AUTOMATA_TYPESUPPORT_LINK})
125+
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/rosidl_generator_cpp>
126+
${CMAKE_CURRENT_SOURCE_DIR}/../mpu6050driver/include)
127+
target_link_libraries(vesc_driver ${libs} ${UT_AUTOMATA_TYPESUPPORT_LINK} i2c)
124128

125129
add_executable(gui
126130
src/gui/gui_main.cc

config/joystick.lua

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,3 @@ joystick_name="Sony_DualShock_4"
99
-- "left": left stick only (horizontal for steering, vertical for drive)
1010
-- "right": right stick only (horizontal for steering, vertical for drive)
1111
joystick_mode = "both"
12-
13-
14-
15-

config/vesc.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,11 @@ max_deceleration = 6.0; -- m/s^2
2323

2424
joystick_normal_speed = 1.0; -- m/s
2525
joystick_turbo_speed = 2.0; -- m/s
26+
27+
-- IMU fusion parameters
28+
fuse_imu = true; -- Set to true to fuse IMU data with odometry using EKF
29+
i2c_bus_number = 7; -- I2C bus number for MPU6050 sensor
30+
calibrate_imu = true; -- Calibrate IMU on startup
31+
imu_gyro_range = 0; -- 0=250, 1=500, 2=1000, 3=2000 deg/s
32+
imu_accel_range = 0; -- 0=2g, 1=4g, 2=8g, 3=16g
33+
imu_dlpf_bandwidth = 0; -- 0=260Hz, 1=184Hz, 2=94Hz, 3=44Hz, 4=21Hz, 5=10Hz, 6=5Hz

src/vesc_driver/ekf_fusion.cpp

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// -*- mode:c++; fill-column: 100; -*-
2+
3+
#include "ekf_fusion.h"
4+
#include <cmath>
5+
#include <iostream>
6+
7+
namespace vesc_driver
8+
{
9+
10+
EKFFusion::EKFFusion(float wheelbase)
11+
: wheelbase_(wheelbase)
12+
{
13+
// Initialize state to zero
14+
state_.setZero();
15+
16+
// Initialize covariance matrix with moderate uncertainty
17+
P_.setIdentity();
18+
P_(0, 0) = 0.01; // x position variance
19+
P_(1, 1) = 0.01; // y position variance
20+
P_(2, 2) = 0.01; // theta variance
21+
P_(3, 3) = 0.1; // velocity variance
22+
P_(4, 4) = 0.1; // angular velocity variance
23+
24+
// Process noise covariance - tuned for vehicle dynamics
25+
Q_.setIdentity();
26+
Q_(0, 0) = 0.001; // x position process noise
27+
Q_(1, 1) = 0.001; // y position process noise
28+
Q_(2, 2) = 0.005; // theta process noise
29+
Q_(3, 3) = 0.1; // velocity process noise
30+
Q_(4, 4) = 0.1; // angular velocity process noise
31+
32+
// IMU measurement noise - gyroscope noise
33+
R_imu_ = 0.01; // rad/s variance
34+
}
35+
36+
void EKFFusion::predict(double dt, float rpm, float steering_angle,
37+
float speed_to_erpm_gain, float speed_to_erpm_offset)
38+
{
39+
if (dt <= 0.0 || dt > 1.0) {
40+
// Skip invalid time steps
41+
return;
42+
}
43+
44+
// Convert RPM to linear velocity
45+
float lin_vel = (rpm - speed_to_erpm_offset) / speed_to_erpm_gain;
46+
47+
// Clamp velocity to zero for minuscule values
48+
if (std::fabs(lin_vel) < 0.01) {
49+
lin_vel = 0.0;
50+
}
51+
52+
// Calculate angular velocity from steering angle
53+
float rot_vel = 0.0;
54+
if (std::fabs(steering_angle) > 1e-6) {
55+
float turn_radius = wheelbase_ / std::tan(steering_angle);
56+
if (std::fabs(turn_radius) > 1e-6) {
57+
rot_vel = lin_vel / turn_radius;
58+
}
59+
}
60+
61+
// Extract current state
62+
float x = state_(0);
63+
float y = state_(1);
64+
float theta = state_(2);
65+
66+
// Predict new state using odometry motion model
67+
float cos_theta = std::cos(theta);
68+
float sin_theta = std::sin(theta);
69+
70+
state_(0) = x + lin_vel * dt * cos_theta; // x
71+
state_(1) = y + lin_vel * dt * sin_theta; // y
72+
state_(2) = theta + rot_vel * dt; // theta
73+
state_(3) = lin_vel; // v
74+
state_(4) = rot_vel; // omega
75+
76+
// Normalize theta to [-pi, pi]
77+
while (state_(2) > M_PI) state_(2) -= 2.0 * M_PI;
78+
while (state_(2) < -M_PI) state_(2) += 2.0 * M_PI;
79+
80+
// Compute Jacobian of motion model F
81+
Eigen::Matrix<float, 5, 5> F = Eigen::Matrix<float, 5, 5>::Identity();
82+
F(0, 2) = -lin_vel * dt * sin_theta; // dx/dtheta
83+
F(0, 3) = dt * cos_theta; // dx/dv
84+
F(1, 2) = lin_vel * dt * cos_theta; // dy/dtheta
85+
F(1, 3) = dt * sin_theta; // dy/dv
86+
F(2, 4) = dt; // dtheta/domega
87+
88+
// Predict covariance: P = F * P * F^T + Q
89+
P_ = F * P_ * F.transpose() + Q_;
90+
}
91+
92+
void EKFFusion::updateIMU(float angular_velocity_z, bool available)
93+
{
94+
if (!available) {
95+
return; // No IMU data available
96+
}
97+
98+
// Measurement model: z = H * x + v
99+
// We're measuring omega (angular velocity) from IMU
100+
// H = [0, 0, 0, 0, 1] - selects the omega state
101+
102+
Eigen::Matrix<float, 1, 5> H;
103+
H << 0, 0, 0, 0, 1;
104+
105+
// Innovation (measurement residual)
106+
float y = angular_velocity_z - state_(4);
107+
108+
// Innovation covariance
109+
float S = H * P_ * H.transpose() + R_imu_;
110+
111+
// Kalman gain
112+
Eigen::Matrix<float, 5, 1> K = P_ * H.transpose() / S;
113+
114+
// Update state estimate
115+
state_ = state_ + K * y;
116+
117+
// Normalize theta
118+
while (state_(2) > M_PI) state_(2) -= 2.0 * M_PI;
119+
while (state_(2) < -M_PI) state_(2) += 2.0 * M_PI;
120+
121+
// Update covariance estimate
122+
Eigen::Matrix<float, 5, 5> I = Eigen::Matrix<float, 5, 5>::Identity();
123+
P_ = (I - K * H) * P_;
124+
}
125+
126+
void EKFFusion::getState(float& x, float& y, float& theta, float& v, float& omega) const
127+
{
128+
x = state_(0);
129+
y = state_(1);
130+
theta = state_(2);
131+
v = state_(3);
132+
omega = state_(4);
133+
}
134+
135+
void EKFFusion::reset()
136+
{
137+
state_.setZero();
138+
P_.setIdentity();
139+
P_(0, 0) = 0.01;
140+
P_(1, 1) = 0.01;
141+
P_(2, 2) = 0.01;
142+
P_(3, 3) = 0.1;
143+
P_(4, 4) = 0.1;
144+
}
145+
146+
} // namespace vesc_driver

src/vesc_driver/ekf_fusion.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// -*- mode:c++; fill-column: 100; -*-
2+
3+
#ifndef VESC_DRIVER_EKF_FUSION_H_
4+
#define VESC_DRIVER_EKF_FUSION_H_
5+
6+
// Suppress Eigen warnings about class-memaccess in NEON optimizations
7+
#pragma GCC diagnostic push
8+
#pragma GCC diagnostic ignored "-Wclass-memaccess"
9+
#include <Eigen/Dense>
10+
#pragma GCC diagnostic pop
11+
12+
namespace vesc_driver
13+
{
14+
15+
/**
16+
* @brief Extended Kalman Filter for fusing odometry with IMU data
17+
*
18+
* State vector: [x, y, theta, v, omega]
19+
* - x, y: position in odom frame
20+
* - theta: orientation (yaw)
21+
* - v: linear velocity
22+
* - omega: angular velocity
23+
*/
24+
class EKFFusion
25+
{
26+
public:
27+
/**
28+
* @brief Constructor
29+
* @param wheelbase The wheelbase of the vehicle (m)
30+
*/
31+
explicit EKFFusion(float wheelbase);
32+
33+
/**
34+
* @brief Predict step using odometry model
35+
* @param dt Time step (seconds)
36+
* @param rpm Motor RPM from VESC
37+
* @param steering_angle Current steering angle (radians)
38+
* @param speed_to_erpm_gain Conversion gain from speed to ERPM
39+
* @param speed_to_erpm_offset Conversion offset from speed to ERPM
40+
*/
41+
void predict(double dt, float rpm, float steering_angle,
42+
float speed_to_erpm_gain, float speed_to_erpm_offset);
43+
44+
/**
45+
* @brief Update step using IMU angular velocity measurement
46+
* @param angular_velocity_z Angular velocity from IMU (rad/s)
47+
* @param available Whether IMU data is available
48+
*/
49+
void updateIMU(float angular_velocity_z, bool available);
50+
51+
/**
52+
* @brief Get current state estimate
53+
* @param x Position x (m)
54+
* @param y Position y (m)
55+
* @param theta Orientation (rad)
56+
* @param v Linear velocity (m/s)
57+
* @param omega Angular velocity (rad/s)
58+
*/
59+
void getState(float& x, float& y, float& theta, float& v, float& omega) const;
60+
61+
/**
62+
* @brief Reset the filter to initial state
63+
*/
64+
void reset();
65+
66+
private:
67+
// State vector [x, y, theta, v, omega]
68+
Eigen::Matrix<float, 5, 1> state_;
69+
70+
// State covariance matrix
71+
Eigen::Matrix<float, 5, 5> P_;
72+
73+
// Process noise covariance
74+
Eigen::Matrix<float, 5, 5> Q_;
75+
76+
// IMU measurement noise covariance (for omega)
77+
float R_imu_;
78+
79+
// Vehicle wheelbase
80+
float wheelbase_;
81+
};
82+
83+
} // namespace vesc_driver
84+
85+
#endif // VESC_DRIVER_EKF_FUSION_H_

0 commit comments

Comments
 (0)