#![no_std]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
#[cfg(all(feature = "defmt", not(test)))]
use defmt::{error, info, trace, warn};
#[cfg(any(not(feature = "defmt"), test))]
mod logging_shim {
macro_rules! nop {
($($tt:tt)*) => {};
}
pub(crate) use nop as error;
pub(crate) use nop as info;
pub(crate) use nop as trace;
pub(crate) use nop as warn;
}
#[cfg(any(not(feature = "defmt"), test))]
use logging_shim::{error, info, trace, warn};
use embassy_time::Timer;
use embedded_hal_async::i2c::I2c;
#[cfg(feature = "magnitude")]
use micromath::F32Ext;
const I2C_ADDR: u8 = 0x2C; const QMC5883P_CHIP_ID: u8 = 0x80;
const REG_CHIP_ID: u8 = 0x00;
const REG_DATA_OUT_X_L: u8 = 0x01;
const REG_STATUS: u8 = 0x09;
const REG_CONTROL1: u8 = 0x0A;
const REG_CONTROL2: u8 = 0x0B;
const REG_AXIS_DEF: u8 = 0x29;
const RESET_VALUE: u8 = 0x80;
const AXIS_DEF_VALUE: u8 = 0x06;
const POLL_READY_LIMIT: usize = 50;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum Mode {
Suspend = 0b00,
Normal = 0b01,
Single = 0b10,
Continuous = 0b11,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum OutputDataRate {
Hz10 = 0b00,
Hz50 = 0b01,
Hz100 = 0b10,
Hz200 = 0b11,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum OverSampleRatio1 {
Ratio8 = 0b00,
Ratio4 = 0b01,
Ratio2 = 0b10,
Ratio1 = 0b11,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum OverSampleRate {
Rate1 = 0b00,
Rate2 = 0b01,
Rate4 = 0b10,
Rate8 = 0b11,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum Range {
Gauss2 = 0b11,
Gauss8 = 0b10,
Gauss12 = 0b01,
Gauss30 = 0b00,
}
impl Range {
pub fn sensitivity(&self) -> f32 {
match self {
Range::Gauss2 => 15000.0,
Range::Gauss8 => 3750.0,
Range::Gauss12 => 2500.0,
Range::Gauss30 => 1000.0,
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum SoftReset {
Reset = 0b1,
NoReset = 0b0,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum SelfTest {
Enabled = 0b1,
Disabled = 0b0,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum SetResetMode {
SetAndResetOff = 0b11,
SetOnly = 0b01,
SetAndResetOn = 0b00,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy)]
pub struct Qmc5883PConfig {
pub mode: Mode,
pub odr: OutputDataRate,
pub rng: Range,
pub osr1: OverSampleRatio1,
pub osr2: OverSampleRate,
set_reset: SetResetMode,
self_test: SelfTest
}
impl Qmc5883PConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_mode(mut self, mode: Mode) -> Self {
self.mode = mode;
self
}
pub fn with_odr(mut self, odr: OutputDataRate) -> Self {
self.odr = odr;
self
}
pub fn with_range(mut self, rng: Range) -> Self {
self.rng = rng;
self
}
pub fn with_osr1(mut self, osr1: OverSampleRatio1) -> Self {
self.osr1 = osr1;
self
}
pub fn with_osr2(mut self, osr2: OverSampleRate) -> Self {
self.osr2 = osr2;
self
}
#[allow(clippy::let_and_return)]
pub fn to_control1_byte(self) -> u8 {
let byte = (self.osr2 as u8) << 6
| (self.osr1 as u8) << 4
| (self.odr as u8) << 2
| (self.mode as u8);
trace!("Control1 Byte: 0b{:08b}", byte);
byte
}
#[allow(clippy::let_and_return)]
pub fn to_control2_byte(self) -> u8 {
let byte = (self.self_test as u8) << 6 | (self.rng as u8) << 2 | (self.set_reset as u8);
trace!("Control2 Byte: 0b{:08b}", byte);
byte
}
}
impl Default for Qmc5883PConfig {
fn default() -> Self {
Self {
mode: Mode::Continuous,
odr: OutputDataRate::Hz200,
rng: Range::Gauss30,
osr1: OverSampleRatio1::Ratio1,
osr2: OverSampleRate::Rate8,
set_reset: SetResetMode::SetAndResetOn,
self_test: SelfTest::Disabled,
}
}
}
pub struct Qmc5883p<I> {
i2c: I,
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QmcError<E> {
I2c(E),
SelfTestFailed,
DataNotReady,
Overflow,
WrongChipId(u8),}
impl<E> From<E> for QmcError<E> {
fn from(err: E) -> Self {
QmcError::I2c(err)
}
}
impl<I, E> Qmc5883p<I>
where
I: I2c<Error = E>,
{
pub fn new(i2c: I) -> Self {
Self { i2c }
}
pub async fn init(&mut self, config: Qmc5883PConfig) -> Result<(), QmcError<E>> {
trace!("Initializing QMC5883P Sensor...");
self.check_id().await?;
self.soft_reset().await?;
self.config_axis_sign().await?;
self.apply_configuration(config).await?;
Timer::after_millis(100).await;
if !self.self_test().await? {
return Err(QmcError::SelfTestFailed);
}
Ok(())
}
pub async fn deinit(&mut self) -> Result<(), QmcError<E>> {
self.configure_control_register_1(0x00).await?;
Ok(())
}
pub async fn apply_configuration(&mut self, config: Qmc5883PConfig) -> Result<(), QmcError<E>> {
self.configure_control_register_1(config.to_control1_byte())
.await?;
self.configure_control_register_2(config.to_control2_byte())
.await?;
Ok(())
}
async fn check_id(&mut self) -> Result<(), QmcError<E>> {
let mut id_buf = [0u8; 1];
match self
.i2c
.write_read(I2C_ADDR, &[REG_CHIP_ID], &mut id_buf)
.await
{
Ok(it) => it,
Err(err) => {
error!("I2C Error during Chip ID read");
return Err(QmcError::I2c(err));
}
};
trace!("Chip ID: 0x{:x}", id_buf[0]);
if id_buf[0] != QMC5883P_CHIP_ID {
warn!("Warning: Expected ID 0x80, got 0x{:x}", id_buf[0]);
Err(QmcError::WrongChipId(id_buf[0]))
} else {
info!("QMC5883P Detected!");
Ok(())
}
}
async fn config_axis_sign(&mut self) -> Result<(), E> {
self.i2c
.write(I2C_ADDR, &[REG_AXIS_DEF, AXIS_DEF_VALUE])
.await?;
Ok(())
}
pub async fn soft_reset(&mut self) -> Result<(), E> {
self.i2c
.write(I2C_ADDR, &[REG_CONTROL2, RESET_VALUE])
.await?;
trace!("Sensor Reset Command Sent");
Timer::after_millis(10).await;
Ok(())
}
async fn configure_control_register_1(&mut self, value: u8) -> Result<(), E> {
self.i2c.write(I2C_ADDR, &[REG_CONTROL1, value]).await?;
Timer::after_millis(50).await;
trace!("Configured Control1 Register");
Ok(())
}
async fn configure_control_register_2(&mut self, value: u8) -> Result<(), E> {
self.i2c.write(I2C_ADDR, &[REG_CONTROL2, value]).await?;
Timer::after_millis(20).await; Ok(())
}
pub async fn self_test(&mut self) -> Result<bool, QmcError<E>> {
info!("--- Starting Self-Test ---");
self.config_axis_sign().await?;
self.configure_control_register_1(0x1D).await?;
let mut baseline = [0i16; 3];
match self.poll_ready_and_check_overflow().await {
Ok(it) => it,
Err(err) => {
error!("Self-Test Failed during baseline read");
return Err(err);
}
};
let mut buf = [0u8; 6];
self.i2c
.write_read(I2C_ADDR, &[REG_DATA_OUT_X_L], &mut buf)
.await?;
baseline[0] = i16::from_le_bytes([buf[0], buf[1]]);
baseline[1] = i16::from_le_bytes([buf[2], buf[3]]);
baseline[2] = i16::from_le_bytes([buf[4], buf[5]]);
self.configure_control_register_2(0x40).await?;
self.i2c
.write_read(I2C_ADDR, &[REG_DATA_OUT_X_L], &mut buf)
.await?;
let x = i16::from_le_bytes([buf[0], buf[1]]);
let y = i16::from_le_bytes([buf[2], buf[3]]);
let z = i16::from_le_bytes([buf[4], buf[5]]);
self.configure_control_register_2(0x00).await?;
let delta_x = (x as i32 - baseline[0] as i32).abs();
let delta_y = (y as i32 - baseline[1] as i32).abs();
let delta_z = (z as i32 - baseline[2] as i32).abs();
Ok(delta_x > 100 && delta_y > 100 && delta_z > 100)
}
pub async fn read_x_y_z(&mut self) -> Result<[i16; 3], QmcError<E>> {
match self.poll_ready_and_check_overflow().await {
Ok(it) => it,
Err(err) => {
error!("Self-Test Failed during baseline read");
return Err(err);
}
};
let mut buf = [0u8; 6];
self.i2c
.write_read(I2C_ADDR, &[REG_DATA_OUT_X_L], &mut buf)
.await?;
trace!("Raw Mag Data: {:?}", buf);
let x = i16::from_le_bytes([buf[0], buf[1]]);
let y = i16::from_le_bytes([buf[2], buf[3]]);
let z = i16::from_le_bytes([buf[4], buf[5]]);
Ok([x, y, z])
}
#[cfg(feature = "magnitude")]
pub async fn read_magnitude(&mut self) -> Result<f32, QmcError<E>> {
let data = self.read_x_y_z().await?;
let x = data[0] as f32;
let y = data[1] as f32;
let z = data[2] as f32;
let magnitude = (x * x + y * y + z * z).sqrt();
Ok(magnitude)
}
pub async fn poll_ready_and_check_overflow(&mut self) -> Result<(), QmcError<E>> {
let mut status = [0u8; 1];
for _ in 0..POLL_READY_LIMIT {
self.i2c
.write_read(I2C_ADDR, &[REG_STATUS], &mut status)
.await?;
if (status[0] & 0x01) != 0 {
if (status[0] & 0x02) != 0 {
error!("Data Overflow Detected");
return Err(QmcError::Overflow);
}
return Ok(());
}
status.fill(0);
Timer::after_millis(10).await;
}
error!("Poll Ready Timeout");
Err(QmcError::DataNotReady)
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate std;
use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
#[tokio::test]
async fn test_read_x_y_z() {
let expectations = [
I2cTransaction::write_read(I2C_ADDR, std::vec![REG_STATUS], std::vec![0x01]),
I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_DATA_OUT_X_L],
std::vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
),
];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.read_x_y_z().await;
assert!(res.is_ok(), "Read operation failed");
let values = res.unwrap();
assert!(values[0] == 0x0201, "Expected: {:#x}", values[0]);
assert!(values[1] == 0x0403, "Expected: {:#x}", values[1]);
assert!(values[2] == 0x0605, "Expected: {:#x}", values[2]);
i2c.done();
}
#[tokio::test]
async fn test_read_x_y_z_with_overflow() {
let expectations = [I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_STATUS],
std::vec![0x03],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.read_x_y_z().await;
assert_eq!(res, Err(QmcError::Overflow), "Overflow should be detected");
i2c.done();
}
#[tokio::test]
async fn test_overflow_detection() {
let expectations = [I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_STATUS],
std::vec![0x03],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.poll_ready_and_check_overflow().await;
assert!(
res == Err(QmcError::Overflow),
"Overflow should be detected"
);
i2c.done();
}
#[tokio::test]
async fn test_soft_reset() {
let expectations = [I2cTransaction::write(
I2C_ADDR,
std::vec![REG_CONTROL2, RESET_VALUE],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.soft_reset().await;
assert!(res.is_ok(), "Soft reset should return ok");
i2c.done();
}
#[tokio::test]
async fn test_config_axis_sign() {
let expectations = [I2cTransaction::write(
I2C_ADDR,
std::vec![REG_AXIS_DEF, AXIS_DEF_VALUE],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.config_axis_sign().await;
assert!(res.is_ok(), "Soft reset should return ok");
i2c.done();
}
#[tokio::test]
async fn test_check_id() {
let expectations = [I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_CHIP_ID],
std::vec![QMC5883P_CHIP_ID],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.check_id().await;
assert!(res.is_ok(), "Correct ID should return ok");
i2c.done();
}
#[tokio::test]
async fn test_wrong_check_id() {
let expectations = [I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_CHIP_ID],
std::vec![QMC5883P_CHIP_ID + 1],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.check_id().await;
assert!(res.is_err(), "Wrong ID should return error");
i2c.done();
}
#[tokio::test]
async fn test_apply_configuration() {
let config = Qmc5883PConfig::default()
.with_osr2(OverSampleRate::Rate8) .with_osr1(OverSampleRatio1::Ratio4) .with_odr(OutputDataRate::Hz100) .with_mode(Mode::Continuous) .with_range(Range::Gauss8);
let expected_ctrl1 = 0xDB;
let expected_ctrl2 = 0x08;
let expectations = [
I2cTransaction::write(I2C_ADDR, std::vec![REG_CONTROL1, expected_ctrl1]),
I2cTransaction::write(I2C_ADDR, std::vec![REG_CONTROL2, expected_ctrl2]),
];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.apply_configuration(config).await;
assert!(res.is_ok());
i2c.done();
}
#[tokio::test]
async fn test_poll_timeout() {
let mut expectations = std::vec::Vec::new();
for _ in 0..POLL_READY_LIMIT {
expectations.push(I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_STATUS],
std::vec![0x00], ));
}
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.poll_ready_and_check_overflow().await;
assert_eq!(res, Err(QmcError::DataNotReady));
i2c.done();
}
#[tokio::test]
async fn test_self_test_success() {
let expectations = [
I2cTransaction::write(I2C_ADDR, std::vec![REG_AXIS_DEF, AXIS_DEF_VALUE]),
I2cTransaction::write(I2C_ADDR, std::vec![REG_CONTROL1, 0x1D]),
I2cTransaction::write_read(I2C_ADDR, std::vec![REG_STATUS], std::vec![0x01]),
I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_DATA_OUT_X_L],
std::vec![100, 0, 100, 0, 100, 0],
),
I2cTransaction::write(I2C_ADDR, std::vec![REG_CONTROL2, 0x40]),
I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_DATA_OUT_X_L],
std::vec![44, 1, 44, 1, 44, 1],
), I2cTransaction::write(I2C_ADDR, std::vec![REG_CONTROL2, 0x00]),
];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.self_test().await;
assert!(res.is_ok());
assert!(res.unwrap(), "Self test should pass with sufficient delta");
i2c.done();
}
#[cfg(feature = "magnitude")]
#[tokio::test]
async fn test_read_magnitude() {
let expectations = [
I2cTransaction::write_read(I2C_ADDR, std::vec![REG_STATUS], std::vec![0x01]),
I2cTransaction::write_read(
I2C_ADDR,
std::vec![REG_DATA_OUT_X_L],
std::vec![0xB8, 0x0B, 0xA0, 0x0F, 0x00, 0x00],
),
];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.read_magnitude().await;
assert!(res.is_ok());
let mag = res.unwrap();
assert!(mag > 4999.0 && mag < 5001.0, "Magnitude calculation failed");
i2c.done();
}
#[tokio::test]
async fn test_deinit_enters_suspend() {
let expectations = [I2cTransaction::write(
I2C_ADDR,
std::vec![REG_CONTROL1, 0x00],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Qmc5883p::new(i2c.clone());
let res = sensor.deinit().await;
assert!(res.is_ok(), "Deinit should succeed");
i2c.done();
}
}