Implementing the IMU Interface
This guide explains how to implement a driver for an Inertial Measurement Unit (IMU): a sensor that combines an accelerometer, a gyroscope, and often a magnetometer. Mounted on a telescope, an IMU can report where the telescope is pointing, which is useful for push-to mounts, for parking checks, and for monitoring vibration.
Introduction to the IMU Interface
IMU support consists of two classes:
INDI::IMUInterface(indiimuinterface.h) defines the IMU capabilities and the standard sensor properties.INDI::IMU(indiimu.h) combinesINDI::DefaultDeviceandINDI::IMUInterface. It adds serial and I2C connection plugins, and turns the IMU’s orientation into telescope coordinates.
For IMU devices, inherit from INDI::IMU. Your driver reads the sensor and passes the readings to the base class. The base class then:
- Publishes acceleration, angular velocity, magnetic field, and orientation to clients
- Runs software sensor fusion (Madgwick or Mahony) for sensors that only provide raw data, to calculate orientation
- Converts orientation into equatorial (HA/DEC) or horizontal (AZ/ALT) coordinates, using the site location and the direction the telescope points relative to the sensor
- Lets users correct the sensor’s mounting orientation and sync it to a known position
Prerequisites
Before implementing the IMU interface, you should have:
- Basic knowledge of C++ programming
- Understanding of the INDI protocol and architecture
- Familiarity with the sensor’s datasheet or library
- Development environment set up (compiler, build tools, etc.)
- INDI library installed
IMU Interface Structure
IMUCapability Enum
Call SetCapability() in your driver’s constructor to declare which data your sensor provides:
enum IMUCapability
{
IMU_HAS_ORIENTATION = 1 << 0, /*!< Has orientation data (Roll, Pitch, Yaw) */
IMU_HAS_ACCELERATION = 1 << 1, /*!< Has acceleration data */
IMU_HAS_GYROSCOPE = 1 << 2, /*!< Has gyroscope data */
IMU_HAS_MAGNETOMETER = 1 << 3, /*!< Has magnetometer data */
IMU_HAS_CALIBRATION = 1 << 4, /*!< Supports calibration */
IMU_HAS_TEMPERATURE = 1 << 5, /*!< Has temperature sensor */
IMU_HAS_STABILITY_MON = 1 << 6, /*!< Supports stability monitoring */
IMU_HAS_SENSOR_FUSION = 1 << 7 /*!< Base-class software sensor fusion is active (Madgwick/Mahony) */
};
There are two kinds of IMU sensors:
- Sensors with on-chip fusion, such as the BNO08x family, calculate orientation themselves. Set
IMU_HAS_ORIENTATIONand pass the orientation toSetOrientationData(). - Raw-data sensors, such as the ICM-20948, only provide acceleration, angular velocity, and magnetic field. Do not set
IMU_HAS_ORIENTATION.INDI::IMUthen setsIMU_HAS_SENSOR_FUSIONitself, and calculates orientation from the raw data.
You do not need to set IMU_HAS_SENSOR_FUSION yourself.
Connection
INDI::IMU offers serial and I2C connections. Call setSupportedConnections() in your constructor with INDI::IMU::CONNECTION_SERIAL, INDI::IMU::CONNECTION_I2C, both, or INDI::IMU::CONNECTION_NONE. After connecting, the base class sets PortFD (the serial port or the I2C bus file descriptor) and calls your Handshake().
INDI::IMU does not set the driver interface flag, so call setDriverInterface(IMU_INTERFACE) in your constructor.
Standard Properties
Sensor Data
| Property | Capability | Elements | Units |
|---|---|---|---|
ORIENTATION |
Orientation or fusion | ROLL, PITCH, YAW, QUATERNION_W |
Degrees |
ACCELERATION |
IMU_HAS_ACCELERATION |
ACCEL_X, ACCEL_Y, ACCEL_Z |
m/s² |
GYROSCOPE |
IMU_HAS_GYROSCOPE |
GYRO_X, GYRO_Y, GYRO_Z |
Degrees or radians per second |
MAGNETOMETER |
IMU_HAS_MAGNETOMETER |
MAG_X, MAG_Y, MAG_Z |
µT |
TEMPERATURE |
IMU_HAS_TEMPERATURE |
TEMPERATURE |
°C |
CALIBRATION_STATUS |
IMU_HAS_CALIBRATION |
CAL_SYS, CAL_GYRO, CAL_ACCEL, CAL_MAG |
Light per sensor |
CALIBRATION_CONTROL |
IMU_HAS_CALIBRATION |
CAL_START, CAL_SAVE, CAL_LOAD, CAL_RESET |
— |
STABILITY_MONITORING |
IMU_HAS_STABILITY_MON |
VIBRATION_LEVEL, STABILITY_MONITORING_STABILITY_THRESHOLD |
— |
Configuration
| Property | Elements | Description |
|---|---|---|
POWER_MODE |
NORMAL, LOW_POWER, SUSPEND |
Sensor power mode |
OPERATION_MODE |
IMU, COMPASS, M4G, NDOF |
Sensor operation (fusion) mode |
DISTANCE_UNITS |
METRIC, IMPERIAL |
Distance units |
ANGULAR_UNITS |
DEGREES, RADIANS |
Units of the gyroscope data you report |
UPDATE_RATE |
RATE |
Sensor update rate in Hz |
DEVICE_INFO |
CHIP_ID, FIRMWARE_VERSION, SENSOR_STATUS |
Information about the sensor (read only) |
SENSOR_FUSION |
FUSION_ENABLE, FUSION_DISABLE |
Enable software fusion (raw-data sensors only) |
FUSION_TYPE |
Madgwick, Mahony | Fusion algorithm (raw-data sensors only) |
FUSION_PARAMS |
β (Madgwick), or Kp and Ki (Mahony) | Fusion algorithm tuning (raw-data sensors only) |
Telescope Pointing
| Property | Description |
|---|---|
COORDINATES |
Where the telescope points: HA/DEC or AZ/ALT, depending on COORDS_TYPE (read only) |
COORDS_TYPE |
EQUATORIAL (HA/DEC) or ALTAZ (AZ/ALT) |
IMU_FRAME |
The sensor’s axis convention: ENU (East-North-Up), NWU (North-West-Up), or SWU (South-West-Up) |
TELESCOPE_VECTOR |
The direction the telescope points, in the sensor’s frame |
ORIENTATION_ADJUSTMENTS |
Multipliers (ROLL_M, PITCH_M, YAW_M) and offsets (ROLL_O, PITCH_O, YAW_O) to correct how the sensor is mounted. A multiplier of −1 inverts an axis. |
SYNC_AXIS |
Sync the reported coordinates to a known position |
GEOGRAPHIC_COORD |
Site location, used to convert orientation to coordinates |
MAGNETIC_DECLINATION |
Difference between magnetic and true north at the site, in degrees |
Key Methods
Methods to Call with Sensor Data
Call these from your driver whenever you read new data. They update the properties and send them to clients:
-
bool SetOrientationData(double i, double j, double k, double w): Passes the orientation as a quaternion (i, j, k, w). The base class applies the orientation adjustments, publishes roll, pitch, and yaw in degrees, and updatesCOORDINATES. Only call it for sensors with on-chip fusion. -
bool SetAccelerationData(double x, double y, double z): Acceleration in m/s². -
bool SetGyroscopeData(double x, double y, double z): Angular velocity, in the units selected inANGULAR_UNITS(degrees per second by default). For raw-data sensors, each call also runs one step of sensor fusion, so call it afterSetAccelerationData()andSetMagnetometerData(). -
bool SetMagnetometerData(double x, double y, double z): Magnetic field in µT. -
bool SetTemperature(double temperature),SetCalibrationStatus(int sys, int gyro, int accel, int mag),SetDeviceInfo(...),SetStabilityMonitoring(...): Report the sensor temperature in °C, the calibration level of each sensor (0 to 3), the chip information, and the vibration level.
Note: In older INDI releases,
INDI::IMUoverrode these four methods with versions that did nothing. If your driver must support those releases, call theINDI::IMUInterfaceversions explicitly, as inINDI::IMUInterface::SetTemperature(t).
Virtual Methods to Override
Override these if your sensor supports the feature. The INDI::IMU defaults do nothing and return false, so the request is reported as failed:
virtual bool Handshake(): Initialize the sensor and verify that it responds.virtual bool StartCalibration(),SaveCalibrationData(),LoadCalibrationData(),ResetCalibration(): Calibration commands, forIMU_HAS_CALIBRATION.virtual bool SetPowerMode(const std::string &mode),SetOperationMode(const std::string &mode): Change the sensor’s power or operation mode.virtual bool SetUpdateRate(double rate): Change the sensor’s update rate in Hz.virtual bool SetAngularUnits(bool degrees),SetDistanceUnits(bool metric): Called when the user changes units.
Example Implementation
Here’s a simplified driver for a raw-data I2C sensor. The sensor library is represented by a hypothetical MySensor class; replace it with your sensor’s library.
#include <libindi/indiimu.h>
#include <memory>
class MyIMU : public INDI::IMU
{
public:
MyIMU()
{
setVersion(1, 0);
// Raw sensor data only. The base class calculates orientation with sensor fusion.
SetCapability(IMU_HAS_ACCELERATION | IMU_HAS_GYROSCOPE | IMU_HAS_MAGNETOMETER | IMU_HAS_TEMPERATURE);
setSupportedConnections(INDI::IMU::CONNECTION_I2C);
setDriverInterface(IMU_INTERFACE);
}
const char *getDefaultName() override
{
return "My IMU";
}
bool initProperties() override
{
INDI::IMU::initProperties();
addAuxControls();
return true;
}
protected:
bool Handshake() override
{
// PortFD is the I2C bus file descriptor, set by the base class.
if (!m_Sensor.begin(PortFD))
{
LOG_ERROR("Sensor is not responding. Check the I2C bus and address.");
return false;
}
SetDeviceInfo("MY-SENSOR", m_Sensor.firmwareVersion(), "Operational");
return true;
}
void TimerHit() override
{
if (!isConnected())
return;
if (m_Sensor.read())
{
SetAccelerationData(m_Sensor.accelX(), m_Sensor.accelY(), m_Sensor.accelZ());
SetMagnetometerData(m_Sensor.magX(), m_Sensor.magY(), m_Sensor.magZ());
// Call last: each gyroscope update runs one step of sensor fusion.
SetGyroscopeData(m_Sensor.gyroX(), m_Sensor.gyroY(), m_Sensor.gyroZ());
SetTemperature(m_Sensor.temperature());
}
SetTimer(getCurrentPollingPeriod());
}
private:
MySensor m_Sensor;
};
static std::unique_ptr<MyIMU> myIMU(new MyIMU());
The polling period sets how often the sensor is read. Sensor fusion integrates the gyroscope readings over time, so read the sensor often and at a steady rate: set a short default polling period with setDefaultPollingPeriod() in initProperties().
For a sensor with on-chip fusion, set IMU_HAS_ORIENTATION and call SetOrientationData() with the quaternion from the sensor:
SetOrientationData(quat.i, quat.j, quat.k, quat.real);
Real-World Examples
The INDI 3rd party repository contains IMU drivers:
- ICM-20948 (
indi-icm-imu): A raw-data sensor using the base class’s sensor fusion, with gyroscope, accelerometer, and magnetometer calibration. - BNO08x (
indi-bno-imu): A sensor with on-chip fusion that reports orientation as a quaternion.