IMU 9-Axis Gyro × micro:bit × MakeCode
Interactive tutorialmicro:bit
First time? Try the interactive tutorial in your browser first, then follow the steps below with your hub.
This page covers MakeCode (blocks / JavaScript) only: how to set it up for the IMU 9-Axis Gyro on BBC micro:bit V1 / V2, how to read every value, the commands it supports and examples. Use the switcher above to change product, host or software.
Which edition and how to connect
Get the Universal I2C edition and connect it to the I2C header of a micro:bit breakout (SDA = P20, SCL = P19), factory address 0x6A. It draws little current, so VCC can go to the micro:bit 3V pin (about 90 mA on V1, 190 mA on V2); on batteries the rail can sag below 3 V, so switch to external 5 V if readings are unstable (share GND with the micro:bit).
Setup
What you need
- Sensor: IMU 9-Axis Gyro (Universal I2C edition). SPIKE and EV3 editions run different firmware and cannot be read by a micro:bit.
- Board: BBC micro:bit V1 or V2, plus a breakout board or crocodile-clip adapter to reach P19 / P20.
- Software: MakeCode for micro:bit in the browser (makecode.microbit.org, blocks / JavaScript); the offline app (Windows / Mac) also works, but adding an extension the first time needs internet.
- Extension: pxt-mbc-sensors v2 (MIT, nothing to download, just paste the URL).
Important
Bench test pending: extension v2.0.5 compiles in MakeCode (V1 + V2) but has not yet been checked block by block on real hardware. Block names follow the GitHub release.
Install the software
- Open makecode.microbit.org → New Project.
- Traditional Chinese block names are included: switch the editor language (gearwheel → Language) if you prefer.
Make the software see the sensor
- Click Advanced → Extensions at the bottom of the toolbox (also in the gearwheel menu).
- Paste
https://github.com/contactmbcrobot/pxt-mbc-sensorsinto the search box and press Enter. - Click the mbc-sensors card. An "MBC Sensors" category appears with six groups: Line, Distance, Gamepad, IMU, Camera and Device.
- To use an example: switch to JavaScript at the top right → paste the example → download the
.hexand drag it onto the MICROBIT drive.
Tip
No approval, no download: teachers just hand the URL to students. To pin a version, paste
https://github.com/contactmbcrobot/pxt-mbc-sensors#v2.0.5.
Wiring
The micro:bit I2C pins are fixed: P19 = SCL, P20 = SDA (on the edge connector, so you need a breakout board or clip adapter). On the MBC 4-pin sensor cable TX goes to SCL and RX to SDA, exactly as when wiring to an Arduino.
| Sensor | micro:bit | Note |
|---|---|---|
| SCL (TX) | P19 | I2C clock |
| SDA (RX) | P20 | I2C data |
| VCC | 3V or external 5 V | power-hungry products must not draw from the micro:bit 3V pin |
| GND | GND | with external power, share GND with the micro:bit |
- 3V pin current: V1 about 90 mA, V2 about 190 mA; on batteries the rail can drop to just over 2 V. Ranger 8-way, Controller PS4 / PS5 and the SharpShot must use external 5 V; other products can try 3V first, with external 5 V recommended on batteries.
- Logic level: MBC product I2C lines are 3.3 V, compatible with the micro:bit without a level shifter.
- This product's factory address is
0x6A; products with different addresses can share the same P19 / P20 bus.
First test
- Check the wiring first with the "Device → wait for device at 0x6A ready" or "device at 0x6A is ready" block.
- Download the
.hexof example 1 on this page to the micro:bit. - If the number on the LEDs changes with what is in front of the sensor, everything is connected.
FAQ
- Values are always 0? With no device connected the reporter blocks return 0 (the internal −32768 is filtered), every yes / no block returns false, and nothing throws. Check in order: wiring (SDA = P20, SCL = P19) → external power on → "device at … is ready" is true.
- On a V1, the compass and accelerometer stop working too? On V1 the external I2C bus is shared with the onboard accelerometer, so a sensor holding SDA low also kills them. V2 has a separate external bus.
- Works on and off? STM32 products (Pathfinder, Ranger, PS2, IMU) clock-stretch, and the nRF52 on the V2 has a known clock-stretching issue (errata 219), so V1 and V2 both need a bench test. ESP32 products (PS4, SharpShot) do not stretch; the extension already waits 2 ms after writing the value id.
- Two products of the same kind? They share an address: first change one with "Device → change device at … to address …" (applied after reboot), then tell the extension with that product's "use address" block.
Reading values
After adding pxt-mbc-sensors, find the blocks in the MBC Sensors category; the JavaScript form is shown too, address 0x6A.
gyro 3D heading headingGyro3D
| Range | Meaning |
|---|---|
-32768~32767 ° |
heading from the 3D quaternion, degrees, 0 at boot/zero, accumulates past 360 (wraps at ±32767, ~91 turns) |
(IMU heading [gyro 3D v] \(°\) :: #00A4E4)
let headingGyro3D = mbc.imuHeading(MbcImuHeading.Gyro3D) // gyro 3D heading:-32768~32767 °Returns 0 when the sensor does not answer; check with "device at … is ready" first.
fused heading headingFused
| Range | Meaning |
|---|---|
-32768~32767 ° |
3D gyro heading slowly corrected by the magnetometer, degrees, relative to the zero point; may exceed 360 |
(IMU heading [fused v] \(°\) :: #00A4E4)
let headingFused = mbc.imuHeading(MbcImuHeading.Fused) // fused heading:-32768~32767 °Related block "on IMU turned by 90 °"
mbc.onImuTurned(90, function () {
})Returns 0 when the sensor does not answer; check with "device at … is ready" first.
gyro 1D heading headingGyro1D
| Range | Meaning |
|---|---|
-32768~32767 ° |
heading integrated about the yaw axis only, degrees, 0 at boot/zero; may exceed 360 |
(IMU heading [gyro 1D v] \(°\) :: #00A4E4)
let headingGyro1D = mbc.imuHeading(MbcImuHeading.Gyro1D) // gyro 1D heading:-32768~32767 °Returns 0 when the sensor does not answer; check with "device at … is ready" first.
magnetic heading headingMag
| Range | Meaning |
|---|---|
-32768~32767 ° |
tilt-compensated magnetometer heading, degrees, relative to the zero point |
(IMU heading [magnetic v] \(°\) :: #00A4E4)
let headingMag = mbc.imuHeading(MbcImuHeading.Magnetic) // magnetic heading:-32768~32767 °Returns 0 when the sensor does not answer; check with "device at … is ready" first.
yaw rate yawRate
| Range | Meaning |
|---|---|
-32768~32767 0.1 dps |
angular rate about the yaw axis (getYawAxis()), 0.1 deg/s, same sign as the heading |
(IMU turn rate \(0.1 °/s\) :: #00A4E4)
let yawRate = mbc.imuYawRate() // yaw rate:-32768~32767 0.1 dpsRelated block "IMU is [still]"
let imuFlag = mbc.imuFlag(MbcImuFlag.Still)Returns 0 when the sensor does not answer; check with "device at … is ready" first.
pitch angle pitch
| Range | Meaning |
|---|---|
-90~+90 ° |
elevation of the board's printed X axis above horizontal: +90 pointing up, −90 pointing down, 0 level (from the gravity vector) |
(IMU pitch \(°\) :: #00A4E4)
let pitch = mbc.imuPitch() // pitch angle:-90~+90 °Returns 0 when the sensor does not answer; check with "device at … is ready" first.
pitch rate pitchRate
| Range | Meaning |
|---|---|
-32768~32767 0.1 dps |
angular rate about the board's printed X axis, 0.1 °/s (right-hand rule, CCW seen from +X is positive); this is gyro X, not the derivative of 06 |
(IMU pitch rate \(0.1 °/s\) :: #00A4E4)
let pitchRate = mbc.imuPitchRate() // pitch rate:-32768~32767 0.1 dpsReturns 0 when the sensor does not answer; check with "device at … is ready" first.
magnetometer trust magTrust
| Range | Meaning |
|---|---|
0~255 |
how much the fusion trusts the magnetometer, 0 |
(IMU magnetic trust :: #00A4E4)
let magTrust = mbc.imuMagTrust() // magnetometer trust:0~255Returns 0 when the sensor does not answer; check with "device at … is ready" first.
magnetic disturbance magDisturb
| Range | Meaning |
|---|---|
0~255 0.01° |
magnetic disturbance level 0~255 (degrees × 100, capped at 255) |
(IMU magnetic disturbance :: #00A4E4)
let magDisturb = mbc.imuMagDisturbance() // magnetic disturbance:0~255 0.01°Returns 0 when the sensor does not answer; check with "device at … is ready" first.
face up upFace
| Range | Meaning |
|---|---|
0~5 |
which axis points up: 0 +X, 1 +Y, 2 +Z, 3 −X, 4 −Y, 5 −Z (physical axes) |
(IMU face up :: #00A4E4)
let upFace = mbc.imuFaceUp() // face up:0~5Related block "IMU face up is [+Z]"
let imuFaceUpIs = mbc.imuFaceUpIs(MbcImuFace.PlusZ)Returns 0 when the sensor does not answer; check with "device at … is ready" first.
gyro axis 1~3 gyro
| Range | Meaning |
|---|---|
-32768~32767 0.1 dps |
10 X, 11 Y, 12 Z angular rate, 0.1 deg/s (physical axes, sign-corrected) |
(IMU [gyro \(0.1 °/s\) v] axis [X v] :: #00A4E4)
let gyro = [mbc.imuAxis(MbcImuSensor.Gyro, MbcAxis.X), mbc.imuAxis(MbcImuSensor.Gyro, MbcAxis.Y), mbc.imuAxis(MbcImuSensor.Gyro, MbcAxis.Z)] // gyro axis 1~3:-32768~32767 0.1 dpsReturns 0 when the sensor does not answer; check with "device at … is ready" first.
accel axis 1~3 accel
| Range | Meaning |
|---|---|
-32768~32767 mg |
13 X, 14 Y, 15 Z acceleration, mg (1000 = 1 g) |
(IMU [accelerometer \(mg\) v] axis [X v] :: #00A4E4)
let accel = [mbc.imuAxis(MbcImuSensor.Accel, MbcAxis.X), mbc.imuAxis(MbcImuSensor.Accel, MbcAxis.Y), mbc.imuAxis(MbcImuSensor.Accel, MbcAxis.Z)] // accel axis 1~3:-32768~32767 mgReturns 0 when the sensor does not answer; check with "device at … is ready" first.
mag axis 1~3 mag
| Range | Meaning |
|---|---|
-32768~32767 mG |
16 X, 17 Y, 18 Z magnetic field, mG (milligauss) |
(IMU [magnetometer \(mG\) v] axis [X v] :: #00A4E4)
let mag = [mbc.imuAxis(MbcImuSensor.Mag, MbcAxis.X), mbc.imuAxis(MbcImuSensor.Mag, MbcAxis.Y), mbc.imuAxis(MbcImuSensor.Mag, MbcAxis.Z)] // mag axis 1~3:-32768~32767 mGReturns 0 when the sensor does not answer; check with "device at … is ready" first.
command status cmdStatus
| Range | Meaning |
|---|---|
0~0xEF03 |
(last command code << 8) | state; state 0 done, 1 busy, 2 failed, 3 rejected (same codes as the SPIKE command reply (mode 8 v3)); reported for E1~EB; undefined codes after EB: ignored on I2C, 0B unchanged (SPIKE replies 3 rejected) |
No dedicated block; in JavaScript read it with mbc.readValue(address, id) (−32768 when nothing answers).
let cmdStatus = mbc.readValue(0x6A, 0x0B) // command status:0~0xEF03Commands
zero heading zeroHeading
zero all four headings (zeroHeadings()); EV3 also accepts the stock gyro-reset WRITE 0x11
IMU zero heading :: #00A4E4
mbc.imuZeroHeading() // zero headingstart accelerometer calibration accelCalStart
start accelerometer calibration (= v1 0x20=0x01); F3 b4 = 1 meanwhile; ignored during magnetometer calibration (0B state 3 rejected)
IMU calibration [accelerometer start v] :: #00A4E4
mbc.imuCalibrate(MbcImuCalib.AccelStart) // start accelerometer calibrationend accelerometer calibration accelCalEnd
finish accelerometer calibration and save to EEPROM (= v1 0x20=0x02); on success the mount axis is re-captured and headings are zeroed once the sensor is level and still (not immediately); rejected if not calibrating (0B state 3); failed check → 0B state 2
IMU calibration [accelerometer end v] :: #00A4E4
mbc.imuCalibrate(MbcImuCalib.AccelEnd) // end accelerometer calibrationstart magnetometer calibration magCalStart
start magnetometer calibration (= v1 0x20=0x03); F3 b5 = 1 meanwhile; ignored if the magnetometer has failed (0B state 3)
IMU calibration [magnetometer start v] :: #00A4E4
mbc.imuCalibrate(MbcImuCalib.MagStart) // start magnetometer calibrationend magnetometer calibration magCalEnd
finish magnetometer calibration and save to EEPROM (= v1 0x20=0x04); headings zeroed right away on success; rejected if not calibrating (0B state 3); failed check → 0B state 2
IMU calibration [magnetometer end v] :: #00A4E4
mbc.imuCalibrate(MbcImuCalib.MagEnd) // end magnetometer calibrationset heading setHeading
[angle int16 little-endian]: all four headings set to this angle (RAM); rejected while calibrating (0B state 3)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xE5, [params]).
north as zero zeroNorth
gyro heading set to the current magnetic bearing 0~359, then only whole turns are removed (absolute bearing); fusion re-init within ~3 s, timeout = failed; rejected while calibrating (0B state 3)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xE6, [params]).
re-estimate gyro bias gyroBiasReset
re-estimates the gyro bias over ~1.5 s of stillness (3 still windows); moved and not done within 4 s = failed; rejected while calibrating (0B state 3)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xE7, [params]).
re-capture mount axis recaptureAxis
re-detects the mounting orientation (headings zeroed after capture); RAM; rejected while calibrating (0B state 3)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xE8, [params]).
cancel calibration cancelCalib
cancels the running accelerometer / magnetometer calibration without saving (old values restored)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xE9, [params]).
heading correction headingCorrection
[uint16 little-endian] actual degrees per turn × 10 (0.1°/turn), 3500~3700; 0 = back to 360.0. Saved to EEPROM 0x60 (written after 3 s, skipped if unchanged), headings zeroed afterwards; missing or out of range → 0B state 2 (SPIKE also 2)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xEA, [params]).
calibration factory reset calibFactoryReset
[A5]: accelerometer, magnetometer calibration and heading correction back to factory values, saved immediately; wrong magic → 0B state 2; rejected while calibrating (3)
No dedicated block; in JavaScript use mbc.writeCmd(0x6A, 0xEB, [params]).
Examples
This page is for people who have never written code. The 5 examples start with "show a value on the LEDs" and build up to "drive a car". Everything is written for the MakeCode web editor with the MBC extension pxt-mbc-sensors v2: switch to JavaScript, paste an example and it runs; switch back to Blocks to see which blocks it becomes.
Important
- Extension pxt-mbc-sensors v2 has not been verified on hardware yet. Every example on this page compiles with the MakeCode CLI (once for V1, once for V2); value directions, ready times and power are still to be confirmed on a real board, see "to be tested" in the hardware checklist below.
- Use the Universal I2C edition of the product. The SPIKE and EV3 editions have different firmware and cannot be read by a micro:bit.
Software
| Editor | micro:bit V1 | micro:bit V2 | How |
|---|---|---|---|
| MakeCode web editor (makecode.microbit.org, Blocks / JavaScript) | ✔ (less memory, but every example here fits) | ✔ | New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → paste the example → Download the .hex and drag it onto the MICROBIT drive |
| MakeCode offline app (Windows / Mac) | ✔ | ✔ | Same; adding the extension the first time needs internet |
| Python Editor (MicroPython) | — | — | no MBC module yet |
- Sound: V2 has a built-in speaker; a V1 needs a buzzer or headphones on P0.
- Servos / motors: the examples use
pins.servoWritePin: one 9 g servo (SG90) on P1; for driving, two continuous-rotation servos (e.g. FS90R), left wheel P1, right wheel P2. Always power servos from an external 4.8–6 V battery pack whose − is joined to the micro:bit GND (common ground); never drive servos from the micro:bit 3V pin. - Block names: with the editor language set to English the blocks read as in the tables below; the tables also give the JavaScript call.
Before you start: hardware checklist (to be tested)
The micro:bit is a 3.3 V board. Each check must pass before an MBC product is connected; "to be tested" marks items extension v2 has not yet confirmed on real hardware.
| Check | This product | Status |
|---|---|---|
| Voltage | MBC products use 3.3 V I2C levels: connect straight to P19 / P20, no level shifter | ✔ |
| Power | about 20 mA, so the micro:bit 3V pin (limit about 90 mA on V1, 190 mA on V2) may do | to be tested |
| I2C pull-ups | the micro:bit already has 4.7 kΩ; if the product also has pull-ups, check the low level stays ≤ 0.3×VDD | to be tested |
| Clock stretching | the STM32 slave stretches the clock by default; the V2 (nRF52833) has a known issue with stretching, test on V1 and V2 | to be tested |
| Address clash | factory address 0x6A, different from every other MBC product, so it can share the bus with the line follower, ranger and gamepad as it is | — |
| Ready time | ready (F3 b0) about 2 s after power-on; keep the board still and flat at power-on and when zeroing, or the heading drifts | to be tested |
If you read nothing: check the wiring (SDA = P20, SCL = P19) → the external power → whether the "device at … is ready" block (Device group, advanced) returns true.
Wiring (the same for every example)
| Sensor | micro:bit |
|---|---|
| SDA | P20 |
| SCL | P19 |
| VCC | 3V (or external 5 V) |
| GND | GND (the servo battery − goes here too) |
The values used on this page
Headings are in degrees, 0 at power-on or after zeroing, growing when turning right, shrinking when turning left, and they keep accumulating past a full turn (no wrap at 360). The "fused" heading = gyro + magnetometer, the steadiest over time.
| Block | JavaScript | Range | Meaning | Used in |
|---|---|---|---|---|
| IMU heading fused (°) | mbc.imuHeading(MbcImuHeading.Fused) |
−32768~32767 | current heading, 0 = direction when zeroed, right = positive | 1, 3, 4, 5 |
| IMU zero heading | mbc.imuZeroHeading() |
命令 E0 | make the current direction 0 | 1, 2, 3, 4, 5 |
| IMU pitch (°) | mbc.imuPitch() |
−90~+90 | nose up / down in degrees | 2 |
| on IMU turned by … ° | mbc.onImuTurned(90, ...) |
事件 | fires each time the fused heading has changed by that many degrees | 2 |
| IMU is still | mbc.imuFlag(MbcImuFlag.Still) |
true / false | status bit 1: the board is not moving | 2 |
| wait for device at … ready | mbc.waitReady(mbc.ADDR_IMU, 3000) |
true / false | waits up to 3 s, true once the IMU is initialised (Advanced → Device) | 1 |
Example 1: Electronic compass
What this example does: Turn the micro:bit and the IMU together and the LED arrow turns the other way, always pointing where "front" was at power-on. Press A to show the heading, B to make the current direction 0.
What you need
- micro:bit V1 or V2 + USB cable
- IMU Gyro (Universal I2C edition) + 4-pin cable
- both boards fixed to one piece of cardboard
Wiring: As in the wiring table above: SDA → P20, SCL → P19, VCC → 3V, GND → GND.
Download: ex01_compass.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.
Code
// IMU × micro:bit example 1: electronic compass (the arrow keeps pointing where 'front' was at power-on)
// What it does: turn the micro:bit and the IMU together and the LED arrow turns the other way, always pointing at the original front. Press A to show the heading, B to make the current direction 0.
// Wiring: IMU SDA → P20, SCL → P19, VCC → 3V (or external 5 V), GND → GND.
// Setup: MakeCode new project → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript, paste this whole file → download to the micro:bit. Keep the IMU flat and still for 2 s after power-on.
// You see: tick at power-on, arrow up; turn right 90° → arrow points left; 180° → arrow down; turn back → arrow up.
const ARROWS = [ArrowNames.North, ArrowNames.NorthEast, ArrowNames.East, ArrowNames.SouthEast,
ArrowNames.South, ArrowNames.SouthWest, ArrowNames.West, ArrowNames.NorthWest] // 8 directions clockwise
let busy = false
if (mbc.waitReady(mbc.ADDR_IMU, 3000)) {
basic.showIcon(IconNames.Yes)
} else {
basic.showIcon(IconNames.No) // no answer: check P19 / P20 and power
}
mbc.imuZeroHeading() // current direction = 0°
basic.forever(function () {
if (busy) {
basic.pause(100)
return
}
let h = mbc.imuHeading(MbcImuHeading.Fused) // degrees, grows turning right, shrinks turning left, may exceed 360
let a = ((h % 360) + 360) % 360 // fold to 0..359
let idx = Math.round(a / 45) % 8 // how many 45° steps turned
basic.showArrow(ARROWS[(8 - idx) % 8], 0) // the arrow points the opposite way, back to the original front
basic.pause(50)
})
input.onButtonPressed(Button.A, function () {
busy = true
basic.showNumber(mbc.imuHeading(MbcImuHeading.Fused))
busy = false
})
input.onButtonPressed(Button.B, function () {
mbc.imuZeroHeading()
})Step by step
ARROWS: 8 arrows clockwise starting from up.- After
mbc.waitReady(mbc.ADDR_IMU, 3000)ticks,mbc.imuZeroHeading(): the current direction = 0°. forever:h = mbc.imuHeading(Fused);a = ((h % 360) + 360) % 360folds it into 0..359 (negative values too).idx = Math.round(a / 45) % 8: how many 45° steps turned.ARROWS[(8 − idx) % 8]: the board turned right by idx steps, so the original front is idx steps to the left; the arrow takes the opposite direction.- A: show the heading; B: zero.
What you should see
- Power-on (board still): tick, arrow up.
- Turn right 90°: arrow points left; A shows about 90.
- 180°: arrow down. Back: up.
- Press B: whichever way you face, the arrow becomes up.
FAQ
- The number creeps although it is not moving? → Gyro drift. Keep the board still at power-on; leave it 10 s to self-calibrate, or press B to zero.
- The arrow turns with the board instead of against it? → Change
(8 − idx) % 8toidxfor the other kind of compass. - An X right at power-on (sensor not found)? → Check SDA = P20 and SCL = P19 are not swapped, that the external power is on, and with crocodile clips that no clip touches the neighbouring pad.
- All zeros in the simulator? → Normal. The MakeCode simulator has no real I2C, every read is 0; download to the micro:bit to see real values.
- No sound on a V1? → The V1 has no speaker; connect a buzzer or headphones between P0 and GND.
Example 2: Turn counter + tilt alarm
What this example does: Every 90° turned → a beep and the count goes up (event block); tilted more than 20° nose up / down → sad face + low alarm; still → asleep face, moving → happy. Press B to reset.
What you need
- Everything from example 1
- On a V1 also a buzzer or headphones on P0
Wiring: Same as example 1.
Download: ex02_turn_alarm.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.
Code
// IMU × micro:bit example 2: turn counter + tilt alarm
// What it does: every 90° turned → a beep and the count goes up by 1; tilted more than 20° nose up / down → sad face + low alarm; still → asleep face, moving → happy. Press B to reset.
// Wiring: IMU SDA → P20, SCL → P19, VCC → 3V, GND → GND. On a V1 connect a buzzer or headphones to P0 for sound (V2 has a speaker).
// Setup: extension and paste steps as in example 1.
// You see: turn it slowly through a full circle on the table → 4 beeps and 1 2 3 4; lift the front edge → sad face + alarm; leave it still → asleep face.
let turns = 0
mbc.imuZeroHeading()
mbc.onImuTurned(90, function () { // fires each time the fused heading has changed by another 90°
turns += 1
music.playTone(Note.C5, 150)
basic.showNumber(turns)
})
input.onButtonPressed(Button.B, function () {
turns = 0
mbc.imuZeroHeading()
basic.showNumber(0)
})
basic.forever(function () {
let p = mbc.imuPitch() // pitch in degrees: nose up or down
if (Math.abs(p) > 20) {
basic.showIcon(IconNames.Sad, 0)
music.playTone(Note.C, 200) // low alarm
} else if (mbc.imuFlag(MbcImuFlag.Still)) { // status bit 1: still
basic.showIcon(IconNames.Asleep, 0)
} else {
basic.showIcon(IconNames.Happy, 0)
}
basic.pause(100)
})Step by step
mbc.imuZeroHeading()zeroes first.mbc.onImuTurned(90, ...): runs once each time the fused heading has changed by another 90° →turns + 1,Note.C5, show the count.forever:p = mbc.imuPitch().Math.abs(p) > 20→ sad face + lowNote.C.- Otherwise
mbc.imuFlag(MbcImuFlag.Still)true → asleep face; false → happy. - B:
turns = 0, zero, show 0.
What you should see
- Turn it slowly through a full circle on the table: 4 beeps, 1 2 3 4.
- Lift the front edge more than 20°: sad face + alarm; flat again: stops.
- Leave it still for 1 s: asleep face; wave it: happy.
FAQ
- 5 beeps for one circle? → Overshoot when turning fast, or turning back counts too (both directions accumulate). It is a "change" event, direction does not matter.
- The tilt alarm never stops? → The IMU is not flat, or the mounting axis is not captured yet (status bit 2). Power on flat and try again.
Example 3: Stabilised turret (the servo keeps pointing the same way)
What this example does: A servo next to the IMU with an arrow on its arm: rotate the base and the servo counter-rotates so the arrow keeps pointing the original way (within ±90°). Press A to reset the reference direction.
What you need
- Everything from example 1
- one 9 g servo (SG90) with a paper arrow
- a 4.8–6 V battery pack and jumper wires
- a board that holds the micro:bit, the IMU and the servo together
Wiring: IMU as in example 1. Servo orange (signal) → P1, red → external 4.8–6 V battery +, brown → battery −, and run a second wire from battery − to micro:bit GND (common ground).
Download: ex03_stabilize_servo.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.
Code
// IMU × micro:bit example 3: stabilised turret (the servo keeps pointing the same way)
// What it does: a servo mounted next to the IMU with an arrow on top: rotate the base and the servo counter-rotates so the arrow keeps pointing the original way (within ±90°). Press A to reset the reference direction.
// Wiring: IMU SDA → P20, SCL → P19, VCC → 3V, GND → GND. Servo orange (signal) → P1, red → external 4.8–6 V battery +, brown → battery − joined to micro:bit GND.
// Setup: a 9 g servo (SG90) and a board that holds the micro:bit, the IMU and the servo together.
// You see: turn the base right by 45° → the servo turns left 45°, the arrow still points the same way; beyond 90° the servo hits its end stop.
mbc.imuZeroHeading()
basic.showIcon(IconNames.Yes)
basic.forever(function () {
let h = mbc.imuHeading(MbcImuHeading.Fused) // positive when turned right
let angle = Math.constrain(90 - h, 0, 180) // base turns right → servo turns left
pins.servoWritePin(AnalogPin.P1, angle)
basic.pause(20)
})
input.onButtonPressed(Button.A, function () {
mbc.imuZeroHeading() // the current direction becomes the new 0°
basic.showIcon(IconNames.Yes, 0)
})Step by step
mbc.imuZeroHeading(): current direction = 0.forever:h = fused heading(right = positive).angle = Math.constrain(90 − h, 0, 180): the base turned right by h, so the servo goes to 90 − h (h to the left) and the arrow still points the original way.pins.servoWritePin(AnalogPin.P1, angle)every 20 ms.- A: zero again; the current direction becomes the new reference.
What you should see
- Turn the base right 45°: the servo goes left 45°, the arrow stays put.
- Beyond 90°: the servo hits its end stop (0° or 180°).
- Press A: servo back to 90°, the current direction becomes the reference.
FAQ
- The servo turns with the base? → The servo is mounted the other way: change
90 − hto90 + h. - It slowly drifts? → Gyro drift; press A to zero. Leaving it still for 10 s lets it self-calibrate.
Example 4: Drive straight (gyro correction)
What this example does: Press A and the car drives forward; whenever it drifts, the heading steers it back, so it runs straight. Press B to stop. The LEDs show which way it corrects. The basic skill of every gyro robot.
What you need
- A two-wheel car: two continuous-rotation servos (e.g. FS90R) as wheels plus a caster
- the IMU flat on the car, printed arrow forward
- a 4.8–6 V battery pack
Wiring: IMU as in example 1, flat on the car with the printed arrow toward the front. Left continuous-rotation servo signal → P1, right → P2; both servo red wires to the 4.8–6 V battery pack +, brown to battery −, and battery − also to micro:bit GND.
Download: ex04_drive_straight.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.
Code
// IMU × micro:bit example 4: drive straight (gyro correction)
// What it does: press A and the car drives forward; whenever it drifts, the IMU heading steers it back, so it runs straight. Press B to stop. The LEDs show which way it is correcting.
// Wiring: IMU SDA → P20, SCL → P19, VCC → 3V, GND → GND. Left continuous-rotation servo signal → P1, right → P2; servos on a 4.8–6 V battery pack whose − is joined to micro:bit GND.
// Setup: a two-wheel car with the IMU flat on it (printed arrow facing forward).
// You see: after A the car drives straight; nudge the front sideways and it turns back to the original heading and carries on.
const KP = 2 // 1° off → 2% speed difference
let running = false
// left servo P1, right servo P2 (continuous rotation); left / right = −100..100, positive = forward
function drive(left: number, right: number): void {
left = Math.constrain(left, -100, 100)
right = Math.constrain(right, -100, 100)
pins.servoWritePin(AnalogPin.P1, Math.round(90 + left * 0.9)) // 90 = stop, 180 = full forward, 0 = full reverse
pins.servoWritePin(AnalogPin.P2, Math.round(90 - right * 0.9)) // the right servo is mounted mirrored, so subtract
}
input.onButtonPressed(Button.A, function () {
mbc.imuZeroHeading() // the starting direction = 0°
running = true
basic.showArrow(ArrowNames.North, 0)
})
input.onButtonPressed(Button.B, function () {
running = false
drive(0, 0)
basic.showIcon(IconNames.No, 0)
})
basic.forever(function () {
if (!running) {
basic.pause(100)
return
}
let h = mbc.imuHeading(MbcImuHeading.Fused) // positive = the nose has drifted right
let turn = Math.constrain(h * KP, -40, 40)
drive(50 - turn, 50 + turn) // drifted right → left slower, right faster → steer back left
if (turn > 5) {
basic.showArrow(ArrowNames.NorthWest, 0)
} else if (turn < -5) {
basic.showArrow(ArrowNames.NorthEast, 0)
} else {
basic.showArrow(ArrowNames.North, 0)
}
basic.pause(20)
})Step by step
function drive(left, right): left wheel P1, right wheel P2, −100..100, positive = forward; the right servo is mirrored so it subtracts.- A:
mbc.imuZeroHeading()makes the start direction 0,running = true; B: stop. forever:h = fused heading, positive = the nose drifted right.turn = Math.constrain(h × KP, −40, 40),KP = 2: 1° off → 2% difference.drive(50 − turn, 50 + turn): drifted right → left slower, right faster → steer back left. The arrow shows the correction.
What you should see
- Press A: the car runs straight, much straighter than without the gyro.
- Nudge the nose 30° sideways: the arrow tilts and the car turns back to its original heading.
- Press B: stop.
FAQ
- It wags left and right? → Change
KPfrom 2 to 1. - It corrects the wrong way (drifts more)? → The IMU is mounted backwards (arrow to the rear) or the servo signs are flipped. Run example 1 first and confirm the number grows when turning right.
Example 5: Drive a square (all together)
What this example does: Press A: drive straight for 1.5 s (gyro keeps it straight) → spin right exactly 90° (by heading, not by time) → repeat 4 times back to the start; the LEDs show which side. Press B to abort.
What you need
- The car from example 4
- about 1 m square of clear floor
Wiring: Same as example 4.
Download: ex05_square_path.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.
Code
// IMU × micro:bit example 5: drive a square (all together)
// What it does: press A: drive straight for 1.5 s (gyro keeps it straight) → spin right exactly 90° → repeat 4 times back to the start; the LEDs show which side it is on. Press B to abort.
// Wiring: as example 4 (IMU P19 / P20; left wheel P1, right wheel P2, servos on their own battery, common ground).
// Setup: the two-wheel car and about 1 m square of clear floor.
// You see: after A the car drives a square showing 1 2 3 4, then stops with a tick. Under- or overshooting corners → lower TURN_SPEED.
const SIDE_MS = 1500 // how long each side takes
const TURN_SPEED = 35
let running = false
// left servo P1, right servo P2 (continuous rotation); left / right = −100..100, positive = forward
function drive(left: number, right: number): void {
left = Math.constrain(left, -100, 100)
right = Math.constrain(right, -100, 100)
pins.servoWritePin(AnalogPin.P1, Math.round(90 + left * 0.9)) // 90 = stop, 180 = full forward, 0 = full reverse
pins.servoWritePin(AnalogPin.P2, Math.round(90 - right * 0.9)) // the right servo is mounted mirrored, so subtract
}
// drive straight for ms milliseconds, holding the heading target with the gyro
function straight(target: number, ms: number): void {
let t0 = control.millis()
while (running && control.millis() - t0 < ms) {
let err = mbc.imuHeading(MbcImuHeading.Fused) - target
let turn = Math.constrain(err * 2, -40, 40)
drive(50 - turn, 50 + turn)
basic.pause(20)
}
}
// spin on the spot until the heading is within 3° of target
function turnTo(target: number): void {
while (running) {
let err = target - mbc.imuHeading(MbcImuHeading.Fused) // positive = still need to turn right
if (Math.abs(err) < 3) {
break
}
let s = err > 0 ? TURN_SPEED : -TURN_SPEED
drive(s, -s) // left forward, right back = turn right
basic.pause(20)
}
drive(0, 0)
basic.pause(300) // settle
}
input.onButtonPressed(Button.A, function () {
if (running) {
return
}
running = true
mbc.imuZeroHeading()
let target = 0
for (let side = 1; side <= 4; side++) {
basic.showNumber(side)
straight(target, SIDE_MS)
target += 90 // heading of the next side
turnTo(target)
}
drive(0, 0)
if (running) {
basic.showIcon(IconNames.Yes)
}
running = false
})
input.onButtonPressed(Button.B, function () {
running = false
drive(0, 0)
basic.showIcon(IconNames.No)
})Step by step
straight(target, ms): drive for ms milliseconds, correcting with "heading − target" every 20 ms, as in example 4.turnTo(target):err = target − heading; stop when|err| < 3; otherwisedrive(s, −s)spins on the spot (positive err → right), then 0.3 s to settle.- A: zero,
target = 0;for side = 1..4: show the side →straight(target, 1500)→target += 90→turnTo(target). - After 4 sides
drive(0, 0)and a tick. - B:
running = false; thewhile (running)loops instraightandturnToexit at once.
What you should see
- Press A: shows 1, straight, right turn; 2, straight, right turn … after 4 sides a tick, back near the start.
- Every corner is 90° and errors do not add up (it watches the heading).
FAQ
- It overshoots and jitters at corners? → Change
TURN_SPEED35 to 25, or< 3to< 5. - Sides are not equal? → Sides are timed, so a sagging battery shortens them; use a line follower or encoders for accuracy.
- A triangle instead? →
for3 times andtarget += 120.
Next steps
- Every block, value and hardware note: IMU Gyro × micro:bit
- What each value id means: Reference
- Extension source and issues: https://github.com/contactmbcrobot/pxt-mbc-sensors
More questions
- Cannot find the MBC blocks? In MakeCode Extensions, paste https://github.com/contactmbcrobot/pxt-mbc-sensors.
Still stuck? Contact us with your software and a screenshot of the program.
Downloads
- MakeCode extension pxt-mbc-sensors (paste this URL in MakeCode Extensions)
- Download centre (libraries, firmware, catalogue for every product)
Sources
- Host and software list:
_build/guides.json - Values and commands:
_build/protocol/imu.json(single source of truth) - Setup:
_build/guides/software/microbit-makecode.en.md、_build/guides/product/imu/microbit-makecode.en.md - Examples:
sensors/imu/microbit-examples.md