Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ jobs:
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

keep-in-sync:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Check that and async versions are in sync
run: make check-async-sync
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ exclude = ["examples/nrf52840/.cargo"]
[dependencies]
embedded-hal = "1.0.0"
fixedvec = "0.2.4"
embedded-hal-async = {version = "1.0.0", optional = true }


[features]
async = ["dep:embedded-hal-async"]
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

# When you've made changes to the async version, updating the sync version is trivial
.PHONY: async-to-sync
async-to-sync:
cat src/interface_async.rs | sed -e 's/[.]await//' -e 's/async //' -e 's/_async//' | grep -vF '#[allow(async_fn_in_trait)]' | rustfmt > src/interface.rs
cat src/bmi2_async.rs | sed -e 's/[.]await//' -e 's/async //' -e 's/_async//' | grep -vF '#[allow(async_fn_in_trait)]' | rustfmt > src/bmi2.rs

# When you've made changes to the sync version, updating the async version is not always as clear
# because there are many possible places to insert async and await, so the best we can do is check.
.PHONY: check-async-sync
check-async-sync:
# Everything must be formatted before we start, because the code might reflow differently in
# sync vs async versions. For local dev we do it automatically. For CI we just check it.
if [ "${CI}" = "" ]; then cargo fmt; fi
cargo fmt --check
# Strip out the async-related keywords, then format, then diff against the sync version.
cat src/interface_async.rs | sed -e 's/[.]await//' -e 's/async //' -e 's/_async//' | grep -vF '#[allow(async_fn_in_trait)]' | rustfmt > /tmp/stripped-interface_async.rs
diff -u /tmp/stripped-interface_async.rs src/interface.rs
cat src/bmi2_async.rs | sed -e 's/[.]await//' -e 's/async //' -e 's/_async//' | grep -vF '#[allow(async_fn_in_trait)]' | rustfmt > /tmp/stripped-bmi2_async.rs
diff -u /tmp/stripped-bmi2_async.rs src/bmi2.rs
# Make sure everything actually compiles once we're done.
cargo check --all-features
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,39 @@ This is an [embedded-hal](https://github.com/rust-embedded/embedded-hal) driver

```rust
// ...

use bmi2::Bmi2;
use bmi2::config;
use bmi2::{types::Burst, I2cAddr, types::PwrCtrl};
use embedded_hal::delay::DelayNs;

// Specify your delay type, for example:
let delay = MyDelay::new(); // Replace with your actual delay implementation

// Choose a buffer size (e.g., 512 bytes), needs to be >= max data burst
const BUFFER_SIZE: usize = 512;

/// Create a new Bmi2 device using I2C with its alternative address (0x69).
/// Configure the max data burst to 255 bytes:
/// - used for the upload of the configuration during initialization.
/// - This is a limitation from your device or its HAL.
let mut bmi = Bmi2::new_i2c(i2c, I2cAddr::Alternative, Burst::Other(255));
/// - This is a limitation from your device or its HAL.
let mut bmi = Bmi2::<_, _, BUFFER_SIZE>::new_i2c(
i2c,
delay,
I2cAddr::Alternative,
Burst::new(255),
);

/// Get the chip id. Should be 0x24 or 36 in decimal
let chip_id = bmi.get_chip_id().unwrap();

/// Initialize the senor.
/// During this process a configuration of > 8kB is uploaded to the sensor.
/// Alternatively, for the BMI260 call its dedicated config:
/// bmi.init(&config::BMI260_CONFIG_FILE).unwrap();
bmi.init(&config::BMI270_CONFIG_FILE).unwrap();

/// Enable power for the accelerometer and the gyroscope.
let pwr_ctrl = PwrCtrl { aux_en: false, gyr_en: true, acc_en: true, temp_en: false };
bmi.set_pwr_ctrl(pwr_ctrl).unwrap();

/// Read the raw data
let data = bmi.get_data().unwrap();

// ...
```
8 changes: 7 additions & 1 deletion examples/nrf52840/basic_read/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use nrf52840_hal::{
pac,
timer::Timer,
twim::{self, Twim},
Delay,
};

use defmt_rtt as _;
Expand All @@ -22,6 +23,7 @@ use bmi2::{types::Burst, types::PwrCtrl, I2cAddr};
#[entry]
fn main() -> ! {
let p = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();

let mut timer = Timer::new(p.TIMER0);
let port0 = p0::Parts::new(p.P0);
Expand All @@ -33,7 +35,11 @@ fn main() -> ! {

let i2c = Twim::new(p.TWIM0, twim_pins, twim::Frequency::K100);

let mut bmi = Bmi2::new_i2c(i2c, I2cAddr::Alternative, Burst::Other(255));
let delay = Delay::new(core.SYST);

const BUFFER_SIZE: usize = 256;

let mut bmi = Bmi2::<_, _, BUFFER_SIZE>::new_i2c(i2c, delay, I2cAddr::Alternative, Burst::new(255));
let chip_id = bmi.get_chip_id().unwrap();
defmt::info!("chip id: {}", chip_id);

Expand Down
144 changes: 103 additions & 41 deletions src/bmi2.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
use embedded_hal::delay::DelayNs;
use fixedvec::FixedVec;

use crate::interface::{I2cAddr, I2cInterface, ReadData, SpiInterface, WriteData};
use crate::registers::Registers;
use crate::{
interface::{I2cInterface, ReadData, SpiInterface, WriteData},
interface_common::I2cAddr,
};

use crate::types::{
AccConf, AccOffsets, AccRange, AccSelfTest, AuxConf, AuxData, AuxIfConf, AxisData, Burst, Cmd,
Data, Drv, Error, ErrorReg, ErrorRegMsk, Event, FifoConf, FifoDowns, GyrConf, GyrCrtConf,
GyrOffsets, GyrRange, GyrSelfTest, IfConf, IntIoCtrl, IntLatch, IntMapData, IntMapFeat,
InternalError, InternalStatus, InterruptStatus, NvConf, PullUpConf, PwrConf, PwrCtrl,
Saturation, Status, WristGestureActivity, FIFO_LENGTH_1_MASK,
Saturation, Status, WristGestureActivity, BMI160_CHIP_ID, BMI260_CHIP_ID, BMI270_CHIP_ID,
FIFO_LENGTH_1_MASK,
};

pub struct Bmi2<I> {
pub struct Bmi2<I, D, const N: usize> {
iface: I,
max_burst: u16,
delay: D,
}

impl<I2C> Bmi2<I2cInterface<I2C>> {
impl<I2C, D, const N: usize> Bmi2<I2cInterface<I2C>, D, N> {
/// Create a new Bmi270 device with I2C communication.
pub fn new_i2c(i2c: I2C, address: I2cAddr, burst: Burst) -> Self {
pub fn new_i2c(i2c: I2C, delay: D, address: I2cAddr, burst: Burst) -> Self {
Bmi2 {
iface: I2cInterface {
i2c,
address: address.addr(),
},
max_burst: burst.val(),
delay,
}
}

Expand All @@ -34,12 +41,16 @@ impl<I2C> Bmi2<I2cInterface<I2C>> {
}
}

impl<SPI> Bmi2<SpiInterface<SPI>> {
impl<SPI, D, const N: usize> Bmi2<SpiInterface<SPI>, D, N>
where
D: DelayNs,
{
/// Create a new Bmi270 device with SPI communication.
pub fn new_spi(spi: SPI, burst: Burst) -> Self {
pub fn new_spi(spi: SPI, delay: D, burst: Burst) -> Self {
Bmi2 {
iface: SpiInterface { spi },
max_burst: burst.val(),
delay,
}
}

Expand All @@ -49,9 +60,10 @@ impl<SPI> Bmi2<SpiInterface<SPI>> {
}
}

impl<I, CommE> Bmi2<I>
impl<I, D, CommE, const N: usize> Bmi2<I, D, N>
where
I: ReadData<Error = Error<CommE>> + WriteData<Error = Error<CommE>>,
D: embedded_hal::delay::DelayNs, // Add constraint for D
{
/// Get the chip id.
pub fn get_chip_id(&mut self) -> Result<u8, Error<CommE>> {
Expand Down Expand Up @@ -149,9 +161,7 @@ where
}

/// Get the detected wrist gesture and activity type.
pub fn get_wrist_gesture_activity(
&mut self,
) -> Result<WristGestureActivity, Error<CommE>> {
pub fn get_wrist_gesture_activity(&mut self) -> Result<WristGestureActivity, Error<CommE>> {
let wr_gest_acc = self.iface.read_reg(Registers::WR_GEST_ACT)?;

Ok(WristGestureActivity::from_reg(wr_gest_acc))
Expand Down Expand Up @@ -426,10 +436,7 @@ where
}

/// Set interrupt 1 feature mapping.
pub fn set_int1_map_feat(
&mut self,
int1_map_feat: IntMapFeat,
) -> Result<(), Error<CommE>> {
pub fn set_int1_map_feat(&mut self, int1_map_feat: IntMapFeat) -> Result<(), Error<CommE>> {
let reg = int1_map_feat.to_reg();
self.iface.write_reg(Registers::INT1_MAP_FEAT, reg)?;
Ok(())
Expand All @@ -442,10 +449,7 @@ where
}

/// Set interrupt 2 feature mapping.
pub fn set_int2_map_feat(
&mut self,
int2_map_feat: IntMapFeat,
) -> Result<(), Error<CommE>> {
pub fn set_int2_map_feat(&mut self, int2_map_feat: IntMapFeat) -> Result<(), Error<CommE>> {
let reg = int2_map_feat.to_reg();
self.iface.write_reg(Registers::INT2_MAP_FEAT, reg)?;
Ok(())
Expand Down Expand Up @@ -581,10 +585,7 @@ where
}

/// Set the accelerometer self test configuration.
pub fn set_acc_self_test(
&mut self,
acc_self_test: AccSelfTest,
) -> Result<(), Error<CommE>> {
pub fn set_acc_self_test(&mut self, acc_self_test: AccSelfTest) -> Result<(), Error<CommE>> {
self.iface
.write_reg(Registers::ACC_SELF_TEST, acc_self_test.to_reg())?;
Ok(())
Expand Down Expand Up @@ -703,46 +704,107 @@ where
Ok(())
}

/// Disable power save mode.
pub fn disable_power_save(&mut self) -> Result<(), Error<CommE>> {
let mut pwr_conf = self.get_pwr_conf()?;
pwr_conf.power_save = false;
self.set_pwr_conf(pwr_conf)?;
// Critical delay after disabling power save
self.delay.delay_us(450);
Ok(())
}

/// Enable power save mode.
pub fn enable_power_save(&mut self) -> Result<(), Error<CommE>> {
let mut pwr_conf = self.get_pwr_conf()?;
pwr_conf.power_save = true;
self.set_pwr_conf(pwr_conf)?;
// Critical delay after enabling power save
self.delay.delay_us(450);
Ok(())
}

/// Initialize sensor.
pub fn init(&mut self, config_file: &[u8]) -> Result<(), Error<CommE>> {
// Verify chip ID first
let chip_id = self.iface.read_reg(Registers::CHIP_ID)?;
if !(chip_id == BMI160_CHIP_ID || chip_id == BMI260_CHIP_ID || chip_id == BMI270_CHIP_ID) {
return Err(Error::InvalidChipId);
}

// Reset Chip, mandatory per datasheet
self.send_cmd(Cmd::SoftReset)?;
self.delay.delay_us(2000);

// Disable advanced power mode
let mut pwr_conf = self.get_pwr_conf()?;
pwr_conf.power_save = false;
self.set_pwr_conf(pwr_conf)?;
self.disable_power_save()?;

// TODO allow config of pre alloc
let mut preallocated_space = alloc_stack!([u8; 512]);
// Verify buffer size
if self.max_burst as usize > N {
return Err(Error::<CommE>::BufferTooSmall);
}

let mut preallocated_space = alloc_stack!([u8; N]);
let mut vec = FixedVec::new(&mut preallocated_space);

// Offset and burst calculation
let mut offset = 0u16;
let max_len = config_file.len() as u16;
let burst = self.max_burst - 1; // Remove 1 for address byte
let burst = if self.max_burst % 2 == 0 {
self.max_burst - 1 // Address byte + even number of data bytes
} else {
self.max_burst - 2 // Make sure we have even data bytes
};

self.set_init_ctrl(0)?;
let init_ctrl = self.get_init_ctrl()?;
self.set_init_ctrl(init_ctrl & 0b1111_1110)?;
self.delay.delay_us(450);

while offset < max_len {
self.set_init_addr(offset / 2)?;

let end = if (offset + burst) > max_len {
max_len
// INIT_ADDR should point to 16-bit words
self.set_init_addr(offset / 2)?; // needs to be divided by 2 because offset is in bytes

// Ensure we're writing complete 16-bit words
let mut chunk_size = burst;
if (chunk_size % 2) != 0 {
// If burst size would result in odd number of bytes, reduce by 1
chunk_size -= 1;
}

let end = if (offset + chunk_size) > max_len {
// For the last chunk, ensure we still write complete 16-bit words
let remaining = max_len - offset;
offset + (remaining - (remaining % 2))
} else {
offset + burst
offset + chunk_size
};

vec.push(Registers::INIT_DATA)
.map_err(|_| Error::<CommE>::Alloc)?;
vec.clear();
vec.push(Registers::INIT_DATA).map_err(|_| Error::Alloc)?;

vec.push_all(&config_file[offset as usize..end as usize])
.map_err(|_| Error::<CommE>::Alloc)?;
.map_err(|_| Error::Alloc)?;

self.iface.write(&mut vec.as_mut_slice())?;
self.iface.write(vec.as_mut_slice())?;

offset += burst;
vec.clear();
offset += chunk_size;
self.delay.delay_us(2);
}

// This operation must not be performed more than once after POR or soft reset.
self.set_init_ctrl(1)?;
self.delay.delay_us(2);

self.enable_power_save()?;

// Initialization takes at most 20ms per datasheet
self.delay.delay_us(20_000);

let internal_status = self.iface.read_reg(Registers::INTERNAL_STATUS)?;

if internal_status & 0b0000_0001 != 1 {
return Err(Error::InitFailed);
}

Ok(())
}
Expand Down
Loading