Saturday, 8 October 2016

Invensense MPU9250 via SPI

This is a note on building support for the MPU9250 9-axis chip in the AdaPilot project (the AdaRacer hardware; the prototype board I have is subtly different from the one shown).

9-axis means it can measure each of acceleration, gyro and magnetic field values in 3 axes, which is very useful if your drone needs to know about its own motion.

Ada compiler, runtime

The compiler is the GNAT GPL 2016 arm-eabi one (built in this case for macOS and available on Sourceforge).

The MCU on the AdaRacer board is an STM32F427VIT6, which is very close to an STM32F429. The RTS is a patched version of the ravenscar-sfp-stm32f429disco RTS supplied as part of AdaCore's GNAT GPL 2016 arm-eabi compiler distribution. The changes were

  • the AdaRacer crystal runs at 24 MHz rather than the 8 MHz of the STMicroelectronics discovery board. I took the liberty of simplifying the code in bsp/setup_pll.adb to use the values calculated by the STM32CubeMX tool directly.
  • the AdaRacer CLI (terminal) is on UART7 rather than the discovery board's USART1.

Peripheral support

The interfaces to the board's peripherals were generated using the SVD2Ada tool from AdaCore. Unfortunately, the STM32F427.svd file was incorrect, so STM32F429.svd was used instead.

MPU9250 features

The MPU9250 contains an MPU6500 accelerator/gyro sensor, and an AK8963 magnetometer. Most people use it via I2C, set up so that the AK8963 has independent (bypass) visibility on the I2C bus. AdaRacer (and the PixRacer design on which it's based) use SPI; in this mode, the AK8963 (which natively supports SPI) has to be accessed via the MPU9250's internal I2C master (the pins that would support bypassed I2C SCL, SDA are dedicated to SPI use).

I don't think that SPI is the best way to manage this chip.

Talking to the MPU9250 part wasn't specially difficult. However, talking to the AK8963 via the MPU9250 was unreliable, to put it mildly.

The process of reading the AK8963's WIA register (the 'who I am' register, should read 16#48#) is (according to the datasheet):

  1. tell the MPU9250 to set I2C_SLV_RNW to 1 and I2C_ID to the AK8963's I2C address in I2C_SLV0_ADDR
  2. tell the MPU9250 to set the register number for WIA (0) in I2C_SLV0_REG
  3. tell the MPU9250 to set I2C_SLV_EN to 1 and I2C_SLV_LENG to the number of bytes to be transferred (1) in I2C_SLV0_CTRL
  4. tell the MPU9250 to read 1 byte starting at EXT_SENS_DATA

However, things aren't quite so simple. For a start, the MPU9250 relies on /CS being asserted (that is, the pin set low) at the start of a particular transfer, and deasserted (set high) at the end of the transfer; that is,

  1. tell the MPU9250 to set I2C_SLV_RNW to 1 and I2C_ID to the AK8963's I2C address in I2C_SLV0_ADDR

becomes

  1. reset /CS
  2. tell the MPU9250 to set I2C_SLV_RNW to 1 and I2C_ID to the AK8963's I2C address in I2C_SLV0_ADDR
  3. set /CS

Secondly, the MPU9250 takes a little while to respond to /CS. For the MPU9250 itself (i.e. accel, gyro) this is 50 μs; for the AK8963, it's 100 μs.

Thirdly, after telling the MPU9250 to arrange the transfer of n bytes from the AK8963 to the MPU9250's internal buffer starting at EXT_SENS_DATA, it will take 25 μs/byte at 400 kHz to actually transfer the data. At the moment I have added a further 400 μs.

Fourthly, it seems a good plan to the PX4 firmware team to ensure the MPU9250's internal I2C is disabled before altering the registers (here, function MPU9250_mag::set_passthrough()).

The whole process then becomes

  1. reset /CS
  2. sleep 100 μs
  3. set I2C_SLV_EN to 0 in I2C_SLV0_CTRL
  4. set /CS
  5. reset /CS
  6. sleep 100 μs
  7. tell the MPU9250 to set I2C_SLV_RNW to 1 and I2C_ID to the AK8963's I2C address in I2C_SLV0_ADDR
  8. set /CS
  9. reset /CS
  10. sleep 100 μs
  11. tell the MPU9250 to set the register number for WIA (0) in I2C_SLV0_REG
  12. set /CS
  13. reset /CS
  14. sleep 100 μs
  15. tell the MPU9250 to set I2C_SLV_EN to 1 and I2C_SLV_LENG to the number of bytes to be transferred (1) in I2C_SLV0_CTRL
  16. set /CS
  17. wait for the data to be transferred, 25 μs/byte + 400 μs
  18. reset /CS
  19. sleep 100 μs
  20. set I2C_SLV_EN to 0 in I2C_SLV0_CTRL -->
  21. set /CS
  22. reset /CS
  23. sleep 100 μs
  24. tell the MPU9250 to read 1 byte starting at EXT_SENS_DATA
  25. set /CS

Issues

Measurement rate

The MPU9250, set to a digital low-pass filter bandwidth of 184 Hz or lower, generates a new reading every millisecond. You don't need to read it this often, of course. The AK8963 can cycle at 10 Hz or 100 Hz, and doesn't seem to like to be read more often than once every 20 ms.

Performance

Related to the complexity of the SPI interface, particularly to the AK8963, the overall sleep time to read the magnetometer data is over a millisecond, in (mostly) 100 μs chunks. The current implementation of the sleep is a busy-wait loop rather than an Ada delay until, because the clock tick in the RTS is set to 1 ms.

Self-test

The acceleration/gyro self-test is satisfactory. I haven't been able to get the AK8963 self-test to pass, however (the Z value is out of spec, both on the AdaRacer board and on a breakout board connected to an STM32F407-Disco).

Values

The acceleration values seem satisfactory ("down" reads -1 g, as you'd expect given that the chip is on the underside of the AdaRacer board). The gyro values certainly change as the board is moved! However, the magnetometer readings ...

A magnetic field caculator for my Lat/Long (51.749910, -2.212471) (relishing.elastic.tungsten at what3words.com if you're interested) shows that the total field strength should be 48,754 nT (487 mG). The breakout board noted above gives a reading of 498 mG, but the AdaRacer board gives 971 mG! Perhaps there's some magnetic component on the board?

Software

The software as tested is on Github and, for AdaPilot people, my personal repository, in both cases at tag report-2016-10-08.

Way ahead

Performance

The main problem is the lengthy time (more than a milliseond of CPU) to read the AK8963 data. Possible approaches include

  1. decreasing the Ada tick rate to 100 μs
  2. using one of the MCU's timers to manage short intervals
  3. using the MPU9250 FIFO

Of these, the least intrusive may be using the FIFO, but the description in the Register Map is quite opaque; also, the FIFO isn't used in the PIX4 Firmware.

Magnetometer

The self-test is failing.

The AdaRacer board is returning twice the expected value. This may be a calibration issue? I'm not sure how this is done (hints about waving the device around in a figure 8?).

Anyway, more evidence required!

No comments:

Post a Comment