├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── ak8963.py ├── mpu6500.py ├── mpu9250.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | micropython_mpu9250.egg-info/ 2 | dist/ 3 | main.py 4 | 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file, in reverse chronological order by release. 4 | 5 | ## [0.4.0](https://github.com/tuupola/micropython-mpu9250/compare/0.3.0...0.4.0) - 2023-12-01 6 | ### Added 7 | - Support for MPU6700 sensors ([#28](https://github.com/tuupola/micropython-mpu9250/pull/28)) 8 | 9 | ## [0.3.0](https://github.com/tuupola/micropython-mpu9250/compare/0.2.1...0.3.0) - 2020-03-22 10 | ### Added 11 | 12 | - Support for internal temperature sensor ([#1](https://github.com/tuupola/micropython-mpu9250/issues/1), [#9](https://github.com/tuupola/micropython-mpu9250/pull/9), [#18](https://github.com/tuupola/micropython-mpu9250/pull/18)) 13 | - Support for gyro calibration ([#5](https://github.com/tuupola/micropython-mpu9250/issues/5), [#10](https://github.com/tuupola/micropython-mpu9250/pull/10)) 14 | 15 | ### Fixed 16 | - Support for standalone MPU6500 sensors ([#15](https://github.com/tuupola/micropython-mpu9250/issues/15), [#16](https://github.com/tuupola/micropython-mpu9250/pull/16)) 17 | 18 | ### Changed 19 | 20 | - Move I2C bypass initialisation from MPU6500 to MPU9250 ([#17](https://github.com/tuupola/micropython-mpu9250/issues/17)) 21 | 22 | ## [0.2.1](https://github.com/tuupola/micropython-mpu9250/compare/0.2.0...0.2.1) - 2019-02-07 23 | ### Fixed 24 | - Gyro degrees to radians conversion ([#8](https://github.com/tuupola/micropython-mpu9250/pull/8)). 25 | 26 | ## [0.2.0](https://github.com/tuupola/micropython-mpu9250/compare/0.1.0...0.2.0)- 2018-04-08 27 | ### Added 28 | - Support for magnetometer factory sensitivity adjustement values `ASAX`, `ASAY` and `ASAZ`. 29 | - Support for setting magnetometer offset and scale calibration values. 30 | ``` 31 | ak8963 = AK8963( 32 | i2c, 33 | offset=(-136.8931640625, -160.482421875, 59.02880859375), 34 | scale=(1.18437220840483, 0.923895823933424, 0.931707933618979) 35 | ) 36 | ``` 37 | - Method for retrieving the magnetometer offset and scale calibration values. 38 | ``` 39 | ak8963 = AK8963(i2c) 40 | offset, scale = ak8963.calibrate(count=256, delay=200) 41 | ``` 42 | 43 | ## 0.1.0 - 2018-02-17 44 | 45 | Initial working release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-2023 Mika Tuupola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | help: 4 | @echo "" 5 | @echo "Available tasks:" 6 | @echo " watch Upload changed library files to board automagically" 7 | @echo " sync Upload library files to board" 8 | @echo " reset Soft reboot the board" 9 | @echo " repl Start a repl session" 10 | @echo " deps Install dependencies with upip" 11 | @echo "" 12 | 13 | watch: 14 | find . -name "*.py" | entr -c sh -c 'make sync && make reset' 15 | 16 | sync: 17 | ampy --port /dev/tty.SLAB_USBtoUART put mpu6500.py 18 | ampy --port /dev/tty.SLAB_USBtoUART put mpu9250.py 19 | ampy --port /dev/tty.SLAB_USBtoUART put ak8963.py 20 | 21 | repl: 22 | screen /dev/tty.SLAB_USBtoUART 115200 23 | 24 | reset: 25 | ampy --port /dev/cu.SLAB_USBtoUART reset 26 | 27 | dist: 28 | python3 setup.py sdist 29 | # twine upload dist/filename.tar.gz 30 | 31 | .PHONY: help watch shell repl reset sync dist 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroPython MPU-9250 (MPU-6500 + AK8963) I2C driver 2 | 3 | MPU-9250 is a System in Package (SiP) which combines two chips: MPU-6500 which contains 3-axis gyroscope and 3-axis accelerometer and an AK8963 which is a 3-axis digital compass. 4 | 5 | ## Usage 6 | 7 | Kevin Wheeler has an [extensive video](https://www.youtube.com/watch?v=ph10GSO8pDk) describing how to use this library. The short code snippets in this README should also help you to get started. First is simple example with never ending loop. 8 | 9 | ```python 10 | import utime 11 | from machine import I2C, Pin 12 | from mpu9250 import MPU9250 13 | 14 | i2c = I2C(scl=Pin(22), sda=Pin(21)) 15 | sensor = MPU9250(i2c) 16 | 17 | print("MPU9250 id: " + hex(sensor.whoami)) 18 | 19 | while True: 20 | print(sensor.acceleration) 21 | print(sensor.gyro) 22 | print(sensor.magnetic) 23 | print(sensor.temperature) 24 | 25 | utime.sleep_ms(1000) 26 | ``` 27 | 28 | By default the library returns 3-tuple of X, Y, Z axis values for either acceleration, gyroscope and magnetometer ie compass. Default units are `m/s^2`, `rad/s`, `uT` and `°C`. It is possible to also get acceleration values in `g` and gyro values `deg/s`. See the example below. Note that both the MPU6500 and the AK8963 drivers are available as separate classes. MPU9250 is actually a composite of those two. 29 | 30 | ```python 31 | import utime 32 | from machine import I2C, Pin 33 | from mpu9250 import MPU9250 34 | from mpu6500 import MPU6500, SF_G, SF_DEG_S 35 | 36 | i2c = I2C(scl=Pin(22), sda=Pin(21)) 37 | mpu6500 = MPU6500(i2c, accel_sf=SF_G, gyro_sf=SF_DEG_S) 38 | sensor = MPU9250(i2c, mpu6500=mpu6500) 39 | 40 | print("MPU9250 id: " + hex(sensor.whoami)) 41 | 42 | while True: 43 | print(sensor.acceleration) 44 | print(sensor.gyro) 45 | print(sensor.magnetic) 46 | print(sensor.temperature) 47 | 48 | utime.sleep_ms(1000) 49 | ``` 50 | 51 | More realistic example usage with timer. If you get `OSError: 26` or `i2c driver install error` after soft reboot do a hard reboot. 52 | 53 | ```python 54 | import micropython 55 | from machine import I2C, Pin, Timer 56 | from mpu9250 import MPU9250 57 | 58 | micropython.alloc_emergency_exception_buf(100) 59 | 60 | i2c = I2C(scl=Pin(22), sda=Pin(21)) 61 | sensor = MPU9250(i2c) 62 | 63 | def read_sensor(timer): 64 | print(sensor.acceleration) 65 | print(sensor.gyro) 66 | print(sensor.magnetic) 67 | print(sensor.temperature) 68 | 69 | print("MPU9250 id: " + hex(sensor.whoami)) 70 | 71 | timer_0 = Timer(0) 72 | timer_0.init(period=1000, mode=Timer.PERIODIC, callback=read_sensor) 73 | ``` 74 | 75 | ## Magnetometer Calibration 76 | 77 | For real life applications you should almost always [calibrate the magnetometer](https://www.appelsiini.net/2018/calibrate-magnetometer/). The AK8963 driver supports both hard and soft iron correction. Calibration function takes two parameters: `count` is the number of samples to collect and `delay` is the delay in millisecods between the samples. 78 | 79 | With the default values of `256` and `200` calibration takes aproximately one minute. While calibration function is running the sensor should be rotated multiple times around each axis. 80 | 81 | NOTE! If using MPU9250 you will first need to open the I2C bypass access to AK8963. This is not needed when using a standalone AK8963 sensor. 82 | 83 | ```python 84 | from machine import I2C, Pin 85 | from mpu9250 import MPU9250 86 | from ak8963 import AK8963 87 | 88 | i2c = I2C(scl=Pin(22), sda=Pin(21)) 89 | 90 | dummy = MPU9250(i2c) # this opens the bybass to access to the AK8963 91 | ak8963 = AK8963(i2c) 92 | offset, scale = ak8963.calibrate(count=256, delay=200) 93 | 94 | sensor = MPU9250(i2c, ak8963=ak8963) 95 | ``` 96 | 97 | After finishing calibration the `calibrate()` method also returns tuples for both hard iron `offset` and soft iron `scale`. To avoid calibrating after each startup it would make sense to strore these values in NVRAM or config file and pass them to the AK8963 constructor. Below example only illustrates how to use the constructor. 98 | 99 | ```python 100 | from machine import I2C, Pin 101 | from mpu9250 import MPU9250 102 | from ak8963 import AK8963 103 | 104 | i2c = I2C(scl=Pin(22), sda=Pin(21)) 105 | dummy = MPU9250(i2c) # this opens the bybass to access to the AK8963 106 | 107 | ak8963 = AK8963( 108 | i2c, 109 | offset=(-136.8931640625, -160.482421875, 59.02880859375), 110 | scale=(1.18437220840483, 0.923895823933424, 0.931707933618979) 111 | ) 112 | 113 | sensor = MPU9250(i2c, ak8963=ak8963) 114 | ``` 115 | 116 | ## Gyro Calibration 117 | 118 | TODO 119 | 120 | ## License 121 | 122 | The MIT License (MIT). Please see [License File](LICENSE) for more information. -------------------------------------------------------------------------------- /ak8963.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018-2023 Mika Tuupola 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to 5 | # deal in the Software without restriction, including without limitation the 6 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | # sell copied of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | # https://github.com/tuupola/micropython-mpu9250 22 | # https://www.akm.com/akm/en/file/datasheet/AK8963C.pdf 23 | 24 | """ 25 | MicroPython I2C driver for AK8963 magnetometer 26 | """ 27 | 28 | __version__ = "0.4.0" 29 | 30 | # pylint: disable=import-error 31 | import ustruct 32 | import utime 33 | from machine import I2C, Pin 34 | from micropython import const 35 | # pylint: enable=import-error 36 | 37 | _WIA = const(0x00) 38 | _HXL = const(0x03) 39 | _HXH = const(0x04) 40 | _HYL = const(0x05) 41 | _HYH = const(0x06) 42 | _HZL = const(0x07) 43 | _HZH = const(0x08) 44 | _ST2 = const(0x09) 45 | _CNTL1 = const(0x0a) 46 | _ASAX = const(0x10) 47 | _ASAY = const(0x11) 48 | _ASAZ = const(0x12) 49 | 50 | _MODE_POWER_DOWN = 0b00000000 51 | MODE_SINGLE_MEASURE = 0b00000001 52 | MODE_CONTINOUS_MEASURE_1 = 0b00000010 # 8Hz 53 | MODE_CONTINOUS_MEASURE_2 = 0b00000110 # 100Hz 54 | MODE_EXTERNAL_TRIGGER_MEASURE = 0b00000100 55 | _MODE_SELF_TEST = 0b00001000 56 | _MODE_FUSE_ROM_ACCESS = 0b00001111 57 | 58 | OUTPUT_14_BIT = 0b00000000 59 | OUTPUT_16_BIT = 0b00010000 60 | 61 | _SO_14BIT = 0.6 # μT per digit when 14bit mode 62 | _SO_16BIT = 0.15 # μT per digit when 16bit mode 63 | 64 | class AK8963: 65 | """Class which provides interface to AK8963 magnetometer.""" 66 | def __init__( 67 | self, i2c, address=0x0c, 68 | mode=MODE_CONTINOUS_MEASURE_1, output=OUTPUT_16_BIT, 69 | offset=(0, 0, 0), scale=(1, 1, 1) 70 | ): 71 | self.i2c = i2c 72 | self.address = address 73 | self._offset = offset 74 | self._scale = scale 75 | 76 | if 0x48 != self.whoami: 77 | raise RuntimeError("AK8963 not found in I2C bus.") 78 | 79 | # Sensitivity adjustement values 80 | self._register_char(_CNTL1, _MODE_FUSE_ROM_ACCESS) 81 | asax = self._register_char(_ASAX) 82 | asay = self._register_char(_ASAY) 83 | asaz = self._register_char(_ASAZ) 84 | self._register_char(_CNTL1, _MODE_POWER_DOWN) 85 | 86 | # Should wait atleast 100us before next mode 87 | self._adjustement = ( 88 | (0.5 * (asax - 128)) / 128 + 1, 89 | (0.5 * (asay - 128)) / 128 + 1, 90 | (0.5 * (asaz - 128)) / 128 + 1 91 | ) 92 | 93 | # Power on 94 | self._register_char(_CNTL1, (mode | output)) 95 | 96 | if output is OUTPUT_16_BIT: 97 | self._so = _SO_16BIT 98 | else: 99 | self._so = _SO_14BIT 100 | 101 | @property 102 | def magnetic(self): 103 | """ 104 | X, Y, Z axis micro-Tesla (uT) as floats. 105 | """ 106 | xyz = list(self._register_three_shorts(_HXL)) 107 | self._register_char(_ST2) # Enable updating readings again 108 | 109 | # Apply factory axial sensitivy adjustements 110 | xyz[0] *= self._adjustement[0] 111 | xyz[1] *= self._adjustement[1] 112 | xyz[2] *= self._adjustement[2] 113 | 114 | # Apply output scale determined in constructor 115 | so = self._so 116 | xyz[0] *= so 117 | xyz[1] *= so 118 | xyz[2] *= so 119 | 120 | # Apply hard iron ie. offset bias from calibration 121 | xyz[0] -= self._offset[0] 122 | xyz[1] -= self._offset[1] 123 | xyz[2] -= self._offset[2] 124 | 125 | # Apply soft iron ie. scale bias from calibration 126 | xyz[0] *= self._scale[0] 127 | xyz[1] *= self._scale[1] 128 | xyz[2] *= self._scale[2] 129 | 130 | return tuple(xyz) 131 | 132 | @property 133 | def adjustement(self): 134 | return self._adjustement 135 | 136 | @property 137 | def whoami(self): 138 | """ Value of the whoami register. """ 139 | return self._register_char(_WIA) 140 | 141 | def calibrate(self, count=256, delay=200): 142 | self._offset = (0, 0, 0) 143 | self._scale = (1, 1, 1) 144 | 145 | reading = self.magnetic 146 | minx = maxx = reading[0] 147 | miny = maxy = reading[1] 148 | minz = maxz = reading[2] 149 | 150 | while count: 151 | utime.sleep_ms(delay) 152 | reading = self.magnetic 153 | minx = min(minx, reading[0]) 154 | maxx = max(maxx, reading[0]) 155 | miny = min(miny, reading[1]) 156 | maxy = max(maxy, reading[1]) 157 | minz = min(minz, reading[2]) 158 | maxz = max(maxz, reading[2]) 159 | count -= 1 160 | 161 | # Hard iron correction 162 | offset_x = (maxx + minx) / 2 163 | offset_y = (maxy + miny) / 2 164 | offset_z = (maxz + minz) / 2 165 | 166 | self._offset = (offset_x, offset_y, offset_z) 167 | 168 | # Soft iron correction 169 | avg_delta_x = (maxx - minx) / 2 170 | avg_delta_y = (maxy - miny) / 2 171 | avg_delta_z = (maxz - minz) / 2 172 | 173 | avg_delta = (avg_delta_x + avg_delta_y + avg_delta_z) / 3 174 | 175 | scale_x = avg_delta / avg_delta_x 176 | scale_y = avg_delta / avg_delta_y 177 | scale_z = avg_delta / avg_delta_z 178 | 179 | self._scale = (scale_x, scale_y, scale_z) 180 | 181 | return self._offset, self._scale 182 | 183 | def _register_short(self, register, value=None, buf=bytearray(2)): 184 | if value is None: 185 | self.i2c.readfrom_mem_into(self.address, register, buf) 186 | return ustruct.unpack("h", buf)[0] 179 | 180 | ustruct.pack_into(">h", buf, 0, value) 181 | return self.i2c.writeto_mem(self.address, register, buf) 182 | 183 | def _register_three_shorts(self, register, buf=bytearray(6)): 184 | self.i2c.readfrom_mem_into(self.address, register, buf) 185 | return ustruct.unpack(">hhh", buf) 186 | 187 | def _register_char(self, register, value=None, buf=bytearray(1)): 188 | if value is None: 189 | self.i2c.readfrom_mem_into(self.address, register, buf) 190 | return buf[0] 191 | 192 | ustruct.pack_into("