├── .gitignore ├── LICENSE ├── README.md ├── bno055.py ├── bno055_base.py └── bno055_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-bno055 2 | 3 | A MicroPython driver for the Bosch BNO055 inertial measurement unit (IMU). This 4 | chip has the advantage of performing sensor fusion in hardware. The driver is 5 | based on the [Adafruit CircuitPython driver](https://github.com/adafruit/Adafruit_CircuitPython_BNO055.git). 6 | 7 | This driver has the following objectives: 8 | * It runs under official MicroPython. 9 | * It is cross-platform and designed to run on Pyboard 1.x, Pyboard D, ESPx. It 10 | should run on any hardware supporting the `machine` module and the I2C 11 | interface. 12 | * There is a minimal version with small memory footprint for ESP8266 (~9.7KB). 13 | * Supports changing the device mode. 14 | * Supports vehicle-relative coordinate transformation on-chip. 15 | * Supports changing the hardware configuration. 16 | * Supports access in interrupt service routines. 17 | * Uses the MicroPython approach to coding (avoids properties/descriptors). 18 | 19 | Testing was done with the [Adafruit BNO055 breakout](https://www.adafruit.com/product/2472). 20 | This chip and breakout come highly recommended. Calibration requires a little 21 | practice, but once done the fusion algorithm is remarkably immune to external 22 | magnetic fields. A field which displaced my hiker's compass by 90° caused at 23 | most 2° of heading change on this device. The raw magnetometer readings changed 24 | radically but heading remained essentially constant. 25 | 26 | # Contents 27 | 28 | 1. [Files and dependencies](./README.md#1-files-and-dependencies) 29 | 2. [Getting started](./README.md#2-getting-started) 30 | 2.1 [Pullups](./README.md#21-pullups) 31 | 2.2 [Clock stretching](./README.md#22-clock-stretching) 32 | 2.3 [Pico issue](./README.md#23-pico-issue) 33 | 2.4 [Basic usage](./README.md#23-basic-usage) 34 | 3. [The BNO055 class](./README.md#3-the-bno055-class) 35 | 3.1 [Constructor](./README.md#31-constructor) 36 | 3.2 [Read only methods](./README.md#32-read-only-methods) Read data from device. 37 | 3.3 [Changing the device configuration](./README.md#33-changing-the-device-configuration) 38 | 3.3.1 [Mode setting](./README.md#331-mode-setting) Modify device operating mode. 39 | 3.3.2 [Rate and range control](./README.md#332-rate-and-range-control) Further settings. 40 | 3.4 [Use in interrupt handlers](./README.md#34-use-in-interrupt-handlers) 41 | 3.5 [Other methods](./README.md#35-other-methods) 42 | 4. [Calibration](./README.md#4-calibration) 43 | 5. [Minimal version](./README.md#5-minimal-version) Minimise RAM usage. 44 | 6. [References](./README.md#6-references) 45 | 46 | # 1. Files and dependencies 47 | 48 | * `bno055_base.py` Base class for device driver. 49 | * `bno055.py` Device driver. 50 | * `bno055_test.py` Simple test program. Can run on Pyboard or (with pin 51 | changes) ESP8266: see code comments. 52 | 53 | The driver has no dependencies. It is designed to be imported using 54 | ```python 55 | from bno055 import * 56 | ``` 57 | In addition to the `BNO055` class this imports symbolic names for modes and 58 | data registers. On highly RAM-constrained targets the base class may be used 59 | alone with some loss of functionality, see 60 | [section 5](./README.md#5-minimal-version). 61 | 62 | # 2. Getting started 63 | 64 | The Adafruit breakout board has a voltage regulator and may be powered from a 65 | 5V or 3.3V supply. Note that other hardware may require a 3.3V source. 66 | 67 | The wiring below is for I2C(1) as used in the test program. 68 | 69 | | pyboard | bno055 | 70 | |:-------:|:------:| 71 | | VIN | VIN | 72 | | GND | GND | 73 | | SCL X9 | SCL | 74 | | SDA X10 | SDA | 75 | 76 | ## 2.1 Pullups 77 | 78 | Pullups (resistors connected to 3.3V) are required on SCL and SDA. The Pyboard 79 | has these on `I2C(1)` and `I2C(2)`, as does the Adafruit BNO055 breakout. 80 | ESP8266 boards have pullups on pins 0 and 2. Pyboard 1.1 pullups are 4.7KΩ, 81 | those on the Adafruit board are 10KΩ. The Raspberry Pico lacks pullups as do 82 | most ESP32 breakout boards. 83 | 84 | I encountered problems with the Pico and the Adafruit board: waveforms had slow 85 | risetimes and invalid data occurred at times. This was solved by adding 1KΩ 86 | resistors, with waveforms showing clean edges. 87 | 88 | As a general comment, my first port of call in the event of any problem would 89 | be to add 1KΩ resistors. 90 | 91 | ## 2.2 Clock stretching 92 | 93 | See [this issue](https://github.com/micropython-IMU/micropython-bno055/issues/4). 94 | 95 | The BNO055 hardware performs I2C clock stretching. I have found no 96 | documentation of this, but measurement suggests a maximum of about 500μs. The 97 | default timeout on Soft I2C is 255μs: it is therefore necessary to specify a 98 | `timeout` value in the SoftI2C constructor call - see below. 99 | 100 | ## 2.3 Pico Issue 101 | 102 | Firmware problems with I2C on the Pico have now been fixed. Please use a daily 103 | build or release build later than 1.18. Running I2C at the 400KHz default can 104 | be unreliable if the I2C pullup resistors are too high. Options are to reduce 105 | speed (Adafruit use 100KHz) or to add 1KΩ resistors from SDA and SCL to 3.3V. 106 | 107 | Refs: [this forum thread](https://forum.micropython.org/viewtopic.php?f=21&t=11745) 108 | and 109 | [this issue](https://github.com/micropython/micropython/issues/8167#issuecomment-1013696765). 110 | 111 | ## 2.4 Basic usage 112 | 113 | ```python 114 | import machine 115 | import time 116 | from bno055 import * 117 | # Pyboard hardware I2C 118 | i2c = machine.I2C(1) 119 | # ESP32 and ESP8266 soft I2C 120 | # i2c = machine.SoftI2C(scl=machine.Pin(2), sda=machine.Pin(0), timeout=100_000) 121 | imu = BNO055(i2c) 122 | calibrated = False 123 | while True: 124 | time.sleep(1) 125 | if not calibrated: 126 | calibrated = imu.calibrated() 127 | print('Calibration required: sys {} gyro {} accel {} mag {}'.format(*imu.cal_status())) 128 | print('Temperature {}°C'.format(imu.temperature())) 129 | print('Mag x {:5.0f} y {:5.0f} z {:5.0f}'.format(*imu.mag())) 130 | print('Gyro x {:5.0f} y {:5.0f} z {:5.0f}'.format(*imu.gyro())) 131 | print('Accel x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.accel())) 132 | print('Lin acc. x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.lin_acc())) 133 | print('Gravity x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.gravity())) 134 | print('Heading {:4.0f} roll {:4.0f} pitch {:4.0f}'.format(*imu.euler())) 135 | ``` 136 | To calibrate the chip move the unit as per [section 4](./README.md#4-calibration) 137 | until calibration values for gyro, accel and mag are 3 and sys is >0. 138 | 139 | Note that if code is started automatically on power up (by a line in main.py) a 140 | delay of 500ms should be applied before instantiating the `BNO055`. This is to 141 | allow for the BNO055 chip startup time (400ms typical). 142 | 143 | ###### [Contents](./README.md#contents) 144 | 145 | # 3. The BNO055 class 146 | 147 | ## 3.1 Constructor 148 | 149 | This takes the following args 150 | 151 | * `i2c` An initialised I2C instance. 152 | * `address=0x28` Default device address. The Adafruit breakout allows this to 153 | be changed to 0x29 for the case where two devices are attached to one bus. 154 | * `crystal=True` If `True` use an external crystal for higher accuracy; the 155 | Adafruit board has a crystal. If the hardware does not have a crystal this 156 | should be `False`: the chip's internal oscillator will be used. 157 | 158 | The following optional args provide for vehicle relative coordinates. The 159 | default values assume that the IMU is mounted component side up and in the 160 | horizontal plane. Alternatives cater for cases where the IMU is rotated or 161 | mounted orthogonally relative to the horizontal plane of the vehicle. 162 | * `transpose=(0, 1, 2)` 163 | * `sign=(0, 0, 0)` 164 | 165 | Axes are numbered 0 for X, 1 for Y and 2 for Z, and the transpose tuple is 166 | `(x, y, z)`. Hence `(0, 1, 2)` implies no transformation. Passing `(1, 0, 2)` 167 | implies a rotation around the Z axis. 168 | 169 | Sign values must be 0 (normal) or 1 (inverted). Hence a board rotated around 170 | the Y axis and mounted upside down would have `sign=(1, 0, 1)` (X and Z axes 171 | inverted). This is further explained in the 172 | [Device datasheet](https://cdn-learn.adafruit.com/assets/assets/000/036/832/original/BST_BNO055_DS000_14.pdf) 173 | section 3.4. 174 | 175 | The constructor blocks for 700ms (1.2s if `crystal==True`). 176 | 177 | ## 3.2 Read only methods 178 | 179 | Return values (numbers are floats unless stated otherwise): 180 | * `mag()` Magnetometer vector `(x, y, z)` in μT (microtesla). 181 | * `accel()` Accelerometer vector `(x, y, z)` in m.s^-2 182 | * `lin_acc()` Acceleration vector `(x, y, z)` after removal of gravity 183 | component (m.s^-2).* 184 | * `gravity()` Gravity vector `(x, y, z)` in m.s^-2 after removal of 185 | acceleration data.* 186 | * `gyro()` Gyro vector `(x, y, z)` in deg.s^-1. 187 | * `euler()` Euler angles in degrees `(heading, roll, pitch)`.* 188 | * `quaternion()` Quaternion `(w, x, y, z)`.* 189 | * `temperature()` Chip temperature as an integer °C (Celcius). 190 | * `calibrated()` `True` if all elements of the device are calibrated. 191 | * `cal_status()` Returns bytearray `[sys, gyro, accel, mag]`. Each element 192 | has a value from 0 (uncalibrated) to 3 (fully calibrated). 193 | * `sensor_offsets()` Returns the current calibration offsets stored on the device as 194 | a `bytearray`. 195 | * `set_offsets()` Sets the current calibration offsets of the device. For example, 196 | this can be called with the results of `sensor_offsets()` after a reset. 197 | * `external_crystal()` `True` if using an external crystal. 198 | * `get_config(sensor)` See [Section 3.3.2](./README.md#332-rate-and-range-control). 199 | 200 | Some methods only produce valid data if the chip is in a fusion mode. If the 201 | mode is changed from the default to a non-fusion one, methods such as `euler` 202 | will return zeros. Such methods are marked with a * above. 203 | 204 | See [my notes on quaternions](https://github.com/peterhinch/micropython-samples/blob/master/README.md#412-quaternions) 205 | for code enabling them to be used to perform 3D rotation with minimal 206 | mathematics. They are easier to use for this purpose than Euler angles. 207 | 208 | ###### [Contents](./README.md#contents) 209 | 210 | ## 3.3 Changing the device configuration 211 | 212 | Many applications will use the default mode of the chip. This section describes 213 | ways of changing this for special purposes, for example where a high update 214 | rate is required. This can arise if readings are used in a feedback loop where 215 | latency can cause stability issues. 216 | 217 | ### 3.3.1 Mode setting 218 | 219 | * `mode(new_mode=None)` If a value is passed, change the mode of operation. 220 | Returns the mode as it was before any change was made. The mode is an integer. 221 | The `BNO055` module provides symbolic names as per the table below. 222 | 223 | The mode of operation defines which sensors are enabled, whether fusion occurs 224 | and if measurements are absolute or relative. The `bno055` module provides the 225 | mode values listed below as integers. 226 | 227 | | Mode | Accel | Compass | Gyro | Absolute | Fusion mode | 228 | |:----------------:|:-----:|:-------:|:----:|:--------:|:-----------:| 229 | | CONFIG_MODE | - | - | - | - | N | 230 | | ACCONLY_MODE | X | - | - | - | N | 231 | | MAGONLY_MODE | - | X | - | - | N | 232 | | GYRONLY_MODE | - | - | X | - | N | 233 | | ACCMAG_MODE | X | X | - | - | N | 234 | | ACCGYRO_MODE | X | - | X | - | N | 235 | | MAGGYRO_MODE | - | X | X | - | N | 236 | | AMG_MODE | X | X | X | - | N | 237 | | IMUPLUS_MODE | X | - | X | - | Y | 238 | | COMPASS_MODE | X | X | - | X | Y | 239 | | M4G_MODE | X | X | - | - | Y | 240 | | NDOF_FMC_OFF_MODE| X | X | X | X | Y | 241 | | NDOF_MODE | X | X | X | X | Y | 242 | 243 | The default mode is `NDOF_MODE` which supports fusion with absolute heading. 244 | This example illustrates restoration of the prior mode (`imu` is a `BNO055` 245 | instance): 246 | ```python 247 | from bno055 import * 248 | # code omitted to instantiate imu 249 | old_mode = imu.mode(ACCGYRO_MODE) 250 | # code omitted 251 | imu.mode(old_mode) 252 | ``` 253 | The purpose of the various modes is covered in the 254 | [Device datasheet](https://cdn-learn.adafruit.com/assets/assets/000/036/832/original/BST_BNO055_DS000_14.pdf) 255 | section 3.3. 256 | 257 | ###### [Contents](./README.md#contents) 258 | 259 | ### 3.3.2 Rate and range control 260 | 261 | In non-fusion modes the chip allows control of the update rate of each sensor 262 | and the range of the accelerometer and gyro. In fusion modes rates are fixed: 263 | the only available change is to the accelerometer range. The folowing shows the 264 | default setings after initialisation (mode is `NDOF`). The update rate of 265 | fusion values is 100Hz. 266 | 267 | | Device | Full scale | Update rate | 268 | |:------:|:-----------:|:-----------:| 269 | | Accel | +-4G | 100Hz | 270 | | Gyro | 2000°/s | 100Hz | 271 | | Mag | - | 20Hz | 272 | 273 | The magnetometer has a single range: units are Micro Tesla (μT). 274 | 275 | In modes which permit it, sensors may be controlled with the following method. 276 | 277 | * `config(dev, value=None)` `dev` is the device: must be `ACC`, `GYRO` or 278 | `MAG` (names defined in `bno055.py`). The `value` arg is a tuple holding the 279 | requested configuration. See below for details specific to each sensor. 280 | 281 | The default value of `None` causes no change to be made. In each case the 282 | method returns the config tuple as it was before any change was made. In 283 | certain circumstances the chip can return an unknown value. This was observed 284 | in the case of the initial value from the magnetometer. In such cases the 285 | result will be `False`. Returning the prior value allows old values to be 286 | restored, e.g. 287 | ```python 288 | old_config = imu.config(ACC, (2, 250)) 289 | # ... 290 | if old_config: # Valid config returned 291 | imu.config(ACC, old_config) # Restore old config 292 | ``` 293 | Note that the hardware will only allow configuration changes in appropriate 294 | modes. For example to change gyro settings the chip must be in a non-fusion 295 | mode which enables the gyro. If the mode is such that changes are not allowed, 296 | failure will be silent. If in doubt check the result by reading back the 297 | resultant config: 298 | ```python 299 | imu.mode(ACCGYRO_MODE) # Allows config change to ACC or GYRO 300 | cfg = (2, 250) # Intended config 301 | imu.config(ACC, cfg) 302 | if imu.config(ACC) == cfg: 303 | print('Success') 304 | ``` 305 | 306 | #### Accelerometer (dev == ACC) 307 | 308 | `value` is a 2-tuple comprising `(range, bandwidth)` 309 | Allowable values: 310 | Range: 2, 4, 8, 16 (G). 311 | Bandwidth: 8, 16, 31, 62, 125, 250, 500, 1000 (Hz). 312 | The outcome of a change may be shown by means of the `.config(ACC)` method. 313 | ```python 314 | from bno055 import * 315 | # code omitted 316 | imu.mode(ACCONLY_MODE) # non-fusion mode 317 | cfg = (2, 1000) # Intended config 318 | imu.config(ACC, cfg) # Update. 319 | if imu.config(ACC) == cfg: 320 | print('Success') 321 | ``` 322 | 323 | #### Gyro (dev == GYRO) 324 | 325 | `value` is a 2-tuple comprising `(range, bandwidth)` 326 | Allowable values: 327 | Range: 125, 250, 500, 1000, 2000 (dps) 328 | Bandwidth: 12, 23, 32, 47, 64, 116, 230, 523 (Hz). 329 | The outcome of a change may be shown by means of the `.config(GYRO)` method. 330 | 331 | #### Magnetometer (dev == MAG) 332 | 333 | `value` is a 1-tuple comprising `(rate,)` being the update rate in Hz. 334 | Allowable values: 335 | Rate: 2, 6, 8, 10, 15, 20, 25, 30 (Hz) 336 | The outcome of a change may be shown by means of the `.config(MAG)` method. 337 | Note that on first call the prior config may be unknown and the method will 338 | return `False`. This is a chip behaviour. 339 | 340 | ###### [Contents](./README.md#contents) 341 | 342 | ## 3.4 Use in interrupt handlers 343 | 344 | The `BNO055` class supports access in interrupt service routines (ISR's) by 345 | means of the `iget` method and `w`, `x`, `y`, and `z` bound variables. The ISR 346 | calls `iget` with the name of the value to be accessed. On return the bound 347 | variables are updated with the raw data from the device. Each value is a signed 348 | integer that requires scaling to be converted to standard units of measurement. 349 | 350 | | Name | Scaling | Units | 351 | |:------------:|:-----------:|:--------:| 352 | | ACC_DATA | 1/100 | m.s^-2 | 353 | | MAG_DATA | 1/16 | μT | 354 | | GYRO_DATA | 1/16 | °.s^-1 | 355 | | GRAV_DATA | 1/100 | m.s^-2 | 356 | | LIN_ACC_DATA | 1/100 | m.s^-2 | 357 | | EULER_DATA | 1/16 | ° | 358 | | QUAT_DATA | 1/(1 << 14) | unitless | 359 | 360 | In each case the integer values must be multiplied by the scaling to give the 361 | units specified. In all cases other than quaternion (`QUAT_DATA`) the `iget` 362 | method sets `.w` to zero. Example usage: 363 | 364 | ```python 365 | def cb(t): 366 | imu.iget(ACC_DATA) 367 | print(imu.w, imu.x, imu.y, imu.z) 368 | 369 | t = pyb.Timer(1, period=200, callback=cb) 370 | ``` 371 | 372 | ## 3.5 Other methods 373 | 374 | * `reset` No args. Equivalent to pulsing the chip's reset line: restores all 375 | power on defaults and resets the calibration status. Blocks for 700ms (1.2s if 376 | the constructor was called with `crystal==True`). Reinstates vehicle relative 377 | transformations specified to the constructor. 378 | 379 | ###### [Contents](./README.md#contents) 380 | 381 | # 4. Calibration 382 | 383 | Calibration requires only movement of the device while running: the process is 384 | internal to the chip and its nature is opaque. The calibration status may be 385 | read by methods described in [section 3.2](./README.md#32-read-only-methods). 386 | 387 | Until the device is calibrated its orientation will be relative to that when it 388 | was powered on. When system calibration status becomes 1 or higher the device 389 | has found magnetic north and orientation values become absolute. 390 | ([Source](https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/device-calibration)). 391 | The status returned by the chip, and hence the return values of `.calibrated` 392 | and `.cal_status` methods can regress after successful calibration. The meaning 393 | of this is unclear. It seems reasonable to assume that once the chip returns a 394 | good status it can be assumed to be OK; the demo scripts make that assumption. 395 | 396 | The following text is adapted from the chip datasheet; it could be clearer. I 397 | recommend watching [this Bosch video](https://youtu.be/Bw0WuAyGsnY) for a good 398 | exposition. 399 | 400 | Though the sensor fusion software runs the calibration algorithm of all the 401 | three sensors (accelerometer, gyroscope and magnetometer) in the background to 402 | remove the offsets, some preliminary steps should be ensured for this automatic 403 | calibration to take place. 404 | 405 | The accelerometer and the gyroscope are relatively less susceptible to external 406 | disturbances, as a result of which the offset is negligible. Whereas the 407 | magnetometer is susceptible to external magnetic field and therefore to ensure 408 | proper heading accuracy, the calibration steps described below have to be 409 | taken. 410 | 411 | Depending on the sensors selected, the following simple steps should be taken 412 | after every ‘Power on Reset’ for proper calibration of the device. 413 | 414 | ### Accelerometer Calibration 415 | 416 | * Place the device in 6 different stable positions for a period of few seconds 417 | to allow the accelerometer to calibrate. 418 | * Make sure that there is slow movement between 2 stable positions. 419 | * The 6 stable positions could be in any direction, but make sure that the 420 | device is lying at least once perpendicular to the x, y and z axis. 421 | * The `cal_status()` method may be used to see the calibration status of the 422 | accelerometer. 423 | 424 | ### Gyroscope Calibration 425 | 426 | * Place the device in a single stable position for a period of few seconds to 427 | allow the gyroscope to calibrate 428 | * The `cal_status()` method may be used to see the calibration status of the 429 | gyroscope. 430 | 431 | ### Magnetometer Calibration 432 | 433 | Magnetometers in general are susceptible to both hard-iron and soft-iron 434 | distortions, but the majority of the cases are rather due to the former. The 435 | steps mentioned below are to calibrate the magnetometer for hard-iron 436 | distortions. 437 | 438 | * Make some random movements (for example: writing the number ‘8’ on air) 439 | until the `cal_status()` method indicates fully calibrated. 440 | * It takes more calibration movements to get the magnetometer calibrated than 441 | in the NDOF mode. 442 | 443 | NDOF: 444 | 445 | * The same random movements have to be made to calibrate the sensor as in the 446 | FMC_OFF mode, but here it takes relatively less calibration movements (and 447 | slightly higher current consumption) to get the magnetometer calibrated. 448 | * The `cal_status()` method can be used to see the calibration status of the 449 | magnetometer. 450 | 451 | 452 | ### Calibration Restoration 453 | 454 | Restoring previous calibration offsets after a reset is also supported, via the 455 | `sensor_offsets()` and `set_offsets()` methods. 456 | 457 | After a successful calibration, `sensor_offsets()` can be called to retrieve a 458 | `bytearray`. This can then, for example, be written to disk for loading after a 459 | reset of the device. 460 | 461 | The corresponding `set_offsets()` method allows for restoring calibration offsets 462 | stored in said `bytearray`. Please be aware that the magnetometer's calibration 463 | status will remain as 0, as per the `cal_status()` method, even after restoring 464 | its offsets. 465 | 466 | ###### [Contents](./README.md#contents) 467 | 468 | # 5. Minimal Version 469 | 470 | This is intended for devices such as ESP8266 where RAM is limited. Note that 471 | the full version will run on ESP8266 using ~14K of RAM. The minimal version 472 | reduces this to just over 9K. 473 | 474 | The minimal version does not support vehicle-relative coordinates, ISR usage 475 | or configuration changes. Mode changes can be done, but symbolic names of modes 476 | are not supplied. The version is primarily intended for use in the default 477 | `NDOF` mode. 478 | 479 | In use the `bno055_base` module is imported and the base class is used. Example 480 | tested on an ESP8266: 481 | ```python 482 | import machine 483 | import time 484 | from bno055_base import BNO055_BASE 485 | 486 | i2c = machine.I2C(-1, scl=machine.Pin(2), sda=machine.Pin(0)) 487 | imu = BNO055_BASE(i2c) 488 | calibrated = False 489 | while True: 490 | time.sleep(1) 491 | if not calibrated: 492 | calibrated = imu.calibrated() 493 | print('Calibration required: sys {} gyro {} accel {} mag {}'.format(*imu.cal_status())) 494 | print('Temperature {}°C'.format(imu.temperature())) 495 | print('Mag x {:5.0f} y {:5.0f} z {:5.0f}'.format(*imu.mag())) 496 | print('Gyro x {:5.0f} y {:5.0f} z {:5.0f}'.format(*imu.gyro())) 497 | print('Accel x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.accel())) 498 | print('Lin acc. x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.lin_acc())) 499 | print('Gravity x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.gravity())) 500 | print('Heading {:4.0f} roll {:4.0f} pitch {:4.0f}'.format(*imu.euler())) 501 | ``` 502 | 503 | # 6. References 504 | 505 | [Adafruit BNO055 breakout](https://www.adafruit.com/product/2472) 506 | [Adafruit CircuitPython driver](https://github.com/adafruit/Adafruit_CircuitPython_BNO055.git). 507 | [Device datasheet](https://cdn-learn.adafruit.com/assets/assets/000/036/832/original/BST_BNO055_DS000_14.pdf) 508 | -------------------------------------------------------------------------------- /bno055.py: -------------------------------------------------------------------------------- 1 | # bno055.py MicroPython driver for Bosch cls nine degree of freedom inertial 2 | # measurement unit module with sensor fusion. 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Radomir Dopieralski for Adafruit Industries. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | 26 | # This is a port of the Adafruit CircuitPython driver to MicroPython, with 27 | # modified/enhanced functionality. 28 | 29 | # Original Author: Radomir Dopieralski 30 | # Ported to MicroPython and extended by Peter Hinch 31 | # This port copyright (c) Peter Hinch 2019 32 | 33 | from micropython import const 34 | from bno055_base import BNO055_BASE 35 | 36 | 37 | CONFIG_MODE = 0x00 38 | ACCONLY_MODE = 0x01 39 | MAGONLY_MODE = 0x02 40 | GYRONLY_MODE = 0x03 41 | ACCMAG_MODE = 0x04 42 | ACCGYRO_MODE = 0x05 43 | MAGGYRO_MODE = 0x06 44 | AMG_MODE = 0x07 45 | IMUPLUS_MODE = 0x08 46 | COMPASS_MODE = 0x09 47 | M4G_MODE = 0x0a 48 | NDOF_FMC_OFF_MODE = 0x0b 49 | NDOF_MODE = 0x0c 50 | 51 | ACC = 0x08 # Registers for configuration (page 1) 52 | MAG = 0x09 53 | GYRO = 0x0a 54 | 55 | ACC_DATA = 0x08 # Data regsiters (page 0) 56 | MAG_DATA = 0x0e 57 | GYRO_DATA = 0x14 58 | GRAV_DATA = 0x2e 59 | LIN_ACC_DATA = 0x28 60 | EULER_DATA = 0x1a 61 | QUAT_DATA = 0x20 62 | 63 | _PAGE_REGISTER = const(0x07) 64 | _AXIS_MAP_SIGN = const(0x42) 65 | _AXIS_MAP_CONFIG = const(0x41) 66 | 67 | class BNO055(BNO055_BASE): 68 | 69 | acc_range = (2, 4, 8, 16) # G 70 | acc_bw = (8, 16, 31, 62, 125, 250, 500, 1000) 71 | gyro_range = (2000, 1000, 500, 250, 125) # dps 72 | gyro_bw = (523, 230, 116, 47, 23, 12, 64, 32) # bandwidth (Hz) 73 | mag_rate = (2, 6, 8, 10, 15, 20, 25, 30) # rate (Hz) 74 | 75 | @classmethod 76 | def _tuple_to_int(cls, dev, v): # Convert (range, bw) to register value 77 | try: 78 | if dev == ACC: 79 | msg = 'Illegal accel range {} or bandwidth {}' 80 | return cls.acc_range.index(v[0]) | (cls.acc_bw.index(v[1]) << 2) 81 | elif dev == GYRO: 82 | msg = 'Illegal gyro range {} or bandwidth {}' 83 | return cls.gyro_range.index(v[0]) | (cls.gyro_bw.index(v[1]) << 3) 84 | elif dev == MAG: 85 | msg = 'Illegal magnetometer rate {}' 86 | return cls.mag_rate.index(v[0]) 87 | except ValueError: 88 | raise ValueError(msg.format(*v)) 89 | 90 | # Return the current config in human readable form 91 | @classmethod 92 | def _int_to_tuple(cls, dev, v): 93 | try: 94 | if dev == ACC: 95 | return (cls.acc_range[v & 3], cls.acc_bw[v >> 2]) 96 | elif dev == GYRO: 97 | return (cls.gyro_range[v & 7], cls.gyro_bw[v >> 3]) 98 | elif dev == MAG: 99 | return (cls.mag_rate[v],) 100 | except IndexError: 101 | return False # Can occur e.g. initial config of magnetometer 102 | raise ValueError('Unknown device.', dev) 103 | 104 | # Convert two bytes to signed integer (little endian) Can be used in an interrupt handler 105 | @staticmethod 106 | def _bytes_toint(lsb, msb): 107 | if not msb & 0x80: 108 | return msb << 8 | lsb # +ve 109 | return - (((msb ^ 255) << 8) | (lsb ^ 255) + 1) 110 | 111 | @staticmethod 112 | def _argcheck(arg, name): 113 | if len(arg) != 3 or not (isinstance(arg, list) or isinstance(arg, tuple)): 114 | raise ValueError(name + ' must be a 3 element list or tuple') 115 | 116 | # Transposition (x, y, z) 0 == x 1 == y 2 == z hence (0, 1, 2) is no change 117 | # Scaling (x, y, z) 0 == normal 1 == invert 118 | def __init__(self, i2c, address=0x28, crystal=True, transpose=(0, 1, 2), sign=(0, 0, 0)): 119 | self._argcheck(sign, 'Sign') 120 | if [x for x in sign if x not in (0, 1)]: 121 | raise ValueError('Sign values must be 0 or 1') 122 | self.sign = sign 123 | self._argcheck(transpose, 'Transpose') 124 | if set(transpose) != {0, 1, 2}: 125 | raise ValueError('Transpose indices must be unique and in range 0-2') 126 | self.transpose = transpose 127 | super().__init__(i2c, address, crystal) 128 | self.buf6 = bytearray(6) 129 | self.buf8 = bytearray(8) 130 | self.w = 0 131 | self.x = 0 132 | self.y = 0 133 | self.z = 0 134 | 135 | def orient(self): 136 | if self.transpose != (0, 1, 2): 137 | a = self.transpose 138 | self._write(_AXIS_MAP_CONFIG, (a[2] << 4) + (a[1] << 2) + a[0]) 139 | if self.sign != (0, 0, 0): 140 | a = self.sign 141 | self._write(_AXIS_MAP_SIGN, a[2] + (a[1] << 1) + (a[0] << 2)) 142 | 143 | # Configuration: if a tuple is passed, convert to int using function from bno055_help.py 144 | def config(self, dev, value=None): 145 | if dev not in (ACC, MAG, GYRO): 146 | raise ValueError('Unknown device:', dev) 147 | if isinstance(value, tuple): 148 | value = self._tuple_to_int(dev, value) # Convert tuple to register value 149 | elif value is not None: 150 | raise ValueError('value must be a tuple or None.') 151 | last_mode = self.mode(CONFIG_MODE) 152 | self._write(_PAGE_REGISTER, 1) 153 | old_val = self._read(dev) 154 | if value is not None: 155 | self._write(dev, value) 156 | self._write(_PAGE_REGISTER, 0) 157 | self.mode(last_mode) 158 | return self._int_to_tuple(dev, old_val) 159 | 160 | # For use in ISR 161 | def iget(self, reg): 162 | if reg == 0x20: 163 | n = 4 164 | buf = self.buf8 165 | else: 166 | n = 3 167 | buf = self.buf6 168 | self._i2c.readfrom_mem_into(self.address, reg, buf) 169 | if n == 4: 170 | self.w = self._bytes_toint(buf[0], buf[1]) 171 | i = 2 172 | else: 173 | self.w = 0 174 | i = 0 175 | self.x = self._bytes_toint(buf[i], buf[i+1]) 176 | self.y = self._bytes_toint(buf[i+2], buf[i+3]) 177 | self.z = self._bytes_toint(buf[i+4], buf[i+5]) 178 | -------------------------------------------------------------------------------- /bno055_base.py: -------------------------------------------------------------------------------- 1 | # bno055_base.py Minimal MicroPython driver for Bosch BNO055 nine degree of 2 | # freedom inertial measurement unit module with sensor fusion. 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Radomir Dopieralski for Adafruit Industries. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | 26 | # This is a port of the Adafruit CircuitPython driver to MicroPython, with 27 | # modified/enhanced functionality. 28 | 29 | # Original Author: Radomir Dopieralski 30 | # Ported to MicroPython and extended by Peter Hinch 31 | # This port copyright (c) Peter Hinch 2019 32 | 33 | import utime as time 34 | import ustruct 35 | from micropython import const 36 | 37 | _CHIP_ID = const(0xa0) 38 | 39 | _CONFIG_MODE = const(0) 40 | _NDOF_MODE = const(0x0c) 41 | 42 | _POWER_NORMAL = const(0x00) 43 | _POWER_LOW = const(0x01) 44 | _POWER_SUSPEND = const(0x02) 45 | 46 | _MODE_REGISTER = const(0x3d) 47 | _PAGE_REGISTER = const(0x07) 48 | _CALIBRATION_REGISTER = const(0x35) 49 | _TRIGGER_REGISTER = const(0x3f) 50 | _POWER_REGISTER = const(0x3e) 51 | _ID_REGISTER = const(0x00) 52 | 53 | ACCEL_OFFSET_X_LSB_ADDR = const(0x55) 54 | ACCEL_OFFSET_X_MSB_ADDR = const(0x56) 55 | ACCEL_OFFSET_Y_LSB_ADDR = const(0x57) 56 | ACCEL_OFFSET_Y_MSB_ADDR = const(0x58) 57 | ACCEL_OFFSET_Z_LSB_ADDR = const(0x59) 58 | ACCEL_OFFSET_Z_MSB_ADDR = const(0x5A) 59 | 60 | MAG_OFFSET_X_LSB_ADDR = const(0x5B) 61 | MAG_OFFSET_X_MSB_ADDR = const(0x5C) 62 | MAG_OFFSET_Y_LSB_ADDR = const(0x5D) 63 | MAG_OFFSET_Y_MSB_ADDR = const(0x5E) 64 | MAG_OFFSET_Z_LSB_ADDR = const(0x5F) 65 | MAG_OFFSET_Z_MSB_ADDR = const(0x60) 66 | 67 | GYRO_OFFSET_X_LSB_ADDR = const(0x61) 68 | GYRO_OFFSET_X_MSB_ADDR = const(0x62) 69 | GYRO_OFFSET_Y_LSB_ADDR = const(0x63) 70 | GYRO_OFFSET_Y_MSB_ADDR = const(0x64) 71 | GYRO_OFFSET_Z_LSB_ADDR = const(0x65) 72 | GYRO_OFFSET_Z_MSB_ADDR = const(0x66) 73 | 74 | ACCEL_RADIUS_LSB_ADDR = const(0x67) 75 | ACCEL_RADIUS_MSB_ADDR = const(0x68) 76 | MAG_RADIUS_LSB_ADDR = const(0x69) 77 | MAG_RADIUS_MSB_ADDR = const(0x6A) 78 | 79 | class BNO055_BASE: 80 | 81 | def __init__(self, i2c, address=0x28, crystal=True, transpose=(0, 1, 2), sign=(0, 0, 0)): 82 | self._i2c = i2c 83 | self.address = address 84 | self.crystal = crystal 85 | self.mag = lambda : self.scaled_tuple(0x0e, 1/16) # microteslas (x, y, z) 86 | self.accel = lambda : self.scaled_tuple(0x08, 1/100) # m.s^-2 87 | self.lin_acc = lambda : self.scaled_tuple(0x28, 1/100) # m.s^-2 88 | self.gravity = lambda : self.scaled_tuple(0x2e, 1/100) # m.s^-2 89 | self.gyro = lambda : self.scaled_tuple(0x14, 1/16) # deg.s^-1 90 | self.euler = lambda : self.scaled_tuple(0x1a, 1/16) # degrees (heading, roll, pitch) 91 | self.quaternion = lambda : self.scaled_tuple(0x20, 1/(1<<14), bytearray(8), '> 6) & 0x03 # sys 128 | s[1] = (cdata >> 4) & 0x03 # gyro 129 | s[2] = (cdata >> 2) & 0x03 # accel 130 | s[3] = cdata & 0x03 # mag 131 | return s 132 | 133 | def calibrated(self): 134 | s = self.cal_status() 135 | # https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/device-calibration 136 | return min(s[1:]) == 3 and s[0] > 0 137 | 138 | def sensor_offsets(self): 139 | lastMode = self._mode 140 | 141 | self.mode(_CONFIG_MODE) 142 | offsets = self._readn(bytearray(22), ACCEL_OFFSET_X_LSB_ADDR) 143 | self.mode(lastMode) 144 | 145 | return offsets 146 | 147 | def set_offsets(self, buf): 148 | lastMode = self._mode 149 | self.mode(_CONFIG_MODE) 150 | 151 | time.sleep_ms(25) 152 | 153 | '''Note: Configuration will take place only when user writes to the last 154 | byte of each config data pair (ex. ACCEL_OFFSET_Z_MSB_ADDR, etc.). 155 | Therefore the last byte must be written whenever the user wants to 156 | changes the configuration.''' 157 | 158 | self._write(ACCEL_OFFSET_X_LSB_ADDR, buf[0]) 159 | self._write(ACCEL_OFFSET_X_MSB_ADDR, buf[1]) 160 | self._write(ACCEL_OFFSET_Y_LSB_ADDR, buf[2]) 161 | self._write(ACCEL_OFFSET_Y_MSB_ADDR, buf[3]) 162 | self._write(ACCEL_OFFSET_Z_LSB_ADDR, buf[4]) 163 | self._write(ACCEL_OFFSET_Z_MSB_ADDR, buf[5]) 164 | 165 | self._write(MAG_OFFSET_X_LSB_ADDR, buf[6]) 166 | self._write(MAG_OFFSET_X_MSB_ADDR, buf[7]) 167 | self._write(MAG_OFFSET_Y_LSB_ADDR, buf[8]) 168 | self._write(MAG_OFFSET_Y_MSB_ADDR, buf[9]) 169 | self._write(MAG_OFFSET_Z_LSB_ADDR, buf[10]) 170 | self._write(MAG_OFFSET_Z_MSB_ADDR, buf[11]) 171 | 172 | self._write(GYRO_OFFSET_X_LSB_ADDR, buf[12]) 173 | self._write(GYRO_OFFSET_X_MSB_ADDR, buf[13]) 174 | self._write(GYRO_OFFSET_Y_LSB_ADDR, buf[14]) 175 | self._write(GYRO_OFFSET_Y_MSB_ADDR, buf[15]) 176 | self._write(GYRO_OFFSET_Z_LSB_ADDR, buf[16]) 177 | self._write(GYRO_OFFSET_Z_MSB_ADDR, buf[17]) 178 | 179 | self._write(ACCEL_RADIUS_LSB_ADDR, buf[18]) 180 | self._write(ACCEL_RADIUS_MSB_ADDR, buf[19]) 181 | 182 | self._write(MAG_RADIUS_LSB_ADDR, buf[20]) 183 | self._write(MAG_RADIUS_MSB_ADDR, buf[21]) 184 | 185 | self.mode(lastMode) 186 | 187 | 188 | # read byte from register, return int 189 | def _read(self, memaddr, buf=bytearray(1)): # memaddr = memory location within the I2C device 190 | self._i2c.readfrom_mem_into(self.address, memaddr, buf) 191 | return buf[0] 192 | 193 | # write byte to register 194 | def _write(self, memaddr, data, buf=bytearray(1)): 195 | buf[0] = data 196 | self._i2c.writeto_mem(self.address, memaddr, buf) 197 | 198 | # read n bytes, return buffer 199 | def _readn(self, buf, memaddr): # memaddr = memory location within the I2C device 200 | self._i2c.readfrom_mem_into(self.address, memaddr, buf) 201 | return buf 202 | 203 | def mode(self, new_mode=None): 204 | old_mode = self._read(_MODE_REGISTER) 205 | if new_mode is not None: 206 | self._write(_MODE_REGISTER, _CONFIG_MODE) # This is empirically necessary if the mode is to be changed 207 | time.sleep_ms(20) # Datasheet table 3.6 208 | if new_mode != _CONFIG_MODE: 209 | self._write(_MODE_REGISTER, new_mode) 210 | time.sleep_ms(10) # Table 3.6 211 | 212 | self._mode = new_mode 213 | return old_mode 214 | 215 | def external_crystal(self): 216 | return bool(self._read(_TRIGGER_REGISTER) & 0x80) 217 | -------------------------------------------------------------------------------- /bno055_test.py: -------------------------------------------------------------------------------- 1 | # bno055_test.py Simple test program for MicroPython bno055 driver 2 | 3 | # Copyright (c) Peter Hinch 2019 4 | # Released under the MIT licence. 5 | 6 | import machine 7 | import time 8 | from bno055 import * 9 | # Tested configurations 10 | # Pyboard hardware I2C 11 | # i2c = machine.I2C(1) 12 | 13 | # Pico: hard I2C doesn't work without this patch 14 | # https://github.com/micropython/micropython/issues/8167#issuecomment-1013696765 15 | i2c = machine.I2C(0, sda=machine.Pin(16), scl=machine.Pin(17)) # EIO error almost immediately 16 | 17 | # All platforms: soft I2C requires timeout >= 1000μs 18 | # i2c = machine.SoftI2C(sda=machine.Pin(16), scl=machine.Pin(17), timeout=1_000) 19 | # ESP8266 soft I2C 20 | # i2c = machine.SoftI2C(scl=machine.Pin(2), sda=machine.Pin(0), timeout=100_000) 21 | # ESP32 hard I2C 22 | # i2c = machine.I2C(1, scl=machine.Pin(21), sda=machine.Pin(23)) 23 | imu = BNO055(i2c) 24 | calibrated = False 25 | while True: 26 | time.sleep(1) 27 | if not calibrated: 28 | calibrated = imu.calibrated() 29 | print('Calibration required: sys {} gyro {} accel {} mag {}'.format(*imu.cal_status())) 30 | print('Temperature {}°C'.format(imu.temperature())) 31 | print('Mag x {:5.0f} y {:5.0f} z {:5.0f}'.format(*imu.mag())) 32 | print('Gyro x {:5.0f} y {:5.0f} z {:5.0f}'.format(*imu.gyro())) 33 | print('Accel x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.accel())) 34 | print('Lin acc. x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.lin_acc())) 35 | print('Gravity x {:5.1f} y {:5.1f} z {:5.1f}'.format(*imu.gravity())) 36 | print('Heading {:4.0f} roll {:4.0f} pitch {:4.0f}'.format(*imu.euler())) 37 | --------------------------------------------------------------------------------