Microbit Demos
Contents
- Intro
- Microbit
- Microbit-only Projects
- Simple Digital Input
- Simple Output
- Sound
- Motors
- Analog Sensors
- Sensors with Extensions
- Ultrasonic Distance Sensor
- RFID Tap to Login
- Temperature And Humidity Sensor
- Rotary Encoder
- Rotary Encoder Painter
- Rotary Encoder with RGB Color Picker
- Air Pressure Sensor
- Magnetic Angle Sensor
- IR Remote Reader
- IR Remote Reader for TCL TV remote
- IR Remote Transmitter for TCL TV
- LCD Display
- LCD Display Graphics
- OLED Display
- OLED Display with Big Numbers
- OLED Display ‘Flappy Bird’ game
- Bluetooth & Radio
- Advanced programs
Intro
The Tangible Interfaces Lab introduces interaction design using the BBC Microbit device and common sensors (see hardware). These code samples are meant to demonstrate functionality to help you get started exploring hardware interaction.
We code in JavaScript as it is a very common programming language. The Blocks style of coding has, in my experience, been more confusing than helpful. JavaScript programs are text, so it is easy to add // comments to explain the code. AI tools are very familiar with JavaScript, and can help to fix bugs or suggest features.
The Microbit is easy to program, no software needs to be installed on your computer, just a chrome web browser. Connect the board to your computer with a USB cable and go to https://makecode.microbit.org for tutorials. You will be able to connect the board and program it from the webpage. It is possible to program the Microbit from the microbit phone or tablet app, but it is harder.
Microbit
The Microbit is a credit card sized computer, with a surprising number of sensors and features built in. It can be programmed just with a web browser, and has a wide set of code extensions written to connect to various hardware. It is IMHO the fastest way to learn hardware making. Other computers such as Raspberry Pi, Arduino, or ESP32 (and many others) are more powerful, but require complex and fragile programming software which are often frustrating to students. Most of the concepts and components on the Microbit can be reused as you progress.
- Basic Microbit kit (Around $25 USD)
Introduction to Microbit
- https://makecode.microbit.org/#editor this is how you program the Microbit
- Microbit Getting Started
- Microbit Edge Connector pins
- Lessons
- More advanced sensor advice
More learning
- Youtube
- Reddit.com/r/microbit/
- Don’t forget our AI friends, they are excellent at translating technical language.
Microbit Component Kits
To design and prototype Tangible Interfaces, you’ll want a bunch of sensors to play with. A fun way to start are sensor packs. There are many variations, you will have to look at the parts you want. Here are a couple to get you started. Note that basic components are often labeled as “For Arduino”, but work with most micro-controllers, such as the Microbit.
- Example Sensor Pack
-
KeyeStudio 4010 BBC Microbit with 45 in 1 Sensor Kit This is a handy kit with sensors that are known to work together. Docs for kit
Microbit-only Projects
These programs work with the microbit alone, no wiring or components needed.
Microbit Button
The simplest Microbit program: The LEDs show a number. Button B increases the number, Button A reduces the number.
// The simplest Microbit program: shows a number on the LEDs.
// Button B increases the value, Button A decreases the value.
// No external wiring needed — just the micro:bit itself.
// --- Setup ---
let value = 0;
basic.showNumber(value);
// --- Button Handlers ---
input.onButtonPressed(Button.B, function () {
value += 1;
basic.showNumber(value);
});
input.onButtonPressed(Button.A, function () {
value -= 1;
basic.showNumber(value);
});Compass
Reads the micro:bit’s built-in compass and displays an arrow on the LED screen pointing in the compass direction.
serial.writeValue("x", angle); sends the compass angle to the Microbit editor web page. Click “Show Data Device” on the left side of the editor to see this very handy way to get data out of the microbit.
// Reads the micro:bit's built-in compass
// Displays an arrow on the LED screen
// pointing in the compass direction
// (N, NE, E, SE, S, SW, W, NW).
// Microbit screen should face up.
// You may need to tilt the Microbit around to calibrate the compass
// --- Setup ---
let angle = 0;
basic.showIcon(IconNames.Triangle);
input.calibrateCompass();
// --- Main Loop ---
basic.forever(function () {
angle = input.compassHeading();
if ((angle >= 0 && angle < 45) || angle >= 360) {
basic.showArrow(ArrowNames.North);
} else if (angle >= 45 && angle < 90) {
basic.showArrow(ArrowNames.NorthWest);
} else if (angle >= 90 && angle < 135) {
basic.showArrow(ArrowNames.West);
} else if (angle >= 135 && angle < 180) {
basic.showArrow(ArrowNames.SouthWest);
} else if (angle >= 180 && angle < 225) {
basic.showArrow(ArrowNames.South);
} else if (angle >= 225 && angle < 270) {
basic.showArrow(ArrowNames.SouthEast);
} else if (angle >= 270 && angle < 315) {
basic.showArrow(ArrowNames.East);
} else if (angle >= 315 && angle < 360) {
basic.showArrow(ArrowNames.NorthEast);
} else {
}
serial.writeValue("angle", angle);
basic.pause(100);
});Shake
Very basic program to demonstrate one of the Microbit’s built in gestures.
// Shake the micro:bit to show a random number between 1 and 6 on the LED screen.
basic.pause(1000); // --- Setup ---
serial.redirectToUSB();
basic.showIcon(IconNames.Chessboard);
music.play(music.tonePlayable(262, music.beat(BeatFraction.Sixteenth)), music.PlaybackMode.UntilDone);
input.onGesture(Gesture.Shake, function () {
// Runs when the micro:bit is shaken
basic.showNumber(randint(1, 6));
basic.pause(4000);
basic.clearScreen();
});Accelerometer Tilt Game
Tilt-to-navigate game: tilt the micro:bit to move a bright dot toward a dimmer target dot on the 5x5 LED grid. Press button A when you reach it to check.
// Tilt-to-navigate game:
// tilt the micro:bit to move a bright dot
// toward a dimmer dot on the 5x5 LED grid.
// Press button B when you reach it to win.
// --- Setup ---
// the LED screen has 5 pixels by 5 pixels
// these are numbered as 0 through 4
// the player starts in the top left LED
let player_x = 0;
let player_y = 0;
// the goal is in the center LED
let goal_x = 2;
let goal_y = 2;
serial.redirectToUSB();
music.play(music.tonePlayable(262, music.beat(BeatFraction.Sixteenth)), music.PlaybackMode.UntilDone);
// --- Main Loop ---
basic.forever(function () {
basic.clearScreen();
if (input.acceleration(Dimension.X) > 500) {
player_x = Math.min(4, player_x + 1);
} else if (input.acceleration(Dimension.X) < -500) {
player_x = Math.max(0, player_x - 1);
}
if (input.acceleration(Dimension.Y) > 500) {
player_y = Math.min(4, player_y + 1);
} else if (input.acceleration(Dimension.Y) < -500) {
player_y = Math.max(0, player_y - 1);
}
led.plotBrightness(player_x, player_y, 255);
led.plotBrightness(goal_x, goal_y, 119);
serial.writeLine("X" + input.acceleration(Dimension.X) + " " + "Y" + input.acceleration(Dimension.Y) + " " + "Z" + input.acceleration(Dimension.Z));
basic.pause(100);
});
// --- Event Handlers ---
// Runs when button B is pressed to check if player reached the goal
input.onButtonPressed(Button.B, function () {
if (player_x == goal_x && player_y == goal_y) {
serial.writeString("WON");
music.play(music.stringPlayable("C5 B A G F E D C ", 120), music.PlaybackMode.UntilDone);
basic.clearScreen();
basic.pause(1000);
goal_x = randint(0, 4);
goal_y = randint(0, 4);
} else {
music.play(music.tonePlayable(131, music.beat(BeatFraction.Sixteenth)), music.PlaybackMode.UntilDone);
basic.clearScreen();
basic.pause(1000);
}
});Simple Digital Input
Digital input is a yes/no or on/off measurement.
Simple External Button Component
Reads an external push button component — the simplest possible digital sensor, just on or off. Use this pattern any time you want a physical button separate from the micro:bit’s built-in A and B buttons.
// Reads an external push button and shows its state on the LED screen.
// This is the simplest possible digital sensor — just on or off.
// Use this pattern any time you want an physical button
//
// Physical setup:
// Button module has 3 pins labeled S (signal), V (power), G (ground).
// Connect S → micro:bit pin 0
// Connect V → micro:bit 3V
// Connect G → micro:bit GND
//
// Digital read: 0 = button pressed, 1 = button released.
// pin 0 is "pulled up" -a little electricity keeps the pin at 1
// Pressing the switch connects the pin to ground, "pulling" it to 0
// --- Setup ---
basic.showIcon(IconNames.SmallDiamond);
pins.setPull(DigitalPin.P0, PinPullMode.PullUp);
// --- Main Loop ---
basic.forever(function () {
if (pins.digitalReadPin(DigitalPin.P0) == 0) {
//Button is pressed"
basic.showIcon(IconNames.Yes);
} else {
//Button is not pressed
basic.clearScreen();
}
basic.pause(100); // wait 1/10th of a second
});Knock Sensor
This code is also a simple digital switch, but demonstrates two new concepts. We are using an “event” code block that runs when a pin changes, and debouncing to avoid double counting knocks. You could use this to make a pedometer for the school of hard knocks.
// Reads a knock sensor and counts knocks on the LED screen.
// This is the simplest digital sensor — just on or off.
//
// This time we are using an event code block.
// The microbit is silently always checking if the pins are changing.
// If we write a special block of code called an evert, the microbit runs it when the change happens.
// This interrupts the basic.forever() loop, so we try to keep them short or we get confused.
//
// The knock sensor has 3 pins labeled S (signal), V (power), G (ground).
// Connect S → micro:bit pin 5
// Connect V → micro:bit 3V
// Connect G → micro:bit GND
//
// The sensor has a built-in pull-up resistor, so its signal is normally HIGH.
// A knock produces a brief LOW pulse pins.onPulsed catches these brief signals.
// this is VERY fast. So fast the springs will bounce back and forth a few times at 10x per second.
// this could look like 10 knocks, which is bad.
// so we
// Pin 5 is chosen for convenience for testing. Pressing Button A is also sets pins 5 to low
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
basic.pause(1000);
basic.clearScreen();
pins.setPull(DigitalPin.P5, PinPullMode.PullNone); // Sensor has its own pull-up; PullNone avoids any conflict
let count = 0; // we are counting the knocks
let TimeSinceLastConnectToGround = 0; // we will use this to de-bounce the knock sensor
let knockRegistered = false; // flag to tell the forever loop to update the display
pins.onPulsed(DigitalPin.P5, PulseValue.Low, function () {
// this runs when the pin 5 gets connected to ground, that is, the wire in the spring knock sensor bends and touches the other wire.
let TimeSinceMicrobitStart = input.runningTime(); // now is the number of milliseconds since the microbit started
let TimeSinceLastCheck = TimeSinceMicrobitStart - TimeSinceLastConnectToGround;
TimeSinceLastConnectToGround = TimeSinceMicrobitStart; // always update — extends the quiet window on each bounce
if (TimeSinceLastCheck > 150) knockRegistered = true;
});
basic.forever(function () {
// this loop just updates the screen
// you might notice that, in theory, we could just update the screen in the event loop
// but basic.showNumber takes time, which interferes with the sensing
// AI is your buddy on this kind of subtle problem.
if (knockRegistered) {
knockRegistered = false;
count += 1;
if (count > 9) count = 0;
basic.showNumber(count); // display runs here, outside the event handler
} else basic.pause(50); // wait a bit
});Motion Sensor
Detects human movement using a PIR (passive infrared) sensor — it senses body heat moving in front of it. When motion is detected, the LED screen shows an eye icon and plays a tone. Wiring: signal → P0, VCC → 3V, GND → GND.

