Microbit Demos

Contents

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.

Introduction to Microbit

More learning

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.

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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

PIR Microbit diagram

// 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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

//
// 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.

microbit.org code link

// 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.

microbit.org code link

// 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.

microbit.org code link

/*
 * 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.

microbit.org code link

/*
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.

microbit.org code link

/*
 * 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.

microbit.org code link

/*
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.

microbit.org code link

/*
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.

microbit.org code link

// 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.

microbit.org code link

// 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

microbit.org code link

// 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).

microbit.org code link

// 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

microbit.org code link

// 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.

microbit.org code link

/*
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.

microbit.org code link

// 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.

microbit.org code link

/*
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.

microbit.org code link

/*
  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.

microbit.org code link

/*
  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.

microbit.org code link

/*
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.

microbit.org code link

/*
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.

microbit.org code link

/*
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!

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:

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.

microbit.org code link

/*
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.

microbit.org code link

/*
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.

microbit.org code link

/*
 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.

microbit.org code link

/*
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.

microbit.org code link

/*
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

microbit.org code link

/*
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.

microbit.org code link

//
// 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).

microbit.org code link

/*
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.

microbit.org code link

/*
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.

microbit.org code link

/*
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.

microbit.org code link

/*
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!

RGB painter

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:

microbit.org code link

/*
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.

microbit.org code link

/*
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).

microbit.org code link

/*
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).

microbit.org code link

/*
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).

microbit.org code link

// 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.

microbit.org code link

/*
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.

microbit.org code link

/*
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) —

microbit.org code link

/*
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

microbit.org code link

/*
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’

microbit.org code link

/*
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.

microbit.org code link

// 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.

Search for and add the basic “bluetooth” extension (this will remove the radio extension).

microbit.org code link

/*
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);
    }
  }
});

Microbit iOS app showing two-wayBluetooth LED, Button, and Pins services, Pin 0 is active on the device

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

microbit.org code link

/*
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

microbit.org code link

/*
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:

microbit.org code link

/*
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.

microbit.org code link

// 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.

microbit.org code link

/*
  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.

microbit.org code link

/*
  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.

microbit.org code link

/*
  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.

microbit.org code link

// 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

microbit.org code link

// 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

microbit.org code link

/*
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.