teensycore 0.1.0

A kernel for the teensy4.0 microcontroller
Documentation
//! mpu6050
//!
//! This example demonstrates how to write a full driver for
//! the MPU-6050 6DoF sensor. It has been tested on physical
//! hardware and worked out pretty well.
//!
//! This code is not for production! It's got a lot of issues.
use teensycore::prelude::*;

const ADDR: u8 = 0x68;

pub const GRAVITY_EARTH: f32 = 9.80665f32;

pub struct SensorData {
    pub accel: Vector3D,
    pub gyro: Vector3D,
}

pub struct Vector3D {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

static mut ACCELEROMETER_RANGE: AccelerometerRange = AccelerometerRange::Normal8g;
static mut GYROSCOPE_RANGE: GyroscopeRange = GyroscopeRange::Low250;

#[derive(Clone, Copy)]
pub enum FilterBandwidth {
    Hz260 = 0, // Delay 0ms
    Hz184 = 1, // Delay 2ms
    Hz94 = 2,  // Delay 3ms
    Hz44 = 3,  // Delay 4.9ms
    Hz21 = 4,  // Delay 8.5ms
    Hz10 = 5,  // Delay 13.8ms
    Hz5 = 6,   // Delay 19ms
}

#[derive(Clone, Copy)]
pub enum GyroscopeRange {
    Low250 = 0,
    Med500 = 1,
    Normal1000 = 2,
    Extra2000 = 3,
}

#[derive(Copy, Clone)]
pub enum AccelerometerRange {
    Low2g = 0,
    Med4g = 1,
    Normal8g = 2,
    Extra16g = 3,
}

/// Perform a single byte write to a specific register address.
fn mpu6050_bus_write(i2c: &I2C, byte_addr: u8, byte: u8) {
    i2c.begin_transmission(ADDR, true);
    i2c.write(&[byte_addr, byte]);
    i2c.end_transmission();
}

/// Perform a single byte read from a specific register address.
fn mpu6050_bus_read(i2c: &I2C, byte_addr: u8) -> u8 {
    i2c.begin_transmission(ADDR, true);
    i2c.write(&[byte_addr]);
    i2c.begin_transmission(ADDR, false);
    let val = i2c.read(false);
    i2c.end_transmission();
    return val;
}

/// Initialize the MPU-6050 device.
pub fn mpu6050_init(i2c: &I2C) {
    // Find the device
    let mut found = false;
    for _ in 0..10 {
        if i2c.debug {
            debug_str(b"Searching for mpu6050");
        }
        i2c.begin_transmission(ADDR, true);
        i2c.write(&[0x75]);
        i2c.begin_transmission(ADDR, false);
        let addr = i2c.read(false);
        i2c.end_transmission();

        if addr == 0x68 {
            if i2c.debug {
                debug_u64(addr as u64, b"Found device!");
            }
            found = true;
            break;
        }
    }

    if !found {
        debug_str(b"Failed to connect to MPU6050");
        loop {}
    }

    // Device reset
    mpu6050_bus_write(&i2c, 0x6B, 0x80);
    wait_exact_ns(MS_TO_NANO * 50);
    if i2c.debug {
        debug_str(b"reset device");
    }

    loop {
        let status = mpu6050_bus_read(&i2c, 0x6B);
        if status & 0x80 == 0 {
            break;
        }
    }

    wait_exact_ns(MS_TO_NANO * 100);

    // MPU6050_SIGNAL_PATH_RESET
    mpu6050_bus_write(&i2c, 0x68, 0x7);
    wait_exact_ns(teensycore::MS_TO_NANO * 100);

    mpu6050_set_sample_divisor(&i2c, 0);
    mpu6050_set_filter_bandwidth(&i2c, FilterBandwidth::Hz10);
    mpu6050_set_gyroscope_range(&i2c, GyroscopeRange::Normal1000);
    mpu6050_set_accelerometer_range(&i2c, AccelerometerRange::Normal8g);

    // Make sure nothing is in standby mode
    mpu6050_bus_write(&i2c, 0x6C, 0x0);
    wait_exact_ns(teensycore::MS_TO_NANO * 100);

    // Disable all FIFO queues
    mpu6050_bus_write(&i2c, 0x23, 0);
    wait_exact_ns(teensycore::MS_TO_NANO * 300);

    // Write power management
    mpu6050_bus_write(&i2c, 0x6B, 0);
    wait_exact_ns(teensycore::MS_TO_NANO * 100);
}

/// Convert the raw gyro reading based on the configured full-scale range.
fn conv_raw_gyro(value: f32) -> f32 {
    let gyro_scale: f32 = match unsafe { GYROSCOPE_RANGE } {
        GyroscopeRange::Low250 => 250.0 / 32768.0,
        GyroscopeRange::Med500 => 500.0 / 32768.0,
        GyroscopeRange::Normal1000 => 1000.0 / 32768.0,
        GyroscopeRange::Extra2000 => 2000.0 / 32768.0,
    };

    return value * gyro_scale; // / gyro_scale;
}

/// Convert the raw accelerometer reading based on the configured full-scale range.
fn conv_raw_accel(value: f32) -> f32 {
    let accel_scale: f32 = match unsafe { ACCELEROMETER_RANGE } {
        AccelerometerRange::Low2g => 16384.0,
        AccelerometerRange::Med4g => 8192.0,
        AccelerometerRange::Normal8g => 4096.0,
        AccelerometerRange::Extra16g => 2048.0,
    };

    return (value / accel_scale) * GRAVITY_EARTH;
}

pub fn mpu6050_set_sample_divisor(i2c: &I2C, sample_rate: u8) {
    mpu6050_bus_write(&i2c, 0x19, sample_rate);
}

pub fn mpu6050_set_filter_bandwidth(i2c: &I2C, bandwidth: FilterBandwidth) {
    mpu6050_bus_write(&i2c, 0x1A, bandwidth as u8);
}

pub fn mpu6050_set_accelerometer_range(i2c: &I2C, range: AccelerometerRange) {
    unsafe {
        ACCELEROMETER_RANGE = range;
    }

    mpu6050_bus_write(&i2c, 0x1C, (range as u8) << 3);
}

pub fn mpu6050_set_gyroscope_range(i2c: &I2C, range: GyroscopeRange) {
    unsafe {
        GYROSCOPE_RANGE = range;
    }
    mpu6050_bus_write(&i2c, 0x1B, (range as u8) << 3);
}

/// Run the accelerometer self-test.
/// NOTE: This has never worked.
pub fn mpu6050_self_test_accelerometer(i2c: &I2C) -> bool {
    // calculate the value
    let value = (0b111 << 5) | ((unsafe { ACCELEROMETER_RANGE } as u8) << 3);
    wait_exact_ns(MS_TO_NANO * 60);

    // First, read the current values
    let untested_values = vector_read(&i2c, 0x3B);

    // set the range and activate the test
    mpu6050_bus_write(&i2c, 0x1C, value);
    wait_exact_ns(330 * MS_TO_NANO);

    let tested_values = vector_read(&i2c, 0x3B);

    // Disable self-test
    mpu6050_bus_write(&i2c, 0x1C, (unsafe { ACCELEROMETER_RANGE } as u8) << 3);

    // Evaluate the results
    let x_result = conv_raw_accel(tested_values.x) - conv_raw_accel(untested_values.x);
    let y_result = conv_raw_accel(tested_values.y) - conv_raw_accel(untested_values.y);
    let z_result = conv_raw_accel(tested_values.z) - conv_raw_accel(untested_values.z);

    return x_result != 0.0
        && y_result != 0.0
        && z_result != 0.0
        && x_result > -14.0
        && x_result < 14.0
        && y_result > -14.0
        && y_result < 14.0
        && z_result > -14.0
        && z_result < 14.0;
}

/// Run the gyroscope self-test.
/// NOTE: This has never worked.
pub fn mpu6050_self_test_gyroscope(i2c: &I2C) -> bool {
    // Calculate the value
    let value = (0b111 << 5) | ((unsafe { GYROSCOPE_RANGE } as u8) << 3);

    // First, read the current values
    let untested_values = vector_read(&i2c, 0x43);

    // Set the range and activate the gyroscope self_test.
    mpu6050_bus_write(&i2c, 0x1B, value);
    wait_exact_ns(33 * MS_TO_NANO);

    // Now evaluate the result
    let tested_values = vector_read(&i2c, 0x43);

    // Disable the self-test
    mpu6050_bus_write(&i2c, 0x1B, (unsafe { GYROSCOPE_RANGE } as u8) << 3);

    // Evaluate the result
    let x_result = conv_raw_gyro(tested_values.x) - conv_raw_gyro(untested_values.x);
    let y_result = conv_raw_gyro(tested_values.y) - conv_raw_gyro(untested_values.y);
    let z_result = conv_raw_gyro(tested_values.z) - conv_raw_gyro(untested_values.z);

    return x_result != 0.0
        && y_result != 0.0
        && z_result != 0.0
        && x_result > -14.0
        && x_result < 14.0
        && y_result > -14.0
        && y_result < 14.0
        && z_result > -14.0
        && z_result < 14.0;
}

/// Read all sensor data from the MPU-6050.
pub fn mpu6050_read_sensors(i2c: &I2C) -> SensorData {
    let bytes = [
        mpu6050_bus_read(&i2c, 0x3B),
        mpu6050_bus_read(&i2c, 0x3C),
        mpu6050_bus_read(&i2c, 0x3D),
        mpu6050_bus_read(&i2c, 0x3E),
        mpu6050_bus_read(&i2c, 0x3F),
        mpu6050_bus_read(&i2c, 0x40),
        mpu6050_bus_read(&i2c, 0x41),
        mpu6050_bus_read(&i2c, 0x42),
        mpu6050_bus_read(&i2c, 0x43),
        mpu6050_bus_read(&i2c, 0x44),
        mpu6050_bus_read(&i2c, 0x45),
        mpu6050_bus_read(&i2c, 0x46),
        mpu6050_bus_read(&i2c, 0x47),
        mpu6050_bus_read(&i2c, 0x48),
    ];

    // NOTE: This should work, but the sensor didn't
    // like it in practice.
    // i2c.begin_transmission(ADDR, true);
    // i2c.write(&[0x3B]); // ACCEL_XOUT_H register address
    // i2c.begin_transmission(ADDR, false);
    // let bytes = i2c.read_burst::<14>();
    // i2c.end_transmission();

    // Read all the data
    // Start with accelerometer
    let accel_x = f32_conv(&bytes, 0);
    let accel_y = f32_conv(&bytes, 2);
    let accel_z = f32_conv(&bytes, 4);

    // Read the gyroscope data
    let gyro_x = f32_conv(&bytes, 8);
    let gyro_y = f32_conv(&bytes, 10);
    let gyro_z = f32_conv(&bytes, 12);

    // Return the converted results.
    return SensorData {
        accel: Vector3D {
            x: conv_raw_accel(accel_x),
            y: conv_raw_accel(accel_y),
            z: conv_raw_accel(accel_z),
        },
        gyro: Vector3D {
            x: conv_raw_gyro(gyro_x),
            y: conv_raw_gyro(gyro_y),
            z: conv_raw_gyro(gyro_z),
        },
    };
}

/// Read 6 registers at once.
fn vector_read(i2c: &I2C, start_addr: u8) -> Vector3D {
    i2c.begin_transmission(ADDR, true);
    i2c.write(&[start_addr]);
    i2c.begin_transmission(ADDR, false);
    let bytes = i2c.read_burst::<6>();
    i2c.end_transmission();

    let x = f32_conv(&bytes, 0);
    let y = f32_conv(&bytes, 2);
    let z = f32_conv(&bytes, 4);

    return Vector3D { x: x, y: y, z: z };
}

/// Given an array, convert two u8 words into
/// a two's-complement f32 value.
fn f32_conv(bytes: &[u8], idx: usize) -> f32 {
    let high_bits = bytes[idx] as u16;
    let low_bits = bytes[idx + 1] as u16;
    let components = (high_bits << 8) | low_bits;
    return match (components & 0x8000) > 0 {
        true => -(!components as f32),
        false => components as f32,
    };
}