Skip to main content

Lesson 11.3: IMU and Contact Sensors

Why IMU and Touch Matter​

Inertial Measurement Units (IMUs) measure the robot's own motion:

  • Linear acceleration (is the robot accelerating forward?)
  • Angular velocity (is the robot rotating?)
  • Orientation (which way is the robot facing?)

IMUs are essential for:

  • Humanoid walking: Detecting balance and adjusting leg position
  • Inertial navigation: Dead reckoning when GPS fails
  • Fall detection: Humanoids know when they're falling

Contact sensors detect when the robot touches something:

  • Feet sensors: Humanoid knows when foot is on ground
  • Gripper sensors: Manipulator knows when object is grasped
  • Bumpers: Mobile robot detects collisions

Together, these sensors give robots proprioception (awareness of their own body) and exteroception (awareness of contacts).


Understanding IMU Data​

The Three Components of IMU​

An IMU typically contains three sensors fused into one package:

1. Accelerometer (measures linear acceleration in x, y, z)

Accelerometer output: (ax, ay, az) in m/s²

Stationary on a table:
ax = 0, ay = 0, az = 9.81 (gravity pulls down!)

Accelerating forward:
ax = 5.0, ay = 0, az = 9.81 (forward movement + gravity)

Jumping:
ax = 0, ay = 0, az = 20 (upward acceleration + gravity)

