Skip to content

Commit 638b1e5

Browse files
committed
Add IMU readings
1 parent 98be568 commit 638b1e5

4 files changed

Lines changed: 319 additions & 2 deletions

File tree

TEST_CHECKLIST.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# VESC Hardware Interface Test Checklist
2+
3+
## IMU State Interface Units Verification
4+
5+
### Quaternion Orientation (unitless, normalized)
6+
- [ ] Verify `orientation.x` is normalized quaternion component
7+
- [ ] Verify `orientation.y` is normalized quaternion component
8+
- [ ] Verify `orientation.z` is normalized quaternion component
9+
- [ ] Verify `orientation.w` is normalized quaternion component
10+
- [ ] Verify quaternion magnitude: sqrt(x² + y² + z² + w²) ≈ 1.0
11+
12+
### Euler Angles (radians)
13+
- [ ] Verify `roll` is in radians (compare raw VESC degrees * π/180)
14+
- [ ] Verify `pitch` is in radians (compare raw VESC degrees * π/180)
15+
- [ ] Verify `yaw` is in radians (compare raw VESC degrees * π/180)
16+
- [ ] Verify range: all values between -π and +π
17+
18+
### Angular Velocity (rad/s)
19+
- [ ] Verify `angular_velocity.x` is in rad/s (compare raw VESC deg/s * π/180)
20+
- [ ] Verify `angular_velocity.y` is in rad/s (compare raw VESC deg/s * π/180)
21+
- [ ] Verify `angular_velocity.z` is in rad/s (compare raw VESC deg/s * π/180)
22+
23+
### Linear Acceleration (m/s²)
24+
- [ ] Verify `linear_acceleration.x` is in m/s² (compare raw VESC 'g' * 9.80665)
25+
- [ ] Verify `linear_acceleration.y` is in m/s² (compare raw VESC 'g' * 9.80665)
26+
- [ ] Verify `linear_acceleration.z` is in m/s² (compare raw VESC 'g' * 9.80665)
27+
- [ ] Verify stationary gravity reading: sqrt(x² + y² + z²) ≈ 9.80665 m/s²
28+
29+
### Magnetic Field (raw units)
30+
- [ ] Verify `magnetic_field.x` matches raw VESC magnetometer value
31+
- [ ] Verify `magnetic_field.y` matches raw VESC magnetometer value
32+
- [ ] Verify `magnetic_field.z` matches raw VESC magnetometer value
33+
34+
## Motor Position Interface
35+
36+
### Position Unit Conversion (radians, mechanical)
37+
- [ ] Verify position is in mechanical radians (output shaft)
38+
- [ ] Test conversion formula: `mechanical_rad = (vesc_deg * π/180) / gear_ratio`
39+
- [ ] With gear_ratio=1.0: verify 360° VESC = 2π rad mechanical
40+
- [ ] With gear_ratio=10.0: verify 3600° VESC = 2π rad mechanical
41+
- [ ] Verify position increases with forward motor rotation
42+
43+
### Position and Pole Pairs
44+
- [ ] Verify pole_pairs does NOT affect position reading
45+
- [ ] Position should be independent of pole_pairs parameter
46+
47+
### Position Command Conversion (radians to degrees)
48+
- [ ] Verify command conversion: `vesc_deg = mechanical_rad * gear_ratio * 180/π`
49+
- [ ] Command 2π rad with gear_ratio=1.0 → verify VESC receives 360°
50+
- [ ] Command π rad with gear_ratio=5.0 → verify VESC receives 900°
51+
52+
## Motor Velocity Interface
53+
54+
### Velocity Unit Conversion (rad/s, mechanical)
55+
- [ ] Verify velocity is in mechanical rad/s (output shaft)
56+
- [ ] Test conversion formula: `mechanical_rad_s = (vesc_erpm / pole_pairs) * (2π/60) / gear_ratio`
57+
- [ ] With pole_pairs=1, gear_ratio=1.0: verify 60 ERPM = 2π rad/s
58+
- [ ] With pole_pairs=7, gear_ratio=1.0: verify 420 ERPM = 2π rad/s
59+
- [ ] With pole_pairs=1, gear_ratio=10.0: verify 600 ERPM = 2π rad/s
60+
61+
### Velocity and Gear Ratio
62+
- [ ] Verify higher gear_ratio reduces mechanical velocity (given constant ERPM)
63+
- [ ] With gear_ratio=1.0 vs 10.0: same ERPM → 10x difference in rad/s
64+
- [ ] Verify velocity sign matches motor rotation direction
65+
66+
### Velocity and Pole Pairs
67+
- [ ] Verify higher pole_pairs increases mechanical velocity (given constant ERPM)
68+
- [ ] With pole_pairs=1 vs 7: same ERPM → 7x difference in rad/s
69+
- [ ] ERPM = motor_rpm * pole_pairs (verify this relationship)
70+
71+
### Velocity Command Conversion (rad/s to ERPM)
72+
- [ ] Verify command conversion: `erpm = mechanical_rad_s * (60/2π) * gear_ratio * pole_pairs`
73+
- [ ] Command 2π rad/s with gear_ratio=1.0, pole_pairs=1 → verify VESC receives 60 ERPM
74+
- [ ] Command 1 rad/s with gear_ratio=5.0, pole_pairs=7 → verify VESC receives ~333 ERPM
75+
76+
## Published topics
77+
78+
- [ ] Verify `~/vesc_state` topic publishes `vesc_msgs::msg::VescState` messages
79+
- [ ] Verify `~/vesc_imu` topic publishes `vesc_msgs::msg::VescImuStamped` messages

