---
# System prepended metadata

title: How to read raw data from IMU sensor ROS 2?
tags: [Sensor]

---

Reading raw data from an [IMU sensor](https://www.onzuu.com/category/inertial-measurement-units) in ROS 2 is a fundamental task for robotics. Here's a comprehensive guide covering the most common scenarios.

![2021-04-19-13.44.38](https://hackmd.io/_uploads/H1x60-CCgg.jpg)

**The Core Concept: The sensor_msgs/msg/Imu Message**
ROS 2 uses a standard message type for IMU data: sensor_msgs/msg/Imu. Understanding this message is key.

```
python

# sensor_msgs/msg/Imu

# This message holds the core IMU data.
std_msgs/Header header
    uint32 seq
    time stamp
    string frame_id

geometry_msgs/Quaternion orientation
    float64 x
    float64 y
    float64 z
    float64 w
# float64[9] orientation_covariance # Row-major representation of the 3x3 covariance matrix

geometry_msgs/Vector3 angular_velocity
    float64 x
    float64 y
    float64 z
# float64[9] angular_velocity_covariance # Row-major representation of the 3x3 covariance matrix

geometry_msgs/Vector3 linear_acceleration
    float64 x
    float64 y
    float64 z
# float64[9] linear_acceleration_covariance # Row-major representation of the 3x3 covariance matrix
```
**Key Fields for "Raw Data":**

* angular_velocity: The raw [gyroscope](https://www.onzuu.com/category/gyroscopes) data (in rad/s), typically in the sensor's body frame.
* linear_acceleration: The raw [accelerometer](https://www.onzuu.com/category/accelerometers) data (in m/s²), typically in the sensor's body frame.
* orientation: This is often fused data (from a filter on the [sensor](https://www.ampheo.com/c/sensors) itself). For truly raw data, you might ignore this or set the covariance to [ -1, 0, 0, 0, 0, 0, 0, 0, 0] to indicate it's not available.

**Scenario 1: Using an Existing IMU Driver (Most Common)**
Most popular IMUs (BMI088, MPU6050, BNO055, etc.) already have open-source ROS 2 drivers. This is the easiest method.

**Step 1: Find and Install a Driver**
Search for your specific IMU model + "ROS 2 driver".

* Example for [BMI088](https://www.ampheo.com/product/bmi088-26901030) on a Raspberry Pi: ros-<distro>-bmi088-driver
* Example for [BNO055](https://www.ampheo.com/product/bno055-26900961) (Adafruit): ros-<distro>-bno055
* Example for MPU6050: You might find a package like mpu6050driver

You can install them via apt or build from source.

**Step 2: Configure and Launch the Driver**
Drivers typically use a launch file and a parameters YAML file.

**Example Launch Command:**

```
bash
    
ros2 launch bmi088_bosch_driver bmi088_node.launch.py
```
**Example Parameters File** (imu_params.yaml):

```
yaml
    
/bmi088_node:
  ros__parameters:
    # Frame ID for the sensor data
    frame_id: "imu_link"
    # Gyro and Accel topics
    topic_imu_raw: "imu/raw_data"
    # Sensor settings
    range_accel: 24 # ±24g
    range_gyro: 2000 # ±2000 dps
    # Device address (I2C)
    bus: 1
    address_accel: 0x19
    address_gyro: 0x69
```
**Step 3: Read the Raw Data with a Subscriber**
Once the driver is running, it will publish data to a topic (e.g., /imu/raw_data). You can write a simple Python node to subscribe to it.

**Python Node:** raw_imu_listener.py

```
python
    
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Imu

class RawIMUListener(Node):

    def __init__(self):
        super().__init__('raw_imu_listener')
        # Subscribe to the raw IMU topic
        self.subscription = self.create_subscription(
            Imu,
            '/imu/raw_data',  # Change this to your actual topic name
            self.imu_callback,
            10)
        self.subscription  # Prevent unused variable warning

    def imu_callback(self, msg):
        # Extract raw accelerometer data (in m/s²)
        accel_x = msg.linear_acceleration.x
        accel_y = msg.linear_acceleration.y
        accel_z = msg.linear_acceleration.z
        
        # Extract raw gyroscope data (in rad/s)
        gyro_x = msg.angular_velocity.x
        gyro_y = msg.angular_velocity.y
        gyro_z = msg.angular_velocity.z
        
        # Print the raw data to the console
        self.get_logger().info(
            f'Accel: ({accel_x:.2f}, {accel_y:.2f}, {accel_z:.2f}) [m/s²] | '
            f'Gyro: ({gyro_x:.2f}, {gyro_y:.2f}, {gyro_z:.2f}) [rad/s]',
            throttle_duration_sec=1.0  # Only log once per second to avoid spam
        )

def main(args=None):
    rclpy.init(args=args)
    raw_imu_listener = RawIMUListener()
    rclpy.spin(raw_imu_listener)
    raw_imu_listener.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
```
CMakeLists.txt and package.xml: Remember to add dependencies (sensor_msgs, rclpy).

**Step 4: Run Your Node**
1. Terminal 1: Launch the IMU driver.
2. Terminal 2: Run your listener node.

```
bash
    
ros2 run your_package_name raw_imu_listener
```
**Scenario 2: Writing Your Own Driver (For Unsupported IMUs)**
If no driver exists, you'll need to write one. This involves:

1. Hardware Communication: Using a library like smbus2 for I2C on Linux or pyserial for UART.
2. Data Parsing: Reading the sensor's registers and converting the raw bytes to meaningful physical values using the formulas in the datasheet.
3. ROS 2 Integration: Creating a node that reads the data and publishes it as a sensor_msgs/msg/Imu message.

**Simplified Python Skeleton for a Custom Driver:**

```
python
    
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Imu
import smbus2
import struct

class CustomIMUDriver(Node):

    def __init__(self):
        super().__init__('custom_imu_driver')
        
        # Parameters
        self.declare_parameter('i2c_bus', 1)
        self.declare_parameter('i2c_address', 0x68)
        self.declare_parameter('frame_id', 'imu_link')
        
        bus = self.get_parameter('i2c_bus').value
        address = self.get_parameter('i2c_address').value
        self.frame_id = self.get_parameter('frame_id').value
        
        # Initialize I2C bus and sensor
        self.bus = smbus2.SMBus(bus)
        self.address = address
        self.initialize_imu() # You implement this
        
        # Create publisher
        self.publisher_ = self.create_publisher(Imu, '/imu/raw', 10)
        
        # Create timer to read data at a fixed rate (e.g., 100Hz)
        timer_period = 0.01  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)

    def initialize_imu(self):
        # Write to configuration registers to wake up the sensor, set ranges, etc.
        # CONSULT YOUR IMU'S DATASHEET
        try:
            # Example: Wake up MPU6050 (PWR_MGMT_1 register)
            self.bus.write_byte_data(self.address, 0x6B, 0x00)
        except Exception as e:
            self.get_logger().error(f"Failed to initialize IMU: {e}")

    def read_raw_data(self):
        # Read the raw byte data from the sensor's data registers
        # CONSULT YOUR IMU'S DATASHEET for register addresses and data format.
        try:
            # Example: Read 14 bytes from MPU6050 (ACCEL_XOUT_H through GYRO_ZOUT_L)
            data = self.bus.read_i2c_block_data(self.address, 0x3B, 14)
            
            # Convert the bytes to signed integers (data format is in the datasheet)
            accel_x = self.convert_bytes(data[0], data[1])
            accel_y = self.convert_bytes(data[2], data[3])
            accel_z = self.convert_bytes(data[4], data[5])
            # ... temperature ...
            gyro_x = self.convert_bytes(data[8], data[9])
            gyro_y = self.convert_bytes(data[10], data[11])
            gyro_z = self.convert_bytes(data[12], data[13])
            
            # Convert raw integer values to physical units (m/s², rad/s)
            # Use the sensitivity factors (LSB/g, LSB/dps) from the datasheet.
            accel_scale = 16384.0  # for ±2g range (LSB/g)
            gyro_scale = 131.0     # for ±250 dps range (LSB/dps)
            
            accel_x = accel_x / accel_scale * 9.81  # Convert to m/s²
            gyro_x = gyro_x / gyro_scale * 0.0174533 # Convert to rad/s
            # ... do the same for y and z ...
            
            return accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z
            
        except Exception as e:
            self.get_logger().error(f"Failed to read IMU data: {e}")
            return 0.0, 0.0, 0.0, 0.0, 0.0, 0.0

    def convert_bytes(self, high_byte, low_byte):
        # Helper function to convert two bytes to a signed 16-bit integer
        value = (high_byte << 8) | low_byte
        return value if value < 32768 else value - 65536

    def timer_callback(self):
        # Read the raw data from the sensor
        accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z = self.read_raw_data()
        
        # Create and populate the IMU message
        imu_msg = Imu()
        imu_msg.header.stamp = self.get_clock().now().to_msg()
        imu_msg.header.frame_id = self.frame_id
        
        # Fill in the raw data
        imu_msg.linear_acceleration.x = accel_x
        imu_msg.linear_acceleration.y = accel_y
        imu_msg.linear_acceleration.z = accel_z
        
        imu_msg.angular_velocity.x = gyro_x
        imu_msg.angular_velocity.y = gyro_y
        imu_msg.angular_velocity.z = gyro_z
        
        # Orientation is not computed from raw data here.
        # You can set the covariance to indicate it's not available.
        imu_msg.orientation_covariance[0] = -1
        
        # Publish the message
        self.publisher_.publish(imu_msg)

def main(args=None):
    rclpy.init(args=args)
    custom_imu_driver = CustomIMUDriver()
    rclpy.spin(custom_imu_driver)
    custom_imu_driver.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()
```
**Key Troubleshooting Tips**
1. Check the Topic: Always list topics first to see what's being published.

```
bash
                                     
ros2 topic list
ros2 topic echo /imu/raw_data  # View the raw data directly
```
2. Frame ID: Ensure the frame_id is correct, especially if you're using tf2 for transformation.

3. Units: Confirm the units. Gyroscope data should be in rad/s, not degrees/s. Accelerometer in m/s².

4. Coordinate Frame: The standard in ROS is ENU (East-North-Up). The frame_id should be a child of a standard frame like base_link or odom. Use static_transform_publisher or a robot_state_publisher to define this transform.

By following these steps, you can successfully read and utilize raw data from almost any IMU [sensor](https://www.ampheoelec.de/c/sensors) in your ROS 2 application.