Important: Accelerometer always measures gravity! Even a stationary sensor reads 9.81 m/s² downward (the acceleration you'd need to cancel gravity).

2. Gyroscope (measures angular velocity in roll, pitch, yaw)

Gyroscope output: (ωx, ωy, ωz) in rad/s

Stationary:
ωx = 0, ωy = 0, ωz = 0 (no rotation)

Spinning around z-axis (yaw) at 1 rotation/second:
ωx = 0, ωy = 0, ωz = 6.28 (2π rad/sec for full rotation)

3. Magnetometer (measures Earth's magnetic field, acts like compass)

Magnetometer output: (mx, my, mz)

Facing north:
mx = large value, my = small, mz = small

Facing east:
mx = small, my = large value, mz = small

3-axis orientation (derived from all three sensors):

Orientation: (roll, pitch, yaw)
- roll: rotation around x-axis (left/right tilt)
- pitch: rotation around y-axis (forward/backward tilt)
- yaw: rotation around z-axis (rotation around vertical)

Noise in IMU Data​

Real IMUs have errors:

Bias: Sensor reads non-zero when it should read zero

Gyroscope sitting still reports: (0.01, -0.02, 0.015) rad/s
(tiny drift, but compounds over time)

Noise: Random fluctuation around true value

Accelerometer reads: 9.81, 9.82, 9.79, 9.81, 9.80, ...
(varies by 0.01 m/s² randomly)

Drift: Errors accumulate over time

Integrating gyroscope velocity → orientation
Over 1 minute: small errors accumulate
Over 10 minutes: orientation estimate becomes unreliable

In simulation, you can add realistic noise models to make training data match real sensor behavior.


Adding an IMU to Your Robot​

Step 1: Define the IMU Sensor in SDF​

Add an <sensor> element of type imu to your robot's main link (usually base_link).

Example: IMU on robot base

<model name="my_robot">
<link name="base_link">
<!-- Robot body... -->

<!-- IMU SENSOR DEFINITION -->
<sensor name="imu" type="imu">
<!-- IMU parameters -->
<imu>
<!-- Accelerometer noise -->
<acceleration_xyzn>
<x>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.05</stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.05</stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.05</stddev>
</noise>
</z>
</acceleration_xyzn>

<!-- Gyroscope noise -->
<angular_velocity_xyzn>
<x>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.01</stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.01</stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0</mean>
<stddev>0.01</stddev>
</noise>
</z>
</angular_velocity_xyzn>
</imu>

<!-- Publishing behavior -->
<always_on>1</always_on>
<update_rate>100</update_rate>

<!-- Topic name -->
<plugin name="imu_controller" filename="libgazebo_ros_imu_sensor.so">
<frame_name>base_link</frame_name>
</plugin>
</sensor>
</link>
</model>

What's happening:

  • <sensor type="imu"> defines this as an inertial measurement unit
  • <acceleration_xyzn> defines noise for accelerometer (x, y, z axes)
  • <angular_velocity_xyzn> defines noise for gyroscope
  • <stddev>0.05</stddev> standard deviation of Gaussian noise
  • <update_rate>100</update_rate> publishes 100 times per second (high frequency)

Step 2: Verify IMU Publishes​

Check IMU topics:

ros2 topic list | grep imu

Expected output:

/imu

Inspect IMU data:

ros2 topic echo /imu --once

Output shows:

header:
seq: 1234
stamp: {sec: 10, nsec: 500000000}
frame_id: base_link
orientation: {x: 0.0, y: 0.0, z: 0.707, w: 0.707} # Quaternion (rotated 90 deg)
orientation_covariance: [0.01, ...] # Uncertainty in orientation
angular_velocity: {x: 0.0, y: 0.0, z: 0.1} # Spinning at 0.1 rad/s
angular_velocity_covariance: [0.001, ...]
linear_acceleration: {x: 0.1, y: 0.0, z: 9.81} # Gravity in z
linear_acceleration_covariance: [0.0025, ...]

Interpretation:

  • orientation: Robot's pose as quaternion (encodes roll, pitch, yaw)
  • angular_velocity: How fast robot is rotating
  • linear_acceleration: Robot's acceleration plus gravity

Adding Contact Sensors​

Contact sensors detect when surfaces touch each other (collisions, grasping, foot contact).

Example: Contact Sensor on Robot Bumper​

<model name="my_robot">
<link name="base_link">
<!-- Robot body... -->

<!-- Create a bumper link (collision detection area) -->
<collision name="bumper_collision">
<geometry>
<box>
<size>0.3 0.1 0.05</size>
</box>
</geometry>
</collision>
</link>

<link name="bumper_link">
<!-- Bumper sensor (just a collision shape) -->
<inertial>
<mass>0.01</mass>
</inertial>
<collision name="bumper_collision">
<geometry>
<box>
<size>0.3 0.1 0.05</size>
</box>
</geometry>
</collision>
</link>

<!-- Joint to mount bumper -->
<joint name="bumper_joint" type="fixed">
<parent>base_link</parent>
<child>bumper_link</child>
<origin xyz="0.15 0 0"/> <!-- Front of robot -->
</joint>

<!-- Contact sensor definition -->
<sensor name="bumper_sensor" type="contact">
<contact>
<collision>bumper_collision</collision>
</contact>
<plugin name="bumper_controller" filename="libgazebo_ros_bumper.so">
<bumper_topic_name>bumper</bumper_topic_name>
</plugin>
</sensor>
</model>

What's happening:

  • bumper_link is a separate link with collision shape
  • <sensor type="contact"> monitors this collision shape
  • When robot bumps into something, the sensor publishes a contact event
  • Plugin translates event into ROS 2 message

Contact Sensor Topics​

Contact events publish to:

/bumper

Message contains:

contacts:
- contact1:
collision1_name: bumper_collision
collision2_name: ground_plane::ground_plane_collision
position: [x, y, z] # Where collision occurred

Exercise: Add IMU to Your Robot​

Task:

  1. Use your robot SDF from previous lessons
  2. Add an IMU sensor to the base_link
  3. Configure accelerometer and gyroscope noise
  4. Launch Gazebo with your robot
  5. Verify /imu topic publishes
  6. Move your robot around and observe IMU values changing
  7. Stop your robot and verify acceleration still reads ~9.81 in z (gravity)

Expected result: You see realistic IMU data reflecting your robot's motion and orientation.


Understanding Quaternions (Advanced Orientation Representation)​

IMU data includes orientation as a quaternion: (x, y, z, w)

Quaternions represent rotations in 3D without gimbal lock (a limitation of Euler angles).

Stationary robot (no rotation):

Quaternion: (0, 0, 0, 1)
Euler angles: (0, 0, 0) = roll 0°, pitch 0°, yaw 0°

Robot rotated 90 degrees around z-axis:

Quaternion: (0, 0, 0.707, 0.707)  ≈ sin(45°), cos(45°)
Euler angles: (0, 0, 90°) = yaw 90°

Conversion (typically handled by libraries):

import math
from tf_transformations import quaternion_from_euler

roll = 0
pitch = 0
yaw = math.radians(90) # 90 degrees in radians

quat = quaternion_from_euler(roll, pitch, yaw)
# Result: (0, 0, 0.707, 0.707)

For most practical purposes, you don't need to understand quaternion math—ROS libraries handle conversion. Just know that orientation is represented this way.


Common IMU and Contact Issues​

Issue 1: IMU Publishes Very Noisy Data​

Symptom: IMU acceleration/rotation values fluctuate wildly

Likely cause:

  • Noise stddev too high
  • Sensor frequency too low (update_rate too low)

Fix:

  1. Increase update_rate: <update_rate>200</update_rate>
  2. Reduce noise: <stddev>0.01</stddev> (down from 0.05)
  3. Post-process in your node (moving average filter)

Issue 2: Contact Sensor Never Triggers​

Symptom: Robot bumps into wall but no contact message

Likely cause:

  • Collision shape not defined correctly
  • Contact sensor not monitoring right collision geometry
  • Collision groups not set to detect

Fix:

  1. Verify collision shape exists in SDF
  2. Double-check collision name in sensor definition
  3. Enable collision detection in physics engine

Issue 3: Gravity Constantly Affects Accelerometer​

Symptom: IMU always reads 9.81 in z, making it hard to detect real acceleration

This is expected! Real IMUs have this problem too.

Solution:

  • Subtract gravity (9.81) from z-axis in your processing node
  • Use sensor fusion: combine gyroscope (which has no gravity) with accelerometer
  • Many robotics libraries provide "IMU fusion" that removes gravity automatically

Validation Checklist​

After adding IMU and contact sensors:

  • IMU sensor is defined in SDF
  • Gazebo launches without errors
  • ros2 topic list shows /imu and /bumper (if contact added)
  • ros2 topic echo /imu shows changing values as robot moves
  • Orientation quaternion values make sense (one value should be close to 1)
  • Acceleration reads ~9.81 in z-axis when stationary
  • Contact sensor triggers when robot collides (if contact added)

Try With AI​

Setup: Describe an IMU configuration problem to your AI assistant.

Prompt Set:

Prompt 1 (Basic): "I added an IMU to my robot. It's on the base_link.
What noise levels (stddev) would you recommend for a robot that
walks on uneven terrain? Should accelerometer noise be higher or
lower than gyroscope noise?"

Prompt 2 (Intermediate): "My robot has a humanoid that falls frequently.
I want the IMU to detect falling (sudden acceleration change).
What IMU update rate would you recommend? 10 Hz, 50 Hz, or 200 Hz?"

Prompt 3 (Advanced): "I'm fusing IMU data (accelerometer + gyroscope +
magnetometer) to estimate robot orientation. Magnetometer drifts in
indoor environments with metal. Should I rely more on accelerometer
or gyroscope for orientation estimation?"

Expected Outcomes:

  • AI suggests appropriate noise levels for realistic simulation
  • AI explains tradeoffs between update rate and processing load
  • AI helps reason about sensor fusion strategies

Safety Note: IMU sensors in simulation are ideal (perfect noise models, no bias drift). Real IMUs have temperature-dependent bias, shock-induced errors, and environmental sensitivity. When deploying real robots, calibrate IMUs in the actual operating environment and validate your sensor fusion algorithms with real hardware.

Optional Stretch: Write a ROS 2 node that listens to /imu, removes gravity from the accelerometer, and detects sudden impacts (acceleration magnitude greater than 20 m/s²). This is the basis for fall detection in humanoid robots.