Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IMU406

A lightweight, non-blocking Arduino library for the IMU406 serial (UART TTL) attitude sensor, with dual-mode serial acquisition — interrupt (on ESP32) or polling (on AVR & everywhere), same API.

The sensor is an on-board-solved 6/9-axis attitude reference system that streams fixed binary frames over UART. This library parses the vendor 29-byte 0x84 protocol and exposes the data as simple getters:

  • Attituderoll, pitch, yaw (float, degrees) with raw ±18000 values
  • Accelerometer — 3-axis acceleration
  • Gyroscope — 3-axis angular rate

No dependency on I2C/SPI; you only need two wires (TX/RX).


Features

Dual-mode serial acquisition — one library, same API, two backends:

  • Interrupt mode (ESP32): frames parsed from the UART onReceive() hook, so loop() does no polling — frees CPU for other tasks and for other serial ports' own interrupt handlers.
  • Polling mode (AVR Uno/Mega & everywhere): update() in loop() — simple, portable, fully non-blocking.

The rest:

  • Frame-sync receive state machine — recovers cleanly from arbitrary byte streams
  • Fully non-blocking in both modes
  • Float angles in degrees plus raw integer accessors
  • 3-axis accelerometer & gyroscope getters
  • hasNewData() new-frame flag and isAlive() link/timeout detection
  • Multiple IMU406 instances can share different serial ports independently
  • Portable across AVR, ESP32/ESP8266, SAMD, STM32, RP2040, nRF52

Installation (Arduino IDE)

  1. Copy the whole IMU406 folder into your Arduino libraries directory:
    <Documents>\Arduino\libraries\IMU406\
    
  2. Restart the Arduino IDE.
  3. Open an example via File → Examples → IMU406 → get_angles.
  4. Tools → Board select your board, Tools → Port select the COM port, then click Upload and open the Serial Monitor at 115200.

Acquisition modes

The library provides two interchangeable ways to consume the IMU406 data stream. They share the exact same getters and parser — the only difference is where incoming bytes are consumed.

Interrupt mode Polling mode
Supported platforms ESP32 (hardware onReceive() hook) All (AVR, ESP32, SAMD, STM32, RP2040…)
Parser driven by UART receive interrupt update() in loop()
Does loop() need update()? No Yes
CPU usage in loop() Minimal Small per call
Best for ESP32 projects that also use other serial interrupts, or want low latency Uno/Mega, simple sketches, or avoiding any interrupt
Other serial ports Untouched — each port keeps its own handler Untouched

Interrupt mode (default on ESP32)

begin(..., true) (or enableInterrupt()) attaches the library's handler to the HardwareSerial onReceive() hook of the port this instance is bound to only. Frames are parsed straight from that interrupt, so loop() is free. Any other serial (e.g. Serial1, Serial) can keep its own onReceive() handler — or stay in polling — without any conflict. See examples/interrupt_mode/interrupt_mode.ino.

On recent ESP32 cores the hook is armed with onlyIfTimeout=false, so it fires as soon as bytes arrive (low latency) rather than only after a timeout.

Polling mode (AVR & elsewhere)

Call update() in loop(). Fully non-blocking; ideal on Uno/Mega or if you prefer to avoid interrupts. Force it anywhere with begin(..., false) or disableInterrupt().

// ESP32: interrupt — no loop polling needed
imu.begin(115200, rxPin, txPin, true);          // or omit the 4th arg

// Anywhere (or forcing fallback): polling
imu.begin(115200, -1, -1, false);
imu.update();                                    // call each loop

TL;DR

What you have Use
ESP32 + IMU406 on Serial2, maybe other interrupts begin(..., true) — interrupt mode
Arduino Uno / Mega 2560 begin(..., false) + update() in loop() — polling
Not sure / changing boards Polling mode is the safe common denominator

Both modes read the same registers, same parse accuracy, same API.


Wiring

IMU406 MCU
TX RX
RX TX
3.3V 3.3V
GND GND

⚠️ TX ↔ RX cross-connect: IMU406 TX goes to the MCU RX pin and IMU406 RX to the MCU TX pin. Reversed wiring is the most common cause of no output.

⚠️ ESP32: the default Serial2 pins differ by family (Serial2 on the ESP32-S3 is not 16/17). Always pass your actual wiring pins to begin().


Quick start

#include <IMU406.h>

#if defined(ARDUINO_ARCH_ESP32)
  IMU406 imu(&Serial2);
  const int RX_PIN = 16, TX_PIN = 17;   // match your wiring
#else
  IMU406 imu(&Serial);                  // AVR etc.
#endif

void setup() {
  Serial.begin(115200);
#if defined(ARDUINO_ARCH_ESP32)
  imu.begin(115200, RX_PIN, TX_PIN, true);   // interrupt mode
#else
  imu.begin(115200, -1, -1, false);          // polling mode (AVR)
#endif
}

