QMI8658

December 26 2025The QMI8658 is an Inertial Measurement Unit (IMU) that contains an accelerometer, gyrometer, and some other nice to have features. Which features it supports depends on the revision of the chip you're working with (officially... sometimes registers stay enabled).An IMU senses at least angular velocity and linear acceleration along 3 axes, which can be fused with special algorithms to tell you your orientation in 3D space.
Random IMU diagram from https://www.usgs.gov/centers/pcmsc/science/inertial-measurement-unit-imuRandom IMU diagram from https://www.usgs.gov/centers/pcmsc/science/inertial-measurement-unit-imu

Why was I using the QMI8658?

I am using a dev board to build a smart wearable. I want to use orientation to wake the device, and also expose orientation to several "apps" running on the device.This IMU was included on the board, and seemed good enough for what I needed. It doesn't include very advanced features or high quality motion processing like a more pricey IMU would, but it's good enough for orientation tracking.My eventual goal of tracking position in 3d space over long periods of time is very difficult or impossible (depending on the timeframe) with this chip.

Lessons Learned

Why rewrite something that already exists?

It doesn't, not with the features I need. The open source drivers for this chip are extremely basic and only support simple initialization and reads.If you want to actually use a specific IC for the reasons which make it special, like various engines on an IMU or specialized power modes, you will likely need to dive into the datasheet.
Screenshot of some listed features from the QMI8658 Revision A datasheet.Screenshot of some listed features from the QMI8658 Revision A datasheet.
The existing drivers for this chip support basic reads and initializations, but none of the power modes, motion detection modes, fifo buffering, etc. These are all of the things that make this chip... this chip.It may be easier to modify an existing driver to support functionality you need, but in this case, I only needed the advanced functionality, so I'd have thrown out everything else.At a certain point, just do it yourself.

Development Process

I find reading 88 page datasheets to be a bit... dry... so I tend to chuck them into ChatGPT and use it as a specialized bot that can tell me what I need.

ChatGPT Parsing the Datasheet

My datasheet bot, given my requirements of needing to offload as much work as possible to the IMU, suggested two features, supported by the 09 revision of the chip, named AttitudeEngine and Motion on Demand.The typical process of tracking rotation with an IMU involves combining multiple sensors' strengths and neutralizing their weaknesses via filtering.
Screenshot of Google's AI search results explaining complementary filtering.Screenshot of Google's AI search results explaining complementary filtering.
My datasheet bot informed me that these features could be combined such that the IMU accumulates and returns a delta quaternion vector via specialized registers that represent a rotation since the last read. This would, theoretically, be a completely offloaded complementary filter. This sounded ideal, as the MCU could request and accumulate these values whenever it wanted.

Bootup Register Initialization Sequence

I asked ChatGPT to produce a register initialization sequence, given the features I needed. These often wind up being wrong because the datasheet information about what needs to be initialized, and in what order (!), is often quite unclear or written with bad English.ChatGPT's PDF parsing is also not perfect, and in my case, it got the register numbers off by one. It's just the right amount of lack of information for it to begin making things up, so you should go through applicable examples/processes in the datasheet to make sure it's not hallucinating.The bootup sequences can also be quite hard to test because you don't know what you dont know:

Data Fetching Functions

This should be quite easy, but I spent a ridiculous amount of time trying to fetch the delta quaternion values from the IMU. It just kept returning 0 for every one.The debugging continued until I realized I should do sanity checks with the whoami and revision registers.The revision register read 0x0A and I immediately facepalmed, looking at the revision 09 datasheet in my downloads folder.After confirming the chip revision with a microscope, It turns out that I had spent the past 2 days working with the incorrect version of the datasheet, and the correct one showed completely different register definitions.Moral of the story: Don't trust your board manufacturer (Waveshare 😠), and double check if things seem suspicious.

Redoing the Bootup Sequence

Once getting the new datasheet and re-deciding what features I needed (because Motion on Demand was not present in this new version), I decided on FIFO buffering because there was no better alternative.I was already familiar with the general initialization, so knocked this out pretty quickly and moved on.

Data Fetching Functions Part 2

