Quadcopter from scratch · Part 3

One shape for every sensor

I haven't gotten a single reading off the sensor yet. And the first real code I wrote isn't even really about the MPU-9250. It's a layer that sits between the sensor and the rest of the code.

Seems backwards I know. Here's my reasoning.

Every chip is different

The MPU-9250 stores its readings in numbered slots called registers and you ask for them by number. A sensor from a different company has different numbers and different scales, and sometimes you talk to it a totally different way.

If the flight code talks to the MPU-9250 directly it ends up full of stuff like "read register 0x3B and divide by 16384." Fine, until you want a different sensor. Then you're going through the code that keeps the drone in the air changing numbers and hoping you didn't miss one.

Honestly that was a lot of why the big flight controllers were hard for me to read. They support so many sensors I couldn't tell where the sensor code ended and the flying code started.

One shape

So every sensor reading has to fit into the same struct, doesn't matter which chip it came from.

typedef struct {
    Vector3f acceleration_mps2;    /* XYZ acceleration in m/s^2. */
    Vector3f angular_velocity_dps; /* XYZ rotation rates in degrees/s. */
    uint32_t timestamp_us;        /* Sample time in microseconds; wraps. */
} ImuSample;

Acceleration in each direction, how fast it's spinning around each axis, and when the reading happened. All in real units so nothing else in the code ever sees the raw numbers from the chip.

I put the units in the names on purpose. Mixing up degrees and radians compiles totally fine and then the drone flips over. I'd rather just see it in the name.

Adapters

The code that turns a specific chip's numbers into an ImuSample is what I'm calling an adapter. The MPU-9250 gets one. A different sensor would get its own, and all it has to do is fill in that struct.

Here's roughly where this is going.

register reads -> MPU-9250 adapter -> ImuSample
    -> attitude estimation -> control corrections -> motor mixing

Everything after ImuSample only sees that struct. So if you have some other IMU, you write one adapter file for it and the flight code stays the same. That's what I want. Use whatever sensor you've got.

It doesn't care about SPI vs I2C either

The adapter also doesn't know how it's wired to the chip.

typedef ImuStatus (*Mpu9250ReadRegister)(
    void *context,
    uint8_t register_address,
    uint8_t *out_value
);

This is a function pointer, if you haven't seen one. The adapter doesn't call a fixed read function. You give it one. That function could use SPI or I2C, or it could be a fake one on my laptop that returns made up bytes so I can test without the board.

The adapter just asks for register 0x75 and gets a byte back. I said last post I'm using SPI, but if I switch to I2C I only write a new read function. The sensor code stays the same.

First thing it does

Last post I mentioned a lot of cheap MPU-9250 boards are actually a different chip. So the first function I wrote checks that.

ImuStatus mpu9250_check_identity(Mpu9250 *device) {
    if (device == NULL) {
        return IMU_STATUS_INVALID_ARGUMENT;
    }
    if (device->read_register == NULL) {
        return IMU_STATUS_NOT_READY;
    }
 
    uint8_t id;
    ImuStatus status = device->read_register(device->context, MPU9250_REG_WHO_AM_I, &id);
 
    /* Only inspect the byte after a successful read. */
    if (status != IMU_STATUS_OK) {
        return status;
    }
 
    if (id != MPU9250_WHO_AM_I_VALUE) {
        return IMU_STATUS_WRONG_IDENTITY;
    }
 
    return IMU_STATUS_OK;
}

These chips have a register called WHO_AM_I with a fixed ID in it. A real MPU-9250 says 0x71. The function reads it, and if the read failed it returns that error before looking at the byte at all. If the ID is wrong it says so, so I'm not reading junk from a chip I don't know how to talk to.

Still missing

A lot. The generic adapter only handles one sensor right now because there's nowhere to keep each sensor's own state yet. I haven't figured out which way X, Y and Z point on the actual drone. And none of this has run on real hardware because the parts haven't shown up.

Next I'm hooking the read function up to SPI and seeing what the chip says when I ask it who it is.

Updates

Email updates are coming. Until then, follow along on X or RSS.