vesc_hardware/include/vesc_hardware/vesc_hardware.hpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "rclcpp_lifecycle/state.hpp"
4545
#include "realtime_tools/realtime_publisher.hpp"
4646
#include "vesc_driver/vesc_interface.hpp"
47+
#include "vesc_msgs/msg/vesc_imu_stamped.hpp"
4748
#include "vesc_msgs/msg/vesc_state.hpp"
4849

4950
namespace vesc_hardware
@@ -146,7 +147,9 @@ class VescHardware : public hardware_interface::SystemInterface {
146147

147148
// VESC packet processing
148149
void processValuesPacket(const vesc_driver::VescPacketValues *values_packet);
150+
void processImuPacket(const vesc_driver::VescPacketImu *imu_packet);
149151
void publishVescState(const vesc_driver::VescPacketValues & values_packet);
152+
void publishVescImu(const vesc_driver::VescPacketImu & imu_packet);
150153

151154
// Conversion functions for VESC values to mechanical values (for reading
152155
// state)
@@ -185,6 +188,24 @@ class VescHardware : public hardware_interface::SystemInterface {
185188
std::atomic<double> hw_state_position_;
186189
std::atomic<double> hw_state_velocity_;
187190

191+
// IMU state storage (atomic for thread-safe access from callback)
192+
std::atomic<double> hw_imu_orientation_x_;
193+
std::atomic<double> hw_imu_orientation_y_;
194+
std::atomic<double> hw_imu_orientation_z_;
195+
std::atomic<double> hw_imu_orientation_w_;
196+
std::atomic<double> hw_imu_roll_;
197+
std::atomic<double> hw_imu_pitch_;
198+
std::atomic<double> hw_imu_yaw_;
199+
std::atomic<double> hw_imu_angular_velocity_x_;
200+
std::atomic<double> hw_imu_angular_velocity_y_;
201+
std::atomic<double> hw_imu_angular_velocity_z_;
202+
std::atomic<double> hw_imu_linear_acceleration_x_;
203+
std::atomic<double> hw_imu_linear_acceleration_y_;
204+
std::atomic<double> hw_imu_linear_acceleration_z_;
205+
std::atomic<double> hw_imu_magnetic_field_x_;
206+
std::atomic<double> hw_imu_magnetic_field_y_;
207+
std::atomic<double> hw_imu_magnetic_field_z_;
208+
188209
// Command storage (servo only - state mirrors command since VESC can't read it)
189210
double hw_command_servo_;
190211

@@ -200,6 +221,12 @@ class VescHardware : public hardware_interface::SystemInterface {
200221
hardware_values_publisher_;
201222
std::shared_ptr<realtime_tools::RealtimePublisher<vesc_msgs::msg::VescState>>
202223
realtime_hardware_values_publisher_;
224+
225+
// Publisher for IMU data
226+
std::shared_ptr<rclcpp::Publisher<vesc_msgs::msg::VescImuStamped>>
227+
imu_publisher_;
228+
std::shared_ptr<realtime_tools::RealtimePublisher<vesc_msgs::msg::VescImuStamped>>
229+
realtime_imu_publisher_;
203230
};
204231

205232
} // namespace vesc_hardware

vesc_hardware/src/vesc_hardware.cpp

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,32 @@
4545
namespace
4646
{
4747
constexpr char CUSTOM_HW_IF_SERVO[] = "servo";
48+
49+
// IMU sensor interface names - Quaternion orientation
50+
constexpr char CUSTOM_HW_IF_ORIENTATION_X[] = "orientation.x";
51+
constexpr char CUSTOM_HW_IF_ORIENTATION_Y[] = "orientation.y";
52+
constexpr char CUSTOM_HW_IF_ORIENTATION_Z[] = "orientation.z";
53+
constexpr char CUSTOM_HW_IF_ORIENTATION_W[] = "orientation.w";
54+
55+
// IMU sensor interface names - Euler angles
56+
constexpr char CUSTOM_HW_IF_ROLL[] = "roll";
57+
constexpr char CUSTOM_HW_IF_PITCH[] = "pitch";
58+
constexpr char CUSTOM_HW_IF_YAW[] = "yaw";
59+
60+
// IMU sensor interface names - Angular velocity
61+
constexpr char CUSTOM_HW_IF_ANGULAR_VELOCITY_X[] = "angular_velocity.x";
62+
constexpr char CUSTOM_HW_IF_ANGULAR_VELOCITY_Y[] = "angular_velocity.y";
63+
constexpr char CUSTOM_HW_IF_ANGULAR_VELOCITY_Z[] = "angular_velocity.z";
64+
65+
// IMU sensor interface names - Linear acceleration
66+
constexpr char CUSTOM_HW_IF_LINEAR_ACCELERATION_X[] = "linear_acceleration.x";
67+
constexpr char CUSTOM_HW_IF_LINEAR_ACCELERATION_Y[] = "linear_acceleration.y";
68+
constexpr char CUSTOM_HW_IF_LINEAR_ACCELERATION_Z[] = "linear_acceleration.z";
69+
70+
// IMU sensor interface names - Magnetic field
71+
constexpr char CUSTOM_HW_IF_MAGNETIC_FIELD_X[] = "magnetic_field.x";
72+
constexpr char CUSTOM_HW_IF_MAGNETIC_FIELD_Y[] = "magnetic_field.y";
73+
constexpr char CUSTOM_HW_IF_MAGNETIC_FIELD_Z[] = "magnetic_field.z";
4874
} // namespace
4975

5076
namespace vesc_hardware
@@ -189,13 +215,24 @@ hardware_interface::CallbackReturn VescHardware::on_init(
189215
if (publish_raw_state_) {
190216
hardware_values_publisher_ =
191217
get_node()->create_publisher<vesc_msgs::msg::VescState>(
192-
"~/hardware_values", 10);
218+
"~/vesc_state", 10);
193219

194220
// Create realtime publisher wrapper
195221
realtime_hardware_values_publisher_ =
196222
std::make_shared<realtime_tools::RealtimePublisher<
197223
vesc_msgs::msg::VescState>>(
198224
hardware_values_publisher_);
225+
226+
// Create publisher for IMU data
227+
imu_publisher_ =
228+
get_node()->create_publisher<vesc_msgs::msg::VescImuStamped>(
229+
"~/vesc_imu", 10);
230+
231+
// Create realtime publisher wrapper
232+
realtime_imu_publisher_ =
233+
std::make_shared<realtime_tools::RealtimePublisher<
234+
vesc_msgs::msg::VescImuStamped>>(
235+
imu_publisher_);
199236
}
200237

201238
return hardware_interface::CallbackReturn::SUCCESS;
@@ -288,9 +325,10 @@ VescHardware::read(
288325
const rclcpp::Time & /*time*/,
289326
const rclcpp::Duration & /*period*/)
290327
{
291-
// Request state from VESC (non-blocking)
328+
// Request state and IMU data from VESC (non-blocking)
292329
if (vesc_interface_) {
293330
vesc_interface_->requestState();
331+
vesc_interface_->requestImuData();
294332
}
295333

296334
// Update state interfaces with current values from atomic variables
@@ -372,6 +410,80 @@ void VescHardware::populate_state_definitions()
372410
false,
373411
[this]() {return hw_command_servo_;}
374412
};
413+
414+
// IMU state interfaces - Quaternion orientation
415+
state_interfaces_[CUSTOM_HW_IF_ORIENTATION_X] = {
416+
false,
417+
[this]() {return hw_imu_orientation_x_.load(std::memory_order_relaxed);}
418+
};
419+
state_interfaces_[CUSTOM_HW_IF_ORIENTATION_Y] = {
420+
false,
421+
[this]() {return hw_imu_orientation_y_.load(std::memory_order_relaxed);}
422+
};
423+
state_interfaces_[CUSTOM_HW_IF_ORIENTATION_Z] = {
424+
false,
425+
[this]() {return hw_imu_orientation_z_.load(std::memory_order_relaxed);}
426+
};
427+
state_interfaces_[CUSTOM_HW_IF_ORIENTATION_W] = {
428+
false,
429+
[this]() {return hw_imu_orientation_w_.load(std::memory_order_relaxed);}
430+
};
431+
432+
// IMU state interfaces - Euler angles
433+
state_interfaces_[CUSTOM_HW_IF_ROLL] = {
434+
false,
435+
[this]() {return hw_imu_roll_.load(std::memory_order_relaxed);}
436+
};
437+
state_interfaces_[CUSTOM_HW_IF_PITCH] = {
438+
false,
439+
[this]() {return hw_imu_pitch_.load(std::memory_order_relaxed);}
440+
};
441+
state_interfaces_[CUSTOM_HW_IF_YAW] = {
442+
false,
443+
[this]() {return hw_imu_yaw_.load(std::memory_order_relaxed);}
444+
};
445+
446+
// IMU state interfaces - Angular velocity
447+
state_interfaces_[CUSTOM_HW_IF_ANGULAR_VELOCITY_X] = {
448+
false,
449+
[this]() {return hw_imu_angular_velocity_x_.load(std::memory_order_relaxed);}
450+
};
451+
state_interfaces_[CUSTOM_HW_IF_ANGULAR_VELOCITY_Y] = {
452+
false,
453+
[this]() {return hw_imu_angular_velocity_y_.load(std::memory_order_relaxed);}
454+
};
455+
state_interfaces_[CUSTOM_HW_IF_ANGULAR_VELOCITY_Z] = {
456+
false,
457+
[this]() {return hw_imu_angular_velocity_z_.load(std::memory_order_relaxed);}
458+
};
459+
460+
// IMU state interfaces - Linear acceleration
461+
state_interfaces_[CUSTOM_HW_IF_LINEAR_ACCELERATION_X] = {
462+
false,
463+
[this]() {return hw_imu_linear_acceleration_x_.load(std::memory_order_relaxed);}
464+
};
465+
state_interfaces_[CUSTOM_HW_IF_LINEAR_ACCELERATION_Y] = {
466+
false,
467+
[this]() {return hw_imu_linear_acceleration_y_.load(std::memory_order_relaxed);}
468+
};
469+
state_interfaces_[CUSTOM_HW_IF_LINEAR_ACCELERATION_Z] = {
470+
false,
471+
[this]() {return hw_imu_linear_acceleration_z_.load(std::memory_order_relaxed);}
472+
};
473+
474+
// IMU state interfaces - Magnetic field
475+
state_interfaces_[CUSTOM_HW_IF_MAGNETIC_FIELD_X] = {
476+
false,
477+
[this]() {return hw_imu_magnetic_field_x_.load(std::memory_order_relaxed);}
478+
};
479+
state_interfaces_[CUSTOM_HW_IF_MAGNETIC_FIELD_Y] = {
480+
false,
481+
[this]() {return hw_imu_magnetic_field_y_.load(std::memory_order_relaxed);}
482+
};
483+
state_interfaces_[CUSTOM_HW_IF_MAGNETIC_FIELD_Z] = {
484+
false,
485+
[this]() {return hw_imu_magnetic_field_z_.load(std::memory_order_relaxed);}
486+
};
375487
}
376488