void loop() {
#if !defined(ARDUINO_ARCH_ESP32)
  imu.update();                        // polling only — not needed on ESP32
#endif
  if (imu.hasNewData()) {
    Serial.print(imu.getRoll());
    Serial.print("/");
    Serial.print(imu.getPitch());
    Serial.print("/");
    Serial.println(imu.getYaw());
  }
}

See examples/get_angles/get_angles.ino for a full read-out of all 9 values, and examples/interrupt_mode/interrupt_mode.ino for interrupt mode alongside an independent Serial1 interrupt handler.


API reference

Setup / acquisition

Method Description
IMU406(HardwareSerial* serial) Construct and bind a serial port.
void begin(long baud=115200, int8_t rxPin=-1, int8_t txPin=-1, bool useInterrupt=true) Open the port and reset state. ESP32 only: rxPin/txPin and interrupt switch.
void enableInterrupt() (ESP32) Attach the onReceive() hook; loop() then needs no update(). No-op elsewhere.
void disableInterrupt() Detach the hook (never touches other ports). Call update() to poll after this.
bool interruptEnabled() true if interrupt mode is currently active.
void update() Poll the bound port and parse. Skipped (no-op) in interrupt mode.
void reset() Reset the receive state machine.

Attitude (degrees)

Method Returns
float getRoll() / getPitch() / getYaw() Float angle in degrees (already ÷100).
int32_t getRollRaw() / getPitchRaw() / getYawRaw() Raw integer ±18000.

Accelerometer

Method Returns
float getAccelX() / getAccelY() / getAccelZ() BCD-decoded value as float.
int32_t getAccelXRaw() / getAccelYRaw() / getAccelZRaw() Raw integer.

Gyroscope

Method Returns
float getGyroX() / getGyroY() / getGyroZ() BCD-decoded value as float.
int32_t getGyroXRaw() / getGyroYRaw() / getGyroZRaw() Raw integer.

Status helpers

Method Description
bool hasNewData() true if a fresh frame was parsed since the last update().
unsigned long lastFrameTime() millis() timestamp of the last parsed frame.
bool isAlive(unsigned long aging_ms=500) true if a frame arrived within aging_ms.

Frame protocol

29-byte frames with header 0x84. All numeric fields are BCD-encoded, 3 bytes per axis.

 0    1..3   4..6   7..9   10..12  13..15  16..18  19..21  22..24  25..27  28
0x84  roll   pitch  yaw    accX    accY    accZ    gyroX   gyroY   gyroZ   checksum
  • Attitude is resolved to an integer ±18000 (e.g. 18000 = 180.00°).
  • Acceleration and angular rate share the same signed-BCD encoding; the physical scale/units depend on the sensor model configuration — apply the datasheet factor (e.g. /100 or /8192) for SI units (m/s², °/s).

Troubleshooting

Symptom Cause / fix
Examples menu shows no IMU406 Wrong library path, or IDE wasn't restarted. Path must be libraries\IMU406\.
All zeros / nothing printed Wiring reversed, wrong baud (try 9600), or monitor baud is not 115200.
Nothing in interrupt mode Check you passed the correct rxPin/txPin, the port is begin()-ed to the right baud, and loop() doesn't also need to call update().
Other serial stopped working This library only attaches onReceive() to its own port; if another port broke, review that port's own handler — they are independent.
Library reported as invalid library.properties must be UTF-8 (no BOM), no stray characters.
Values drift / are noisy Normal for raw serial IMUs; add filtering / calibration on top.
Wrong SPI/I2C expected This is a serial module — it uses two UART pins, not I²C/SPI.

Changelog

v2.0.0

Interrupt-driven reception. This is a major release that adds a second acquisition mode and a small breaking API change.

  • New: Interrupt mode on ESP32 — frames parsed from the UART onReceive() hook, so loop() does no polling.
  • New: Public begin(..., bool useInterrupt), enableInterrupt(), disableInterrupt(), interruptEnabled().
  • New: Example interrupt_mode showing IMU406 on Serial2 (interrupt) while Serial1 uses its own independent handler.
  • Cross-platform: Polling mode retained for AVR Uno/Mega & all other boards.
  • API change: feed() is now private (use update() or feedAll()).
  • On recent ESP32 cores the hook is armed with onlyIfTimeout=false for low latency.

v1.0.0

Initial release.

  • Non-blocking frame-sync parser for the 29-byte 0x84 protocol.
  • Attitude roll/pitch/yaw (float degrees) + raw ±18000 integers.
  • 3-axis accelerometer and gyroscope getters.
  • hasNewData(), lastFrameTime(), isAlive() helpers.
  • First example get_angles.

License

MIT License. See LICENSE. Author: StrayerSQH.

About

Arduino library for the IMU406 serial (UART TTL) attitude sensor: non-blocking parser for the 29-byte 0x84 BCD protocol, exposing roll/pitch/yaw plus 3-axis accel and gyro.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages