Skip to main content
Sensor parts provide additional data about your vehicle’s state and environment.

IMU (Inertial Measurement Unit)

Measures acceleration, rotation, and orientation. Location: donkeycar/parts/imu.py:9

Supported Sensors

  • MPU6050: 6-axis (accelerometer + gyroscope)
  • MPU9250: 9-axis (accelerometer + gyroscope + magnetometer)
from donkeycar.parts.imu import IMU, SENSOR_MPU6050, SENSOR_MPU9250

imu = IMU(
    addr=0x68,                      # I2C address
    poll_delay=0.0166,              # Poll interval (60Hz)
    sensor=SENSOR_MPU6050,          # Sensor type
    dlp_setting=0                   # Digital low-pass filter (0=disabled)
)
Installation:
# For MPU6050
sudo apt install python3-smbus
pip install mpu6050-raspberrypi

# For MPU9250  
pip install mpu9250-jmdev
Enable I2C:
sudo raspi-config
# Interface Options -> I2C -> Enable
Usage:
# Add to vehicle
V.add(imu, 
      outputs=['imu/accel_x', 'imu/accel_y', 'imu/accel_z',
               'imu/gyro_x', 'imu/gyro_y', 'imu/gyro_z', 'imu/temp'],
      threaded=True)

# Or run directly
accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temp = imu.run()
Outputs:
  • Accelerometer (m/s²): Linear acceleration on X, Y, Z axes
  • Gyroscope (rad/s): Angular velocity on X, Y, Z axes
  • Temperature (°C): Die temperature
  • Magnetometer (MPU9250 only, μT): Magnetic field on X, Y, Z axes
Digital Low-Pass Filter:
# Common DLP settings (gyro/accel bandwidth)
# 0: Disabled (8kHz/1kHz)
# 1: 184Hz/188Hz
# 2: 94Hz/98Hz
# 3: 44Hz/42Hz
# 4: 21Hz/20Hz
# 5: 10Hz/10Hz
# 6: 5Hz/5Hz

imu = IMU(sensor=SENSOR_MPU6050, dlp_setting=3)  # 44Hz filter
Testing:
cd ~/mycar
python -c "from donkeycar.parts.imu import IMU; import time; imu = IMU(); [print(imu.run()) or time.sleep(0.1) for _ in range(10)]"

GPS

Global positioning from NMEA-compatible GPS modules. Location: donkeycar/parts/gps.py

GpsNmeaPositions

Converts NMEA sentences to UTM coordinates.
from donkeycar.parts.gps import GpsNmeaPositions
from donkeycar.parts.serial_port import SerialPort, SerialLineReader

# Open serial port
serial_port = SerialPort(
    port='/dev/ttyUSB0',  # Or /dev/ttyAMA0 for Pi GPIO
    baudrate=9600,
    timeout=0.5
)

# Read NMEA lines
line_reader = SerialLineReader(
    serial_port,
    max_lines=10
)

# Convert to positions
gps_reader = GpsNmeaPositions(debug=False)

# Add to vehicle
V.add(line_reader, outputs=['nmea/lines'], threaded=True)
V.add(gps_reader, inputs=['nmea/lines'], outputs=['gps/positions'])
Outputs:
  • List of (timestamp, x, y) tuples
  • x, y in UTM coordinates (meters)

GpsPosition

Combined GPS reader that returns the latest position. Location: donkeycar/parts/gps.py:54
from donkeycar.parts.gps import GpsPosition
from donkeycar.parts.serial_port import SerialPort

serial_port = SerialPort('/dev/ttyUSB0', baudrate=9600)
gps = GpsPosition(serial=serial_port, debug=False)

V.add(gps, outputs=['gps/timestamp', 'gps/x', 'gps/y'], threaded=True)
NMEA Parsing: Supports GPRMC and GNRMC sentences:
$GPRMC,003918.00,A,3806.92281,N,12235.64362,W,0.090,,060322,,,D*67
Parsed to:
  • Latitude/Longitude in degrees
  • Converted to UTM (x, y) in meters
  • Timestamp from sentence
Hardware Setup:
# For USB GPS
ls /dev/ttyUSB*  # Find device

# For GPIO serial (disable console first)
sudo raspi-config
# Interface Options -> Serial Port
#   -> "Would you like a login shell accessible over serial?" No
#   -> "Would you like the serial port hardware enabled?" Yes

# Check
ls /dev/ttyAMA0 /dev/serial0
Installation:
pip install pynmea2 pyserial utm
Testing:
# Raw NMEA output
cat /dev/ttyUSB0

# Test with script
cd ~/mycar
python -m donkeycar.parts.gps --serial /dev/ttyUSB0 --baudrate 9600

Encoders

Measure wheel rotation for odometry and velocity. Note: The encoder.py module is deprecated. Use tachometer.py and odometer.py instead.

Rotary Encoder (Legacy)

Location: donkeycar/parts/encoder.py:128 GPIO-based rotary encoder (deprecated in favor of donkeycar.parts.tachometer.Tachometer).
from donkeycar.parts.encoder import RotaryEncoder

encoder = RotaryEncoder(
    mm_per_tick=0.306096,  # Distance per encoder tick
    pin=13,                 # GPIO pin
    poll_delay=0.0166,     # Poll rate (60Hz)
    debug=False
)