377489
void VescHardware::populate_command_definitions()
@@ -418,6 +530,93 @@ void VescHardware::processValuesPacket(
418530
publishVescState(*values_packet);
419531
}
420532

533+
void VescHardware::processImuPacket(
534+
const vesc_driver::VescPacketImu *imu_packet)
535+
{
536+
if (!imu_packet) {
537+
return;
538+
}
539+
540+
// Conversion lambda: degrees to radians
541+
auto deg_to_rad = [](double deg) { return deg * M_PI / 180.0; };
542+
543+
// Standard gravity constant for converting acceleration from 'g' to m/s²
544+
constexpr double STANDARD_GRAVITY = 9.80665;
545+
546+
// Store quaternion orientation (already in correct units)
547+
hw_imu_orientation_x_.store(imu_packet->q_x(), std::memory_order_relaxed);
548+
hw_imu_orientation_y_.store(imu_packet->q_y(), std::memory_order_relaxed);
549+
hw_imu_orientation_z_.store(imu_packet->q_z(), std::memory_order_relaxed);
550+
hw_imu_orientation_w_.store(imu_packet->q_w(), std::memory_order_relaxed);
551+
552+
// Store Euler angles (convert from degrees to radians)
553+
hw_imu_roll_.store(deg_to_rad(imu_packet->roll()), std::memory_order_relaxed);
554+
hw_imu_pitch_.store(deg_to_rad(imu_packet->pitch()), std::memory_order_relaxed);
555+
hw_imu_yaw_.store(deg_to_rad(imu_packet->yaw()), std::memory_order_relaxed);
556+
557+
// Store angular velocity (convert from degrees/second to radians/second)
558+
hw_imu_angular_velocity_x_.store(deg_to_rad(imu_packet->gyr_x()), std::memory_order_relaxed);
559+
hw_imu_angular_velocity_y_.store(deg_to_rad(imu_packet->gyr_y()), std::memory_order_relaxed);
560+
hw_imu_angular_velocity_z_.store(deg_to_rad(imu_packet->gyr_z()), std::memory_order_relaxed);
561+
562+
// Store linear acceleration (convert from 'g' to m/s²)
563+
hw_imu_linear_acceleration_x_.store(imu_packet->acc_x() * STANDARD_GRAVITY, std::memory_order_relaxed);
564+
hw_imu_linear_acceleration_y_.store(imu_packet->acc_y() * STANDARD_GRAVITY, std::memory_order_relaxed);
565+
hw_imu_linear_acceleration_z_.store(imu_packet->acc_z() * STANDARD_GRAVITY, std::memory_order_relaxed);
566+
567+
// Store magnetic field (in raw units as received)
568+
hw_imu_magnetic_field_x_.store(imu_packet->mag_x(), std::memory_order_relaxed);
569+
hw_imu_magnetic_field_y_.store(imu_packet->mag_y(), std::memory_order_relaxed);
570+
hw_imu_magnetic_field_z_.store(imu_packet->mag_z(), std::memory_order_relaxed);
571+
572+
// Publish raw IMU data
573+
publishVescImu(*imu_packet);
574+
}
575+
576+
void VescHardware::publishVescImu(
577+
const vesc_driver::VescPacketImu & imu_packet)
578+
{
579+
if (!realtime_imu_publisher_) {
580+
return;
581+
}
582+
583+
if (realtime_imu_publisher_->trylock()) {
584+
auto & msg = realtime_imu_publisher_->msg_;
585+
586+
// Set timestamp
587+
msg.header.stamp = get_node()->now();
588+
msg.header.frame_id = "";
589+
590+
// Yaw, Pitch, Roll (in degrees as received)
591+
msg.imu.ypr.x = imu_packet.yaw();
592+
msg.imu.ypr.y = imu_packet.pitch();
593+
msg.imu.ypr.z = imu_packet.roll();
594+
595+
// Linear acceleration (in m/s²)
596+
msg.imu.linear_acceleration.x = imu_packet.acc_x();
597+
msg.imu.linear_acceleration.y = imu_packet.acc_y();
598+
msg.imu.linear_acceleration.z = imu_packet.acc_z();
599+
600+
// Angular velocity (in degrees/second as received)
601+
msg.imu.angular_velocity.x = imu_packet.gyr_x();
602+
msg.imu.angular_velocity.y = imu_packet.gyr_y();
603+
msg.imu.angular_velocity.z = imu_packet.gyr_z();
604+
605+
// Compass (in raw units as received)
606+
msg.imu.compass.x = imu_packet.mag_x();
607+
msg.imu.compass.y = imu_packet.mag_y();
608+
msg.imu.compass.z = imu_packet.mag_z();
609+
610+
// Orientation quaternion
611+
msg.imu.orientation.w = imu_packet.q_w();
612+
msg.imu.orientation.x = imu_packet.q_x();
613+
msg.imu.orientation.y = imu_packet.q_y();
614+
msg.imu.orientation.z = imu_packet.q_z();
615+
616+
realtime_imu_publisher_->unlockAndPublish();
617+
}
618+
}
619+
421620
void VescHardware::publishVescState(
422621
const vesc_driver::VescPacketValues & values_packet)
423622
{
@@ -495,6 +694,10 @@ void VescHardware::vescPacketCallback(
495694
fw_packet->paired() ? "yes" : "no"
496695
);
497696
}
697+
} else if (packet->name() == "ImuData") {
698+
const auto *imu_packet =
699+
dynamic_cast<const vesc_driver::VescPacketImu *>(packet.get());
700+
processImuPacket(imu_packet);
498701
}
499702
}
500703

0 commit comments

Comments
 (0)