1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-19 03:23:49 +01:00
chrysn 841e4deee5 lsm303agr: New sensor driver using external Rust crate
It is enabled by saul_default on microbit-v2.

Co-authored-by: Marian Buschsieweke <marian.buschsieweke@ovgu.de>
2022-07-10 21:27:12 +02:00

124 lines
4.8 KiB
Rust

#![no_std]
use lsm303agr::{interface, mode, Lsm303agr, AccelOutputDataRate::Hz50};
use riot_wrappers::{saul, println, i2c, cstr::cstr, mutex::Mutex};
use saul::{Phydat, registration};
// FIXME: Is this the way we want to go? It's mimicking the C way, but we could just as well take
// the board config from some YAML.
include!(concat!(env!("BOARDDIR"), "/include/lsm303agr-config.rs"));
const NDEVICES: usize = I2C_DEVICES.len();
static DRIVER: registration::Driver<SaulLSM> = registration::Driver::new();
static DRIVER_MAG: registration::Driver<SaulLSM, MagAspect> = registration::Driver::new();
// These two being in mutexes is somewhat unnecessary (the mutexes are locked at startup and then
// never unlocked). The alternative is to unsafely access them (asserting that auto_init_lsm303agr
// / init will only ever be called once), or hiding that assertion at some preprocessor level (like
// cortex-m-rt's main does).
//
// Doing it at runtime comes at the cost of two global mutexes in memory, and some more startup
// calls.
//
// Using an Option (with .insert) rather than MaybeUninit (with .write) is another step that
// sacrifices minimal resources (could be none at all, didn't check) for readability.
// This can't go into ROM because it has a .next pointer that is altered at runtime when some other
// device is registered. (In an alternative implementation where all SAUL registries are managed by
// XFA, this would be possible; finding the point in time when they are ready to be used would be
// tricky, though.).
static REG: Mutex<[Option<registration::Registration<SaulLSM>>; NDEVICES]> = Mutex::new([None; NDEVICES]);
static REG_MAG: Mutex<[Option<registration::Registration<SaulLSM, MagAspect>>; NDEVICES]> = Mutex::new([None; NDEVICES]);
// This can't go into ROM because it contains an inner Mutex (and possibly state data from the
// Lsm303agr instance, didn't bother to check)
static LSM: Mutex<[Option<SaulLSM>; NDEVICES]> = Mutex::new([None; NDEVICES]);
#[no_mangle]
pub extern "C" fn auto_init_lsm303agr() {
if let Err(e) = init() {
println!("LSM303AGR init error: {}", e);
}
}
/// Initialize the configured LSM303AGR device, returning an error string for debug if anything
/// goes wrong
fn init() -> Result<(), &'static str> {
let lsm = LSM
.try_leak()
.expect("LSM303AGR init is only called once");
let reg = REG
.try_leak()
.expect("LSM303AGR init is only called once");
let reg_mag = REG_MAG
.try_leak()
.expect("LSM303AGR init is only called once");
for (&i2cdev, (lsm, (reg, reg_mag))) in I2C_DEVICES.iter().zip(lsm.iter_mut().zip(reg.iter_mut().zip(reg_mag.iter_mut()))) {
let mut device = Lsm303agr::new_with_i2c(i2c::I2CDevice::new(i2cdev));
device.init()
.map_err(|_| "Device initialization failed")?;
device.set_accel_odr(Hz50)
.map_err(|_| "Device configuration failed")?;
let lsm = lsm.insert(SaulLSM { device: Mutex::new(device) });
let reg = reg.insert(registration::Registration::new(&DRIVER, lsm, Some(cstr!("LSM303AGR accelerometer"))));
let reg_mag = reg_mag.insert(registration::Registration::new(&DRIVER_MAG, lsm, Some(cstr!("LSM303AGR magnetometer"))));
reg.register_static();
reg_mag.register_static();
}
Ok(())
}
struct SaulLSM {
device: Mutex<Lsm303agr<interface::I2cInterface<i2c::I2CDevice>, mode::MagOneShot>>,
}
impl registration::Drivable for &SaulLSM {
const CLASS: saul::Class = saul::Class::Sensor(Some(saul::SensorClass::Accel));
const HAS_READ: bool = true;
fn read(self) -> Result<Phydat, registration::Error> {
// SAUL doesn't guarantee exclusive access; different threads may read simultaneously.
let mut device = self.device.try_lock()
.ok_or(registration::Error)?;
let data = device.accel_data()
.map_err(|_| registration::Error)?;
// Data is in the +-2g range by default, which doesn't overflow even the i16 SAUL uses
Ok(Phydat::new(&[data.x as _, data.y as _, data.z as _], Some(saul::Unit::G), -3))
}
}
struct MagAspect(&'static SaulLSM);
impl From<&'static SaulLSM> for MagAspect {
fn from(input: &'static SaulLSM) -> Self {
Self(input)
}
}
impl registration::Drivable for MagAspect {
const CLASS: saul::Class = saul::Class::Sensor(Some(saul::SensorClass::Mag));
const HAS_READ: bool = true;
fn read(self) -> Result<Phydat, registration::Error> {
// SAUL doesn't guarantee exclusive access; different threads may read simultaneously.
let mut device = self.0.device.try_lock()
.ok_or(registration::Error)?;
let data = nb::block!(device.mag_data())
.map_err(|_| registration::Error)?;
// Original data is in nanotesla
return Ok(Phydat::fit(&[data.x, data.y, data.z], Some(saul::Unit::T), -9))
}
}