bma2xx/
lib.rs

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