V.add(encoder, 
      inputs=['user/throttle'],
      outputs=['enc/velocity'],
      threaded=True)
Requires: pigpio
sudo apt-get install pigpio python3-pigpio
sudo systemctl enable pigpiod
sudo systemctl start pigpiod
Calibration:
  1. Measure wheel circumference (C) in mm
  2. Count encoder ticks per revolution (T)
  3. Set mm_per_tick = C / T
Outputs:
  • velocity (m/s): Current speed
  • distance (m): Total distance traveled

Tachometer (Modern)

For current speed measurement, use:
from donkeycar.parts.tachometer import Tachometer, GpioEncoder

encoder = GpioEncoder(pin=13)
tach = Tachometer(encoder, meters_per_tick=0.000306096)

V.add(tach, outputs=['enc/velocity'], threaded=True)

Odometer (Modern)

For distance measurement:
from donkeycar.parts.odometer import Odometer

odometer = Odometer(tachometer=tach)

V.add(odometer, 
      inputs=['enc/velocity'],
      outputs=['enc/distance'])

Ultrasonic Sensors

For obstacle detection and distance measurement. Location: Custom implementation needed
import RPi.GPIO as GPIO
import time

class UltrasonicSensor:
    def __init__(self, trigger_pin, echo_pin):
        self.trigger = trigger_pin
        self.echo = echo_pin
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.trigger, GPIO.OUT)
        GPIO.setup(self.echo, GPIO.IN)
        
    def run(self):
        # Send trigger pulse
        GPIO.output(self.trigger, GPIO.HIGH)
        time.sleep(0.00001)
        GPIO.output(self.trigger, GPIO.LOW)
        
        # Measure echo
        while GPIO.input(self.echo) == GPIO.LOW:
            start = time.time()
        while GPIO.input(self.echo) == GPIO.HIGH:
            end = time.time()
            
        # Calculate distance
        duration = end - start
        distance = duration * 34300 / 2  # cm
        return distance
    
    def shutdown(self):
        GPIO.cleanup()

# Usage
sensor = UltrasonicSensor(trigger_pin=23, echo_pin=24)
V.add(sensor, outputs=['ultrasonic/distance'])

LIDAR

For 2D/3D environment scanning. Location: donkeycar/parts/lidar.py (if available)
# Example for RPLidar
from rplidar import RPLidar

class LidarPart:
    def __init__(self, port='/dev/ttyUSB0'):
        self.lidar = RPLidar(port)
        self.running = True
        self.scan_data = []
        
    def update(self):
        for scan in self.lidar.iter_scans():
            if not self.running:
                break
            self.scan_data = scan
    
    def run_threaded(self):
        return self.scan_data
    
    def shutdown(self):
        self.running = False
        self.lidar.stop()
        self.lidar.disconnect()

Configuration

In myconfig.py:
# IMU
HAVE_IMU = True
IMU_SENSOR = 'mpu6050'  # or 'mpu9250'
IMU_ADDRESS = 0x68
IMU_DLP_CONFIG = 0

# GPS
HAVE_GPS = True
GPS_SERIAL_PORT = '/dev/ttyUSB0'
GPS_BAUDRATE = 9600

# Encoder
HAVE_ODOM = True
ENCODER_TYPE = 'GPIO'  # or 'ARDUINO'
ENCODER_PIN = 13
MM_PER_TICK = 0.306096
ENC_POLL_RATE = 60

# Ultrasonic
HAVE_ULTRASONIC = False
ULTRASONIC_TRIGGER_PIN = 23
ULTRASONIC_ECHO_PIN = 24

Data Recording

Add sensor outputs to TubWriter:
# In manage.py
inputs = ['cam/image_array',
          'user/angle', 'user/throttle', 'user/mode',
          'imu/accel_x', 'imu/accel_y', 'imu/accel_z',
          'imu/gyro_x', 'imu/gyro_y', 'imu/gyro_z',
          'gps/x', 'gps/y',
          'enc/velocity']

types = ['image_array',
         'float', 'float', 'str',
         'float', 'float', 'float',
         'float', 'float', 'float', 
         'float', 'float',
         'float']

Using Sensor Data in Models

For IMU-augmented models:
from donkeycar.parts.keras import KerasPilot

class ImuPilot(KerasPilot):
    def run(self, img_arr, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z):
        # Use IMU data along with image
        return self.inference_from_dict({
            'img_in': normalize_image(img_arr),
            'imu_in': np.array([accel_x, accel_y, accel_z, 
                                gyro_x, gyro_y, gyro_z])
        })

Common Issues

IMU Not Detected

# Check I2C
sudo i2cdetect -y 1
# Should show 0x68 or 0x69

# If not found, check connections and enable I2C
sudo raspi-config

GPS No Fix

  • Ensure antenna has clear view of sky
  • Wait 30-60 seconds for initial fix (cold start)
  • Check NMEA output: cat /dev/ttyUSB0
  • Verify GPS LED is blinking

Encoder Not Counting

  • Check GPIO pin connections
  • Verify pigpio daemon is running: sudo systemctl status pigpiod
  • Test with multimeter or oscilloscope
  • Check for proper pull-up/pull-down resistors

Serial Port Permission Denied

# Add user to dialout group
sudo usermod -a -G dialout $USER
# Log out and back in

# Or temporarily
sudo chmod 666 /dev/ttyUSB0

Next Steps

Build docs developers (and LLMs) love