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}