1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
//! Driver for the Bosch BMA222E and family
//! ==========================================
//! Currently this only support I2C configuration as I can't remove
//! the itty bitty package from its home on a populated PCB. If you
//! want to play with one, you can find them in Xfinity XR11 remote
//! controls. 😊
//!
//! The register maps in this family are very similar and so it
//! should be possible to support the following chips with minimal
//! changes to the code here:
//!
//! * BMA222 - 8bit resolution
//! * BMA250 - 10bit resolution
//! * BMA253 - 12bit resolution
//! * BMA255 - 12bit resolution
//! * BMA280 - 14bit resolution
//!
//! Specifically, these chips should work with the library now, but
//! you wouldn't benefit from the enhanced resolution.
//!
//! More info on the product line from Bosch's website:
//! https://www.bosch-sensortec.com/bst/products/all_products/bma222e
//!
//! ### What's currently supported
//!
//! 1. Accessing the chip via I2C
//! 2. Reading X, Y, Z axis and whether they've updated since last poll
//! 3. Reading the event FIFO for X, Y, Z axis (32 element deep queue version of #1)
//! 4. Changing FIFO mode (stream, fifo, bypass) and reading how full the FIFO is
//! 5. Checking what data is in the EEPROM and how many writes it has left
//! 6. Software reset
//! 7. Reading temperature
//!
//! ### What's *not* currently supported
//!
//! If anything here seems meaningful to you, feel free to reach out
//! and help implement these features. I just didn't need any of them
//! personally.
//!
//! 1. Any chip other than the BMA222E
//! 2. Accessing the chip via SPI
//! 3. Changing which axis are thrown on the FIFO
//! 4. Enabling interrupts for slope, tap, FIFO full, orientation, etc
//! 5. Tap detection
//! 6. Acceleration data filters
//! 7. Power modes (normal, deep sleep, low power, suspend)
//! 8. Lower power sleep duration
//! 9. Whether to shadow acceleration data or not
//! 10. Configuration of the INT1 and INT2 pins nor their sources
//! 11. Interrupt thresholds for acceleration, slope, etc
//! 12. FIFO watermark interrupt level
//! 13. Self-test
//! 14. Actually programming the EEPROM or setting the values
//! 15. Setting the offset compensation
//! 16. Offset compensation
//! 17. And many more!
//!
//!
//! ### Example usage:
//! ```
//! fn main() {
//! // Get an instance of an i2c struct here with one of the [device crates](https://github.com/rust-embedded/awesome-embedded-rust#device-crates).
//!
//! let mut accel = Bma222e::new(i2c);
//!
//! accel.reset().unwrap();
//!
//! examples(accel).unwrap();
//! }
//!
//! fn examples(accel: Bma222e) -> Result<(), ()> {
//! let chip_id = accel.who_am_i()?;
//! println!("About to Begin. ID is {} and should be {}", chip_id, bma222e::IDENTIFIER)?;
//!
//! let temp = accel.temperature()?;
//! println!("Temperature: {}", temp)?;
//!
//! let writes_remaining = accel.eeprom_writes_remaining()?;
//! println!("EEPROM Writes Remaining: {}", writes_remaining)?;
//!
//! let nvm_data = accel.eeprom_data()?;
//! hprint!("EEPROM Data: ")?;
//! for byte in &nvm_data {
//! hprint!("{:02X} ", byte)?;
//! }
//! println!()?;
//!
//!
//! // FIFO-related goodness!
//! // TODO: Find out why the output data wasn't consistent
//! let fifo_size = accel.fifo_size()?;
//! println!("Events in the FIFO: {}", fifo_size)?;
//! let mut events = [AxisData {value: 0, changed: false}; 3];
//! accel.fifo_set_mode(FIFOConfig::BYPASS)?;
//! accel.fifo_get(&mut events)?;
//!
//! let x = accel.axis_x()?;
//! let y = accel.axis_y()?;
//! let z = accel.axis_z()?;
//! println!("X:{0:03} Y:{1:03} Z:{2:03}",
//! x.value,
//! y.value,
//! z.value)?;
//!
//! Ok(())
//! }
//! ```
#![no_std]
#![deny(missing_docs)]
#![deny(warnings)]
extern crate embedded_hal as hal;
use core::fmt;
use hal::blocking::i2c::{Write, WriteRead};
/// Create an instance of the accelerometer
pub struct Bma222e<I2C> {
device: I2C,
}
/// The address on the bus. TODO: Support alt addresses
pub const ADDRESS: u8 = 0x19;
/// This identifier changes based on the product in the range.
pub const IDENTIFIER: u8 = 0xF8;
/// How long the NVM register bank is (differs between BMA222 and BMA222E)
const EEPROM_LENGTH: usize = 5;
const REG_CHIPID: u8 = 0x00;
const REG_XAXIS: u8 = 0x02;
const REG_YAXIS: u8 = 0x04;
const REG_ZAXIS: u8 = 0x06;
const REG_TEMPERATURE: u8 = 0x08;
const REG_FIFO_STATUS: u8 = 0x0E;
const REG_RESET: u8 = 0x14;
const REG_EEPROM_CONTROL: u8 = 0x33;
const REG_EEPROM_START: u8 = 0x38;
const REG_FIFO_CONFIG: u8 = 0x3E;
const REG_FIFO_DATA: u8 = 0x3F;
/// Various FIFO operating modes
pub enum FIFOConfig {
/// Don't use the FIFO. Reads from the FIFO are the immediate value
BYPASS = 0,
/// Collect readings and don't drop readings if buffer is full
FIFO = 1,
/// Collect readings but discard the oldest
STREAM = 2,
}
// TODO: This need to go into FIFO_CONFIG
/*
pub enum FIFOAxis {
ALL = 0,
X = 1,
Y = 2,
Z = 3
}
*/
#[derive(Copy, Clone)]
/// Holds accelerometer data
pub struct AxisData {
/// What the value was when the accelerometer was sampled
pub value: u16,
/// Whether the data has changed since the last sample
pub changed: bool,
}
impl fmt::Display for AxisData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Value: {} Changed?: {}", self.value, self.changed)
}
}
impl<I2C, E> Bma222e<I2C>
where
I2C: WriteRead<Error = E> + Write<Error = E>,
{
/// Create a new instance for fun and profit!
pub fn new(dev: I2C) -> Self {
Self { device: dev }
}
/// Helper to load / unload buffers to send along
fn write(&mut self, register: u8, data: &[u8]) -> Result<(), E> {
let mut input = [0u8; 16]; // Can't dynamically size this, so 16 it is!
assert!(
data.len() < 16 - 1,
"write() can only take buffers up to 15 bytes ☹️"
);
input[1..=data.len()].copy_from_slice(data);
input[0] = register;
self.device.write(ADDRESS, &input)?;
Ok(())
}
/// Helper to load / unload buffers to send along
fn read(&mut self, register: u8, data: &mut [u8]) -> Result<(), E> {
self.device.write_read(ADDRESS, &[register], data)?;
Ok(())
}
/// Helper to grab single byte registers
fn single_read(&mut self, register: u8) -> Result<u8, E> {
let mut out = [0u8; 1];
self.read(register, &mut out)?;
Ok(out[0])
}
/// Soft-reset the chip
pub fn reset(&mut self) -> Result<(), E> {
// Magic value for the reset is 0xB6
self.write(ADDRESS, &[REG_RESET, 0xB6])?;
Ok(())
}
/// Read the temperature of the chip
pub fn temperature(&mut self) -> Result<u8, E> {
let value = self.single_read(REG_TEMPERATURE)?;
Ok(value.wrapping_add(23))
}
/// Return the chip's identifier. While there are many devices in this
/// family, this crate was written for the BMA222E and you should
/// compare this value to REG_CHIPID.
pub fn who_am_i(&mut self) -> Result<u8, E> {
Ok(self.single_read(REG_CHIPID)?)
}
/// Set the FIFO mode for events
pub fn fifo_set_mode(&mut self, mode: FIFOConfig) -> Result<(), E> {
let value = (mode as u8) << 6;
self.write(REG_FIFO_CONFIG, &[value])?;
Ok(())
}
/// How many events are stored in the FIFO
pub fn fifo_size(&mut self) -> Result<u8, E> {
let value = self.single_read(REG_FIFO_STATUS)?;
Ok(value & 0b0111_1111)
}
/// Did the event FIFO overflow?
pub fn fifo_overflow(&mut self) -> Result<bool, E> {
let value = self.single_read(REG_FIFO_STATUS)?;
Ok(value & 0b1000_0000 != 0)
}
/// Pull accelerometer data from the FIFO.
pub fn fifo_get(&mut self, data: &mut [AxisData]) -> Result<(), E> {
// Implementation detail here: Ideally you would use a longer read buffer
// to avoid register selection overhead on I2C, but we can't dynamically
// size the array length in Rust yet (as of 1.32)
let mut out = [0u8; 6];
// TODO: Other chips in this family have 6, 10 or 12 bits of accuracy. This
// needs to change to support the least significant bits existing in the
// second byte
// TODO: Support different number of X, Y, and Z elements
let mut index: usize = 0;
for item in data {
self.read(REG_FIFO_DATA, &mut out)?;
item.changed = out[index] & 0x01 != 0;
item.value = u16::from(out[index + 1]);
if index == 3 {
index = 0;
} else {
index += 1;
}
}
Ok(())
}
/// Grab an axis and create an AxisData
pub fn element_get(&mut self, register: u8) -> Result<AxisData, E> {
let mut out = [0u8; 2];
self.read(register, &mut out)?;
let item = AxisData {
value: u16::from(out[1]),
changed: out[0] & 0x01 != 0,
};
Ok(item)
}
/// Grab data for X axis
pub fn axis_x(&mut self) -> Result<AxisData, E> {
Ok(self.element_get(REG_XAXIS)?)
}
/// Grab data for Y axis
pub fn axis_y(&mut self) -> Result<AxisData, E> {
Ok(self.element_get(REG_YAXIS)?)
}
/// Grab data for Z axis
pub fn axis_z(&mut self) -> Result<AxisData, E> {
Ok(self.element_get(REG_ZAXIS)?)
}
/// How many writes are left in the device's EEPROM. Each write will decrement
/// this counter and the chip will deny writes at zero, so use any writes
/// sparingly. The counter is only four bits wide (16 total writes)
pub fn eeprom_writes_remaining(&mut self) -> Result<u8, E> {
// The top four bits store the value. Using magic values here
let value = self.single_read(REG_EEPROM_CONTROL)?;
let value = (value | 0b1111_0000) >> 4;
Ok(value)
}
/// Pull values from internal EEPROM
pub fn eeprom_data(&mut self) -> Result<[u8; EEPROM_LENGTH], E> {
let mut out = [0u8; EEPROM_LENGTH];
self.read(REG_EEPROM_START, &mut out)?;
Ok(out)
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(1 + 1, 2);
}
}