initial commit

This commit is contained in:
2026-02-09 14:16:46 +01:00
commit 65655aa78e
12 changed files with 2079 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

80
Cargo.lock generated Normal file
View File

@@ -0,0 +1,80 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-hal-async"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
dependencies = [
"embedded-hal",
]
[[package]]
name = "maybe-async"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "mmc56x3"
version = "0.1.0"
dependencies = [
"bitflags",
"embedded-hal",
"embedded-hal-async",
"maybe-async",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"

15
Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "mmc56x3"
version = "0.1.0"
edition = "2024"
[dependencies]
embedded-hal-async = { version = "1.0.0" }
maybe-async = { version = "0.2.10" }
embedded-hal = { version = "1.0.0", optional = true }
bitflags = { version = "2.10.0", default-features = false }
[features]
sync = ["embedded-hal", "maybe-async/is_sync"]

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
A Rust driver for the [MMC56X3](https://www.adafruit.com/product/5579) Triple-axis Magnetometer.
Based on the [arduino driver](https://github.com/adafruit/Adafruit_MMC56x3).
# Usage
See [examples](./example). Also make sure to check the [datasheet](https://cdn-learn.adafruit.com/assets/assets/000/113/957/original/MMC5603NJ_RevB_7-12-18.pdf).

View File

@@ -0,0 +1,8 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "picotool load --update --verify --execute -t elf"
[build]
target = "thumbv6m-none-eabi"
[env]
DEFMT_LOG = "debug"

1
example/rp2040_embassy/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1437
example/rp2040_embassy/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
[package]
name = "rp2040_embassy"
version = "0.1.0"
edition = "2024"
[dependencies]
embassy-embedded-hal = { version = "0.5.0" }
embassy-sync = { version = "0.7.2", features = ["log"] }
embassy-executor = { version = "0.9.1" , features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "log"] }
embassy-time = { version = "0.5.0", features = ["log"] }
embassy-rp = { version = "0.9.0" , features = ["log", "time-driver", "critical-section-impl", "rp2040"] }
embassy-futures = { version = "0.1.2" }
embassy-usb-logger = { version = "0.5.1" }
embassy-usb = { version = "0.5.1", features = ["log"] }
cortex-m = { version = "0.7.7", features = ["inline-asm"] }
cortex-m-rt = "0.7.5"
critical-section = "1.2.0"
heapless = "0.9.2"
log = "0.4"
mmc56x3 = { path = "../../" }

View File

@@ -0,0 +1,21 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tlink-rp.x");
}

View File

@@ -0,0 +1,5 @@
MEMORY {
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
RAM : ORIGIN = 0x20000000, LENGTH = 264K
}

View File

@@ -0,0 +1,76 @@
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_rp::{
bind_interrupts,
i2c::{self, I2c},
peripherals::{self, USB},
usb::Driver,
};
use embassy_time::{Delay, Timer};
use log::{error, info};
use mmc56x3::MMC56X3;
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
for _ in 0..20 {
error!("PANIC: {info}");
}
info!("Doing cold boot from panic");
embassy_rp::rom_data::reset_to_usb_boot(0, 0);
loop {}
}
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => embassy_rp::usb::InterruptHandler<USB>;
I2C0_IRQ => i2c::InterruptHandler<peripherals::I2C0>;
});
#[embassy_executor::task]
async fn logger_task(driver: Driver<'static, USB>) {
embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver);
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let driver = Driver::new(p.USB, Irqs);
spawner.must_spawn(logger_task(driver));
let sda = p.PIN_4;
let scl = p.PIN_5;
let i2c = I2c::new_async(p.I2C0, scl, sda, Irqs, i2c::Config::default());
let mut device = MMC56X3::new(i2c, Delay);
device.init().await.expect("Failed to init");
device
.set_data_rate(mmc56x3::DataRate::Hz(100))
.await
.expect("Failed to set data rate");
device
.set_continuous_mode(true)
.await
.expect("Failed to set continuous mode");
for _ in 0..20 {
Timer::after_secs(1).await;
// let result = device.read_temperature().await;
// device.trigger_messurement().await.expect("Failed to trigger trigger_messurement");
match device.read_messurement().await {
Ok(d) => info!("Got: {:?}", d),
Err(e) => error!("Error: {:?}", e),
}
}
info!("Doing cold boot");
Timer::after_secs(1).await;
embassy_rp::rom_data::reset_to_usb_boot(0, 0);
}

405
src/lib.rs Normal file
View File