// Detects human movement using a PIR (passive infrared) motion sensor.
// When motion is detected, the LED screen shows an eye icon and plays a sound.
//
// How it works: the sensor detects body heat moving in front of it.
// Digital read: 1 = motion detected, 0 = no motion.
// The sensor has a ~2 second warm-up time when first powered on.
//
// Physical setup:
// Sensor has 3 pins labeled S (signal), V (power), G (ground).
// Connect S → micro:bit pin 0
// Connect V → micro:bit 3V
// Connect G → micro:bit GND
// --- Setup ---
basic.pause(2000);
music.play(music.tonePlayable(988, music.beat(BeatFraction.Eighth)), music.PlaybackMode.InBackground);
pins.setPull(DigitalPin.P0, PinPullMode.PullDown);
basic.showIcon(IconNames.Asleep);
// --- Main Loop ---
basic.forever(function () {
if (pins.digitalReadPin(DigitalPin.P0) == 1) {
basic.showIcon(IconNames.Happy);
serial.writeLine("Motion detected!");
} else {
basic.showIcon(IconNames.Asleep);
serial.writeLine("No motion");
}
basic.pause(100);
});Capacitive Touch via Microbit Pin
Detects touch using a single wire or small piece of metal. Note this is less reliable than an engineered component.
// Skin Capacitive switch — human skin activates. No sensor module needed.
//
// Physical setup:
// Wire 1: micro:bit P0 → first conductive surface (what they touch)
// No 3V or ground connection needed.
//
// Note! Very finicky, may activate unexpectedly, especially with a long wire.
// --- Setup ---
basic.pause(1000);
music.play(music.tonePlayable(988, music.beat(BeatFraction.Eighth)), music.PlaybackMode.InBackground);
basic.showIcon(IconNames.Heart);
pins.touchSetMode(TouchTarget.P0, TouchTargetMode.Capacitive);
// --- Main Loop ---
basic.forever(function () {
if (input.pinIsPressed(TouchPin.P0)) {
basic.showIcon(IconNames.Yes);
serial.writeLine("Touched!");
} else {
basic.showIcon(IconNames.SmallDiamond);
}
basic.pause(100);
});Capacitive Touch Sensor
Detects touch using a single external capacitive sensor component in KeyeStudio kit— no mechanical button needed. Any conductive surface (metal, fruit, foil, water) can become a touch input.
// Detects touch using a capacitive touch sensor — no mechanical button needed.
// Touching the sensor pad lights up the LED screen
//
// Digital read: 1 = touched, 0 = not touched.
//
// Physical setup:
// Sensor has 3 pins labeled S (signal), V (power), G (ground).
// Connect S → micro:bit pin 0
// Connect V → micro:bit 3V
// Connect G → micro:bit GND
// You can attach a wire to the sensor pad to extend the touch area.
// --- Setup ---
basic.pause(1000);
music.play(music.tonePlayable(988, music.beat(BeatFraction.Eighth)), music.PlaybackMode.InBackground);
pins.setPull(DigitalPin.P0, PinPullMode.PullDown);
basic.showIcon(IconNames.Heart);
// --- Main Loop ---
basic.forever(function () {
if (pins.digitalReadPin(DigitalPin.P0) == 1) {
basic.showIcon(IconNames.Yes);
serial.writeLine("Touched!");
} else {
basic.showIcon(IconNames.SmallDiamond);
}
basic.pause(100);
});Capacitive Touch Sensor MPR121
MPR121 chip is a more dependable chip that handles multiple touches and sensing materials.
To use the Microbit with a MPR121 Capacitive Touch chip, add the extension via Extensions > search for github.com/1010Technologies/pxt-makerbit-touch.
//
// Detects touch using a MPR121 capacitive touch sensor — no mechanical button needed.
//
// To use the Microbit with a MPR121 Capacitive Touch chip,
// add the Extensions > search for `github.com/1010Technologies/pxt-makerbit-touch`.
//
// Physical setup:
// MPR121 SDA -> P20
// MPR121 SCL -> P19
// MPR121 VCC -> 3.3V
// MPR121 GND -> GND
// MPR121 has pins to the wire or foil to test with.
// You can attach a wire to the sensor pad to extend the touch area.
/*
Note that the makerbit extension's touch pins did not match my MPR121 breakout board pins
It might be that the makerbit board is wired differently than the MPR121 breakout board
T16 is MPR121 pin 0
T15 is MPR121 pin 1
T14 is MPR121 pin 2
T13 is MPR121 pin 3
T12 is MPR121 pin 4
T11 is MPR121 pin 5
T10 is MPR121 pin 6
T9 is MPR121 pin 7
T8 is MPR121 pin 8
T7 is MPR121 pin 9
T6 is MPR121 pin 10
T5 is MPR121 pin 11
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
makerbit.onTouch(TouchSensor.T5, TouchAction.Touched, function () {
basic.showIcon(IconNames.Yes);
});
makerbit.onTouch(TouchSensor.T5, TouchAction.Released, function () {
basic.clearScreen();
});
basic.forever(function () {
if (makerbit.wasTouched()) {
serial.writeLine("" + makerbit.touchSensor() + " was Touched");
}
basic.pause(100);
});Skin Conduction Switch
Human skin completes the circuit between two wires — no mechanical button or sensor module needed. A tiny amount of current flows through the body when a person bridges two conductors. Works with any two conductive surfaces: metal plates, door handle + floor mat, two strips of foil, fruit + foil.
// Skin conduction switch — human skin completes the circuit between two wires.
// No sensor module needed. Works with any two conductive surfaces a person bridges
// with their body: two metal plates, a handle + frame, fruit + foil, etc.
//
// How it works:
// Capacitive mode with a GND reference wire held by the person.
// The GND wire stabilizes the person's electric potential, making capacitive
// detection reliable. TouchMode.Resistive does not work for skin.
//
// Physical setup:
// Wire 1: micro:bit P0 → conductive surface (what they touch)
// Wire 2: micro:bit GND → person holds this wire (bare wire, clip, or foil pad)
// Touching the surface while holding GND triggers the sensor.
//
// Examples:
// Two metal plates on a desk — bridge with fingertips
// Door handle (P0) + floor mat (GND) — triggers when person grabs handle
// Two strips of foil — touch both to trigger
// --- Setup ---
basic.pause(1000);
music.play(music.tonePlayable(988, music.beat(BeatFraction.Eighth)), music.PlaybackMode.InBackground);
basic.showIcon(IconNames.Heart);
pins.touchSetMode(TouchTarget.P0, TouchTargetMode.Capacitive);
// --- Main Loop ---
basic.forever(function () {
if (input.pinIsPressed(TouchPin.P0)) {
basic.showIcon(IconNames.Yes);
serial.writeLine("Touched!");
} else {
basic.showIcon(IconNames.SmallDiamond);
}
basic.pause(100);
});Hall Magnetic Sensor
Detects a nearby magnet using a Hall effect sensor. Magnets can be hidden inside objects, behind walls, or under surfaces to create invisible triggers — this simple sensor is used everywhere in devices, cars, and homes.
// Detects a nearby magnet using a Hall effect magnetic sensor.
// Magnets can be hidden inside objects, behind walls, or under surfaces
// to create invisible triggers — no visible button or switch needed.
//
//
// Hold a magnet close to the sensor face to trigger it.
// Try taping a small magnet to a game piece, box lid, or sliding panel.
//
// Physical setup:
// Sensor has 3 pins labeled S (signal), V (power), G (ground).
// Connect S → micro:bit pin 0
// Connect V → micro:bit 3V
// Connect G → micro:bit GND
//
// This code is for a sensor that goes low (== 0) when a magnet is present.
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
pins.setPull(DigitalPin.P0, PinPullMode.PullUp); // we set the pin to "PullUp" (Be positive unless it is connected to ground)
// --- Main Loop ---
basic.forever(function () {
if (pins.digitalReadPin(DigitalPin.P0) == 0) {
basic.showIcon(IconNames.Yes); //Magnet detected!
} else {
basic.clearScreen();
}
basic.pause(100);
});Simple Output
Setting Digital (on/off) or Analog (e.g. brightness) output.
“Breathing” LED
A LED is either on or off, but if you switch it very, very fast, it looks brighter of dimmer. Here is a sample to make an LED look like it is breathing.
/*
* Make a LED breathe, Button A slows it down and Button B speeds it up
*/
input.onButtonPressed(Button.A, function () {
// Button A: slow down (longer pause between steps)
pauseDuration += 5;
// keep the pause in bounds
if (pauseDuration > 200) {
pauseDuration = 200;
}
serial.writeLine("pauseDuration:" + pauseDuration);
});
input.onButtonPressed(Button.B, function () {
// Button B: speed up (shorter pause between steps)
pauseDuration += 0 - 5;
// keep the pause in bounds
if (pauseDuration < 5) {
pauseDuration = 5;
}
serial.writeLine("pauseDuration:" + pauseDuration);
});
basic.pause(1000); // avoid flash on programming
basic.showIcon(IconNames.Chessboard);
basic.clearScreen();
serial.redirectToUSB();
// --- Setup ---
pins.analogWritePin(AnalogPin.P0, 0); // 0 to 1023
let brightness = 0;
let direction = 1; // +1 brightening, -1 dimming
let pauseDuration = 20; // ms between steps, lower is faster
basic.forever(function () {
// --- Main Loop ---
if (brightness >= 1023) {
direction = -1;
} else if (brightness <= 0) {
direction = 1;
}
brightness += direction * 25; // fixed step keeps the fade smooth at any speed
brightness = Math.max(0, Math.min(1023, brightness));
serial.writeLine("brightness:" + brightness);
pins.analogWritePin(AnalogPin.P0, brightness);
basic.pause(pauseDuration);
});RGB LED Color Wheel
A RGB (Red, Green, Blue) LED is simply three LEDs in one close together, the same trick used in every monitor and TV. You turn them on or off individually, or you can brighten or dim each in turn to make a color wheel animation.
/*
Cycles the colors on a RGB LED module
The RGB module has 4 pins:
1) Connect to Pin 0 (this controls Red LED)
2) Connect to Pin 1 (this controls Green LED)
3) Connect to Pin 2 (this controls Blue LED)
4) Voltage 3.3v seems to work
*/
// --- Setup ---
basic.pause(1000); // avoid flash on programming
basic.showIcon(IconNames.Chessboard);
basic.clearScreen();
serial.redirectToUSB();
let phaseNames = ["RedToGreen", "GreenToBlue", "BlueToRed"]; // for readable serial logging
let color_wheel_position = 0; // 0-359
basic.forever(function () {
let phase = Math.floor(color_wheel_position / 120); // which color phase: 0, 1, or 2
let phase_percent = ((color_wheel_position - 120 * phase) / 120) * 100; // 0-100% progress through this phase
let red = 0,
green = 0,
blue = 0; // the amount of each color 0-99%
if (phase == 0) {
// RedToGreen
green = phase_percent;
red = 100 - phase_percent;
} else if (phase == 1) {
// GreenToBlue
blue = phase_percent;
green = 100 - phase_percent;
} else {
// BlueToRed
red = phase_percent;
blue = 100 - phase_percent;
}
// Note: LED pins work inverted — lower analog value = brighter.
// pins.analogWritePin(AnalogPin.P0, 0) = fully on
// pins.analogWritePin(AnalogPin.P0, 1023) = fully off
pins.analogWritePin(AnalogPin.P0, 1023 - red * (1023 / 100)); // scale color range up to 0-1023 that analog out uses
pins.analogWritePin(AnalogPin.P1, 1023 - green * (1023 / 100)); // scale color range up to 0-1023 that analog out uses
pins.analogWritePin(AnalogPin.P2, 1023 - blue * (1023 / 100)); // scale color range up to 0-1023 that analog out uses
serial.writeLine("ColorPhase:" + phaseNames[phase] + " red:" + Math.round(red) + "% green:" + Math.round(green) + "% blue:" + Math.round(blue) + "%");
basic.pause(10);
color_wheel_position = (color_wheel_position + 1) % 360;
});Neopixel RGB strips
Neopixel is the popular name, coined by Adafruit, for a category of RGB LED strips where you can set each LED individually. Many manufacturers use the same protocol based on the WS2812B chip. They are most known as a flexible strip of LEDs, but they also come in useful shapes like rings or even grids of LEDs. Regardless of the shape, they are wired as a linear string of LEDs.
Many strips ship with a JST SM 3-pin clip (Red = +5V, White = GND, Green = Data) To drive the strip from the micro:bit, wire the clip to the micro:bit: Green to a digital pin, White to GND, and Red to a separate 5V supply — never the micro:bit’s own 3V pin, which can’t supply enough current for a full strip. Make sure to connect the grounds of the microbit and the Neopixel strip!
In the makecode window, add the extension microsoft/pxt-neopixel to make this code work.
/*
* Requires the "neopixel" extension (Extensions > search "microsoft/pxt-neopixel")
*
* Sized for a 144-LED WS2812B strip (144 pixels/m, ~1m / 3.2ft length).
*
* Wiring: the strip's input end has a 3-pin clip -- Red = +5V, White = GND,
* Green = Data. These strips often ship with a mini controller (battery or
* USB box with a button/remote for preset colors) that plugs into this same
* clip. To drive the strip from the micro:bit instead, unplug that mini
* controller and wire the clip directly: Green -> micro:bit P0, White ->
* micro:bit GND, Red -> a separate 5V supply.
*
* Power: at full white, 144 pixels can draw up to ~8.6A (60mA each) -- far
* more than the micro:bit or a USB port supplies. Never power the strip from
* the micro:bit's 3V pin; use a separate 5V supply rated for the LED count,
* and make sure its GND is tied to the micro:bit's GND.
* Data: the micro:bit's 3.3V logic is below the WS2812B's 5V spec. It often
* works for short runs, but a logic-level shifter (or a ~330-470 ohm resistor
* on the data line) improves reliability, especially over 1m+ of strip.
*/
basic.pause(1000); // avoid flash on programming
basic.showIcon(IconNames.Chessboard);
basic.clearScreen();
serial.redirectToUSB();
let strip = neopixel.create(DigitalPin.P0, 144, NeoPixelMode.RGB);
strip.setBrightness(40);
basic.forever(function () {
// rainbow animation
strip.rotate(1);
strip.show();
basic.pause(100);
});
input.onButtonPressed(Button.A, function () {
strip.showColor(neopixel.colors(NeoPixelColors.Red));
});
input.onButtonPressed(Button.B, function () {
strip.showColor(neopixel.colors(NeoPixelColors.Blue));
});
input.onGesture(Gesture.Shake, function () {
strip.showRainbow(1, 360);
strip.show();
});MAX7219 Dot Matrix Display
A 4-in-1 MAX7219 module chains four 8x8 LED matrices on one board into a single 32x8 pixel display, driven over SPI. It is a cheap way to show scrolling text, numbers, or simple pixel animations — a step up from the micro:bit’s own 5x5 grid.
In the makecode window, add the extension by searching “MAX7219”, or pasting https://github.com/alankrantas/pxt-MAX7219_8x8.
Note: 4-in-1 boards are often wired internally in a different order and orientation than four matrices chained by hand, so the display may come out rotated or in reverse panel order — the extension has a block to correct this, shown in the example.
/*
Scrolls a text message across a 4-in-1 MAX7219 dot matrix module --
four 8x8 LED matrices on one PCB, chained together into a single 32x8 display.
Button A shows a bouncing dot animation instead; Button B goes back to scrolling text.
MakeCode extension required:
In the MakeCode editor, click "Extensions" and search for "MAX7219", or paste
https://github.com/alankrantas/pxt-MAX7219_8x8
Physical setup:
The module has 5 pins. It uses SPI, so any digital pins work, but these are the extension's defaults:
Connect VCC -> micro:bit 3V (or 5V from an external supply -- 4 chained modules draw more than the micro:bit's 3V pin alone)
Connect GND -> micro:bit GND
Connect DIN -> micro:bit pin 15 (data in; some modules label this DOUT on the daisy-chain-out side -- use the DIN/CS/CLK side, not the DOUT side)
Connect CS -> micro:bit pin 16 (also labeled LOAD)
Connect CLK -> micro:bit pin 13
Note: 4-in-1 modules are often wired internally in a different order/orientation than
four separate matrices chained by hand. This one needed a 90° counter-clockwise
correction and reversed panel order below (for_4_in_1_modules) to display right-side up
and in the correct left-to-right order -- if yours still looks wrong, try the other
rotation_direction values, or set reversed back to false.
*/
basic.pause(1000); // prevent screen flash
const MATRIX_COUNT = 4; // number of 8x8 matrices chained together (32x8 pixels total)
max7219_matrix.setup(
MATRIX_COUNT,
DigitalPin.P16, // CS (LOAD)
DigitalPin.P15, // DIN (MOSI)
DigitalPin.P14, // MISO, unused but required by the setup block
DigitalPin.P13, // CLK (SCK)
);
max7219_matrix.for_4_in_1_modules(rotation_direction.counterclockwise, true); // corrects a module that displays rotated 90° clockwise with panels in reverse order
max7219_matrix.brightnessAll(8); // 0 (dim) to 15 (max)
let mode = "scroll"; // "scroll" | "bounce"
input.onButtonPressed(Button.A, function () {
// Button A: switch to the bouncing dot animation
mode = "bounce";
});
input.onButtonPressed(Button.B, function () {
// Button B: switch back to scrolling text
mode = "scroll";
});
let dotX = 0;
let dotY = 0;
let dotDirX = 1;
let dotDirY = 1;
basic.forever(function () {
// --- Main Loop ---
if (mode === "scroll") {
max7219_matrix.scrollText("Hello world!", 75, 500);
} else {
// Bounce a single dot across the full 32x8 area (8 columns per matrix x 4 matrices)
// Clear the whole chain first so the dot doesn't leave a ghost behind on the panel it just left.
max7219_matrix.clearAll();
let matrix = max7219_matrix.getEmptyMatrix();
max7219_matrix.setValueInMatrix(matrix, dotX % 8, dotY, 1);
max7219_matrix.displayLEDsForOne(matrix, Math.idiv(dotX, 8));
dotX += dotDirX;
dotY += dotDirY;
if (dotX <= 0 || dotX >= MATRIX_COUNT * 8 - 1) {
dotDirX = 0 - dotDirX;
}
if (dotY <= 0 || dotY >= 7) {
dotDirY = 0 - dotDirY;
}
basic.pause(60);
}
});Sound
Sound Input Simple
Simple demo of reading soundLevel input.soundLevel() reads the volume on the built in microphone and gives it a number from 0-255. This can be used as variable, as seen in the forever loop, or an event as seen input.onSound(). The microbit microphone cannot effectively record sound, it is more of a loudness detector.
/*
Simple demo of reading soundLevel
input.soundLevel() reads the volume on the built in microphone and gives it a number from 0-255
this can be used as variable, as seen in the forever loop, or an event as seen input.onSound()
The microbit microphone cannot effectively record sound, it is more of a loudness detector
*/
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
input.setSoundThreshold(SoundThreshold.Loud, 200); // this defines the sound level that is "Loud"
basic.forever(function () {
led.plotBarGraph(input.soundLevel(), 255);
basic.pause(100);
});
input.onSound(DetectedSound.Loud, function () {
// if a sound is "Loud", show an icon
basic.showIcon(IconNames.Yes);
});Sound Volume Meter
Reads the built-in microphone and displays sound level as a bar graph on the 5×5 LED grid. All five columns show the same bar, so the whole screen acts as one wide meter.
// tangible_interfaces_VU_meter https://makecode.microbit.org/S28568-79057-92892-69857
/*
* Microbit VU Meter
*
* Reads the built-in microphone and displays sound level as a bar graph
* on the 5×5 LED grid. All five columns show the same bar, so the whole
* screen acts as one wide meter.
*
* Two visual layers:
* bar — follows the live sound level in real time. Drawn at 50% brightness.
* peak — jumps up instantly when the bar exceeds it, but falls slowly on a timer.
* Drawn at 100% brightness so it stands out above the dimmer bar.
*
* The "peak hold" effect is the same technique used in recording-studio VU meters
* and most phone volume indicators. The slow-falling dot makes it easy to see
* the loudest recent moment even after the sound has dropped.
*
* Button A: decrease input gain (need louder sounds to fill the meter — good for loud rooms)
* Button B: increase input gain (quieter sounds fill the meter — good for quiet rooms)
* Press either button to see the current gain level (1–5) scroll across the screen.
*
* Hardware: BBC Microbit v2 (built-in microphone required — v1 does not have one)
*
* What to expect:
* Silence: all LEDs off
* Quiet: one or two rows lit from the bottom at half brightness
* Loud: tall bar, bright peak dot floating at the top
* Very loud: all five rows lit, top row at full brightness
*
* Tuning:
* Increase PEAK_DECAY_MS to make the peak dot fall more slowly (stickier).
* Decrease PEAK_DECAY_MS to make it fall faster (snappier).
*/
// --- Display tuning constants ---
const PEAK_DECAY_MS = 200; // time between each one-row drop of the peak dot (milliseconds)
const BAR_BRIGHTNESS = 128; // bar LED brightness: 128 out of 255 ≈ 50% — deliberately dim
const PEAK_BRIGHTNESS = 255; // peak LED brightness: 255 = 100% — full on, so it stands out
// --- Input gain (sensitivity) ---
// The mic returns 0–255. The gain ceiling sets what counts as "full scale" on the meter.
// Lower ceiling = more sensitive (quieter sounds fill the meter).
// Higher ceiling = less sensitive (only loud sounds fill the meter).
const GAIN_CEILINGS = [255, 200, 155, 100, 50]; // five gain levels; index 0 = quietest, index 4 = loudest
let gainLevel = 2; // default: index 2 = ceiling 155, the middle setting
// --- State variables ---
let barHeight = 0; // current bar height in rows from the bottom (0 = silent, 5 = loudest)
let peakHeight = 0; // highest bar height seen recently, on the same 0–5 scale
let lastDecayTime = 0; // timestamp (ms) of the last peak decay step — used to pace the falling dot
// --- Startup sequence ---
basic.pause(1000); // wait 1 s before starting — avoids the power-on click triggering the meter
basic.showIcon(IconNames.Chessboard); // checkerboard flash: visible proof the program loaded
basic.clearScreen(); // blank the screen before the meter loop begins
lastDecayTime = input.runningTime(); // record the start time so the decay timer has a reference point
// --- Button A: decrease gain (need louder sounds to fill the meter) ---
input.onButtonPressed(Button.A, function () {
if (gainLevel > 0) {
// stop at index 0 — already at the lowest gain setting
gainLevel -= 1; // step down to the next less-sensitive setting
}
basic.showNumber(gainLevel + 1); // show current level as 1–5 (more readable than 0–4)
});
// --- Button B: increase gain (quieter sounds fill the meter) ---
input.onButtonPressed(Button.B, function () {
if (gainLevel < 4) {
// stop at index 4 — already at the highest gain setting
gainLevel += 1; // step up to the next more-sensitive setting
}
basic.showNumber(gainLevel + 1); // show current level as 1–5
});
// --- Main loop: runs continuously, as fast as the Microbit can ---
basic.forever(function () {
// ── Step 1: Read the microphone ──────────────────────────────────────────
let soundLevel = input.soundLevel(); // built-in mic: 0 (silence) → 255 (very loud)
// Look up the ceiling for the current gain setting
let ceiling = GAIN_CEILINGS[gainLevel]; // e.g. gainLevel=2 → ceiling=155
// Map 0–ceiling into 0–5 rows, then clamp to 5 in case soundLevel exceeds ceiling
// Math.map rescales a number from one range to another
// Math.floor drops the decimal to give a whole number of rows
// Math.min prevents the bar from going above the top of the grid when gain is high
barHeight = Math.min(5, Math.floor(Math.map(soundLevel, 0, ceiling, 0, 5)));
// ── Step 2: Decay peak before the peak-hold check ────────────────────────
// Decay runs first so that a simultaneous new sound immediately snaps peak back up
if (input.runningTime() - lastDecayTime > PEAK_DECAY_MS) {
// has enough time passed?
if (peakHeight > 0) {
// only step down if peak is above the bottom
peakHeight -= 1; // move peak one row toward the bottom
}
lastDecayTime = input.runningTime(); // reset the decay timer
}
// ── Step 3: Peak hold — snap up instantly if bar is now above peak ───────
if (barHeight > peakHeight) {
peakHeight = barHeight; // bar exceeded peak: snap peak up immediately, no delay
}
// After steps 2 and 3, peakHeight is always >= barHeight
// ── Step 4: Draw the 5×5 grid ────────────────────────────────────────────
// Loop through every column and row and set each LED individually
for (let x = 0; x <= 4; x++) {
// x: column 0 (left edge) → 4 (right edge)
for (let y = 0; y <= 4; y++) {
// y: row 0 (top edge) → 4 (bottom edge) — Microbit counts from the top
// The Microbit counts rows from the top (y=0). A volume meter fills from the bottom.
// Convert y to "rows from bottom" so row 0 = quiet end, row 4 = loud end.
let rowFromBottom = 4 - y; // y=4 (bottom LED) → 0; y=0 (top LED) → 4
if (peakHeight > 0 && rowFromBottom == peakHeight - 1) {
// ── Peak dot ────────────────────────────────────────────────
// This row is the top of where the bar last peaked.
// Full brightness so the dot stands out above the dimmer bar below it.
led.plotBrightness(x, y, PEAK_BRIGHTNESS); // 255 = 100% bright
} else if (rowFromBottom < barHeight) {
// ── Bar fill ─────────────────────────────────────────────────
// This row is inside the current bar — between the bottom and the peak.
led.plotBrightness(x, y, BAR_BRIGHTNESS); // 128 ≈ 50% bright
} else {
// ── Off ──────────────────────────────────────────────────────
// This row is above the bar and not the peak dot — no sound here.
led.unplot(x, y); // turn this LED off
}
}
}
});Sound-reactive LED animation
Listens to music on the built-in microphone. Each time the beat crosses a loudness threshold, a colored ring fires from the center of the matrix and expands outward like a smoke ring — bright at the ring edge, fading to black on either side. Two rings are active at once; they alternate slots and can overlap, mixing colors where they cross.
// tangible_interfaces_sound_DJ_LED_animation https://makecode.microbit.org/S34840-21340-44158-76031
/*
* Beat-reactive smoke ring animation — 16×16 NeoPixel matrix
*
* Listens to music on the built-in microphone. Each time the beat crosses
* a loudness threshold, a colored ring fires from the center of the matrix
* and expands outward like a smoke ring — bright at the ring edge, fading
* to black on either side. Two rings are active at once; they alternate
* slots and can overlap, mixing colors where they cross.
*
* Beat detection technique: upward threshold crossing.
* A kick drum or bass hit causes a sharp spike in overall volume.
* We watch for the moment the level crosses from below the threshold
* to above it — that's the beat. A cooldown prevents one hit from
* firing twice. This works well for dance music at 120 BPM; it is less
* reliable for jazz or acoustic music with no dominant transient.
*
* Hardware:
* BTF-LIGHTING WS2812B 16×16 256-pixel matrix, serpentine from top-right
* Data line → Microbit P1 (change DATA_PIN below if wired differently)
* Power: external 5V supply direct to matrix — do NOT power from USB alone
* Note: Microbit outputs 3.3V; WS2812B spec is 3.5V minimum. This usually
* works in practice. If the matrix behaves erratically, add a 74AHCT125
* level shifter between P1 and the matrix data line.
*
* NeoPixel extension required — add via MakeCode Extensions menu.
*
* Tuning:
* BEAT_THRESHOLD — raise if false triggers in a noisy room; lower if beats are missed
* RING_SPEED — higher = faster expansion
* RING_WIDTH — higher = wider, softer ring edge
*/
// --- Hardware configuration ---
const DATA_PIN = DigitalPin.P1; // change this if your data wire is on a different pin
const NUM_PIXELS = 256; // 16 × 16 matrix
const MATRIX_W = 16; // pixels across
const MATRIX_H = 16; // pixels tall
// --- Beat detection ---
const BEAT_THRESHOLD = 180; // sound level (0–255) that counts as a beat — raise if noisy room
const BEAT_COOLDOWN_MS = 250; // minimum ms between beats — prevents one kick from firing twice
// --- Ring animation ---
const RING_SPEED = 0.02; // expansion speed in pixels per millisecond — ring reaches edge in ~550ms
const RING_WIDTH = 1.8; // falloff distance in pixels — controls how wide/soft the ring edge is
const MAX_RADIUS = 11; // distance from center to corner: sqrt(7.5²+7.5²) ≈ 10.6, rounded up
// --- NeoPixel matrix setup ---
let matrix = neopixel.create(DATA_PIN, NUM_PIXELS, NeoPixelMode.RGB); // RGB mode handles GRB wire order internally
matrix.setBrightness(128); // global brightness cap at 50% — full white at 256 pixels needs serious current
// --- Pre-compute distance from center for every pixel (done once at startup) ---
// Avoids calling Math.sqrt() 256 times every frame
// Indexed by wire pixel index (accounting for serpentine layout)
let distTable: number[] = [];
for (let y = 0; y < MATRIX_H; y++) {
for (let x = 0; x < MATRIX_W; x++) {
// Serpentine layout starting at top-right:
// even rows run right→left, odd rows run left→right
let wireIndex = y % 2 == 0 ? y * 16 + (15 - x) : y * 16 + x;
let dx = x - 7.5; // offset from center (center falls between pixels 7 and 8)
let dy = y - 7.5;
distTable[wireIndex] = Math.sqrt(dx * dx + dy * dy); // Euclidean distance from center
}
}
// --- Ring state — two ring slots, alternated on each beat ---
let ringActive = [false, false]; // is this ring slot currently expanding?
let ringStartTime = [0, 0]; // ms timestamp when this ring was fired
let ringHue = [0, 128]; // color of each ring (0–255 hue wheel); starts on red and cyan
let nextRingSlot = 0; // which slot to fire next (alternates 0 → 1 → 0 → ...)
let nextHue = 0; // hue assigned to the next beat (advances around the color wheel)
// --- Beat detection state ---
let prevSoundLevel = 0; // previous mic reading — needed to detect upward threshold crossing
let lastBeatTime = 0; // ms timestamp of the last detected beat
// --- Startup sequence ---
basic.pause(1000); // brief delay — avoids the power-on click triggering a false beat
basic.showIcon(IconNames.Chessboard); // visible confirmation the program loaded
basic.clearScreen(); // blank the Microbit display before the main loop
// --- Main loop ---
basic.forever(function () {
let now = input.runningTime(); // current time in ms — used for ring age and beat cooldown
let level = input.soundLevel(); // built-in mic: 0 (silence) → 255 (loud)
// ── Beat detection ────────────────────────────────────────────────────────
// Fire on the upward crossing: level just jumped above BEAT_THRESHOLD
// The cooldown prevents the same hit from triggering twice
if (level > BEAT_THRESHOLD && prevSoundLevel <= BEAT_THRESHOLD && now - lastBeatTime > BEAT_COOLDOWN_MS) {
ringActive[nextRingSlot] = true; // activate this ring slot
ringStartTime[nextRingSlot] = now; // record when it fired
ringHue[nextRingSlot] = nextHue; // assign the current hue
nextRingSlot = (nextRingSlot + 1) % 2; // flip to the other slot for next beat
nextHue = (nextHue + 43) % 256; // advance hue ~60° around the wheel (43/256 ≈ 60°/360°)
lastBeatTime = now; // reset the cooldown timer
}
prevSoundLevel = level; // store this reading for next frame's crossing check
// ── Draw every pixel ──────────────────────────────────────────────────────
// For each pixel, sum the color contributions from both ring slots
for (let i = 0; i < NUM_PIXELS; i++) {
let dist = distTable[i]; // pre-computed distance from center for this pixel
let r = 0; // accumulated red channel (0–255, may temporarily exceed)
let g = 0; // accumulated green channel
let b = 0; // accumulated blue channel
// Add contribution from each ring slot
for (let ri = 0; ri < 2; ri++) {
if (!ringActive[ri]) {
// skip inactive ring slots
continue;
}
let age = now - ringStartTime[ri]; // how long this ring has been expanding (ms)
let radius = age * RING_SPEED; // current radius in pixels
if (radius > MAX_RADIUS) {
// ring has expanded past the corners
ringActive[ri] = false; // deactivate — it's off-screen
continue;
}
// Falloff: brightness peaks at the ring edge, drops to zero at RING_WIDTH pixels away
let diff = Math.abs(dist - radius); // how far this pixel is from the ring edge
let edgeFalloff = Math.max(0, 1 - diff / RING_WIDTH); // 1.0 at ring edge, 0 at RING_WIDTH away
// Overall fade: ring starts at full brightness and dims as it expands toward the edges
let expansionFade = Math.max(0, 1 - radius / MAX_RADIUS); // 1.0 at center, 0.0 at MAX_RADIUS
let brightness = edgeFalloff * expansionFade; // combine both fades
// Convert this ring's hue to RGB and scale by brightness
let color = hueToRgb(ringHue[ri]); // full-saturation RGB for this hue
r += ((color >> 16) & 0xff) * brightness; // red channel contribution
g += ((color >> 8) & 0xff) * brightness; // green channel contribution
b += (color & 0xff) * brightness; // blue channel contribution
}
// Clamp each channel to 255 and set the pixel
// Using Math.min instead of bitwise clamp for clarity
matrix.setPixelColor(i, neopixel.rgb(Math.min(255, Math.round(r)), Math.min(255, Math.round(g)), Math.min(255, Math.round(b))));
}
matrix.show(); // push all 256 pixel values to the matrix (~8ms wire transmission time)
});
// --- Convert hue (0–255) to a packed RGB color at full saturation and brightness ---
// The hue wheel is divided into 6 sectors; one primary channel rises or falls linearly in each sector
function hueToRgb(hue: number): number {
let h = hue % 256; // keep hue in range (handles any accumulated overflow)
let sector = Math.idiv(h * 6, 256); // which 1/6 of the wheel: 0=red, 1=yellow, 2=green, 3=cyan, 4=blue, 5=magenta
let offset = (h * 6) % 256; // position within that sector (0–255)
switch (sector) {
case 0:
return neopixel.rgb(255, offset, 0); // red → yellow
case 1:
return neopixel.rgb(255 - offset, 255, 0); // yellow → green
case 2:
return neopixel.rgb(0, 255, offset); // green → cyan
case 3:
return neopixel.rgb(0, 255 - offset, 255); // cyan → blue
case 4:
return neopixel.rgb(offset, 0, 255); // blue → magenta
default:
return neopixel.rgb(255, 0, 255 - offset); // magenta → red
}
}Frequency Generate
This program plays Ode to Joy — stays within C4–B4, so the detector can recognize every note
// tangible_interfaces_frequency_generate
basic.pause(1000);
basic.showIcon(IconNames.Chessboard); // --- Startup indicator ---
basic.clearScreen();
basic.forever(function () {
// Ode to Joy — stays within C4–B4, so the detector can recognize every note
music.play(music.stringPlayable("E4 E4 F4 G4 G4 F4 E4 D4 C4 C4 D4 E4 E4 D4 D4", 120), music.PlaybackMode.UntilDone);
basic.pause(1000); // pause between repeats so the detector can reset
});Frequency Detect
This program enables the microbit to use an electret microphone sensor to recognize a notes produced by another microbit — One Octave (C4 to B4).
// tangible_interfaces_frequency_detect
/*
* Tone Detector
* This program enables the microbit to use an electret microphone sensor to recognize a simple note frequency
* produced by another microbit — One Octave (C4 to B4).
*
* This demonstrates one simple technique of using math to identify which frequency is playing.
* Classic arcade tunes are made with simple "square waves" (simple, pure frequencies).
* This technique will not work on music or even singing, as the multiple frequencies
* are too complex. Fast Fourier Transform is the mathematical approach, but it requires more computing power
* than the microbit has. A Raspberry Pi or any computer or phone has a fast enough chip.
*
* The microbit is not fast enough to do those calculations, so we use a
* trick, zero-crossing frequency detection. Sound is a back and forth wave, like AC power.
* Sometimes it is positive, sometimes negative.
*
* A square wave tone is sound waves moving back and forth extremely regularly.
* Each time the signal crosses the midpoint, half a cycle has passed.
* Measuring the time between crossings gives us the frequency.
*
* When we convert the microphone AC signal from the analog to digital pin (ADC),
* silence on the ADC reads as ~512 (the midpoint of 0–1023).
* The positive half of the wave gets set from 513 to 1023,
* the negative half of the wave gets set from 511 to 0.
* So we just count each time the ADC reading is on either side of silence.
* But remember, the readings will not be exact!
*
* A "Schmitt trigger" uses two thresholds instead of one. The signal
* must swing past MID+margin before it counts as "high," and past
* MID−margin before it counts as "low." This ignores small wiggles
* near the midpoint that would otherwise create false crossings.
* The margin auto-scales to however loud the signal actually is.
*
* Each iteration: collect crossings for 500ms (Phase 1), then decide
* whether the crossings were consistent enough to name a note (Phase 2).
* Consistent half-periods = real tone. Scattered half-periods = noise.
*
* What to expect:
* Silence: LED is dark, serial shows "-"
* Noise: LED shows X, serial shows "X"
* Recognized tone: LED shows letter (C D E F G A B)
*
* Tuning:
* Watch the serial output. If you get false detections in a noisy room,
* lower MAX_SPREAD_MICROS or raise MIN_CROSSINGS.
*
* Hardware:
* Connect an electret microphone module (e.g. MAX9814) to P0:
* VCC → 3V GND → GND OUT → P0
*
* If you are having trouble with two microbits generating & recognizing tones,
* wire the two microbits GNDs together to eliminate one common error.
*/
basic.pause(1000); // Start up pause to avoid flash
basic.showIcon(IconNames.Chessboard); // Startup indicator
basic.clearScreen();
// --- Tuning constants ---
const MIDPOINT = 512; // ADC center: silence reads here
const WINDOW_MS = 500; // how long each collect phase runs (milliseconds)
const MIN_CROSSINGS = 20; // minimum crossings to trust the result
const MAX_SPREAD_MICROS = 250; // if half-periods vary more than this (µs), it's noise
const MIN_HALF_PERIOD_US = 800; // shorter than this = above B4; reject
const MAX_HALF_PERIOD_US = 2500; // longer than this = below C4; reject
const MIN_SCHMITT_MARGIN = 30; // floor for the Schmitt hysteresis band
const MAX_SILENCE_AMPLITUDE = 60; // peak-to-peak below this = silence, not noise
// --- Accumulators — reset at the start of every collect window ---
let periodSum = 0; // running total of all measured half-periods (µs)
let periodCount = 0; // how many valid half-periods collected this window
let minPeriod = 999999; // shortest half-period seen (used to compute spread)
let maxPeriod = 0; // longest half-period seen (used to compute spread)
let peakMin = MIDPOINT; // lowest ADC sample seen (used to auto-scale Schmitt margin)
let peakMax = MIDPOINT; // highest ADC sample seen (used to auto-scale Schmitt margin)
let schmittAbove = false; // true = signal is currently above the high threshold
let lastCrossingTime = 0; // 0 means "no crossing seen yet this window"
function recordCrossing(): void {
// --- Record one zero-crossing and measure the half-period ---
let now = control.micros();
if (lastCrossingTime === 0) {
lastCrossingTime = now; // first crossing — just start the clock
} else {
let halfPeriod = now - lastCrossingTime;
// Ignore crossings outside the frequency range we care about
if (halfPeriod > MIN_HALF_PERIOD_US && halfPeriod < MAX_HALF_PERIOD_US) {
periodSum += halfPeriod;
periodCount++;
if (halfPeriod < minPeriod) minPeriod = halfPeriod;
if (halfPeriod > maxPeriod) maxPeriod = halfPeriod;
}
lastCrossingTime = now;
}
}
basic.forever(function () {
// --- Main loop: collect, then analyze, repeat ---
// ── Phase 1: Collect crossings for WINDOW_MS milliseconds ────────────────
periodSum = 0;
periodCount = 0;
minPeriod = 999999;
maxPeriod = 0;
peakMin = 1023;
peakMax = 0;
schmittAbove = false;
lastCrossingTime = 0;
let windowStart = control.millis();
while (control.millis() - windowStart < WINDOW_MS) {
// this is a very fast loop. The basic.forever has a built in 50ms pause which would prevent this technique from working
let sample = pins.analogReadPin(AnalogPin.P0);
// Track the signal's swing so the Schmitt margin adapts to loudness
if (sample < peakMin) peakMin = sample;
if (sample > peakMax) peakMax = sample;
let dynamicMid = Math.round((peakMax + peakMin) / 2);
let margin = Math.round((peakMax - peakMin) / 4);
if (margin < MIN_SCHMITT_MARGIN) margin = MIN_SCHMITT_MARGIN;
// Schmitt trigger: count crossings only on full swings past the band
if (!schmittAbove && sample >= dynamicMid + margin) {
schmittAbove = true; // signal swung up past high threshold
recordCrossing();
} else if (schmittAbove && sample <= dynamicMid - margin) {
schmittAbove = false; // signal swung down past low threshold
recordCrossing();
}
basic.pause(0); // this micro pause allows other event types like button presses
}
// ── Phase 2: Analyze and display ─────────────────────────────────────────
let spread = maxPeriod - minPeriod; // how much the half-periods varied — low = consistent tone, high = noise
if (periodCount >= MIN_CROSSINGS && spread < MAX_SPREAD_MICROS) {
// Half-periods are plentiful and consistent: confident it's a real tone
let avgHalfPeriod = Math.round(periodSum / periodCount);
let frequency = Math.round(500000 / avgHalfPeriod); // µs half-period → Hz
// --- Return note closest to frequency ---
const NOTE_NAMES = ["C", "D", "E", "F", "G", "A", "B"]; // --- Note table: one octave, C4 to B4 ---
const NOTE_FREQS = [262, 294, 330, 349, 392, 440, 494]; // Hz
let bestIndex = 0;
let bestDistance = Math.abs(frequency - NOTE_FREQS[0]);
for (let i = 1; i < NOTE_FREQS.length; i++) {
let dist = Math.abs(frequency - NOTE_FREQS[i]);
if (dist < bestDistance) {
bestDistance = dist;
bestIndex = i;
}
}
let noteName = NOTE_NAMES[bestIndex];
basic.showString(noteName);
serial.writeLine(noteName + " " + frequency + " Hz crossings=" + periodCount + " spread=" + spread + " µs");
} else {
let amplitude = peakMax - peakMin;
if (amplitude < MAX_SILENCE_AMPLITUDE) {
// Signal barely moved — silence
basic.clearScreen();
serial.writeLine("- amp=" + amplitude);
} else {
// Signal was loud but periods were inconsistent — noise
basic.showIcon(IconNames.No);
serial.writeLine("X crossings=" + periodCount + " spread=" + spread + " amp=" + amplitude);
}
}
// end of very fast cycle, take a breath, finish forever loop
});MIDI Bluetooth Music Instrument
Turn the micro:bit into a wireless musical instrument that generates MIDI (Musical Instrument Digital Interface), the common technical standard and digital language that lets electronic instruments, computers, and other audio gear talk to each other. This code is tested to work with MacOS and iOS over bluetooth
// In MakeCode, open Extensions and paste this URL to add the extension:
// https://github.com/RBilsland/pxt-bluetooth-midi
// It is a fork of microsoft/pxt-bluetooth-midi that builds for the micro:bit V2 and adds macOS support.
/*
Turn the micro:bit into a wireless musical instrument.
The micro:bit advertises itself as a Bluetooth Low Energy MIDI device. A phone or
laptop connects to it and receives note-on / note-off messages, the same language
a USB keyboard controller speaks. No audio travels over Bluetooth, only the
instructions "start this note" and "stop this note". The sound is produced on the
computer by whatever synth or app is listening, such as a GarageBand software
instrument.
The Bluetooth MIDI service starts on its own when the program boots. There is no
start-service call to make.
WHAT THIS PROGRAM DOES
- Button A held: play note A (C). Release to stop.
- Button B held: play note B (G, a fifth above C).
- Both buttons held: play a chord. Each new press steps through a four-chord
progression in C major (C, G, A minor, F), the I-V-vi-IV pattern behind
countless pop songs.
- Tilt left or right while anything is sounding: bend the pitch down or up.
- Screen shows a small heart while waiting, a full heart once a device connects.
PLATFORM SUPPORT
- The RBilsland fork builds for both the micro:bit V1 and V2. (The original Microsoft extension is V1 only and crashes on a V2 board.)
CONNECT ON macOS
- Open Audio MIDI Setup, found in Applications, then Utilities.
- From the Window menu choose Show MIDI Studio, then click the Bluetooth icon.
- Click Connect next to the "uBit" entry.
- Open GarageBand, create a project, add a Software Instrument track like piano, if not already there
- The Microbit will play notes in that instrument.
CONNECT ON iOS
- Connect from inside a GarageBand: tap the settings icon, then Advanced, then Bluetooth MIDI Devices, then tap the "uBit" entry
- Android: works with a scanner app such as nRF Connect.
- Windows: needs a separate BLE MIDI bridge app; not covered here.
PAIRING
- In MakeCode project settings, set Bluetooth pairing to "No Pairing Required" or "JustWorks"
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard); // waiting for a connection
// A MIDI channel is one instrument voice. Channel 1, set to General MIDI
// program 1, Acoustic Grand Piano. Programs run 1 to 128: 25 is nylon guitar,
// 57 is trumpet, 82 is a synth lead.
let instrument = midi.channel(1);
instrument.setInstrument(1);
// MIDI note numbers: 60 is middle C. Adding 1 is one semitone, adding 12 is one
// octave.
const NOTE_A = 60; // C4, played by button A
const NOTE_B = 67; // G4, played by button B
// Each chord is a list of MIDI notes played together. A press of both buttons
// plays the next one and wraps around to the start.
const PROGRESSION = [
[60, 64, 67], // C major (I)
[67, 71, 74], // G major (V)
[57, 60, 64], // A minor (vi)
[53, 57, 60], // F major (IV)
];
let chordStep = 0;
const BEND_CENTER = 8192; // pitch bend is 14-bit, 0 to 16383, center is 8192
// The notes currently sounding, so they can be turned off later.
let sounding: number[] = [];
function silence() {
for (let n of sounding) instrument.noteOff(n);
sounding = [];
}
// Stop whatever is playing, then strike a fresh set of notes. Restriking every
// time means a button press always sounds, even when the note was part of the
// chord that came before it.
function playNotes(next: number[]) {
silence();
for (let n of next) instrument.noteOn(n);
sounding = next;
}
bluetooth.onBluetoothConnected(function () {
basic.showIcon(IconNames.Heart);
});
bluetooth.onBluetoothDisconnected(function () {
basic.showIcon(IconNames.SmallHeart);
silence(); // drop any note left hanging when the link breaks
});
// Remember last loop's buttons so notes and screen only change on a real press
// or release, not on every loop.
let prevA = false;
let prevB = false;
basic.forever(function () {
let a = input.buttonIsPressed(Button.A);
let b = input.buttonIsPressed(Button.B);
if (a != prevA || b != prevB) {
//prevA/prevB prevents re-triggering every 20 ms.
if (a && b) {
// both buttons: play the current chord, then advance the progression
playNotes(PROGRESSION[chordStep]);
led.plotBarGraph(chordStep + 1, PROGRESSION.length); // 1 to 4 bars, instant
chordStep = (chordStep + 1) % PROGRESSION.length;
} else if (a) {
playNotes([NOTE_A]);
basic.clearScreen();
led.plot(0, 2); // left dot
} else if (b) {
playNotes([NOTE_B]);
basic.clearScreen();
led.plot(4, 2); // right dot
} else {
silence();
basic.clearScreen();
}
prevA = a;
prevB = b;
}
// Tilt bends the pitch of whatever is sounding. A flat, still micro:bit sits
// at the center value, so a resting instrument stays in tune.
if (sounding.length > 0) {
// acceleration on X runs about -1023 tilted left to 1023 tilted right
let tilt = input.acceleration(Dimension.X);
let bend = Math.constrain(BEND_CENTER + Math.round(tilt * 4), 0, 16383);
instrument.pitchBend(bend);
} else {
instrument.pitchBend(BEND_CENTER);
}
basic.pause(20);
});
/* Reference
https://support.microbit.org/support/solutions/articles/19000053392-how-do-i-play-a-midi-instrument-on-the-micro-bit
http://www.multiwingspan.co.uk/micro.php?page=midi
*/Motors
Servo simple
Sweeps a servo motor back and forth from 0 to 180 degrees continuously.
/*
A basic demo of servo movement
Sweeps a servo motor back and forth from 0 to 180 degrees continuously.
NOTE if writing a new microbit program, go to Extensions and search for "Servo" and add it.
Physical setup for typical microservo
They typically come with a 3 wire ribbon
Orange wire → micro:bit pin 0
Red wire → micro:bit 3V
Brown wire→ micro:bit GND
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
servos.P0.setRange(0, 180);
let direction = 1;
let angle = 0;
basic.clearScreen();
basic.forever(function () {
// --- Main Loop ---
if (angle > 180 || angle < 0) {
direction = direction * -1;
}
angle += direction;
servos.P0.setAngle(angle);
serial.writeValue("angle", angle);
});Steering Servo
Steers a servo motor up and down using buttons A and B. A simple demo of using buttons to control physical output.
// A simple demo: steers a servo motor with buttons A and B.
let rotation = 100; //
servos.P1.setRange(0, 180);
basic.forever(function () {
// --- Main Loop ---
servos.P1.setAngle(rotation);
basic.pause(200);
});
input.onButtonPressed(Button.A, function () {
// Button A moves the servo clockwise
if (rotation < 180) {
rotation += 10;
}
});
input.onButtonPressed(Button.B, function () {
// Button B moves the servo counter clockwise
if (rotation > 0) {
rotation += -10;
}
});Control Servo with analog sensor
Controls a servo motor position using an analog sensor input. The sensor reading on P1 is mapped to a servo angle on P0.
/*
Servo steered by an Analog Sensor
The basic potentiometer module (rotational varible resistor) is an intuitive control
What other input might you use?
See the Sonar Servo demos)
No extensions required — uses built-in micro:bit blocks only.
Physical setup:
Analog sensor
S pin to Microbit Pin 0
V pin to Voltage
G pin to Ground
Servo
Orange Wire to Microbit Pin 1
Red Wire to voltage
Brown Wire to Ground
*/
basic.pause(1000); // prevent LED flash when programming
basic.showIcon(IconNames.Chessboard); // --- Setup ---
serial.redirectToUSB();
basic.forever(function () {
// --- Main Loop ---
let servo = Math.map(pins.analogReadPin(AnalogReadWritePin.P0), 0, 1023, 0, 180);
led.plotBarGraph(servo, 180);
pins.servoWritePin(AnalogPin.P1, servo);
serial.writeLine("" + servo);
});Servo with Finger Bend Sensor
Controls a servo motor position using another analog sensor, the flex bend sensor you can attach to a glove. The sensor reading on P1 is mapped to a servo angle on P0.
/*
Flex Sensor — Finger Bend to Servo Gripper
Reads a flex sensor on Pin P0.
Maps finger bend to a servo on Pin P1.
Straight finger = open gripper (0°). Bent finger = closed gripper (180°).
Wiring:
- Flex sensor: one end to 3.3V, other end to P0
- 47kΩ resistor one end to P0 , other end to GND
- Servo: orange → P1, red → 3.3V, brown → GND
*/
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
serial.redirectToUSB();
// Calibrate first: bend and straighten your finger while watching serial output.
// Set these to match your actual readings.
const FLEX_STRAIGHT = 700; // reading when finger is straight (sensor flat)
const FLEX_BENT = 300; // reading when finger is fully bent
basic.forever(function () {
let flexReading = pins.analogReadPin(AnalogPin.P0);
let servoAngle = Math.map(flexReading, FLEX_STRAIGHT, FLEX_BENT, 0, 180); //convert the current value in the range of FLEX_STRAIGHT to FLEX_BENT, map it to the servo range
servoAngle = Math.max(0, Math.min(180, servoAngle)); // limit values to safe range for servo
pins.servoWritePin(AnalogPin.P1, servoAngle);
led.plotBarGraph(flexReading, 1023);
serial.writeLine("flex: " + flexReading + " | servo: " + Math.round(servoAngle));
basic.pause(50);
});The same modules, with more complex code to smooth out the natural variations in sensor movement.
/*
Flex Sensor with Smoothing — Finger Bend to Servo Gripper
Reads a flex sensor on Pin P0, applies moving average smoothing,
maps smoothed finger bend to a servo on Pin P1.
Straight finger = open gripper (0°). Bent finger = closed gripper (180°).
Wiring:
- Flex sensor: one end to 3.3V, other end to P0
- 47kΩ resistor: one end to P0, other end to GND
- Servo: orange → P1, red → 3.3V, brown → GND
HOW TO CALIBRATE:
Run the sensor and watch "raw" in the serial output.
Hold finger straight → note the value → set FLEX_STRAIGHT
Bend finger fully → note the value → set FLEX_BENT
*/
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
serial.redirectToUSB();
// --- Calibration (update after observing your sensor) ---
const FLEX_STRAIGHT = 700; // reading when finger is straight
const FLEX_BENT = 300; // reading when finger is fully bent
// --- Moving Average Setup ---
let readings_history: number[] = [];
const readings_history_size = 3; // lower = more responsive, higher = smoother
/*
2–3 = responsive, good for servo control
5 = smooth balance (try this if servo feels jittery)
10 = very smooth, but lags behind fast bends
*/
const FLEX_MIN = Math.min(FLEX_BENT, FLEX_STRAIGHT);
const FLEX_MAX = Math.max(FLEX_BENT, FLEX_STRAIGHT);
let midpoint = Math.round((FLEX_MIN + FLEX_MAX) / 2);
for (let i = 0; i < readings_history_size; i++) {
readings_history.push(midpoint);
}
// --- Output Variables ---
let raw_sensor_value = 0;
let smoothed_value = midpoint;
let servoAngle = 0;
// --- Main Loop ---
basic.forever(function () {
raw_sensor_value = pins.analogReadPin(AnalogPin.P0);
// Step 1: Range filter — ignore readings outside the calibrated sensor range
if (raw_sensor_value < FLEX_MIN || raw_sensor_value > FLEX_MAX) {
serial.writeLine("raw=" + raw_sensor_value + " [out of range, skipping]");
return;
}
// Step 2: Moving average — add new reading, drop oldest
readings_history.push(raw_sensor_value);
if (readings_history.length > readings_history_size) {
readings_history.shift();
}
let sum = 0;
for (let i = 0; i < readings_history.length; i++) {
sum += readings_history[i];
}
smoothed_value = Math.round(sum / readings_history.length);
// Step 3: Map smoothed value to servo angle
servoAngle = Math.map(smoothed_value, FLEX_STRAIGHT, FLEX_BENT, 0, 180);
servoAngle = Math.max(0, Math.min(180, servoAngle));
pins.servoWritePin(AnalogPin.P1, servoAngle);
led.plotBarGraph(smoothed_value, 1023);
serial.writeLine("raw=" + raw_sensor_value + " smoothed=" + smoothed_value + " servo=" + Math.round(servoAngle));
basic.pause(50);
});Fan Module
Controls an L9110 fan module for physical, tactile output — you can feel the wind on your skin. Button A turns the fan forwards, Button B turns it reverse, Touch Logo stops fan.
/*
Controls a fan module — a small motor with a propeller that blows air.
The microbit pins are not strong enough to power the motor
so the module has a L9110, a small chip common for controlling motors
Button A turns the fan forwards
Button B turns it reverse
Touch Logo stops fan
Physical setup:
Fan module has 4 pins: INA, INB, VCC, GND.
Connect INA → micro:bit pin 0
Connect INB → micro:bit pin 1
Connect VCC → micro:bit Voltage
Connect GND → micro:bit GND
The fan may be very weak using just microbit power
you may want to use additional battery power
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
pins.digitalWritePin(DigitalPin.P0, 0); // Make sure fan starts off
pins.digitalWritePin(DigitalPin.P1, 0); // Make sure fan starts off
input.onButtonPressed(Button.A, function () {
// Button A: turn fan on
pins.digitalWritePin(DigitalPin.P0, 1);
pins.digitalWritePin(DigitalPin.P1, 0);
basic.showArrow(ArrowNames.West);
serial.writeLine("Fan forwards");
});
input.onButtonPressed(Button.B, function () {
// Button B: turn fan off
pins.digitalWritePin(DigitalPin.P0, 0);
pins.digitalWritePin(DigitalPin.P1, 1);
basic.showArrow(ArrowNames.East);
serial.writeLine("Fan backwards");
});
input.onLogoEvent(TouchButtonEvent.Pressed, function () {
// Stop Fan
pins.digitalWritePin(DigitalPin.P0, 0);
pins.digitalWritePin(DigitalPin.P1, 0);
basic.showIcon(IconNames.No);
serial.writeLine("Fan stop");
});Fan Module with speed control
Same set up as previous, but now we can increase the speed with the B button and decrease with the A button. Touch the logo to stop. This demonstrates analogWritePin, which is another term for PWM (Pulse Width Modulation) This is simply turning the pin on and off very very fast to simulate it being partially on. This technique is very widely used in hardware, for example to dim LED lights.
/*
Controls a fan module — a small motor with a propeller that blows air.
The microbit pins are not strong enough to power the motor
so the module has a L9110, a small chip common for controlling motors
Button A reduces the speed, even going backwards
Button B increases the speed
Touch Logo stops fan
Physical setup:
Fan module has 4 pins: INA, INB, VCC, GND.
Connect INA → micro:bit pin 0
Connect INB → micro:bit pin 1
Connect VCC → micro:bit Voltage
Connect GND → micro:bit GND
The fan may be very weak using just microbit power
you may want to use additional battery power
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
pins.digitalWritePin(DigitalPin.P0, 0); // Make sure fan starts off
pins.digitalWritePin(DigitalPin.P1, 0); // Make sure fan starts off
let speed = 0; // speed can go from -100 to +100
let speedIncrement = 10; // how much we change the speed each button click
input.onButtonPressed(Button.A, function () {
// Button A: decrease the fan speed
speed -= speedIncrement; // reduce one increment of speed
if (speed < -100) speed = -100; // keep the speed in bounds
changeSpeed();
basic.showArrow(ArrowNames.South);
serial.writeLine("Fan- speed=" + speed);
});
input.onButtonPressed(Button.B, function () {
// Button A: increase the fan speed
speed += speedIncrement; // increase one increment of speed
if (speed > 100) speed = 100; // keep the speed in bounds
changeSpeed();
basic.showArrow(ArrowNames.North);
serial.writeLine("Fan+ speed=" + speed);
});
input.onLogoEvent(TouchButtonEvent.Pressed, function () {
// Stop Fan
speed = 0;
changeSpeed();
serial.writeLine("Fan stop");
});
function changeSpeed() {
// briefly stop the pins (so fast no one will notice!).
// This is to prevent the motor from trying to go both ways
pins.digitalWritePin(DigitalPin.P0, 0); // turn off the pin
pins.digitalWritePin(DigitalPin.P1, 0); // turn off the pin
let analogSpeed = Math.map(Math.abs(speed), 0, 100, 0, 1023); // convert speed variable into a output value
if (speed > 0) {
pins.analogWritePin(AnalogPin.P0, analogSpeed);
} else if (speed < 0) {
pins.analogWritePin(AnalogPin.P1, analogSpeed);
}
}Relay Module
Controls a relay — an electrically operated switch that lets your micro:bit turn real-world devices on and off (lamps, fans, motors). You’ll hear a satisfying “click” when it switches. Button A = on, Button B = off.
/*
Controls a relay module — an electrically operated switch that can turn
real-world devices on and off. You'll hear a satisfying "click" when it switches.
A relay lets your micro:bit control things like lamps, fans, or motors
that need more power than the micro:bit can provide directly.
PLEASE be extremely careful if you are using higher voltage components!!!
Button A turns the relay on (click!), Button B turns it off.
Digital write: 1 = relay ON (closed circuit), 0 = relay OFF (open circuit).
Physical setup:
Relay module has 3 low-voltage pins: S (signal), V (power), G (ground).
Connect S → micro:bit pin 0
Connect V → micro:bit 3V
Connect G → micro:bit GND
The relay also has screw terminals for the high-voltage side:
COM (common), NO (normally open), NC (normally closed).
For basic testing, just listen for the click — no need to wire the screw terminals.
To control a device: wire it through COM and NO so it turns on when the relay activates.
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
pins.digitalWritePin(DigitalPin.P0, 0);
// --- Event Handlers ---
input.onButtonPressed(Button.A, function () {
// Button A: turn relay on
pins.digitalWritePin(DigitalPin.P0, 1);
basic.showIcon(IconNames.Yes);
serial.writeLine("Relay ON");
});
input.onButtonPressed(Button.B, function () {
// Button B: turn relay off
pins.digitalWritePin(DigitalPin.P0, 0);
basic.showIcon(IconNames.No);
serial.writeLine("Relay OFF");
});Analog Sensors
‘Analog’ today is thought of as the opposite of digital, but its original meaning was a computer worked as an “analogy” of reality. The book “Building SimCity” has a fantastic exploration into the early days of simulation and computing.
‘Analog’ input typically means the microbit compares the voltage on a pin compared to the power voltage. The microbit does not know the actual voltage, it knows the proportion. You may notice that the microbit requires 3.3v, but you can power with 2 AA batteries. This means it will still work when the voltage changes slightly, even as the battery depletes (up to a point).
Many simple sensors will communicate the output by ‘dividing’ the voltage, sending part to the input pin and the rest to ground. This is why many packaged sensors need a voltage and a ground pin, in addition to the sensor pin.
Voltage Divider Sensors
If the sensor has only 2 pins, for example, the flex sensor, one would connect one leg to power, the second to the pin, but also add a resistor from the pin to ground. This set up is called a “voltage divider”. The proportion of the sensor’s resistance to the fixed resistor determines the voltage range.
Some experimentation is often necessary to find the ideal resistor for your sensor!
- Using a resistor value that is roughly equal to the sensor’s average resistance during use will give you the widest range of numbers (e.g., a reading of 200 when flat vs. 800 when bent).
- Higher resistance values (like 47kΩ or 100kΩ) pull less current from your battery, which is ideal for wearable or portable projects.
- If you place the fixed resistor between the Analog Pin and Ground (pull-down), the voltage reading will decrease as you bend the sensor (because the sensor’s resistance increases). This works in reverse if you put the resistor to power.
For the Microbit, one reads the voltage like so:
AnalogReading = pins.analogReadPin(AnalogPin.P0);
This returns a value from 0 to 1023. The microbit is slicing the input voltage into 1024 slices and telling you the number.
Side note: 1023 is not a random number; 1024 is a common number in computing; 10 bits gives you 1024 combinations. You may recognize 1024x768 as a monitor resolution. 10 bits was chosen as a “good enough” precision for most users. Higher resolution sensors will slice the measurement more finely.
A key detail to understand is that the microbit does not know the absolute measurement of the sensor, or even the voltage, it just knows the proportion.
Finally, each sensor will have its own behavior, it will default to a certain number at rest, and max out at another number, not necessarily from 0-1023. To make it easier to understand, we ‘normalize’ the measurement to 0 to 100%. We might write this as:
AnalogReading = pins.analogReadPin(AnalogPin.P0);
value_at_rest = 200; // discovered through testing
value_at_max_bend = 800; // discovered through testing
percentage = Math.map(AnalogReading, value_at_rest, value_at_max_bend, 0, 100);
percentage = Math.constrain(percentage, 0, 100); // clamp if sensor exceeds tested range
percentage = Math.round(percentage);
It is our job as designers to test the start / end points of the sensors and ‘design’ the input range to output behavior. For example, we can determine value_at_rest and value_at_max_bend using the basic analog sensor code example below to print raw values while moving the sensor.
Note the measurement is usually not linear (proportional from the physical input). You might define different ranges with different meanings, for example, three ranges of partially bent flex sensor.
5v Sensors on a 3.3v Microbit ?? Yes, with a Voltage Divider
Some sensors use higher voltage; 5 volts is especially common. The MQ-2 Gas sensor needs 5 volts to run its heating element. You may notice that at 100% the sensor would output 5 volts, above the 3.3v that the Microbit can handle. It may damage the entire board, or sometimes “burn out” a single pin. Many new experimenters have experienced this confusion where the code works, but not one pin.
To convert from 5V to 3.3V, we use a simple “voltage divider.” This is just two resistors:
- Resistor 1 (10kΩ): sensor’s Analog out → micro:bit P0
- Resistor 2 (15kΩ): micro:bit P0 → GND
5 volt Analog out pin ──── 10kΩ ──── micro:bit P0
│
15kΩ
│
GND
Unfortunately, because the divided signal never reaches the micro:bit’s full 3.3V reference, you’re not using the complete range of the analog-to-digital converter. The maximum Analog to Digital reading of pins.analogReadPin(AnalogPin.P0)on 5V tops out around 930, losing around 9% of 1023. You trade a little resolution across the board for that safety margin.
If you want to get more precision but play closer to the max voltage, use these resistors: Resistor 1 (1.8 kΩ): sensor’s Analog out → micro:bit P0 Resistor 2 (3.3 kΩ): micro:bit P0 → GND
The maximum Analog to Digital reading of pins.analogReadPin(AnalogPin.P0)on 5V tops out around 1003, losing only about 2% of 1023 — but with standard ±5% tolerance resistors, worst-case output can reach 3.35V, above the 3.3V target. Not enough to damage the board, but enough to clip or skew a reading at the top of the range.
Sadly, there is no free lunch!
Basic Analog sensor
Reads an analog sensor on pin P0 and displays the value as a bar graph on the LED screen. An example sensor is the “potentiometer” (a rotating variable resistor) that changes the voltage that the microbit sees.
/*
Reads an analog sensor on pin P0
Displays the value as a bar graph on the LED screen
Send the raw value and percentage to the computer.
*/
basic.pause(1000); // Start up
basic.showIcon(IconNames.Chessboard);
let AnalogReading = 0;
basic.forever(function () {
// --- Main Loop ---
AnalogReading = pins.analogReadPin(AnalogPin.P0);
led.plotBarGraph(AnalogReading, 1023);
serial.writeLine("Analog Reading " + AnalogReading + "|" + Math.round(Math.map(AnalogReading, 0, 1023, 0, 99)) + "%");
basic.pause(100);
});Pressure/Force Sensor
Reads a thin-film pressure sensor and shows how hard you’re pressing as a bar graph. Unlike a button (on/off), this sensor gives an analog value of the pressure is applied.
/*
Reads a thin-film pressure (force) sensor and displays how hard you're pressing.
Unlike a button (on/off), this sensor gives an analog value — it knows HOW MUCH
force is applied. Squeeze harder = higher reading.
Use cases: squeeze toys, sit-on sensors, footstep detection, grip strength.
Physical setup:
Sensor has 3 pins labeled S (signal), V (power), G (ground).
Connect S → micro:bit pin 0
Connect V → micro:bit 3V
Connect G → micro:bit GND
Press the round film area with your finger to see values change.
*/
basic.pause(1000); // Start up
basic.showIcon(IconNames.Chessboard);
let forceReading = 0;
basic.forever(function () {
// --- Main Loop ---
forceReading = pins.analogReadPin(AnalogPin.P0);
led.plotBarGraph(forceReading, 1023);
serial.writeLine("forceReading " + forceReading + "|" + Math.round(Math.map(forceReading, 0, 1023, 0, 99)) + "%");
basic.pause(100);
});Joystick Input
Reads an analog joystick and displays position as a dot on the LED screen. Pressing the joystick button plays a sound.
The joystick is actually simply 2 rotating variable resistors, with springs to return them to the center.
/*
Reads an analog joystick and displays x and y position as a dot on the LED screen.
Pressing the joystick button plays a sound.
Wiring: Y direction → pin 0, X direction → pin 1, Switch → pin 2
MAP scales the 0-1023 input to 0-4 (pixel positions on the LED grid)
ROUND converts to whole numbers for LED coordinates
Digital read pin 2: 1 = button clicked
*/
basic.pause(1000); // Start up
serial.redirectToUSB();
basic.showIcon(IconNames.Chessboard);
let Y = 0;
let X = 0;
// --- Main Loop ---
basic.forever(function () {
basic.clearScreen();
X = Math.round(Math.map(pins.analogReadPin(AnalogReadWritePin.P1), 0, 1023, 4, 0));
Y = Math.round(Math.map(pins.analogReadPin(AnalogReadWritePin.P2), 0, 1023, 0, 4));
if (pins.digitalReadPin(DigitalPin.P0) == 1) {
music.play(
music.createSoundExpression(WaveShape.Sine, 5000, 0, 255, 0, 500, SoundExpressionEffect.None, InterpolationCurve.Linear),
music.PlaybackMode.InBackground,
);
basic.showIcon(IconNames.SmallDiamond);
} else {
led.plotBrightness(X, Y, 255);
}
basic.pause(100);
serial.writeNumbers([X, Y, 0]);
});Ultraviolet Sensor
When should you reapply sunscreen? This example reads a GUVA-S12SD UV sensor and estimates the UV index (0-11). A similar product page states “You can convert the voltage to UV Index by dividing by 0.1V. So if the output voltage is 0.5V, the UV Index is about 5.
/*
Reads a GUVA-S12SD ultraviolet (UV) sensor and estimates the UV index.
UV index 0-2 is low, 3-5 moderate, 6-7 high, 8-10 very high, 11+ extreme.
Great for wearable projects: when should you reapply sunscreen?
Analog read: the sensor outputs a voltage proportional to UV intensity.
Formula: UV Index = voltage / 0.1V (linear, per Adafruit GUVA-S12SD spec).
Physical setup:
Sensor has 3 pins labeled S (signal), V (power), G (ground).
Connect S → micro:bit pin 0
Connect V → micro:bit 3V
Connect G → micro:bit GND
Point the sensor window toward the sky or a UV light source.
*/
basic.pause(1000); // Start up
serial.redirectToUSB();
basic.showIcon(IconNames.Chessboard);
let analogInput = 0;
let uvVoltage = 0;
let uvIndex = 0;
basic.forever(function () {
// --- Main Loop ---
// Step 1: Read the raw sensor value (0 to 1023)
analogInput = pins.analogReadPin(AnalogPin.P0);
// Step 2: Convert to voltage — micro:bit reads 0–3.3V as 0–1023
uvVoltage = (analogInput / 1023) * 3.3;
// Step 3: Convert voltage to UV index — sensor outputs 0.1V per UV index unit
uvIndex = Math.round(uvVoltage / 0.1);
basic.showNumber(uvIndex); // Show the UV index number on the LED screen
serial.writeLine("raw " + analogInput + " | voltage: " + uvVoltage + "V | uvIndex: " + uvIndex);
basic.pause(1000);
});Analog Alcohol Sensor
Reads an MQ-3 alcohol sensor that detects alcohol vapor in the air. Try holding hand sanitizer near the sensor. Needs 5V and a few minutes to warm up.
The sensor ‘works’, it detects alcohol. It also demonstrates the extreme difficulty of engineering a device with life and death consequences.
If you test it, you will see that the device takes a while to warm up, it takes a while to respond and the sensor takes a while to “de-respond”. In addition, calculating blood alcohol level from alcohol in the air is no simple matter. For example, you need a dependable background to compare it to.
We can’t simply say “an analog reading above 700 = drunk” because the sensor reading needs to be compared to the air in the room. If that room is a biker bar, you will get a different base measurement than outside. Temperature affects the atomization of alcohol, etc, etc.
Even professional machines have been shown to have engineering errors. Machines marketed as precise to the third decimal place were found to produce results that were sometimes 40% too high. NY Times Article “These Machines Can Put You in Jail. Don’t Trust Them.”
This is not to say the sensor does not ‘work’ but that the way they work is often a complex and under-defined question that a designer needs to engage deeply with.
/*
MQ-2 alcohol sensor - Use to detect hand sanitizer or other alcohol. The sensor heats up during use. This is normal.
Wiring:
MQ-2 VCC to 5V
MQ-2 GND → GND
MQ-2 A0 → micro:bit pin 0
Microbit VCC → 3.3V (not 5V — the analog output follows VCC and will exceed micro:bit's pin limit at 5V)
Microbit GND → GND
Phases:
Warmup — 90 seconds for sensor to stabilize. Press A to skip if already warm.
Calibrate — press A to record clean-air baseline (averages 10 readings over 5 seconds)
Measure — bar graph shows rise above baseline; serial shows raw values
*/
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
const CALIBRATE_SAMPLES = 10;
const WARMUP_TICKS = 180; // 90 seconds at 500ms per tick
let baseline = 0;
let IsWarmedUp = false;
let ticks = 0;
input.onButtonPressed(Button.A, function () {
IsWarmedUp = true; // give user option to skip warmup, fo example if restarting program
});
basic.forever(function () {
let reading = pins.analogReadPin(AnalogPin.P0);
if (IsWarmedUp == false) {
ticks += 1;
serial.writeValue("warmup", reading);
if (ticks >= WARMUP_TICKS) {
IsWarmedUp = true;
basic.showString("Press A");
}
} else if (input.buttonIsPressed(Button.A)) {
// is button a pressed? stop and take sample calibration
basic.showIcon(IconNames.SmallDiamond);
let sum = 0;
for (let i = 0; i < CALIBRATE_SAMPLES; i++) {
sum += pins.analogReadPin(AnalogPin.P0);
basic.pause(500);
}
baseline = sum / CALIBRATE_SAMPLES;
serial.writeValue("baseline", baseline);
basic.showIcon(IconNames.Yes);
} else {
// normal usage mode
led.plotBarGraph(reading - baseline, 1023 - baseline);
serial.writeLine("gas: " + reading + " base: " + baseline);
}
basic.pause(500);
});Sensors with Extensions
The MakeCode editor has the ability to use code from experience programmers, called extensions. These can be written in a more complex language to do actions too complicated for beginners. This is common in programming. In fact, most programming involves stitching together code from previous generations of programmers.
Ultrasonic Distance Sensor
Reads an ultrasonic distance sensor (HC-SR04) and displays the distance as a bar graph on the LED screen. In the microbit code editor, open “extensions”, search for ‘Sonar’ and add. Example project Example video
/*
Reads an ultrasonic distance sensor (HC-SR04) and displays the distance
as a bar graph on the LED screen and logs distance values over serial.
This is a widely used sensor for small learning robots, but is very basic.
It can sense an object target larger than a pen up to a wall.
But is not a camera, it just knows if something is there, not its shape
or if there are multiple objects.
For background, the ultrasonic distance sensor works like a bat.
It sends a sound "ping" and counts the time for the echo.
The trig pin triggers the ping (output from micro:bit to sensor).
The echo pin receives the return signal (input from sensor to micro:bit).
Note if writing a program from scratch, add the "Sonar" extension in Makecode editor.
*/
basic.pause(1000); // --- Setup ---
basic.showIcon(IconNames.Chessboard);
serial.writeLine("Start Ultrasonic Sensor");
let DistanceInCM = 0;
let trigPin = DigitalPin.P1; // connect to the trig pin on the distance sensor
let echoPin = DigitalPin.P0; // connect to the echo pin on the distance sensor
basic.forever(function () {
// --- Main Loop ---
DistanceInCM = sonar.ping(trigPin, echoPin, PingUnit.Centimeters);
if (DistanceInCM == 0) return; // the device returns 0 if there is no object in view
if (DistanceInCM > 200) return; // the device occasionally returns wild numbers, ignore them
led.plotBarGraph(DistanceInCM, 200); // measured as a proportion of 200 cm, adjust as needed
serial.writeLine("DistanceInCM=" + DistanceInCM);
basic.pause(100);
});RFID Tap to Login
Track logins using the PN532 RFID / NFC reader. Make the microbit into a security system, or connect a motor to lock a cookie jar. Store the logins to the datalogger to track visitors.
In the microbit code editor, open “extensions”, search for and add ‘https://github.com/DFRobot/pxt-NFCI2C`
Note this extension also enables other NFC features like reading and writing a tiny amount of data or URL to the cards, but this only works on certain NFC card types, in specific circumstances. Fun, but be ready for frustration.
//
// Tap-to-login with a PN532 RFID / NFC reader.
// - Tap a tag: an enrolled tag shows a tick, any other tag shows a cross.
// - Hold button A and tap a tag to add it to the list. If the tag is
// already on the reader, lift it and tap again while holding A.
// - Press buttons A+B together: forget every enrolled tag.
// The list is kept in memory, so it clears when the micro:bit loses power.
//
// In MakeCode: Extensions > paste https://github.com/DFRobot/pxt-NFCI2C
//
// Wiring, PN532 over I2C:
// PN532 SDA -> micro:bit P20
// PN532 SCL -> micro:bit P19
// PN532 VCC -> micro:bit 3V
// PN532 GND -> micro:bit GND
// Set the PN532's DIP switch to I2C, then unplug and replug its power so
// it reads the switch. Hold a tag flat, about 1 cm from the antenna - a
// tag resting directly on the chip is often too close to read.
// Tag IDs allowed to "log in". Starts empty; fill it by enrolling tags.
let enrolledIDs: string[] = [];
// The last tag ID handled, to prevent double reads of a tap
let lastTag = "";
input.onButtonPressed(Button.AB, function () {
// erase all tags from memory
enrolledIDs = [];
lastTag = "";
basic.showIcon(IconNames.Skull);
});
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
basic.clearScreen();
basic.forever(function () {
let tag = NFC.getUid(); // the tag's ID, or "No NFC Card!" when none is near
if (tag == "No NFC Card!") {
lastTag = ""; // tag has left; ready for the next tap
basic.pause(200);
} else if (tag == lastTag) {
basic.pause(200); // same tag still sitting on the reader
return;
} else {
// there is a tag, and it is a new tap
lastTag = tag;
if (input.buttonIsPressed(Button.A)) {
// Button A is being held down, so add to enrolledIDs
if (enrolledIDs.indexOf(tag) < 0) {
enrolledIDs.push(tag);
serial.writeLine("ID now enrolled: " + tag + " (" + enrolledIDs.length + " total)");
basic.showIcon(IconNames.Diamond);
} else {
serial.writeLine("ID was already enrolled: " + tag + " (" + enrolledIDs.length + " total)");
basic.showIcon(IconNames.Yes);
}
} else if (enrolledIDs.indexOf(tag) >= 0) {
serial.writeLine("ID allowed: " + tag);
basic.showIcon(IconNames.Yes);
} else {
serial.writeLine("ID denied: " + tag);
basic.showIcon(IconNames.No);
}
basic.pause(1000);
basic.clearScreen();
}
});Temperature And Humidity Sensor
Reads temperature and humidity from a DHT11 sensor — one sensor, two readings. The core of every smart thermostat or weather station. Requires the “DHT11_DHT22” MakeCode extension (search “DHT11” in Extensions).
/*
Reads temperature and humidity from a DHT11 sensor, a common low cost, but not extremely precise sensor.
One sensor gives you two readings — the core of every smart thermostat or weather station.
The DHT22 is an improved version, but more accurate sensors exist, including
SHT31, SHT40, or BME280 (which also measures barometric pressure)
Note: this program requires a MakeCode extension.
In the MakeCode editor, click "Extensions" and search for "DHT11".
Add the "DHT11_DHT22" extension by Alan Krantas.
https://makecode.microbit.org/pkg/alankrantas/pxt-dht11_dht22#dht11_dht22-querydata
Physical setup:
Sensor has 3 pins labeled S (signal), V (power), G (ground).
Connect S → micro:bit pin 0
Connect V → micro:bit 3V
Connect G → micro:bit GND
*/
basic.showIcon(IconNames.Chessboard);
basic.pause(1000); // Wait for sensor to stabilize before first read
dht11_dht22.selectTempType(tempType.celsius);
basic.forever(function () {
// --- Main Loop ---
// Query the sensor. These are the default values for a 3 pin PCB version of the sensor
// The last true parameter delays by 2 second between readings (the sensor is slow).
dht11_dht22.queryData(DHTtype.DHT11, DigitalPin.P0, true, false, true);
// Only read if query succeeded — bad wiring or signal noise can cause checksum failure
if (dht11_dht22.readDataSuccessful()) {
let temperature = dht11_dht22.readData(dataType.temperature);
let humidity = dht11_dht22.readData(dataType.humidity);
serial.writeLine("tempC=" + temperature + " humidity=" + humidity);
} else {
serial.writeLine("sensor error");
}
});Rotary Encoder
Demonstrates a KY-040 rotary encoder module: twist to change a value shown as a bar graph, press the button to reset. Uses my RotaryEncoderPlus extension.
/*
Demonstrates a KY-040 rotary encoder module on a PCB.
Twist to change a value
shown as a bar graph on the LED screen
Press the button to reset.
Requires an extension. click "Extensions" in the menu and paste this URL into the search box:
https://github.com/steveturbek/pxt-rotary-encoder-KY-040-plus
hit return and click to add this extension to your project.
The Rotary encoder module PCB board should have these labels:
GND – Ground
+ – Power (3.3V)
SW – Switch: button press signal (goes LOW when pressed)
CLK – Clock: main pulse used to detect rotation
DT – Data: compared to CLK to determine direction
If the Rotary encoder is a bare component, it has 5 pins/legs:
3 on one side (rotation):
Connect to CLK pin on micro:bit
Connect to GND
Connect to DT pin on micro:bit
2 on the other side (built-in button):
SW1 → connect to GND
SW2 → connect to SW pin on micro:bit
Note: CLK and DT may be swapped on your component, swap wires if rotation direction is reversed.
There is no main loop in this program
*/
basic.pause(1000); // --- Setup ---
basic.showIcon(IconNames.Chessboard);
rotaryEncoderPlus.connectEncoder1(); //Uses CLK=PO DT=P1 SW-P2
let count = 13;
led.plotBarGraph(count, 25);
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E1, rotaryEncoderPlus.EncoderEvent.CounterClockwise, function () {
// Runs when encoder is rotated left (counter-clockwise)
if (count > 1) {
count -= 1;
}
serial.writeValue("count", count);
led.plotBarGraph(count, 25);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E1, rotaryEncoderPlus.EncoderEvent.Clockwise, function () {
// Runs when encoder is rotated right (clockwise)
if (count < 21) {
count += 1;
}
serial.writeValue("count", count);
led.plotBarGraph(count, 25);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E1, rotaryEncoderPlus.EncoderEvent.ButtonPress, function () {
// Runs when encoder button is pressed
basic.showNumber(1);
basic.pause(1000);
led.plotBarGraph(count, 25);
});Rotary Encoder Painter
Demonstrates 3 KY-040 rotary encoders to “paint” the LEDs different brightnesses, like an old ‘Etch-a-Sketch’. Uses my RotaryEncoderPlus extension.
/*
Demonstrates 3 KY-040 rotary encoders with a sort of
"Etch-a-Sketch" designer for the 25 micro:bit LEDs
1 controls the x position of a dot on the LED screen
2 controls the y position of a dot on the LED screen
3 controls the brightness of the dot on the LED screen
Press the button on the brightness encoder to clear the screen.
Requires a microbit extension.
In your makecode.microbit.org project, click "Extensions" in the menu and paste this URL into the search box:
https://github.com/steveturbek/pxt-rotary-encoder-KY-040-plus
hit return and click to add this extension to your project.
The rotary encoder module PCB board should have these labels:
GND – Ground
+ – Power (3.3V)
SW – Switch: button press signal (goes LOW when pressed)
CLK – Clock: main pulse used to detect rotation
DT – Data: compared to CLK to determine direction
If the Rotary encoder is a bare component, it has 5 pins/legs:
3 pins on one side (rotation):
Connect to CLK pin on micro:bit
Connect to GND
Connect to DT pin on micro:bit
2 pins on the other side (built-in button):
SW1 → connect to GND
SW2 → connect to SW pin on micro:bit
There is no main loop in this program
*/
basic.pause(1000); // --- Setup ---
basic.showIcon(IconNames.Chessboard);
rotaryEncoderPlus.connectEncoder1(); //Uses CLK=PO DT=P1 SW-P2
rotaryEncoderPlus.connectEncoder2(); //Uses CLK=P8 DT=P9 SW=P13
rotaryEncoderPlus.connectEncoder3(); //Uses CLK=P14 DT=P15 SW=P16
let x = 2;
let y = 2;
let brightness = 64; // 0 to 255
basic.clearScreen();
led.plotBrightness(x, y, brightness);
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E1, rotaryEncoderPlus.EncoderEvent.CounterClockwise, function () {
// Runs when encoder is rotated left (counter-clockwise)
if (x > 0) {
x -= 1;
}
serial.writeLine("x=" + x + " y=" + y + " brightness=" + brightness);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E1, rotaryEncoderPlus.EncoderEvent.Clockwise, function () {
// Runs when encoder is rotated right (clockwise)
if (x < 4) {
x += 1;
}
led.plotBrightness(x, y, brightness);
serial.writeLine("x=" + x + " y=" + y + " brightness=" + brightness);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E2, rotaryEncoderPlus.EncoderEvent.CounterClockwise, function () {
// Runs when encoder is rotated left (counter-clockwise)
if (y > 0) {
y -= 1;
}
led.plotBrightness(x, y, brightness);
serial.writeLine("x=" + x + " y=" + y + " brightness=" + brightness);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E2, rotaryEncoderPlus.EncoderEvent.Clockwise, function () {
// Runs when encoder is rotated right (clockwise)
if (y < 4) {
y += 1;
}
led.plotBrightness(x, y, brightness);
serial.writeLine("x=" + x + " y=" + y + " brightness=" + brightness);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E3, rotaryEncoderPlus.EncoderEvent.CounterClockwise, function () {
// Runs when encoder is rotated left (counter-clockwise)
if (brightness >= 32) {
brightness -= 32;
}
led.plotBrightness(x, y, brightness);
serial.writeLine("x=" + x + " y=" + y + " brightness=" + brightness);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E3, rotaryEncoderPlus.EncoderEvent.Clockwise, function () {
// Runs when encoder is rotated right (clockwise)
if (brightness <= 223) {
brightness += 32;
}
led.plotBrightness(x, y, brightness);
serial.writeLine("x=" + x + " y=" + y + " brightness=" + brightness);
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E3, rotaryEncoderPlus.EncoderEvent.ButtonPress, function () {
for (let ix = 0; ix < 5; ix++) {
for (let iy = 0; iy < 5; iy++) {
led.plotBrightness(x, y, 0);
}
}
});Rotary Encoder with RGB Color Picker
RGB rotary encoder color picker: twist to cycle through colors, press the button to send the selected color hex value over serial. Uses my RotaryEncoderPlus extension.
/*
RGB rotary encoder color picker: twist to cycle through colors on the built-in
RGB LED, press the button to send the selected hex color value over serial.
Requires an extension. Click "Extensions" in the menu and paste this URL:
https://github.com/steveturbek/pxt-rotary-encoder-KY-040-plus
The RGB Encoder has 2 sides with pins
Facing the 3 pin side, left to right
1) Encoder A connect to Pin 9
2) Ground
3) Encoder B connect to Pin 8
Facing the 5 pin side, left to right
4) Voltage. 3.3v seems to work
5) Connect to Pin 2 (this controls Blue LED)
6) Switch connect to Pin 7
7) Connect to Pin 1 (this controls Green LED)
8) Connect to Pin 0 (this controls Red LED)
Electronics ribbon cables are usually color coded.
The color of the wires does not matter functionally
but be careful not to mix up LED colors with wire colors!
a version of https://en.wikipedia.org/wiki/Stroop_effect
Note: LED pins work inverted — lower analog value = brighter.
pins.analogWritePin(AnalogPin.P0, 0) = fully on
pins.analogWritePin(AnalogPin.P0, 1023) = fully off
This RGB component may be a bit tricky to wire up, but it's a fun project and the result is pretty cool!
Unfortunately, this RGB LED Rotary Encoder is not very well documented, this guide was very helpful https://qbalsdon.github.io/circuitpython/rotary-encoder/python/led/2021/02/27/rgb-rotary-encoder.html
I was able to convert into code for the micro:bit using my rotaryEncoderPlus library
*/
basic.pause(1000); // avoid flash on programming
led.enable(false); // Disable the built-in LED matrix since we're using the pins to control the RGB LED instead
serial.redirectToUSB();
// connectAdvanced lets us specify all three pins and set activeHigh=true for the RGB encoder's
// active-high switch (connects to 3.3V when pressed, unlike a standard KY-040 which connects to GND)
rotaryEncoderPlus.connectAdvanced(rotaryEncoderPlus.EncoderID.E2, DigitalPin.P8, DigitalPin.P9, DigitalPin.P7, rotaryEncoderPlus.SwitchType.ActiveHigh);
let color_position = 0; // what color we're on, from 0-255, where 0=red, 85=green, 170=blue, and back to 255=red
let red = 255; //how much Red in the color, from 0-255. We start at red, so red=255, green=0, blue=0
let green = 0; //how much Green in the color, from 0-255
let blue = 0; //how much Blue in the color, from 0-255
function update_color() {
// Cycle through red → green → blue → red across 256 positions
let pos = color_position;
if (pos < 85) {
red = 255 - pos * 3;
green = pos * 3;
blue = 0;
} else if (pos < 170) {
pos -= 85;
red = 0;
green = 255 - pos * 3;
blue = pos * 3;
} else {
pos -= 170;
red = pos * 3;
green = 0;
blue = 255 - pos * 3;
}
pins.analogWritePin(AnalogPin.P0, 1023 - red * 4);
pins.analogWritePin(AnalogPin.P1, 1023 - green * 4);
pins.analogWritePin(AnalogPin.P2, 1023 - blue * 4);
}
function to_hex(value: number) {
let hex_digits = "0123456789abcdef";
return hex_digits[Math.idiv(value, 16)] + hex_digits[value % 16];
}
update_color();
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E2, rotaryEncoderPlus.EncoderEvent.Clockwise, function () {
color_position = (color_position + 5) % 256;
update_color();
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E2, rotaryEncoderPlus.EncoderEvent.CounterClockwise, function () {
color_position -= 5;
if (color_position < 0) {
color_position = 255;
}
update_color();
});
rotaryEncoderPlus.onEvent(rotaryEncoderPlus.EncoderID.E2, rotaryEncoderPlus.EncoderEvent.ButtonPress, function () {
serial.writeLine("#" + to_hex(red) + to_hex(green) + to_hex(blue)); // makes a RGB hex color value used in web coding, #FF0000 is RED
// Flash white briefly to confirm
pins.analogWritePin(AnalogPin.P0, 0);
pins.analogWritePin(AnalogPin.P1, 0);
pins.analogWritePin(AnalogPin.P2, 0);
basic.pause(200);
update_color();
});This RGB Rotary Encoder program can drive animation on the RGB Painter web page (chrome browser only). Connect the microbit to the RGB rotary encoder and keep the microbit plugged into the computer via the USB cable . Choose colors on the RGB encoder and click to fill the screen with colorful stars!

Air Pressure Sensor
The MPXV7002DP is an integrated pressure sensor designed to measure both positive and negative pressure and vacuum from -2 to 2 kPa (kilo Pascals), or around .3 Pounds per Square Inch (PSI). The sensor has both medical and aviation uses:
- Medical devices — spirometers, CPAP/ventilator flow sensing, respiratory monitoring
- Breath-controlled interfaces using blow sensors, for example steering wheelchairs
- Pitot tube is used to measure airspeed — drones, small aircraft, wind tunnels
- HVAC duct airflow pressure
/*
Reads an MPXV7002DP air pressure sensor. Connect a tube to one of its inlets:
blowing into the tube reads as positive pressure, sucking on it reads as negative.
Range: -2 to 2 kPa. (roughly, pressure when playing a wind instrument)
Display: a bar graph across the whole LED grid, empty at -2 kPa (max suck), half full
at 0, and full at +2 kPa (max blow).
Datasheet: https://www.nxp.com/docs/en/data-sheet/MPXV7002.pdf
Wiring:
The MPXV7002DP uses 5V power; the micro:bit's pins tolerate up to 3.3V max.
Because the sensor's output can reach 4.5V, add a voltage divider before P0:
Resistor 1 (2.2 kΩ): sensor's Analog out → micro:bit P0
Resistor 2 (4.7 kΩ): micro:bit P0 → GND
*/
// Voltage divider resistors, sensor's Analog out -> P0 -> GND (see wiring comment above).
const DIVIDER_R1_OHMS = 2200;
const DIVIDER_R2_OHMS = 4700;
// Fraction of the sensor's output voltage that reaches P0: R2 / (R1 + R2)
const DIVIDER_RATIO = DIVIDER_R2_OHMS / (DIVIDER_R1_OHMS + DIVIDER_R2_OHMS);
// the MPXV7002DP communicates its pressure value via voltage, so
const ADC_MAX_READING = 1023; // micro:bit analogReadPin's top value
const ADC_MAX_VOLTAGE = 3.3; // micro:bit pin voltage at ADC_MAX_READING
const SENSOR_SUPPLY_VOLTAGE = 5.0; // Vs in the datasheet transfer function
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
// Zero-calibration: average a few readings at rest and use that as the baseline,
// since resistor tolerance and sensor offset error mean 0 kPa rarely reads as exactly 0.
let baseline_kPa = 0;
for (let i = 0; i < 8; i++) {
let reading = pins.analogReadPin(AnalogPin.P0);
let pinVoltage = (reading / ADC_MAX_READING) * ADC_MAX_VOLTAGE;
let sensorVoltage = pinVoltage / DIVIDER_RATIO;
baseline_kPa += (sensorVoltage / SENSOR_SUPPLY_VOLTAGE - 0.5) / 0.2;
basic.pause(50);
}
baseline_kPa /= 8;
basic.forever(function () {
// Read raw analog input and convert to the voltage at the micro:bit pin
let reading = pins.analogReadPin(AnalogPin.P0);
let pinVoltage = (reading / ADC_MAX_READING) * ADC_MAX_VOLTAGE;
// Undo the voltage divider to recover the sensor's actual output voltage.
let sensorVoltage = pinVoltage / DIVIDER_RATIO;
// Apply the sensor's transfer function (from the datasheet above) to get pressure in kPa
// P = ((Vout / Vs) - 0.5) / 0.2, where Vs is the sensor's supply voltage
let pressure_kPa = (sensorVoltage / SENSOR_SUPPLY_VOLTAGE - 0.5) / 0.2 - baseline_kPa;
pressure_kPa = Math.round(pressure_kPa * 100) / 100; // trim the very long decimals
led.plotBarGraph(pressure_kPa + 2, 4); // maps -2..2 kPa onto 0..4 for the bar graph
serial.writeValue("Pressure (kPa)", pressure_kPa);
basic.pause(100);
});Magnetic Angle Sensor
The AS5600 is a very widely used sensor to measure and angle or count rotations. Attach a small magnet to a knob handle or motor shaft and place a few millimeters the chip: no mechanical contact, wear, or dead zone.
/*
Demonstrates a AS5600 magnetic angle sensor, which senses a rotating N/S magnetic field up to 3mm away. These sensors are often used measure angle or rotation without friction, noise, or mechanical failure. Magnetic angle sensors can rotate 360 degrees around and have absolute location detection like potentiometers.
Physical setup:
AS5600 SDA -> micro:bit P20
AS5600 SCL -> micro:bit P19
AS5600 VCC -> micro:bit 3V
AS5600 GND -> micro:bit GND
AS5600 DIR -> GND (turning clockwise increases angle) or VCC (counterclockwise). NOTE: leaving it unconnected causes unstable, noisy readings.
Mount a small diametrically magnetized magnet up to 3mm above the chip, centered over it.
*/
// --- Main Loop ---
basic.forever(function () {
pins.i2cWriteNumber(0x36, 0x0b, NumberFormat.UInt8BE, true);
let status = pins.i2cReadNumber(0x36, NumberFormat.UInt8BE, false);
if (!(status & 0x20)) {
// 0x20 is the magnet-detected bit within the status byte
serial.writeLine("no magnet detected");
basic.clearScreen();
return;
}
pins.i2cWriteNumber(0x36, 0x0c, NumberFormat.UInt8BE, true); // Ask for a reading. 0x36 is the AS5600 memory ADDRESS, 0x0c is the angle memory address
let raw = pins.i2cReadNumber(0x36, NumberFormat.UInt16BE, false); // Read reading. 0x36 is the AS5600 memory ADDRESS: 2 bytes, 12-bit raw angle, 0-4095
let angle = Math.round(((raw & 0x0fff) * 360) / 4096); // register is 12-bit (values 0–4095) divide by that and multiply by 360 to get an angle. Rounded for clean output but AS5600 resolution is ~.1°
pins.i2cWriteNumber(0x36, 0x1b, NumberFormat.UInt8BE, true); // point to the magnitude register
let magnitude = pins.i2cReadNumber(0x36, NumberFormat.UInt16BE, false) & 0x0fff; // 12-bit field strength, 0-4095
let strengthPercent = Math.round((magnitude / 4095) * 100);
serial.writeLine("angle: " + angle + " strength: " + strengthPercent + "%"); // sent to the serial monitor
led.plotBarGraph(angle, 360); // quick visual feedback on the LED matrix
basic.pause(100);
});IR Remote Reader
Reads button presses from the Keyestudio IR remote and decodes the button. Other remotes may work, but you may need to adjust it with trial and error. Requires the “MakerBit IR Receiver” MakeCode extension (search “makerbit-ir” in Extensions).
/*
Reads button presses from the KEYESTUDIO IR remote control.
The micro:bit shows which button was pressed.
MakeCode extension required:
In the MakeCode editor, click "Extensions" and search for "makerbit-ir".
Add the "MakerBit IR Receiver" extension by 1010Technologies.
Physical setup:
IR receiver S → micro:bit pin 0
IR receiver V → micro:bit 3V
IR receiver G → micro:bit GND
Point the IR remote directly at the receiver module (line of sight).
Note KEYESTUDIO code extension is missing useful features, makerbit is better
https://wiki.keyestudio.com/Ks0027_keyestudio_Digital_IR_Transmitter_Module
https://wiki.keyestudio.com/Ks0026_keyestudio_Digital_IR_Receiver_Module
https://video.keyestudio.com/?s=Ks0026
https://docs.keyestudio.com/projects/KS4009-KS4010/en/latest/KS4009-KS4010.html#project-53-ir-remote-control-decoding
*/
basic.pause(1000); // --- Setup ---
makerbit.connectIrReceiver(DigitalPin.P0, IrProtocol.Keyestudio); // Connect IR receiver on pin 0 using Keyestudio protocol
basic.showIcon(IconNames.Chessboard);
makerbit.onIrDatagram(function () {
// This runs every time any IR button is pressed.
// The IR remote sends a 32-bit datagram (address + command + error-check bytes).
// irDatagram() returns the full 32-bit hex code as a string like "0x00FF02FD".
let hex = makerbit.irDatagram();
let buttonCode = makerbit.irButton(); // turns the hex into a number code specifically for the Keyestudio remote. Note this doesn't update if it doesn't recognize a button from the datagram, e.g. if you use an unknown TV remote command
let buttonName = "";
// convert code to a human readable symbol
switch (buttonCode) {
case 98:
buttonName = "^";
case 168:
buttonName = "v";
case 34:
buttonName = "<";
case 194:
buttonName = ">";
case 2:
buttonName = "+";
case 104:
buttonName = "1";
case 152:
buttonName = "2";
case 176:
buttonName = "3";
case 48:
buttonName = "4";
case 24:
buttonName = "5";
case 122:
buttonName = "6";
case 16:
buttonName = "7";
case 56:
buttonName = "8";
case 90:
buttonName = "9";
case 66:
buttonName = "*";
case 74:
buttonName = "0";
case 82:
buttonName = "#";
default:
serial.writeLine("Unknown code:" + buttonCode);
buttonName = " ";
}
serial.writeLine("IR hex: " + hex + " button: " + buttonName + " buttonCode: " + buttonCode);
basic.showString(buttonName);
});IR Remote Reader for TCL TV remote
Reads button presses from a TCL TV remote, which uses NEC protocol. Other remotes may work, but you may need to adjust it with trial and error. Requires the “MakerBit IR Receiver” MakeCode extension (search “makerbit-ir” in Extensions).
/*
Reads button presses from a TCL Roku IR remote control
The micro:bit shows which button was pressed.
MakeCode extension required:
In the MakeCode editor, click "Extensions" and search for "makerbit-ir-receiver".
Add the "MakerBit IR Receiver" extension by 1010Technologies.
Physical setup:
IR receiver S → micro:bit pin 0
IR receiver V → micro:bit 3V
IR receiver G → micro:bit GND
Point the IR remote directly at the receiver module (line of sight).
*/
basic.pause(1000); // --- Setup ---
makerbit.connectIrReceiver(DigitalPin.P0, IrProtocol.NEC); // Connect IR receiver on pin 0 using NEC protocol
basic.showIcon(IconNames.Chessboard);
let lastCommandSection = -1;
makerbit.onIrDatagram(function () {
// This runs every time any IR button is pressed.
// The IR remote sends a 32-bit datagram (address + command + error-check bytes).
// irDatagram() returns the full 32-bit hex code as a string like "0x00FF02FD".
let hex = makerbit.irDatagram();
const commandSection = parseInt(hex.substr(6, 4), 16);
// While a button is held, this remote resends a full frame with the
// command byte +1 and its complement byte -1 (commandSection + 0xFF),
// instead of a bare NEC repeat frame. A genuine new press of any button
// always starts from its own base code, never lastCommandSection + 0xFF,
// so this match alone is enough to detect a held-repeat and ignore it —
// no timeout needed (a timeout is fragile against slow handlers, e.g.
// basic.showString(), which blocks long enough to blow past a short window).
if (commandSection === lastCommandSection + 0xff) {
return;
}
lastCommandSection = commandSection;
let buttonName = "";
// --- Button Decoder ---
// TCL roku remote uses NEC codes (mostly)
switch (hex) {
case "0x57E3E817":
basic.showIcon(IconNames.Skull);
buttonName = "Power";
break;
case "0x57E3F00F":
basic.showIcon(IconNames.EighthNote);
buttonName = "Vol+";
break;
case "0x57E308F7":
basic.showIcon(IconNames.QuarterNote);
buttonName = "Vol-";
break;
case "0x57E304FB":
basic.showIcon(IconNames.No);
buttonName = "Mute";
break;
case "0x57E39867":
basic.showArrow(ArrowNames.North);
buttonName = "Up";
break;
case "0x57E3CC33":
basic.showArrow(ArrowNames.South);
buttonName = "Down";
break;
case "0x57E37887":
basic.showArrow(ArrowNames.West);
buttonName = "Left";
break;
case "0x57E3B44B":
basic.showArrow(ArrowNames.East);
buttonName = "Right";
break;
case "0x57E354AB":
basic.showIcon(IconNames.Yes);
buttonName = "OK";
break;
case "0x57E36699":
basic.showIcon(IconNames.LeftTriangle);
buttonName = "Return";
break;
case "0x57E3C03F":
basic.showIcon(IconNames.House);
buttonName = "Home";
break;
case "0x57E31EE1":
basic.showIcon(IconNames.Ghost);
buttonName = "Replay";
break;
case "0x57E38679":
basic.showIcon(IconNames.Chessboard);
buttonName = "Options";
break;
case "0x57E32CD3":
basic.showArrow(ArrowNames.West);
buttonName = "Rewind";
break;
case "0x57E332CD":
basic.showIcon(IconNames.Triangle);
buttonName = "PlayPause";
break;
case "0x57E3AA55":
basic.showArrow(ArrowNames.East);
buttonName = "FastForward";
break;
case "0x57E34AB5":
basic.showString("N");
buttonName = "Netflix";
break;
case "0x57E3D22D":
basic.showString("A");
buttonName = "Amazon";
break;
case "0x57E30AF5":
basic.showString("C");
buttonName = "CBSnews";
break;
case "0x57E3609F":
basic.showString("S");
buttonName = "Sling";
break;
default:
basic.showIcon(IconNames.Confused);
buttonName = " Unknown (" + hex + ")";
}
// serial.writeLine("IR hex: " + hex + " button: " + buttonName);
serial.writeLine(buttonName);
});IR Remote Transmitter for TCL TV
Increase and decrease volume on TCL brand TV, which uses NEC protocol. Other remotes may work, but you may need to adjust it with trial and error. Requires the “MakerBit IR Receiver” MakeCode extension (search “makerbit-ir” in Extensions) and my extension at https://github.com/steveturbek/pxt-makerbit-ir-transmitter.git (temporary fix for critical bug in makerbit-ir-transmitter).
// tangible_interfaces_ir_remote_transmitter_NEC_TCL
/*
Dedicated TCL Roku TV volume remote.
Button A sends Volume Down, Button B sends Volume Up
using codes captured from the real TCL remote (see ir_remote_reader_NEC_TCL.ts)
Tested and works, but needs to be close to TV. 1 meter for 1 IR transmitter running at 5v, 2 meters for 2 IR
Physical setup:
IR transmitter module has 3 pins labeled S (signal), V (power), G (ground).
Connect signal → micro:bit pin 1
Connect V → micro:bit 3V
Connect G → micro:bit GND
Point the IR transmitter LED at your TV.
*/
/*
use my extension https://github.com/steveturbek/pxt-makerbit-ir-transmitter.git
temporary fix for critical bug in makerbit-ir-transmitter
(I will replace this link when the code is updated)
*/
basic.pause(1000); // --- Setup ---
basic.showIcon(IconNames.Chessboard);
basic.clearScreen();
makerbit.connectIrSenderLed(AnalogPin.P1);
// Codes captured from the real TCL Roku remote via ir_remote_reader_NEC_TCL.ts
const TCL_VOL_DOWN = "0x57E308F7";
const TCL_VOL_UP = "0x57E3F00F";
input.onButtonPressed(Button.A, function () {
serial.writeLine("Vol-");
basic.showIcon(IconNames.EighthNote);
makerbit.sendIrDatagram(TCL_VOL_DOWN);
basic.clearScreen();
});
input.onButtonPressed(Button.B, function () {
serial.writeLine("Vol+");
basic.showIcon(IconNames.QuarterNote);
makerbit.sendIrDatagram(TCL_VOL_UP);
basic.clearScreen();
});LCD Display
Displays text on the classic LCD screen (2 rows of 16 characters) found on microwaves, thermostats, and vending machines.
/*
Displays text and numbers on a 1602 LCD screen (2 rows of 16 characters).
This classic display is on microwaves, thermostats, and vending machines.
Great for showing sensor readings or status messages.
MakeCode extension required:
In the MakeCode editor, click "Extensions"
Search for "makecode-extensions/i2clcd1602".
Add the extension.
Physical setup:
The LCD module has 4 pins. It uses I2C, so it must go on specific pins:
Connect GND → micro:bit GND
Connect VCC → micro:bit 5V (use battery pack — LCD needs 5V for backlight)
Connect SCL → micro:bit pin 19 (I2C clock)
Connect SDA → micro:bit pin 20 (I2C data)
Note: If you have a sensor shield, you can plug into one of the I2C ports.
Tip: if the screen looks blank, adjust the blue potentiometer on the back.
*/
basic.pause(1000); // --- Setup ---
I2C_LCD1602.LcdInit(0); // Address 0 means auto-detect. Try 39 or 63 if auto doesn't work.
I2C_LCD1602.ShowString("Hello!", 0, 0); // text, column, row
I2C_LCD1602.ShowString("micro:bit", 0, 1); // text, column, row
I2C_LCD1602.BacklightOn();
I2C_LCD1602.clear();
let count = 0;
basic.forever(function () {
// --- Main Loop ---
// Show a counter and temperature that update every second
I2C_LCD1602.ShowString("Count: " + count + " ", 0, 0);
I2C_LCD1602.ShowString("Temp: " + input.temperature() + "C ", 0, 1);
serial.writeValue("count", count);
count += 1;
basic.pause(1000);
});LCD Display Graphics
Demonstrates graphics on the classic 1602 LCD screen (2 rows of 16 characters). You can make a sort of graphics bar with characters.
/*
LCD Graphics demo - fills both rows with a dither gradient.
Button B: fill left to right (4 clicks per column, then moves to next column)
Button A: reverse/unfill
This is not very practical, but it shows how to use the LCD's custom character feature to make simple graphics.
MakeCode extension required:
In the MakeCode editor, click "Extensions"
Search for "makecode-extensions/i2clcd1602".
Add the extension.
Physical setup:
The LCD module has 4 pins. It uses I2C, so it must go on specific pins:
Connect GND → micro:bit GND
Connect VCC → micro:bit 5V (use battery pack — LCD needs 5V for backlight)
Connect SCL → micro:bit pin 19 (I2C clock)
Connect SDA → micro:bit pin 20 (I2C data)
Note: If you have a sensor shield, you can plug into one of the I2C ports.
Tip: if the screen looks blank, adjust the blue potentiometer on the back.
*/
basic.pause(1000);
I2C_LCD1602.LcdInit(0);
I2C_LCD1602.BacklightOn();
I2C_LCD1602.clear();
let topRow = "";
topRow += String.fromCharCode(127); //Left Arrow
topRow += "A------------B";
topRow += String.fromCharCode(126);
I2C_LCD1602.ShowString(topRow, 0, 0); //Top row with arrows on the sides
let fillState = 0; // how much the bottom row is filled up
function getChar(level: number): string {
// which character should we use for each space, based on fillState
// 16 columns × 4 steps each = 64 total steps
// 0 = all empty, 64 = all full
if (level <= 0) return " ";
if (level == 1) return ":";
if (level == 2) return "=";
if (level == 3) return "#";
return String.fromCharCode(255); // █ full block character
}
function drawDisplay() {
let row = ""; // the bottom row starts empty,
// we build it up character by character based on fillState
for (let col = 0; col < 16; col++) {
let level = Math.min(4, Math.max(0, fillState - col * 4));
row += getChar(level);
}
I2C_LCD1602.ShowString(row, 0, 1); // draw the bottom row based on fillState
}
input.onButtonPressed(Button.B, function () {
if (fillState < 64) {
fillState += 1;
drawDisplay();
}
});
input.onButtonPressed(Button.A, function () {
if (fillState > 0) {
fillState -= 1;
drawDisplay();
}
});OLED Display
Displays text and numbers on a 0.96” OLED screen (128x64 pixels) —
/*
Displays text and numbers on a 0.96" oled screen (128x64 pixels).
This tiny screen gives you complex visual output. Text, shapes, etc
MakeCode extension required:
In the MakeCode editor, click "Extensions" and search for "pythom1234/pxt-oled".
Physical setup:
The oled module has 4 pins. It uses I2C, so it must go on specific pins:
Connect GND → micro:bit GND
Connect VCC → micro:bit 3V
Connect SCL → micro:bit pin 19 (I2C clock)
Connect SDA → micro:bit pin 20 (I2C data)
Note: If using a shield or breakout board, there may be specific I2C ports.
*/
basic.pause(1000); // --- Setup ---
let count = 0;
oled.init();
basic.forever(function () {
// --- Main Loop ---
count += 1;
// Show a counter that updates every second
oled.clear(false);
let text = "Count: " + count;
oled.drawLine(0, 0, 127, 63, true, false); // top left to bottom right
oled.drawLine(127, 0, 0, 63, true, false); //top right to bottom left
oled.drawRect(0, 0, 127, 63, true, false, false); // screen border box
oled.drawRect(20, 25, 100, 40, false, true, false); // black area behind text
oled.drawText(text, 27, 27, true, false); // add some text
oled.draw(); //nothing is shown till you call draw() — this lets you build up the screen in memory and then update it all at once, which looks smoother.
basic.pause(1000);
});OLED Display with Big Numbers
Displays giant styled numbers on a 0.96” OLED screen, but demonstrates a technique to encode large numbers as bitmap images
/*
Displays text and numbers on a 0.96" OLED screen (128x64 pixels).
This tiny screen gives you real visual output beyond the 5x5 LED grid —
show sensor readings, messages, or simple graphics.
MakeCode extension required:
pythom1234/pxt-oled
Physical setup:
The OLED module has 4 pins. It uses I2C, so it must go on specific pins:
Connect GND → micro:bit GND
Connect VCC → micro:bit 3V
Connect SCL → micro:bit pin 19 (I2C clock)
Connect SDA → micro:bit pin 20 (I2C data)
Note: If using a shield or breakout board, there may be specific I2C ports.
*/
basic.pause(1000); // --- Setup ---
// These are custom number bitmaps generated by tangible-interfaces.com/code/font_to_makecode.html
// Font: Space Mono Bold 700 | Height: 60px. Source Google Fonts
const GLYPH_D0 = images.createImage(
". . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . # # # # # # # # # # # # # # # . . . . . . . . # # # # # # # # # # # # # # . . .\n. . # # # # # # # # # # # # # . . . . . . . . . . . . # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # . . . . . . . . . . . . . . # # # # # # # # # # # # . .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . # # # # # . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . # # # # # # # # . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . # # # # # # # # # # . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . # # # # # # # # # # # # . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . # # # # # # # # # # # # . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . # # # # # # # # # # # # # # . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . # # # # # # # # # # # # # # . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . # # # # # # # # # # # # # # . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . # # # # # # # # # # # # # # . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . # # # # # # # # # # # # . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . # # # # # # # # # # # # . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . # # # # # # # # # # . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . # # # # # # # # . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . # # # # . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. . # # # # # # # # # # # # . . . . . . . . . . . . . . # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # . . . . . . . . . . . . # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # . . . . . . . . # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . . .",
);
const GLYPH_D1 = images.createImage(
". . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . # # # # # # # # # # # . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . # # # # # # # # # # # . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . # # # # # # # # # # # . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . # # # # # # # # # # # . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . # # # # # # # # # # # . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . # # # # # # # # # # # . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . # # # # # # # # # # . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . # # # # # # # # # # # . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . # # # # # # # # # # # . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . # # # # # # # # # # # . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #",
);
const GLYPH_D2 = images.createImage(
". . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # . . . . . . . . . # # # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # .\n. . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # . .\n. . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # . .\n. . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . .\n. . # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . .\n. . # # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # .",
);
const GLYPH_D3 = images.createImage(
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # . . . . . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # . . . . . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # .\n. . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . .",
);
const GLYPH_D4 = images.createImage(
". . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . # # # # # # # # # # # # . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . # # # # # # # # # # # . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . # # # # # # # # # # # # . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . # # # # # # # # # # # . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . # # # # # # # # # # # . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . # # # # # # # # # # # # . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . # # # # # # # # # # # . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . # # # # # # # # # # # # . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . # # # # # # # # # # # . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . # # # # # # # # # # # # . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . # # # # # # # # # # # . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . # # # # # # # # # # # # . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . # # # # # # # # # # # . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . .",
);
const GLYPH_D5 = images.createImage(
". # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . . . # # # # # # # # # # # # . . . . . . . . . . .\n. # # # # # # # # # # # . . . . . . . # # # # # # # # # # # # # # # # . . . . . . . . .\n. # # # # # # # # # # # . . . . . # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. # # # # # # # # # # # . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. # # # # # # # # # # # . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. # # # # # # # # # # # . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. # # # # # # # # # # # . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. # # # # # # # # # # # . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. # # # # # # # # # # # # # # # # # # # # # . . . . . . # # # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # # # # # # # . . . . . . . . . # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # . . . . . . . . . . . . . . # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . . . .",
);
const GLYPH_D6 = images.createImage(
". . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . # # # # # # # # # # # # # # # # . . . . . # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # . .\n. # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . . . # # # # # # # # # # . . . . . . . . . . . .\n# # # # # # # # # # # . . . . . . # # # # # # # # # # # # # # # . . . . . . . . .\n# # # # # # # # # # # . . . . # # # # # # # # # # # # # # # # # # # . . . . . . .\n# # # # # # # # # # # . . . # # # # # # # # # # # # # # # # # # # # # . . . . . .\n# # # # # # # # # # # . . # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n# # # # # # # # # # # . . # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n# # # # # # # # # # # # # # # # # # . . . . . . # # # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # # . . . . . . . . . . . # # # # # # # # # # # # # # .\n# # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n# # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # # . . . . . . . . . # # # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # # # # # . . . # # # # # # # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # . . . . . . . . . . . . . . . .",
);
const GLYPH_D7 = images.createImage(
"# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # .\n. . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . .\n. . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . .\n. . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . .\n. . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . .\n. . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # . . . . .\n. . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . .\n. . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . .\n. . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . .\n. . . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . .\n. . . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . .\n. . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . .\n. . . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . .\n. . . . . . . . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . .\n. . . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . .\n. . . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . .\n. . . . . # # # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . . . . . .",
);
const GLYPH_D8 = images.createImage(
". . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . # # # # # # # # # # # # # # # # # . . . . # # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # . .\n. . # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. . # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. . # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. . # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # . .\n. . # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # . .\n. . # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # . .\n. . # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # . . .\n. . . . # # # # # # # # # # # # # # # . . . . # # # # # # # # # # # # # # # . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . # # # # # # # # # # # # # # . . . . . . . . # # # # # # # # # # # # # # . . .\n. . # # # # # # # # # # # # # . . . . . . . . . . . . # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # .\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # # #\n. # # # # # # # # # # # # # . . . . . . . . . . . . . . # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # . . . . . . . . . . . . # # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # # # # # # . . . . # # # # # # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # # # . . . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # # # . . . . . . . . . . . . . . .",
);
const GLYPH_D9 = images.createImage(
". . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # # # . . . . . # # # # # # # # # # # # # # # # . .\n. . # # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # # .\n. # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n# # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # #\n. # # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # #\n. # # # # # # # # # # # # # # . . . . . . . . . . . # # # # # # # # # # # # # # #\n. . # # # # # # # # # # # # # # # # . . . . . # # # # # # # # # # # # # # # # # #\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # . . # # # # # # # # # # #\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # . . # # # # # # # # # # #\n. . . . . . . # # # # # # # # # # # # # # # # # # # # . . . # # # # # # # # # # #\n. . . . . . . . # # # # # # # # # # # # # # # # # # . . . . # # # # # # # # # # #\n. . . . . . . . . . # # # # # # # # # # # # # # # . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . # # # # # # # # # # # . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . . # # # # # # # # # # #\n. # # # # # # # # # # # . . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . . # # # # # # # # # # # #\n. # # # # # # # # # # # # . . . . . . . . . . . . . . . # # # # # # # # # # # # .\n. . # # # # # # # # # # # # . . . . . . . . . . . . . # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # . . . . . . . . . . # # # # # # # # # # # # # # # .\n. . # # # # # # # # # # # # # # # # # . . . # # # # # # # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . .\n. . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . .\n. . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . .\n. . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . .\n. . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . .\n. . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . .\n. . . . . . . . # # # # # # # # # # # # # # # # # # # # # # # # # . . . . . . . .\n. . . . . . . . . . # # # # # # # # # # # # # # # # # # # # # . . . . . . . . . .\n. . . . . . . . . . . . # # # # # # # # # # # # # # # # # . . . . . . . . . . . .\n. . . . . . . . . . . . . . . . # # # # # # # # # # . . . . . . . . . . . . . . .",
);
const FONT: { [key: string]: Image } = {
"0": GLYPH_D0,
"1": GLYPH_D1,
"2": GLYPH_D2,
"3": GLYPH_D3,
"4": GLYPH_D4,
"5": GLYPH_D5,
"6": GLYPH_D6,
"7": GLYPH_D7,
"8": GLYPH_D8,
"9": GLYPH_D9,
};
function drawChar(char: string, x: number, y: number): number {
const img = FONT[char];
if (!img) return x;
oled.drawImage(img, x, y, true, false, false);
return x + img.width() + 2;
}
function drawString(text: string, x: number, y: number): void {
let cursor = x;
for (let i = 0; i < text.length; i++) {
cursor = drawChar(text.charAt(i), cursor, y);
}
}
// Usage:
oled.init();
oled.clear(false);
oled.draw();
let count = 0;
// --- Main Loop ---
// Show a counter from 0 to 59 that updates every second
basic.forever(function () {
oled.clear(false);
// drawString("" + count, 0, 2); //simple left aligned text
// centered text
const text = "" + count;
const glyphWidth = GLYPH_D0.width();
const totalWidth = text.length * glyphWidth + (text.length - 1) * 2; // 2px spacing
const x = (128 - totalWidth) / 2;
drawString(text, x, 2);
oled.draw();
count = (count + 1) % 60;
basic.pause(1000);
});OLED Display ‘Flappy Bird’ game
A simple version of the classic game ‘flappy bird’
/*
Flappy Bird on a 128x64 OLED display.
Press A to flap. Avoid the walls. Score goes up each wall you pass.
Press A on game over screen to restart.
MakeCode extension required:
In the MakeCode editor, click "Extensions" and search for:
https://github.com/Pythom1234/pxt-oled
Physical setup:
Connect OLED GND → micro:bit GND
Connect OLED VCC → micro:bit 3V
Connect OLED SCL → micro:bit pin 19
Connect OLED SDA → micro:bit pin 20
*/
const BIRD_X = 20;
const BIRD_SIZE = 5;
const GRAVITY = 1;
const FLAP_STRENGTH = -8;
const WALL_WIDTH = 8;
const GAP_SIZE = 32;
const WALL_SPEED = 2;
const SCREEN_W = 128;
const SCREEN_H = 64;
let birdY = 0;
let birdVel = 0;
let wallX = 0;
let gapY = 0;
let score = 0;
let alive = false;
input.onButtonPressed(Button.A, function () {
// --- Button ---
if (alive) {
birdVel = FLAP_STRENGTH;
music.play(music.tonePlayable(880, music.beat(BeatFraction.Sixteenth)), music.PlaybackMode.InBackground);
} else {
startGame();
}
});
function startGame() {
birdY = SCREEN_H / 2;
birdVel = 0;
wallX = SCREEN_W;
gapY = randint(6, SCREEN_H - GAP_SIZE - 6);
score = 0;
alive = true;
}
function checkCollision(): boolean {
// Bird is a square from (BIRD_X, birdY) to (BIRD_X+BIRD_SIZE, birdY+BIRD_SIZE)
// Wall spans wallX to wallX+WALL_WIDTH, with gap from gapY to gapY+GAP_SIZE
const birdRight = BIRD_X + BIRD_SIZE;
const birdBottom = birdY + BIRD_SIZE;
const wallRight = wallX + WALL_WIDTH;
// Check horizontal overlap
if (birdRight <= wallX || BIRD_X >= wallRight) return false;
// Check vertical overlap with either wall segment
const hitTopWall = birdY < gapY;
const hitBottomWall = birdBottom > gapY + GAP_SIZE;
return hitTopWall || hitBottomWall;
}
oled.init(); // --- Setup ---
oled.clear(false);
oled.drawText("FLAPPY BIRD", 18, 20, true, false);
oled.drawText("Press A to start", 4, 40, true, false);
oled.draw();
basic.forever(function () {
// --- Main Loop ---
if (alive) {
// Physics
birdVel += GRAVITY;
birdY += birdVel;
// Hit ceiling or floor
if (birdY <= 0) {
birdY = 0;
birdVel = 0;
}
if (birdY + BIRD_SIZE >= SCREEN_H) {
alive = false;
}
// Move wall left; reset when off screen
wallX -= WALL_SPEED;
if (wallX + WALL_WIDTH < 0) {
wallX = SCREEN_W;
gapY = randint(6, SCREEN_H - GAP_SIZE - 6);
score += 1;
music.play(music.tonePlayable(523, music.beat(BeatFraction.Sixteenth)), music.PlaybackMode.InBackground);
}
// Collision check
if (checkCollision()) {
alive = false;
music.play(music.tonePlayable(131, music.beat(BeatFraction.Quarter)), music.PlaybackMode.UntilDone);
}
// Draw frame
oled.clear(false);
// Draw wall: top segment above gap
if (gapY > 0) {
oled.drawRect(wallX, 0, wallX + WALL_WIDTH - 1, gapY - 1, true, true, false);
}
// Draw wall: bottom segment below gap
if (gapY + GAP_SIZE < SCREEN_H) {
oled.drawRect(wallX, gapY + GAP_SIZE, wallX + WALL_WIDTH - 1, SCREEN_H - 1, true, true, false);
}
// Draw bird
oled.drawRect(BIRD_X, birdY, BIRD_X + BIRD_SIZE - 1, birdY + BIRD_SIZE - 1, true, true, false);
// Draw score (top-left)
oled.drawText("" + score, 2, 2, true, false);
oled.draw();
} else {
// Game over screen
oled.clear(false);
oled.drawText("GAME OVER", 22, 16, true, false);
oled.drawText("Score: " + score, 30, 32, true, false);
oled.drawText("Press A", 34, 48, true, false);
oled.draw();
}
basic.pause(50);
});Bluetooth & Radio
The microbit has surprisingly powerful Bluetooth and radio functionality built-in.
Wireless Radio Network
Microbit has a proprietary wireless protocol that only talks to other micro:bits — no pairing, no phone, no browser. It’s fast and simple, but just blasts a message to anyone on the same “group”, like a walkie-talkie channel. It takes two devices to really demonstrate the functionality, but one can make a fun social network game with a class. Radio uses the same hardware as the bluetooth, so you can use either radio or bluetooth, but not both.
// Simple wireless messaging between two micro:bits using the radio.
// Press button A to send a message; received messages display on the LED screen.
/*
More information at https://makecode.microbit.org/reference/radio
*/
// --- Setup ---
radio.setGroup(1); //channels from 0-255
// --- Event Handlers ---
// Sends a message when button A is pressed
input.onButtonPressed(Button.A, function () {
radio.sendString("Hi! from " + control.deviceName());
});
// Runs when a radio message is received
radio.onReceivedString(function (receivedString: string) {
serial.writeLine(receivedString);
basic.showString(receivedString);
});Bluetooth to Microbit App
The microbit app (iOS/Android) has a “Monitor and Control” section. If you pair the microbit with the phone, you can do two-way communication over Bluetooth: In theory, you could write a completely new iOS app using Bluetooth to integrate the Micro:bit this way. This project is really a demonstration of the built-in microbit bluetooth services, rather than something useful in itself.
- The LED service means the microbit sends the state of the LEDs to the phone, and the phone can turn them on or off.
- The Button Service sends the state of the buttons and the phone can activate the buttons.
- The IO Pin Service reads and sets the state of the pins. It could be useful when prototyping to monitor the micro:bit’s pins and read/write it from a phone without needing to reprogram it.
Search for and add the basic “bluetooth” extension (this will remove the radio extension).
/*
Connect Microbit to the Microbit App (iOS/android) via bluetooth
This program is really a demonstration of the built-in microbit bluetooth services, rather than something useful in itself.
Search for and add the basic "bluetooth" extension (this will remove the radio extension).
The microbit app has a "Monitor and Control" section. If you pair the microbit with the phone, you can do 2 way communication over bluetooth.
This program demonstrates that the microbit and app can send each other signals.
The LED service means the microbit sends the state of the LEDs to the phone, and the phone can turn them on or off.
The Button Service sends the state of the buttons and the phone can activate them.
The IO Pin Service reads and sets the state of the pins. It could be useful when prototyping to monitor the micro:bit's pins and read/write it from a phone without needing to reprogram it.
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
bluetooth.startButtonService(); // https://makecode.microbit.org/reference/bluetooth/start-button-service
bluetooth.startLEDService(); //https://makecode.microbit.org/reference/bluetooth/start-led-service
bluetooth.startIOPinService(); //https://makecode.microbit.org/reference/bluetooth/start-io-pin-service
input.onButtonPressed(Button.A, function () {
// turn all LEDs off
basic.clearScreen();
});
input.onButtonPressed(Button.B, function () {
// toggle all LEDs
for (let x = 0; x < 5; x++) {
for (let y = 0; y < 5; y++) {
led.toggle(x, y);
}
}
});
Bluetooth to Browser
This microbit program demonstrates sending messages back and forth between microbit and browser running this custom webpage. you could extend this code to make a physical joystick for a web based game, for example.
Search for and add the basic “bluetooth” extension (this will remove the radio extension).
Notes
- In the Makecode Project Settings, set Bluetooth to No Pairing Required.
- Only works in Chrome and other Chromium browsers; not in Safari, Firefox, or any browser on iOS.
/*
This microbit program demonstrates sending messages back and forth between microbit and browser running tangible.turbek.com/examples_microbit/bluetooth_webpage.html
You could extend this code to make a physical joystick for a web based game, for example.
Search for and add the "bluetooth" extension (this will remove the radio extension).
NOTES
- In the Makecode Project Settings, set Bluetooth to **No Pairing Required**.
- Only works in Chrome and other Chromium browsers; not in Safari, Firefox, or any browser on iOS.
The Bluetooth UART service allows another device such as a smartphone to exchange any data it wants to with the micro:bit
https://makecode.microbit.org/reference/bluetooth/start-uart-service
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
bluetooth.startUartService();
bluetooth.setTransmitPower(4); //0 is min - 7 is max
bluetooth.onBluetoothConnected(function () {
basic.showIcon(IconNames.Yes);
bluetooth.uartWriteLine("Hello from Microbit.");
});
bluetooth.onBluetoothDisconnected(function () {
basic.showIcon(IconNames.No);
});
bluetooth.onUartDataReceived(serial.delimiters(Delimiters.NewLine), function () {
const data = bluetooth.uartReadUntil(serial.delimiters(Delimiters.NewLine));
basic.showString(data);
});
input.onButtonPressed(Button.A, function () {
bluetooth.uartWriteLine("Button A was pressed");
basic.showString("A");
});
input.onButtonPressed(Button.B, function () {
bluetooth.uartWriteLine("Button B was pressed");
basic.showString("B");
});Bluetooth Music Remote with Rotary Encoder
A micro:bit can pretend to be a Bluetooth keyboard, mouse, gamepad, or media remote and your laptop or phone won’t know the difference.
This program acts as a media remote. Bluetooth pair it with a computer or phone, and every microbit events can be sent as real media-key command — play/pause, volume, skip track — that Spotify, YouTube, etc; the operating system already has a driver for “Bluetooth remote,” and this project just becomes one.
This project uses the cool microbit-pxt-blehid extension.
This is a fun project; everyone loves music and coming up with a unique remote control is a fun design challenge. Project details
/*
A micro:bit can pretend to be a Bluetooth keyboard, mouse, gamepad, or media remote and your laptop or phone won't know the difference.
This program acts as a media remote. Bluetooth pair it with a computer or phone, and every microbit events can be sent as real media-key command — play/pause, volume, skip track — that Spotify, YouTube, etc; the operating system already has a driver for "Bluetooth remote," and this project just becomes one.
This project uses the the microbit-pxt-blehid extension by Bill Siever. A Human Interface Device (HID) is Operating System code thats sends keyboard or media-key input to software listening to it, like Spotify.
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
let last_action_time = 0;
let current_display = "";
let display_timer = 0;
const DEBOUNCE_MS = 100; // Debounce for encoder (media controls need more time)
const DISPLAY_CLEAR_MS = 800; // How long to show feedback
RotaryEncoder.onRotateEvent(RotationDirection.Left, function () {
let now = input.runningTime();
if (now - last_action_time > DEBOUNCE_MS) {
media.sendCode(media.keys(media._MediaKey.vol_up));
current_display = "vol_up";
display_timer = now;
basic.showArrow(ArrowNames.North);
last_action_time = now;
}
});
RotaryEncoder.onRotateEvent(RotationDirection.Right, function () {
let now = input.runningTime();
if (now - last_action_time > DEBOUNCE_MS) {
media.sendCode(media.keys(media._MediaKey.vol_down));
current_display = "vol_down";
display_timer = now;
basic.showArrow(ArrowNames.South);
last_action_time = now;
}
});
RotaryEncoder.onPressEvent(function () {
let now = input.runningTime();
if (now - last_action_time > DEBOUNCE_MS) {
media.sendCode(media.keys(media._MediaKey.playPause));
current_display = "play_pause";
display_timer = now;
basic.showIcon(IconNames.Yes);
last_action_time = now;
}
});
input.onButtonPressed(Button.A, function () {
let now = input.runningTime();
media.sendCode(media.keys(media._MediaKey.previous));
current_display = "prev";
display_timer = now;
basic.showArrow(ArrowNames.West);
last_action_time = now;
});
input.onButtonPressed(Button.B, function () {
let now = input.runningTime();
media.sendCode(media.keys(media._MediaKey.next));
current_display = "next";
display_timer = now;
basic.showArrow(ArrowNames.East);
last_action_time = now;
});
media.startMediaService();
RotaryEncoder.init(DigitalPin.P2, DigitalPin.P1, DigitalPin.P0);
basic.forever(function () {
let now = input.runningTime();
// Clear display after inactivity
if (current_display !== "" && now - display_timer > DISPLAY_CLEAR_MS) {
basic.clearScreen();
current_display = "";
}
basic.pause(50);
});Bluetooth Flying Mouse
This program turns the microbit into a flying remote. Bluetooth pair it with a computer or phone, and tilt the microbit like an airplane to steer the mouse. Button A is left-click, Button B is right-click.
This project uses the cool microbit-pxt-blehid extension.
To implement the flying mouse:
- Program microbit
- In computer’s Bluetooth settings, select uBit[NAME] and connect
- Fly the cursor around!
/*
A micro:bit can pretend to be a Bluetooth keyboard, mouse, gamepad, or media remote and your laptop or phone won't know the difference.
This program acts as a mouse for your computer. Bluetooth pair it with a computer or phone, and tilt to steer the mouse. Button A acts as left click, Button B acts as right click
This project uses the the microbit-pxt-blehid extension by Bill Siever. A Human Interface Device (HID) is Operating System code thats sends keyboard or media-key input to software listening to it, like Spotify.
To use:
- Program microbit
- In computer's Bluetooth settings, select uBit[NAME] and connect
- Fly the cursor around!
Note: it will continue to reconnect, you may need to "forget device"
*/
// --- Setup ---
basic.pause(1000);
basic.showIcon(IconNames.Chessboard);
mouse.startMouseService();
input.onButtonPressed(Button.A, function () {
mouse.click();
});
input.onButtonPressed(Button.B, function () {
mouse.rightClick();
});
basic.forever(function () {
// measure the microbit's tilt to steer the mouse
// acceleration with the milli-g, which is 1/1000 of a g. A g is as much acceleration as you get from Earth's gravity
// When the micro:bit is lying flat on a surface with the screen pointing up, x is 0, y is 0, z is -1023
// normalize acceleration to a useful amount of mouse movement
let mouse_x = Math.round(Math.max(Math.min(input.acceleration(Dimension.X) / 10, 100), -100));
let mouse_y = Math.round(Math.max(Math.min(input.acceleration(Dimension.Y) / 10, 100), -100));
// Move the mouse by the given amounts in the x (horizontal) and y (vertical) directions. x can be from -127 to 127. y can be from -127 to 127.
mouse.movexy(mouse_x, mouse_y);
serial.writeLine("x:" + mouse_x + " y:" + mouse_y);
basic.pause(100); // update no more than 10 times per second
});Advanced programs
These are programs with the same components, but more complex concepts and techniques.
MP3 player with DFplayer mini
This board enables you to play MP3s from an SD card you can load from your laptop. It has some limitations: mono not stereo sound and you can’t access the MP3 song names and data, but fun demo.
// Demonstrates the DF player component
// uses dfplayermini extension https://makecode.microbit.org/pkg/51bit/dfplayerminimini
// Wiring:
// dfplayermini TX -> micro:bit P1
// dfplayermini RX -> micro:bit P0
// dfplayermini VCC -> 3.3V
// dfplayermini GND -> GND
// Speaker -> dfplayermini SPK_1 and SPK_2
//
// SD card setup: put MP3s in folder /01/ named 001.mp3, 002.mp3, etc.
const TOTAL_TRACKS = 5;
const VOLUME = 15; // 0–30
let currentTrack = 1;
let playing = false;
// initialize on P0 (RX from dfplayermini) and P1 (TX to dfplayermini)
dfplayermini.connect(SerialPin.P0, SerialPin.P1);
basic.pause(1000); // wait for module to boot
dfplayermini.setVolume(VOLUME);
basic.showIcon(IconNames.Heart);
// Button A: play next track (wraps around)
input.onButtonPressed(Button.A, function () {
dfplayermini.press(dfplayermini.playType.PlayNext);
playing = true;
basic.showNumber(currentTrack);
currentTrack = currentTrack >= TOTAL_TRACKS ? 1 : currentTrack + 1;
});
// Button B: toggle pause / resume
input.onButtonPressed(Button.B, function () {
if (playing) {
dfplayermini.press(dfplayermini.playType.Pause);
playing = false;
basic.showIcon(IconNames.No);
} else {
dfplayermini.press(dfplayermini.playType.Play);
playing = true;
basic.showIcon(IconNames.Yes);
}
});
// A + B: stop and reset to track 1
input.onButtonPressed(Button.AB, function () {
dfplayermini.press(dfplayermini.playType.Stop);
playing = false;
currentTrack = 1;
basic.showLeds(`
. # . # .
# . # . #
. # . # .
# . # . #
. # . # .
`);
});Soil Moisture Sensor with Data Smoothing
A soil moisture sensor demonstrates why hardware sensors need data smoothing — soil moisture sensors are notoriously noisy due to electrical interference, oxidation on the probes, and capacitive effects. This demo shows three techniques: range filtering, moving average, and calibrated mapping to a stable 0–100% moisture reading.
/*
Soil Moisture Sensor — with Data Smoothing
Soil moisture sensors are notoriously noisy: electrical interference in the
soil, oxidation on the metal probes, and capacitive effects all cause random
spikes. This makes them a perfect real-world case for data smoothing.
Wiring:
S (signal) → micro:bit pin P0
V (power) → micro:bit 3V
G (ground) → micro:bit GND
Insert the metal prongs into soil (or dip in water to test)
Try it:
- Hold the probes in dry air → low values, stable
- Press fingers across both probes → values spike from skin conductance
- Dip in water → high values, but noisy
- Watch the raw vs smoothed values in the serial — smoothed stays calm
This demo shows three techniques, applied in order:
1. Range filtering — reject physically impossible values
2. Moving average — smooth out jitter using a rolling history
3. Calibrated map — convert smoothed values to a stable 0–100% moisture
HOW TO CALIBRATE:
Run the sensor and watch "raw_sensor_value" in the serial.
Hold probes in dry air → note the value → set SENSOR_MIN
Dip probes in water → note the value → set SENSOR_MAX
*/
basic.pause(1000); // skips the flash when programming
basic.showIcon(IconNames.Chessboard);
// --- Calibration (update these after observing your sensor) ---
const SENSOR_MIN = 50; // probes in dry air
const SENSOR_MAX = 750; // probes submerged in water
// --- Moving Average Setup ---
let readings_history: number[] = []; // A history of the last N readings.
const readings_history_size = 5; // how many readings to average together (the "window size")
/*
Larger readings_history_size = smoother but slower to respond when input changes.
2 = very responsive, still a bit jittery
5 = good balance (default)
10 = very smooth, but lags behind fast changes
*/
// Fill the history with a midpoint so the first output isn't garbage
let midpoint = Math.round((SENSOR_MIN + SENSOR_MAX) / 2);
for (let i = 0; i < readings_history_size; i++) {
readings_history.push(midpoint);
}
// --- Output Variables ---
let raw_sensor_value = 0;
let smoothed_average_input_value = midpoint;
let value_as_percent = 50;
// --- Main Loop ---
basic.forever(function () {
basic.pause(100);
raw_sensor_value = pins.analogReadPin(AnalogPin.P0);
// Step 1: Range filter — ignore readings outside the possible sensor range
if (raw_sensor_value < SENSOR_MIN || raw_sensor_value > SENSOR_MAX) {
// we will ignore this reading from average, but still output it so you can see the noise in the serial
serial.writeLine("raw_sensor_value=" + raw_sensor_value + " [ out of " + SENSOR_MIN + "-" + SENSOR_MAX + " range]");
return; // skip the rest of the loop and wait for the next reading
}
// Step 2: Moving average — add new reading to end, drop oldest from front
readings_history.push(raw_sensor_value);
if (readings_history.length > readings_history_size) {
readings_history.shift();
}
let sum = 0;
for (let i = 0; i < readings_history.length; i++) {
sum += readings_history[i];
}
smoothed_average_input_value = Math.round(sum / readings_history.length); // average of the history
// Step 3: Map smoothed value to 0–100% moisture
value_as_percent = Math.round(Math.map(smoothed_average_input_value, SENSOR_MIN, SENSOR_MAX, 0, 100));
value_as_percent = Math.max(0, Math.min(100, value_as_percent)); // clamp to 0–100
led.plotBarGraph(value_as_percent, 100);
serial.writeLine("raw_sensor_value=" + raw_sensor_value + " smoothed=" + smoothed_average_input_value + " moisture%=" + value_as_percent);
});Servo sonar with smoothing
Controls a servo motor based on an ultrasonic distance sensor reading, with data smoothing applied to prevent jittery movement.
/*
Servo + Sonar Smoothing — Controlling a Servo with a Distance Sensor
Requires the 'servo' and 'microsoft/pxt-sonar' extensions.
Raw sonar readings are noisy — without smoothing, the servo will jitter
even when nothing is moving. Same three techniques as analog_data_smoothing:
1. Range filtering — reject physically impossible distances
2. Moving average — smooth out jitter using a rolling history
3. Calibrated map — convert smoothed distance to a stable servo angle
Physical setup:
- Sonar: P0 = trigger, P1 = echo
- Servo: P2
HOW TO CALIBRATE:
Run the sensor and watch "raw_sensor_value" in the serial.
Hold something at your closest useful distance → note the value → set SENSOR_MIN
Hold something at your farthest useful distance → note the value → set SENSOR_MAX
*/
basic.pause(1000); // skips the flash when programming
basic.showIcon(IconNames.Chessboard);
// --- Calibration (update these after observing your sensor) ---
const SENSOR_MIN = 2; // closest useful distance in cm
const SENSOR_MAX = 40; // farthest useful distance in cm
// --- Servo Setup ---
servos.P2.setRange(0, 180);
// --- Moving Average Setup ---
let readings_history: number[] = []; // A history of the last N readings.
const readings_history_size = 5; // how many readings to average together (the "window size")
/*
Larger readings_history_size = smoother but slower to respond when input changes.
2 = very responsive, still a bit jittery
5 = good balance (default)
10 = very smooth, but lags behind fast changes
*/
// Fill the history with a midpoint so the first output isn't garbage
let midpoint = Math.round((SENSOR_MIN + SENSOR_MAX) / 2);
for (let i = 0; i < readings_history_size; i++) {
readings_history.push(midpoint);
}
// --- Output Variables ---
let raw_sensor_value = 0;
let smoothed_average_input_value = midpoint;
let servo_angle = 90;
// --- Main Loop ---
basic.forever(function () {
basic.pause(100);
raw_sensor_value = sonar.ping(DigitalPin.P0, DigitalPin.P1, PingUnit.Centimeters);
// Step 1: Range filter — ignore readings outside the possible sensor range
if (raw_sensor_value < SENSOR_MIN || raw_sensor_value > SENSOR_MAX) {
// we will ignore this reading from average, but still output it so you can see the noise in the serial
serial.writeLine("raw_sensor_value=" + raw_sensor_value + " [ out of " + SENSOR_MIN + "-" + SENSOR_MAX + " range]");
return;
}
// Step 2: Moving average — add new reading to end, drop oldest from front
readings_history.push(raw_sensor_value);
if (readings_history.length > readings_history_size) {
readings_history.shift();
}
let sum = 0;
for (let i = 0; i < readings_history.length; i++) {
sum += readings_history[i];
}
smoothed_average_input_value = Math.round(sum / readings_history.length); // average of the history
// Step 3: Map smoothed distance to servo angle
servo_angle = Math.round(Math.map(smoothed_average_input_value, SENSOR_MIN, SENSOR_MAX, 0, 180));
servo_angle = Math.max(0, Math.min(180, servo_angle)); // clamp to valid servo range
servos.P2.setAngle(servo_angle);
led.plotBarGraph(servo_angle, 180);
serial.writeLine("raw_sensor_value=" + raw_sensor_value + " smoothed=" + smoothed_average_input_value + " servo_angle=" + servo_angle);
});Servo sonar with blended average smoothing
Controls a servo motor based on an ultrasonic distance sensor reading, with data smoothing applied to prevent jittery movement. This example uses blended average smoothing (also called Exponential Moving Average): instead of storing the last N readings, it keeps a single running value and nudges it toward each new reading by a fixed fraction. Less code, less memory — but the tradeoff is it responds a bit faster to change than the history approach.
/*
Servo + Sonar Smoothing — EMA (Exponential Moving Average) version
Same goal as servo_sonar_smoothing.ts, but uses EMA instead of a history array.
EMA keeps a running average in a single variable by blending each new reading
into the previous average using a smoothing factor (ALPHA).
Physical setup:
- Sonar: P0 = trigger, P1 = echo
- Servo: P2
Same three techniques:
1. Range filtering — reject physically impossible distances
2. EMA smoothing — blend new reading into running average
3. Calibrated map — convert smoothed distance to a stable servo angle
HOW TO CALIBRATE:
Run the sensor and watch "raw_sensor_value" in the serial.
Hold something at your closest useful distance → note the value → set SENSOR_MIN
Hold something at your farthest useful distance → note the value → set SENSOR_MAX
*/
basic.pause(1000); // skips the flash when programming
basic.showIcon(IconNames.Chessboard);
// --- Calibration (update these after observing your sensor) ---
const SENSOR_MIN = 2; // closest useful distance in cm
const SENSOR_MAX = 40; // farthest useful distance in cm
// --- EMA Setup ---
// ALPHA controls how much each new reading influences the average.
// Low ALPHA = smooth but slow to react. High ALPHA = fast but jittery.
// 0.1 = very smooth, slow to follow fast movement
// 0.2 = good balance (default)
// 0.5 = responsive, but less smoothing
const ALPHA = 0.2;
// --- Servo Setup ---
servos.P2.setRange(0, 180);
// --- Output Variables ---
let raw_sensor_value = 0;
// Start at midpoint so the first output isn't garbage
let smoothed_average_input_value = Math.round((SENSOR_MIN + SENSOR_MAX) / 2);
let servo_angle = 90;
// --- Main Loop ---
basic.forever(function () {
basic.pause(100);
raw_sensor_value = sonar.ping(DigitalPin.P0, DigitalPin.P1, PingUnit.Centimeters);
// Step 1: Range filter — ignore readings outside the possible sensor range
if (raw_sensor_value < SENSOR_MIN || raw_sensor_value > SENSOR_MAX) {
// we will ignore this reading from average, but still output it so you can see the noise in the serial
serial.writeLine("raw_sensor_value=" + raw_sensor_value + " [ out of " + SENSOR_MIN + "-" + SENSOR_MAX + " range]");
return;
}
// Step 2: EMA — blend new reading into running average
// Each new reading nudges the average by a fraction (ALPHA) of the difference.
// The further the new reading is from the average, the bigger the nudge.
smoothed_average_input_value = Math.round(smoothed_average_input_value + ALPHA * (raw_sensor_value - smoothed_average_input_value));
// Step 3: Map smoothed distance to servo angle
servo_angle = Math.round(Math.map(smoothed_average_input_value, SENSOR_MIN, SENSOR_MAX, 0, 180));
servo_angle = Math.max(0, Math.min(180, servo_angle)); // clamp to valid servo range
servos.P2.setAngle(servo_angle);
led.plotBarGraph(servo_angle, 180);
serial.writeLine("raw_sensor_value=" + raw_sensor_value + " smoothed=" + smoothed_average_input_value + " servo_angle=" + servo_angle);
});Flappy pixel
A minimal version of Flappy Bird on the micro:bit 5x5 LED screen. Press button A to flap upward; avoid the walls scrolling toward you.
// A minimal version of Flappy Bird on the micro:bit 5x5 LED screen.
// Press button A to flap upward; avoid the walls scrolling toward you.
// --- Event Handlers ---
// Runs when button A is pressed - flap the bird upward
input.onButtonPressed(Button.A, function () {
birdY += -1;
if (birdY < 0) {
birdY = 0;
}
});
// --- Setup ---
let birdY = 0;
basic.clearScreen();
let score = 0;
birdY = 0;
let wallX = 5;
let WallHoleY = 2;
// --- Main Loop ---
basic.forever(function () {
basic.clearScreen();
if (wallX < 0) {
wallX = 4;
WallHoleY = randint(0, 4);
}
for (let index = 0; index <= 4; index++) {
led.plotBrightness(wallX, index, 28);
}
led.unplot(wallX, WallHoleY);
if (birdY > 4) {
birdY = 4;
}
led.plot(0, birdY);
if (wallX == 0) {
if (birdY == WallHoleY) {
music.play(music.tonePlayable(523, music.beat(BeatFraction.Quarter)), music.PlaybackMode.UntilDone);
} else {
music.play(music.tonePlayable(131, music.beat(BeatFraction.Quarter)), music.PlaybackMode.UntilDone);
}
}
wallX += -1;
birdY += 1;
basic.pause(1000);
});Surveyor
Using the accelerometer to make the compass more accurate
// Surveyor Game
// The microbit uses compass to point, starting north
// When A button is pressed, the angle is shifted 22.5 degrees counter clockwise
// When B button is pressed, the angle is shifted 22.5 degrees clockwise
// When A and B button are pressed, the current compass angle is recorded
// the outside LED closest to the correct compass direction is lit with a dim light
// when the device is orientated in the correct direction. the top, center LED glows at full power
// when the accelerometer is close to level, it shows a dim dot in center of LED
// otherwise, the dim LED should m
basic.pause(1000); // brief pause avoids LED flash when programming
basic.showIcon(IconNames.Chessboard);
serial.redirectToUSB();
serial.writeLine("=== Start Surveyor ===");
music.play(music.tonePlayable(262, music.beat(BeatFraction.Sixteenth)), music.PlaybackMode.UntilDone); // startup tone
input.calibrateCompass(); // must be done before compassHeading() is reliable — tilt board around when prompted
let desiredCompassHeading = 0; // default to North
let bubbleX = 2; // centered
let bubbleY = 2; // centered
let bubbleAccuracy = 300; // how flat microbit needs to be
// 16 outer LEDs clockwise from top-center (N), one per 22.5°
let ringX = [2, 3, 4, 4, 4, 4, 4, 3, 2, 1, 0, 0, 0, 0, 0, 1];
let ringY = [0, 0, 0, 1, 2, 3, 4, 4, 4, 4, 4, 3, 2, 1, 0, 0];
basic.forever(function () {
// --- Main Loop ---
bubbleX = 2;
bubbleY = 2;
basic.clearScreen();
if (input.acceleration(Dimension.X) > bubbleAccuracy) {
bubbleX = 3;
} else if (input.acceleration(Dimension.X) < -bubbleAccuracy) {
bubbleX = 1;
}
if (input.acceleration(Dimension.Y) > bubbleAccuracy) {
bubbleY = 3;
} else if (input.acceleration(Dimension.Y) < -bubbleAccuracy) {
bubbleY = 1;
}
if (bubbleX == 2 && bubbleY == 2) led.plotBrightness(bubbleX, bubbleY, 255);
else led.plotBrightness(bubbleX, bubbleY, 40);
// light up the outer ring LED pointing toward the desiredCompassHeading.
// If desiredCompassHeading = input.compassHeading(), light up led.plotBrightness(2, 0, 255)
let relativeHeading = (desiredCompassHeading - input.compassHeading() + 360) % 360;
let step = Math.round(relativeHeading / 22.5) % 16;
led.plotBrightness(ringX[step], ringY[step], step === 0 ? 255 : 10);
if (step === 0 && bubbleX == 2 && bubbleY == 2) {
// centered and level, make a visible +
for (let x = 0; x < 5; x++) {
led.plotBrightness(x, 2, 100);
}
for (let y = 0; y < 5; y++) {
led.plotBrightness(2, y, 100);
}
led.plotBrightness(1, 1, 100);
led.plotBrightness(3, 1, 100);
}
serial.writeLine(
"desiredCompassHeading=" +
desiredCompassHeading +
" currentCompass=" +
input.compassHeading() +
" X=" +
input.acceleration(Dimension.X) +
" bubbleX=" +
bubbleX +
" Y=" +
input.acceleration(Dimension.Y) +
" bubbleY=" +
bubbleY,
);
basic.pause(100);
});
input.onButtonPressed(Button.A, function () {
desiredCompassHeading = Math.max(0, desiredCompassHeading - 22.5);
});
input.onButtonPressed(Button.B, function () {
desiredCompassHeading = Math.min(360, desiredCompassHeading + 22.5);
});
input.onButtonPressed(Button.AB, function () {
desiredCompassHeading = input.compassHeading();
});MakeCode Data Logging
The Microbit MakeCode editor has built-in data logging that saves to the microbit’s flash memory. You can log data, then download it as a CSV file when you connect the Microbit to your computer via USB. For (much) more storage capacity, we can add a SD card module (~$5) via SPI pins. Code adapted from microbit.org Environmental Data Logger
/*
A data logger that records temperature and light levels once per minute
to the micro:bit's built-in flash storage.
When the Microbit is plugged into a computer, the log can be accessed like a USB drive.
If you open MY_DATA.HTM in a web browser, you can view the log, make a graph
Each reading is timestamped from when the program started, so note when you start the logger
You can download it as a CSV format, which can be opened in spreadsheet software for analysis.
This code runs every 60,000 milliseconds (1 minute) The microbit should have enough storage for about 6 days (9300 rows of data)
No extensions required. Uses the built-in Data Logger on micro:bit v2.
Controls:
Button A → Start logging (shows checkmark)
Button B → Pause logging (shows X)
Button A+B → Erase log and reset (shows skull)
Log full → Logging stops automatically (shows filled square)
Note from adafruit: If you have multiple recording sessions, new data is appended to the bottom of an existing file. To ensure you get new data only, either delete the old log.csv or rename the file. The program will create a new log.csv for the new data. Ensure logging is off before looking to modify files.
https://learn.adafruit.com/data-logging-and-file-storage-in-makecode/datalogger-blocks
*/
basic.pause(1000); // --- Setup ---
let logging = false;
basic.showIcon(IconNames.Chessboard); // Ready indicator
datalogger.setColumnTitles("temperature", "light");
basic.clearScreen();
loops.everyInterval(60000, function () {
// --- Logging Loop: every 60 seconds, record a reading if logging is active
if (logging) {
basic.showIcon(IconNames.Heart); // Pulse to show a reading was taken
datalogger.log(datalogger.createCV("temperature", input.temperature()), datalogger.createCV("light", input.lightLevel()));
basic.clearScreen();
}
});
datalogger.onLogFull(function () {
// When the log fills up, stop logging and show a filled square
logging = false;
basic.showLeds(`
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
`);
});
input.onButtonPressed(Button.A, function () {
// Button A: start logging
logging = true;
basic.showIcon(IconNames.Yes);
});
input.onButtonPressed(Button.AB, function () {
// Button A+B: erase the log and reset column headers
basic.showIcon(IconNames.Skull);
datalogger.deleteLog();
datalogger.setColumnTitles("temperature", "light");
});
input.onButtonPressed(Button.B, function () {
// Button B: pause logging
logging = false;
basic.showIcon(IconNames.No);
});Microbit and AI
The Microbit CreateAI project is a free, web-based tool for students to explore AI through movement and machine learning . You can use micro:bit CreateAI to train an ML model to collect movement data from the micro:bit accelerometer.