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);
    }
}