FIFO buffering is a very simply concept. Data goes in the back of the buffer, and data leaves the buffer from the front. Surely you learned about this in CS 101. The IMU readings are 12 bytes each (2 sensors w/ 3 axes w/ 2 bytes per axis).On this chip, there are 1536 bytes of memory for the FIFO, or 128 readings when both sensors are enabled. Depending on your Output Data Rate (ODR), you will need to read from this FIFO with a maximum delay between readings, otherwise you will lose data. The benefit of a FIFO is that the IMU can keep track of its state while the MCU does other stuff, and reads can be done in batches, reducing I2C overhead.I have an LVGL timer going off every 25 ms to calculate the framebuffer and service the display, touchscreen interrupts happening at most every 10ms, and various other peripherals on the same i2c bus. Reading the IMU at the required frequency would saturate the MCU.There is a value on this IMU called the "waterfall value" where, when the data in the FIFO crosses a certain value, the IMU will raise an interrupt to notify the MCU that data is available.It's important that the MCU services this notification promptly so we don't lose data. The interrupt simply pushes a dummy value to a FreeRTOS queue, which is received by a thread. Of course, we don't want the interrupt doing much work before returning, but this method of signaling to the thread is not ideal. In the future I will likely change it to a simple bit field / flag.How to fetch from the FIFO:Now what is with the discrepency here? Why do I need to issue a command to enter read mode but read a register to exit it? Who knows. The chip makers certainly don't tell you, and this is why my datasheet bot is incredibly helpful. The way I'd assume you want to enter read mode is by writing a bit to the read mode field of the FIFO control register. This would've led me astray.

Interpreting and Filtering the Data Samples

The data obviously comes in over the I2C bus as bytes, which must be "casted" via pointer magic to the correct structure type on the host.Each axis for each sensor comes in as a pair of bytes, one high and one low. The order these bytes come in is determined by the little or big endian switch on the CTRL1 register.
A single reading of both the Gyro and Accel fill into this struct with 2 bytes for each axis, high and low. Shown in little endian order.A single reading of both the Gyro and Accel fill into this struct with 2 bytes for each axis, high and low. Shown in little endian order.

Complementary Filtering

As explained already, the gyro reads each axis in degrees/second, while the accelerometer reads each axis in meters/second.The accelerometer readings can be transformed into Euler angles with atan2f, with a reference to gravity (assuming there is no other linear acceleration of the IMU). The gyro can be integrated over time to produce Euler angles relative to its starting position.These two angles don't necessarily have the same reference frame, to start, but we can use the accelerometer to nudge the gyrometer's reference point every iteration towards downward.The complementary filter does this while combining the best of the sensors while neutralizing out the worst:The complementary filter is extremely simple, just a weighted sum between the updated/nudged gyro value and the accelerometer value. This is done in a loop, with the output of the previous iteration feeding into the next iteration's gyro integration.

Calibration

The previously referred to weighted sum of the complementary filter can neutralize some of the error of the gyrometer, but in my case the error was too large, and you should calibrate anyway for best results.I am calibrating by waiting for the interrupt to be triggered once, reading all fifo values, and generating an average gyrometer reading for each axis. This is then subtracted from the readings before they are used.It's important not to calibrate the accelerometer the same way we did the gyrometer since we care about which direction gravity is pulling us. If you calibrated the accelerometer while the device was flat, the z axis read 0g when flat and 2g when inverted.

LVGL Axes Indicator

For a demo with the new driver, I built a simple RGB axis indicator, as you'd see in a CAD software or a 3D printer slicer.It uses an orthographic projection, which basically just throws away the z axis of the 3d vector because I don't need depth here.
The axes indicator running on a prototype of the wearable.The axes indicator running on a prototype of the wearable.
Note: The Z axis (blue) points into the board, which is quite difficult to see with the orthographic projection. This is just a v0.1 after all.

Where To Next?

Missing data from the FIFO spells disaster for the gyrometer integration. Fortunately, the accelerometer can pull the gyrometer back to it's gravitational reference, but for a second it jumps around heavily. This would not be positive for whomever is using the data.Occasionally, LVGL tasks take a long time and the FIFO doesn't get read often enough. Ideally, the IMU would always be serviced with the correct timing, so decoupling the "apps" running on the watch from keeping track of the IMU state is necessary for my use.Because I wrote the driver myself, I can implement a thread that reads and empties the FIFO every n milliseconds, effectively decoupling it.

Driver Use Process

Instructions for use are listed in the README.md. If you want to take a look at the driver, it is linked below:https://github.com/citren0/idf-QMI8658c