Skip to main content

mpu9250_dmp/
dmp.rs

1use super::dmp_constants::*;
2use super::get_i2c_bus_path;
3use super::{
4    AccelDlpf, AccelFsr, FifoReadError, GyroDlpf, GyroFsr, MPU9250_SLAVE_ADDR, Mpu9250,
5    Mpu9250Sample,
6};
7use ak8963::{
8    Ak8963, Ak8963Sample, SampleRate as Ak8963SampleRate, Sensitivity as Ak8963Sensitivity,
9};
10use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
11use i2cdev::core::*;
12use i2cdev::linux::{LinuxI2CDevice, LinuxI2CError};
13use ndarray::prelude::*;
14use std::cmp;
15
16const GYRO_SF: u32 = 46850825;
17
18const QUAT_ERROR_THRESH: i32 = (1 << 16);
19const QUAT_MAG_SQ_NORMALIZED: i32 = (1 << 28);
20const QUAT_MAG_SQ_MIN: i32 = (QUAT_MAG_SQ_NORMALIZED - QUAT_ERROR_THRESH);
21const QUAT_MAG_SQ_MAX: i32 = (QUAT_MAG_SQ_NORMALIZED + QUAT_ERROR_THRESH);
22
23#[derive(Clone, Copy)]
24enum Mpu6500Reg {
25    BankSel = 0x6d,
26    MemRw = 0x6f,
27    PrgmStartH = 0x70,
28}
29
30impl Mpu6500Reg {
31    pub fn addr(&self) -> u8 {
32        *self as u8
33    }
34}
35
36const MPU6500_BANK_SIZE: usize = 256;
37
38pub enum DmpInterruptMode {
39    /// Interrupts after one FIFO Period (set by sample rate)
40    Continuous,
41    /// Interrupts on a tap event
42    Gesture,
43}
44
45pub struct DigitalMotionProcessor {
46    i2c_dev: LinuxI2CDevice,
47    pub mpu: Mpu9250,
48    pub mag: Ak8963,
49    pub debug: bool,
50}
51
52impl DigitalMotionProcessor {
53    pub fn new(
54        i2c_bus: i32,
55        mpu_addr: Option<u16>,
56        mpu_whoami: Option<u8>,
57        ak_addr: Option<u16>,
58    ) -> Result<DigitalMotionProcessor, LinuxI2CError> {
59        let i2c_dev = LinuxI2CDevice::new(get_i2c_bus_path(i2c_bus), MPU9250_SLAVE_ADDR)?;
60
61        let mut mpu = Mpu9250::new(i2c_bus, mpu_addr, mpu_whoami, None)?;
62
63        mpu.enable_bypass()?;
64
65        let mag = Ak8963::new(
66            i2c_bus,
67            ak_addr,
68            Ak8963Sensitivity::Opt16bit,
69            Ak8963SampleRate::Opt100Hz,
70        ).unwrap();
71
72        mpu.disable_bypass()?;
73
74        Ok(DigitalMotionProcessor {
75            i2c_dev,
76            mpu,
77            mag,
78            debug: false,
79        })
80    }
81
82    pub fn set_debug(&mut self, debug: bool) {
83        self.debug = debug;
84        self.mpu.set_debug(debug);
85    }
86
87    pub fn initialize(&mut self, sample_rate: u16) -> Result<(), LinuxI2CError> {
88        self.mpu.reset()?;
89        self.mpu.set_sample_rate(sample_rate)?;
90        self.mpu.write_accel_fsr(AccelFsr::Opt2G)?;
91        self.mpu.write_gyro_fsr(GyroFsr::Opt250)?;
92        self.mpu.write_accel_dlpf(AccelDlpf::Opt41)?;
93        self.mpu.write_gyro_dlpf(GyroDlpf::Opt41)?;
94
95        self.load_motion_driver_firmware()?;
96
97        // Match MPU's sample rate.
98        self.write_fifo_rate(200)?;
99
100        self.enable_quaternion_accel_gyro_in_fifo()?;
101
102        self.set_interrupt_mode(DmpInterruptMode::Continuous)?;
103
104        self.mpu.dmp_enable()?;
105
106        self.mpu.setup_mag_in_fifo()?;
107        Ok(())
108    }
109
110    pub fn write_memory(&mut self, mem_addr: u16, data: &[u8]) -> Result<(), LinuxI2CError> {
111        let length = data.len();
112        if (((mem_addr & 0xFF) as usize) + length) > MPU6500_BANK_SIZE {
113            panic!("Exceeds bank size");
114        }
115
116        // First, write the mem address to bank select.
117        // Note: Address written as little-endian
118        self.i2c_dev.write(&[
119            Mpu6500Reg::BankSel.addr(),
120            (mem_addr >> 8) as u8,
121            (mem_addr & 0xFF) as u8,
122        ])?;
123
124        // Second, write contents.
125        let mut data2 = vec![Mpu6500Reg::MemRw.addr()];
126        for x in data.iter() {
127            data2.push(*x);
128        }
129        self.i2c_dev.write(&data2)?;
130        Ok(())
131    }
132
133    pub fn read_memory(&mut self, mem_addr: u16, length: u8) -> Result<Vec<u8>, LinuxI2CError> {
134        if (((mem_addr & 0xFF) + (length as u16)) as usize) > MPU6500_BANK_SIZE {
135            panic!("Exceeds bank size");
136        }
137
138        // First, write the mem address to bank select.
139        // Note: Address written as little-endian
140        self.i2c_dev.write(&[
141            Mpu6500Reg::BankSel.addr(),
142            (mem_addr >> 8) as u8,
143            (mem_addr & 0xFF) as u8,
144        ])?;
145
146        let mut data = vec![0; length as usize];
147        self.i2c_dev.write(&[Mpu6500Reg::MemRw.addr()])?;
148        self.i2c_dev.read(&mut data)?;
149        Ok(data)
150    }
151
152    fn load_motion_driver_firmware(&mut self) -> Result<(), LinuxI2CError> {
153        let mut write_offset: u16 = 0; // equivalent to bytes written
154        while write_offset < DMP_CODE_SIZE {
155            let bytes_to_write = cmp::min(DMP_LOAD_CHUNK, DMP_CODE_SIZE - write_offset);
156            let firmware_slice =
157                &DMP_FIRMWARE[write_offset as usize..(write_offset + bytes_to_write) as usize];
158            self.write_memory(write_offset, firmware_slice)?;
159            write_offset += bytes_to_write;
160            // TODO: Read DMP memory to verify correctness of write.
161        }
162
163        self.i2c_dev
164            .write(&[Mpu6500Reg::PrgmStartH.addr(), 0x04, 0x00])?;
165        Ok(())
166    }
167
168    fn set_interrupt_mode(&mut self, mode: DmpInterruptMode) -> Result<(), LinuxI2CError> {
169        match mode {
170            DmpInterruptMode::Continuous => {
171                self.write_memory(
172                    CFG_FIFO_ON_EVENT,
173                    &[
174                        0xd8, 0xb1, 0xb9, 0xf3, 0x8b, 0xa3, 0x91, 0xb6, 0x09, 0xb4, 0xd9,
175                    ],
176                )?;
177            }
178            DmpInterruptMode::Gesture => {
179                self.write_memory(
180                    CFG_FIFO_ON_EVENT,
181                    &[
182                        0xda, 0xb1, 0xb9, 0xf3, 0x8b, 0xa3, 0x91, 0xb6, 0xda, 0xb4, 0xda,
183                    ],
184                )?;
185            }
186        };
187        Ok(())
188    }
189
190    fn write_fifo_rate(&mut self, rate: u16) -> Result<(), LinuxI2CError> {
191        // Sets the rate the DMP adds data to the FIFO relative to the sample
192        // rate. Setting rate=200 puts DMP on par with the sample rate.
193        // rate=100 causes the DMP to enqueue at half the sample rate.
194        let regs_end: [u8; 12] = [
195            DINAFE, DINAF2, DINAAB, 0xc4, DINAAA, DINAF1, DINADF, DINADF, 0xbb, 0xaf, DINADF,
196            DINADF,
197        ];
198        let div = ((DMP_MAX_RATE / rate) - 1) as u16;
199
200        self.write_memory(D_0_22, &[(div >> 8) as u8, (div & 0xff) as u8])?;
201        self.write_memory(CFG_6, &regs_end)?;
202        Ok(())
203    }
204
205    pub fn enable_6x_lp_quat(&mut self) -> Result<(), LinuxI2CError> {
206        self.write_memory(CFG_8, &[DINA20, DINA28, DINA30, DINA38])?;
207        Ok(())
208    }
209
210    pub fn disable_6x_lp_quat(&mut self) -> Result<(), LinuxI2CError> {
211        self.write_memory(CFG_8, &[0xA3; 4])?;
212        Ok(())
213    }
214
215    fn enable_quaternion_accel_gyro_in_fifo(&mut self) -> Result<(), LinuxI2CError> {
216        // Set integration scale factor.
217        let mut tmp = vec![];
218        tmp.write_u32::<BigEndian>(GYRO_SF).unwrap();
219        self.write_memory(D_0_104, &tmp)?;
220
221        // Send sensor data to the FIFO.
222        let mut buf10: [u8; 10] = [0; 10];
223
224        buf10[0] = 0xA3;
225        // Raw accel
226        buf10[1] = 0xC0;
227        buf10[2] = 0xC8;
228        buf10[3] = 0xC2;
229        // Raw gyro
230        buf10[4] = 0xC4;
231        buf10[5] = 0xCC;
232        buf10[6] = 0xC6;
233        // Unknown
234        buf10[7] = 0xA3;
235        buf10[8] = 0xA3;
236        buf10[9] = 0xA3;
237        self.write_memory(CFG_15, &buf10)?;
238
239        // No tap or android orient
240        self.write_memory(CFG_27, &[0xD8])?;
241        // Disable tap feature
242        self.write_memory(CFG_20, &[0xD8])?;
243        // Disable orientation feature
244        self.write_memory(CFG_ANDROID_ORIENT_INT, &[0xD8])?;
245
246        self.enable_6x_lp_quat()?;
247        self.mpu.reset_fifo()?;
248
249        Ok(())
250    }
251
252    pub fn read_fifo(&mut self) -> Result<DmpSample, DmpFifoReadError> {
253        let data = self
254            .mpu
255            .read_fifo_strict(35)
256            .map_err(|e| DmpFifoReadError::Read(e))?;
257
258        if self.debug {
259            println!("FIFO packet data (mag+mpu): {:?}", &data[..]);
260        }
261        let mag_output = self.mag.parse_sample_data(&data[0..7]);
262        let quaternion = Self::parse_quaternion(&data[7..23]).ok_or(DmpFifoReadError::Parse)?;
263        let taitbryan = Self::quaternion_to_taitbryan(&quaternion);
264        let mpu_output = Mpu9250::parse_accel_and_gyro(
265            &data[23..35],
266            self.mpu.accel_fsr.get_sensitivity_mss(),
267            self.mpu.gyro_fsr.get_scalar(),
268        );
269
270        Ok(DmpSample {
271            mag: mag_output,
272            mpu: mpu_output,
273            quaternion,
274            taitbryan,
275        })
276    }
277
278    fn parse_quaternion(data: &[u8]) -> Option<Array1<f32>> {
279        let q_data = array![
280            BigEndian::read_i32(&data[0..4]),
281            BigEndian::read_i32(&data[4..8]),
282            BigEndian::read_i32(&data[8..12]),
283            BigEndian::read_i32(&data[12..16]),
284        ];
285
286        let mut q: Array1<f64> = array![
287            q_data[0] as f64,
288            q_data[1] as f64,
289            q_data[2] as f64,
290            q_data[3] as f64,
291        ];
292        let norm = (q[0].powi(2) + q[1].powi(2) + q[2].powi(2) + q[3].powi(2)).sqrt();
293        q[0] /= norm;
294        q[1] /= norm;
295        q[2] /= norm;
296        q[3] /= norm;
297
298        // Check that the magnitude normalizes to one.
299        // We scale down the numbers to avoid 64-bit int math.
300        let mut q_verify: Vec<i32> = vec![
301            q_data[0] >> 16,
302            q_data[1] >> 16,
303            q_data[2] >> 16,
304            q_data[3] >> 16,
305        ];
306        for v in &mut q_verify {
307            match (*v).checked_mul(*v) {
308                None => return None,
309                Some(sq_v) => *v = sq_v,
310            }
311        }
312        // Check for integer overflow.
313        let quat_mag_sq = q_verify[0].checked_add(q_verify[1]).and_then(|v| {
314            v.checked_add(q_verify[2])
315                .and_then(|v2| v2.checked_add(q_verify[3]))
316        });
317        match quat_mag_sq {
318            None => return None,
319            Some(quat_mag_sq) => {
320                if (quat_mag_sq < QUAT_MAG_SQ_MIN) || (quat_mag_sq > QUAT_MAG_SQ_MAX) {
321                    return None;
322                }
323            }
324        }
325        Some(array![q[0] as f32, q[1] as f32, q[2] as f32, q[3] as f32])
326    }
327
328    pub fn quaternion_to_taitbryan(q: &Array1<f32>) -> Array1<f32> {
329        let tb1 = 2.0 * (q[0] * q[2] - q[1] * q[3]).asin();
330        let tb0 =
331            (2.0 * (q[2] * q[3] + q[0] * q[1])).atan2(1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]));
332        let tb2 =
333            (2.0 * (q[1] * q[2] + q[0] * q[3])).atan2(1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3]));
334        array![tb0, tb1, tb2]
335    }
336}
337
338#[derive(Clone, Debug)]
339pub struct DmpSample {
340    pub mag: Option<Ak8963Sample>,
341    pub mpu: Mpu9250Sample,
342    pub quaternion: Array1<f32>,
343    pub taitbryan: Array1<f32>,
344}
345
346#[derive(Debug)]
347pub enum DmpFifoReadError {
348    Read(FifoReadError),
349    Parse,
350}