@@ -0,0 +1,405 @@
#![no_std]
#[cfg(feature = "sync")]
use embedded_hal::i2c::{self};
use embedded_hal_async::delay::DelayNs;
#[cfg(not(feature = "sync"))]
use embedded_hal_async::i2c::{self};
use bitflags::bitflags;
const REG_XOUT_0: u8 = 0x00;
const REG_XOUT_1: u8 = 0x01;
const REG_YOUT_0: u8 = 0x02;
const REG_YOUT_1: u8 = 0x03;
const REG_ZOUT_0: u8 = 0x04;
const REG_ZOUT_1: u8 = 0x05;
const REG_XOUT_2: u8 = 0x06;
const REG_YOUT_2: u8 = 0x07;
const REG_ZOUT_2: u8 = 0x08;
const REG_TOUT: u8 = 0x09;
const REG_STATUS1: u8 = 0x18;
const REG_ODR: u8 = 0x1A;
const REG_CONTROL0: u8 = 0x1B;
const REG_CONTROL1: u8 = 0x1C;
const REG_CONTROL2: u8 = 0x1D;
const REG_PRODUCT_ID: u8 = 0x39;
const DEFAULT_ADDRESS: u8 = 0x30;
const DEVICE_ID: u8 = 0x10;
bitflags! {
/// Flags for status_1 register
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct StatusRegisterFlags: u8 {
/// This bit is an indicator of successfully reading its OTP memory either as part of its power up
/// sequence, or after an I2C command that reloads the OTP memory, such as resetting the chip
/// and refreshing the OTP registers
const OTP_READ_DONE = 0b0001_0000;
/// This bit is an indicator of the selftest signal, it keeps low once the device PASS selftest.
const SAT_SENSOR = 0b0010_0000;
/// This bit indicates that a measurement of magnetic field is done and the data is ready to be
/// read. This bit is reset only when any of the magnetic data registers is read.
const MESSUREMENT_M_DONE = 0b0100_0000;
/// This bit indicates that a measurement of temperature is done and the data is ready to be read.
/// This bit is reset only when the temperature register is read.
const MESSUREMENT_T_DONE = 0b1000_0000;
}
}
bitflags! {
/// Flags for the control 0 register
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Control0RegisterFlags: u8 {
/// Take Measure of Magnetic field, or TM_M bit. Writing a 1 into this location causes the chip to
/// perform a magnetic measurement. This bit is self-clearing at the end of each measurement.
const TAKE_MESSUREMENT_M = 0b0000_0001;
/// Take Measure of Temperature, or TM_T bit. Writing a 1 into this location causes the chip to
/// perform a temperature measurement. This bit is self-clearing at the end of each measurement.
const TAKE_MESSUREMENT_T = 0b0000_0010;
/// Writing a 1 into this location will cause the chip to do the Set operation, which will allow large set
/// current to flow through the sensor coils for 375ns. This bit is self-cleared at the end of Set operation.
const DO_SET = 0b0000_1000;
/// Writing a 1 into this location will cause the chip to do the Reset operation, which will allow large
/// reset current to flow through the sensor coils for 375ns. This bit is self-cleared at the end of Reset operation.
const DO_RESET = 0b0001_0000;
/// Writing a 1 into this location will enable the function of automatic set/reset. This function applies
/// to both on-demand and continuous-time measurements. This bit must be set to 1 in order to
/// activate the feature of periodic set. This bit is recommended to set to “1” in the application.
const AUTO_SET_RESET_EN = 0b0010_0000;
/// Writing a 1 into this location will enable the function of automatic self-test. The threshold in
/// register 1EH, 1FH, 20H should be set before this bit is set to 1. This bit clears itself after the
/// operation is completed.
const AUTO_SELF_TEST_EN = 0b0100_0000;
/// Writing a 1 into this location will start the calculation of the measurement period according to the
/// ODR. This bit should be set before continuous-mode measurements are started. This bit is self-
/// cleared after the measurement period is calculated by internal circuits.
const CMM_FRE_EN = 0b1000_0000;
}
}
bitflags! {
/// Flags for the control 1 register
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Control1RegisterFlags: u8 {
const BANDWIDTH_0 = 0b0000_0001;
const BANDWIDTH_1 = 0b0000_0010;
/// Writing “1” will disable this channel, and reduce Measurement Time and total charge per
/// measurement.When a channel is disabled it is simply skipped during Take Measure routine. Its
/// output register is not reset and will maintain the last value written to it when this channel was
/// active.
const X_INHIBI = 0b0000_0100;
/// Writing “1” will disable this channel, and reduce Measurement Time and total charge per
/// measurement.When a channel is disabled it is simply skipped during Take Measure routine. Its
/// output register is not reset and will maintain the last value written to it when this channel was
/// active. Note: Y/Z needs to be inhibited the same time in case needed.
const Y_INHIBIT = 0b0000_1000;
/// Writing “1” will disable this channel, and reduce Measurement Time and total charge per
/// measurement.When a channel is disabled it is simply skipped during Take Measure routine. Its
/// output register is not reset and will maintain the last value written to it when this channel was
/// active. Note: Y/Z needs to be inhibited the same time in case needed.
const Z_INHIBIT = 0b0001_0000;
/// Writing 1 into this location will bring a DC current through the self-test coil of the sensor. This
/// current will cause an offset of the magnetic field. This function is used to check whether the
/// sensor has been saturated.
const ST_ENP = 0b0010_0000;
/// The function of this bit is similar to ST_ENP, but the offset of the magnetic field is of opposite
/// polarity.
const ST_ENM = 0b0100_0000;
/// Software Reset. Writing “1”will cause the part to reset, similar to power-up. It will clear all registers
/// and also re-read OTP as part of its startup routine. The power on time is 20mS
const SW_RESET = 0b1000_0000;
}
}
bitflags! {
/// Flags for the control 2 register
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Control2RegisterFlags: u8 {
const PDR_0 = 0b0000_0001;
const PDR_1 = 0b0000_0010;
const PDR_2 = 0b0000_0100;
/// Writing 1 into this location will enable the function of periodical set.
const EN_PRD_SET = 0b0000_1000;
/// The device will enter continuous mode, if ODR has been set to a non-zero value and a 1 has
/// been written into Cmm_freq_en. The internal counter will start counting as well since this bit
/// is set.
const CMM_EN = 0b0001_0000;
/// If this bit is set to 1 to achieve 1000Hz ODR.
const HPOWER = 0b1000_0000;
}
}
#[derive(Debug, Clone, Copy)]
pub struct MagneticMessurement {
/// X-Axis in µT
pub x: f32,
/// Y-Axis in µT
pub y: f32,
/// Z-Axis in µT
pub z: f32,
}
/// At what rate
pub enum DataRate {
Hz(u8),
Max1000Hz,
}
#[derive(Debug)]
pub enum Error<E> {
I2c(E),
Timeout,
NotAvailableInContinuousMode,
}
impl<E> From<E> for Error<E> {
fn from(e: E) -> Self {
Error::I2c(e)
}
}
/// The MMC56X3 sensor
pub struct MMC56X3<I, D>
where
I: i2c::I2c,
D: DelayNs,
{
i2c: I,
delay: D,
is_continuous_mode: bool,
}
impl<I, D> MMC56X3<I, D>
where
I: i2c::I2c,
D: DelayNs,
{
pub fn new(i2c: I, delay: D) -> Self {
Self {
i2c,
delay,
is_continuous_mode: false,
}
}
pub async fn init(&mut self) -> Result<(), Error<I::Error>> {
self.reset().await?;
Ok(())
}
/// Resets the sensor to an initial state
pub async fn reset(&mut self) -> Result<(), Error<I::Error>> {
self.write_reg_controll_1(Control1RegisterFlags::SW_RESET)
.await?;
self.delay.delay_ms(20).await; // According to the datasheet power on time is 20ms
self.magnet_set_reset().await?;
self.set_continuous_mode(false).await?;
Ok(())
}
/// Pulse large currents through the sense coils to clear any offset
pub async fn magnet_set_reset(&mut self) -> Result<(), Error<I::Error>> {
self.write_reg_controll_0(Control0RegisterFlags::DO_SET)
.await?;
self.delay.delay_ns(375).await; // According to the datasheet this is how long it takes.
self.write_reg_controll_0(Control0RegisterFlags::empty())
.await?;
Ok(())
}
pub async fn set_continuous_mode(&mut self, enable: bool) -> Result<(), Error<I::Error>> {
if enable {
self.write_reg_controll_0(Control0RegisterFlags::CMM_FRE_EN)
.await?;
self.write_reg_controll_2(Control2RegisterFlags::CMM_EN)
.await?;
self.is_continuous_mode = true;
} else {
self.write_reg_controll_2(Control2RegisterFlags::empty())
.await?;
self.is_continuous_mode = false;
}
Ok(())
}
pub async fn set_data_rate(&mut self, rate: DataRate) -> Result<(), Error<I::Error>> {
match rate {
DataRate::Hz(hz) => {
self.write_reg_odr(hz).await?;
self.write_reg_controll_2(Control2RegisterFlags::empty())
.await?;
Ok(())
}
DataRate::Max1000Hz => {
self.write_reg_odr(255).await?;
self.write_reg_controll_2(Control2RegisterFlags::HPOWER)
.await?;
Ok(())
}
}
}
/// Read temperature in Celcius with steps of 0.8 C
pub async fn read_temperature(&mut self) -> Result<f32, Error<I::Error>> {
if self.is_continuous_mode {
return Err(Error::NotAvailableInContinuousMode);
}
self.write_reg_controll_0(Control0RegisterFlags::TAKE_MESSUREMENT_T)
.await?;
self.wait_for_status_flag(StatusRegisterFlags::MESSUREMENT_T_DONE)
.await?;
let t_out = self.read_reg_temperature().await?;
// The start of the temperature range starts a -75c
let temperature = -75.0 + (t_out as f32 * 0.8);
Ok(temperature)
}
pub async fn read_messurement(&mut self) -> Result<MagneticMessurement, Error<I::Error>> {
let mut data = [0u8; 9];
self.read_registers(REG_XOUT_0, &mut data).await?;
let x = ((data[0] as u32) << 12) | ((data[1] as u32) << 4) | ((data[6] as u32) >> 4);
let y = ((data[2] as u32) << 12) | ((data[3] as u32) << 4) | ((data[7] as u32) >> 4);
let z = ((data[4] as u32) << 12) | ((data[5] as u32) << 4) | ((data[8] as u32) >> 4);
const OFFSET: u32 = 1 << 19;
let x = x.wrapping_sub(OFFSET) as i32;
let y = y.wrapping_sub(OFFSET) as i32;
let z = z.wrapping_sub(OFFSET) as i32;
// Apply resolution. At 20 Bit mode.
const RESOLUTION: f32 = 0.00625;
let fx: f32 = x as f32 * RESOLUTION;
let fy: f32 = y as f32 * RESOLUTION;
let fz: f32 = z as f32 * RESOLUTION;
Ok(MagneticMessurement {
x: fx,
y: fy,
z: fz,
})
}
pub async fn trigger_messurement(&mut self) -> Result<(), Error<I::Error>> {
self.write_reg_controll_0(Control0RegisterFlags::TAKE_MESSUREMENT_M)
.await?;
self.wait_for_status_flag(StatusRegisterFlags::MESSUREMENT_M_DONE)
.await
}
#[inline(always)]
pub async fn read_product_id(&mut self) -> Result<u8, Error<I::Error>> {
self.read_register(REG_PRODUCT_ID).await
}
async fn wait_for_status_flag(
&mut self,
flag: StatusRegisterFlags,
) -> Result<(), Error<I::Error>> {
for _ in 0..300 {
let status = self.read_reg_status().await?;
if status.contains(flag) {
return Ok(());
}
self.delay.delay_ms(5).await;
}
Err(Error::Timeout)
}
#[inline(always)]
async fn read_reg_temperature(&mut self) -> Result<u8, Error<I::Error>> {
self.read_register(REG_TOUT).await
}
#[inline(always)]
async fn read_reg_status(&mut self) -> Result<StatusRegisterFlags, Error<I::Error>> {
Ok(StatusRegisterFlags::from_bits_truncate(
self.read_register(REG_STATUS1).await?,
))
}
#[inline(always)]
async fn write_reg_odr(&mut self, data: u8) -> Result<(), Error<I::Error>> {
self.write_register(REG_ODR, data).await
}
#[inline(always)]
async fn write_reg_controll_0(
&mut self,
value: Control0RegisterFlags,
) -> Result<(), Error<I::Error>> {
self.write_register(REG_CONTROL0, value.bits()).await
}
#[inline(always)]
async fn write_reg_controll_1(
&mut self,
value: Control1RegisterFlags,
) -> Result<(), Error<I::Error>> {
self.write_register(REG_CONTROL1, value.bits()).await
}
#[inline(always)]
async fn write_reg_controll_2(
&mut self,
value: Control2RegisterFlags,
) -> Result<(), Error<I::Error>> {
self.write_register(REG_CONTROL2, value.bits()).await
}
#[inline(always)]
async fn write_register(&mut self, reg: u8, value: u8) -> Result<(), Error<I::Error>> {
self.i2c.write(DEFAULT_ADDRESS, &[reg, value]).await?;
Ok(())
}
async fn read_register(&mut self, reg: u8) -> Result<u8, Error<I::Error>> {
let mut data = [0u8; 1];
self.i2c
.write_read(DEFAULT_ADDRESS, &[reg], &mut data)
.await?;
Ok(data[0])
}
#[inline(always)]
async fn read_registers(&mut self, reg: u8, buffer: &mut [u8]) -> Result<(), Error<I::Error>> {
self.i2c.write_read(DEFAULT_ADDRESS, &[reg], buffer).await?;
Ok(())
}
}