Controller 2.4G (PS2) × micro:bit × MakeCode

Interactive tutorialmicro:bit

First time? Try the interactive tutorial in your browser first, then follow the steps below with your hub.

This page covers MakeCode (blocks / JavaScript) only: how to set it up for the Controller 2.4G (PS2) on BBC micro:bit V1 / V2, how to read every value, the commands it supports and examples. Use the switcher above to change product, host or software.

Which edition and how to connect

Get the Universal I2C edition and connect it to the I2C header of a micro:bit breakout (SDA = P20, SCL = P19), factory address 0x42. It draws little current, so VCC can go to the micro:bit 3V pin (about 90 mA on V1, 190 mA on V2); on batteries the rail can sag below 3 V, so switch to external 5 V if readings are unstable (share GND with the micro:bit).

Controller 2.4G (PS2) × BBC micro:bit V1 / V2

Setup

What you need

  • Sensor: Controller 2.4G (PS2) (Universal I2C edition). SPIKE and EV3 editions run different firmware and cannot be read by a micro:bit.
  • Board: BBC micro:bit V1 or V2, plus a breakout board or crocodile-clip adapter to reach P19 / P20.
  • Software: MakeCode for micro:bit in the browser (makecode.microbit.org, blocks / JavaScript); the offline app (Windows / Mac) also works, but adding an extension the first time needs internet.
  • Extension: pxt-mbc-sensors v2 (MIT, nothing to download, just paste the URL).

Important

Bench test pending: extension v2.0.5 compiles in MakeCode (V1 + V2) but has not yet been checked block by block on real hardware. Block names follow the GitHub release.

Install the software

  1. Open makecode.microbit.org → New Project.
  2. Traditional Chinese block names are included: switch the editor language (gearwheel → Language) if you prefer.

Make the software see the sensor

  1. Click Advanced → Extensions at the bottom of the toolbox (also in the gearwheel menu).
  2. Paste https://github.com/contactmbcrobot/pxt-mbc-sensors into the search box and press Enter.
  3. Click the mbc-sensors card. An "MBC Sensors" category appears with six groups: Line, Distance, Gamepad, IMU, Camera and Device.
  4. To use an example: switch to JavaScript at the top right → paste the example → download the .hex and drag it onto the MICROBIT drive.

Tip

No approval, no download: teachers just hand the URL to students. To pin a version, paste https://github.com/contactmbcrobot/pxt-mbc-sensors#v2.0.5.

Wiring

The micro:bit I2C pins are fixed: P19 = SCL, P20 = SDA (on the edge connector, so you need a breakout board or clip adapter). On the MBC 4-pin sensor cable TX goes to SCL and RX to SDA, exactly as when wiring to an Arduino.

Sensor micro:bit Note
SCL (TX) P19 I2C clock
SDA (RX) P20 I2C data
VCC 3V or external 5 V power-hungry products must not draw from the micro:bit 3V pin
GND GND with external power, share GND with the micro:bit
  • 3V pin current: V1 about 90 mA, V2 about 190 mA; on batteries the rail can drop to just over 2 V. Ranger 8-way, Controller PS4 / PS5 and the SharpShot must use external 5 V; other products can try 3V first, with external 5 V recommended on batteries.
  • Logic level: MBC product I2C lines are 3.3 V, compatible with the micro:bit without a level shifter.
  • This product's factory address is 0x42; products with different addresses can share the same P19 / P20 bus.

Connect the gamepad to the receiver

The PS2 2.4G gamepad and its receiver are paired at the factory; no pairing button needed.

  1. Connect the receiver and turn the controller on.
  2. Switch the gamepad on; it connects by itself.
  3. Watch the green LED on the receiver: on = no gamepad yet; off = gamepad connected.
  4. The ANALOG (or MODE) light on the gamepad must be on for the sticks to give values. The receiver switches to this mode on connect; if the light is off, press the ANALOG button in the middle of the gamepad.

Caution

2.4 GHz is a shared band (Wi-Fi, microwave ovens). With 12 or more sets in use at one venue they may interfere; in that case use the Bluetooth version (PS4 / PS5).

First test

  1. Check the wiring first with the "Device → wait for device at 0x42 ready" or "device at 0x42 is ready" block.
  2. Download the .hex of example 1 on this page to the micro:bit.
  3. If the number on the LEDs changes with what is in front of the sensor, everything is connected.

FAQ

  • Values are always 0? With no device connected the reporter blocks return 0 (the internal −32768 is filtered), every yes / no block returns false, and nothing throws. Check in order: wiring (SDA = P20, SCL = P19) → external power on → "device at … is ready" is true.
  • On a V1, the compass and accelerometer stop working too? On V1 the external I2C bus is shared with the onboard accelerometer, so a sensor holding SDA low also kills them. V2 has a separate external bus.
  • Works on and off? STM32 products (Pathfinder, Ranger, PS2, IMU) clock-stretch, and the nRF52 on the V2 has a known clock-stretching issue (errata 219), so V1 and V2 both need a bench test. ESP32 products (PS4, SharpShot) do not stretch; the extension already waits 2 ms after writing the value id.
  • Two products of the same kind? They share an address: first change one with "Device → change device at … to address …" (applied after reboot), then tell the extension with that product's "use address" block.

Reading values

After adding pxt-mbc-sensors, find the blocks in the MBC Sensors category; the JavaScript form is shown too, address 0x42.

16 buttons buttons16

16 buttons
Range Meaning
0~65535 bit all 16 buttons in one word (PSX order, 1 = pressed): bit0 SELECT, 1 L3, 2 R3, 3 START, 4 Up, 5 Right, 6 Down, 7 Left, 8 L2, 9 R2, 10 L1, 11 R1, 12 Triangle, 13 Circle, 14 Cross, 15 Square; 0 when not connected
(gamepad buttons bit mask :: #00A4E4)
let buttons16 = mbc.padButtons()   // 16 buttons:0~65535 bit

Related block "gamepad button [cross] is pressed"

let padButtonPressed = mbc.padButtonPressed(MbcPadButton.Cross)

Related block "on gamepad button [cross] [pressed]"

mbc.onPadButton(MbcPadButton.Cross, MbcPadButtonEvent.Pressed, function () {
})

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

d-pad dpad

d-pad
Range Meaning
0~15 bit Up 1, Right 2, Down 4, Left 8 (added when pressed together), 0~15; 0 when not connected
(gamepad [d-pad v] buttons \(0-15\) :: #00A4E4)
let dpad = mbc.padGroup(MbcPadGroup.Dpad)   // d-pad:0~15 bit

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

shape buttons shape

shape buttons
Range Meaning
0~15 bit Triangle 1, Circle 2, Cross 4, Square 8 (added when pressed together), 0~15; 0 when not connected; high nibble of 0x22 'R'
(gamepad [shapes v] buttons \(0-15\) :: #00A4E4)
let shape = mbc.padGroup(MbcPadGroup.Shapes)   // shape buttons:0~15 bit

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

shoulder buttons shoulder

shoulder buttons
Range Meaning
0~15 bit L2 1, R2 2, L1 4, R1 8 (added when pressed together), 0~15; 0 when not connected
(gamepad [shoulders v] buttons \(0-15\) :: #00A4E4)
let shoulder = mbc.padGroup(MbcPadGroup.Shoulders)   // shoulder buttons:0~15 bit

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

function buttons func

function buttons
Range Meaning
0~15 bit SELECT 1, L3 2, R3 4, START 8 (added when pressed together), 0~15; 0 when not connected; high nibble of 0x22 'G'
(gamepad [functions v] buttons \(0-15\) :: #00A4E4)
let func = mbc.padGroup(MbcPadGroup.Functions)   // function buttons:0~15 bit

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

connected connected

Range Meaning
0~1 1 = gamepad connected (psx.read() succeeds), 0 = not; while disconnected all buttons read 0 and sticks are centred
let connected = mbc.padConnected()   // connected:0~1

Returns false when the sensor does not answer.

battery battery

Range Meaning
0 % 0~100; PS2 pads report no battery → always 0 (PS4 only)
(gamepad battery percent :: #00A4E4)
let battery = mbc.padBattery()   // battery:0 %

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

L2 analog l2Analog

Range Meaning
0~255 L2 pressure 0~255 (only DualShock 2 in analog mode reports pressure; otherwise pressed = 255, released = 0); 0 when not connected
(gamepad trigger [L2 v] analog :: #00A4E4)
let l2Analog = mbc.padTrigger(MbcPadTrigger.L2)   // L2 analog:0~255

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

R2 analog r2Analog

Range Meaning
0~255 R2 pressure 0~255 (same rule as 08); 0 when not connected
(gamepad trigger [R2 v] analog :: #00A4E4)
let r2Analog = mbc.padTrigger(MbcPadTrigger.R2)   // R2 analog:0~255

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

system keys sysKeys

Range Meaning
0 bit b0 PS button, b1 capture button; PS2 pads have none → always 0 (PS4 only)

No dedicated block; in JavaScript read it with mbc.readValue(address, id) (−32768 when nothing answers). Related block: "gamepad system button [PS] is pressed".

let sysKeys = mbc.readValue(0x42, 0x0A)   // system keys:0 bit

gamepad type padType

Range Meaning
0~2 0 none, 1 PS2 digital mode, 2 PS2 analog mode (3 PS4 / 4 PS5 only appear on the PS4 receiver)
(gamepad type :: #00A4E4)
let padType = mbc.padType()   // gamepad type:0~2

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

stick LX / LY / RX / RY stick

stick LX / LY / RX / RY
Range Meaning
-100~+100 10 LX, 11 LY, 12 RX, 13 RY: −100 to +100, 0 = centre, right / up positive (raw 0255 with ±7 dead zone); 0 when not connected. EV3 / SPIKE / MATRIX carry 099 (50 = centre) or 1~9 (5 = centre), see each platform
(gamepad joystick [left X v] :: #00A4E4)
let stick = [mbc.padStick(MbcPadAxis.LeftX), mbc.padStick(MbcPadAxis.LeftY), mbc.padStick(MbcPadAxis.RightX), mbc.padStick(MbcPadAxis.RightY)]   // stick LX / LY / RX / RY:-100~+100

Returns 0 when the sensor does not answer; check with "device at … is ready" first.

Commands

rumble vibrate

[strength 0~255], 0 = stop; the PS2 receiver ACKs and ignores it (pad rumble disabled since 250614), effective on PS4 only

gamepad rumble strength (200) :: #00A4E4
mbc.padRumble(200)   // rumble

light bar colour setLed

[R][G][B] 0~255 each; PS2 pads have no light bar, the receiver ACKs and ignores it, effective on PS4 only

gamepad light bar colour [#ff0000] :: #00A4E4
mbc.padLight(0xff0000)   // light bar colour

Examples

This page is for people who have never written code. The 5 examples start with "show a value on the LEDs" and build up to "drive a car". Everything is written for the MakeCode web editor with the MBC extension pxt-mbc-sensors v2: switch to JavaScript, paste an example and it runs; switch back to Blocks to see which blocks it becomes.

Important

  • Extension pxt-mbc-sensors v2 has not been verified on hardware yet. Every example on this page compiles with the MakeCode CLI (once for V1, once for V2); value directions, ready times and power are still to be confirmed on a real board, see "to be tested" in the hardware checklist below.
  • Use the Universal I2C edition of the product. The SPIKE and EV3 editions have different firmware and cannot be read by a micro:bit.

Tip

The PS2 pad 2.4 GHz dongle plugs into the receiver; switch the pad on and it connects by itself (receiver LED: see the product page). The receiver must run the Universal I2C edition firmware. PS2 pads have no rumble, light bar or battery reading; those three blocks are ignored.

Software

Editor micro:bit V1 micro:bit V2 How
MakeCode web editor (makecode.microbit.org, Blocks / JavaScript) ✔ (less memory, but every example here fits) ✔ New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → paste the example → Download the .hex and drag it onto the MICROBIT drive
MakeCode offline app (Windows / Mac) ✔ ✔ Same; adding the extension the first time needs internet
Python Editor (MicroPython) — — no MBC module yet
  • Sound: V2 has a built-in speaker; a V1 needs a buzzer or headphones on P0.
  • Servos / motors: the examples use pins.servoWritePin: one 9 g servo (SG90) on P1; for driving, two continuous-rotation servos (e.g. FS90R), left wheel P1, right wheel P2. Always power servos from an external 4.8–6 V battery pack whose − is joined to the micro:bit GND (common ground); never drive servos from the micro:bit 3V pin.
  • Block names: with the editor language set to English the blocks read as in the tables below; the tables also give the JavaScript call.

Before you start: hardware checklist (to be tested)

The micro:bit is a 3.3 V board. Each check must pass before an MBC product is connected; "to be tested" marks items extension v2 has not yet confirmed on real hardware.

Check This product Status
Voltage MBC products use 3.3 V I2C levels: connect straight to P19 / P20, no level shifter ✔
Power the receiver is an STM32 with a 2.4 GHz module, about 40 mA, so the micro:bit 3V pin (limit about 90 mA on V1, 190 mA on V2) may do; use external 5 V if unstable to be tested
I2C pull-ups the micro:bit already has 4.7 kΩ; if the product also has pull-ups, check the low level stays ≤ 0.3×VDD to be tested
Clock stretching the STM32 slave stretches the clock by default; the V2 (nRF52833) has a known issue with stretching, test on V1 and V2 to be tested
Address clash factory address 0x42, the same as the PS4/PS5 receiver; to use two receivers change one with the "change device address" block (Device group) —
Ready time the receiver is ready (F3 b0) right after power-on; F3 b1 = 1 once a gamepad is connected, the examples use "gamepad is connected" to be tested

If you read nothing: check the wiring (SDA = P20, SCL = P19) → the external power → whether the "device at … is ready" block (Device group, advanced) returns true.

Wiring (the same for every example)

PS2 Gamepad Receiver × micro:bit
Sensor micro:bit
SDA P20
SCL P19
VCC 3V (or external 5 V)
GND GND (the servo battery − goes here too)

The values used on this page

Each stick axis is −100..+100, 0 when released, right and up are positive. Buttons are picked by name from a dropdown.

Block JavaScript Range Meaning Used in
gamepad joystick left X / Y, right X / Y mbc.padStick(MbcPadAxis.LeftY) −100~+100 0 = centre, right / up = positive 1, 5
gamepad button … is pressed mbc.padButtonPressed(MbcPadButton.Up) true / false any of the 16: d-pad, triangle / circle / cross / square, L1 R1 L2 R2, L3 R3, select / share, start / options 4
on gamepad button … pressed / released mbc.onPadButton(MbcPadButton.Cross, MbcPadButtonEvent.Pressed, ...) 事件 runs once at the moment of pressing or releasing 2
gamepad trigger L2 / R2 analog mbc.padTrigger(MbcPadTrigger.R2) 0~255 PS2 digital pads give only 0 (released) or 255 (pressed); only DualShock 2 in analog mode reports pressure 3
gamepad is connected mbc.padConnected() true / false while disconnected every button reads 0 and the sticks are centred 1, 2, 4, 5
gamepad battery percent mbc.padBattery() 0~100 always 0 on PS2 pads (only PS4 / PS5 report it) 5
wait for device at … ready mbc.waitReady(mbc.ADDR_PAD, 3000) true / false waits up to 3 s, true when the receiver answers (Advanced → Device) 1

Example 1: Joystick dot

What this example does: Push the left stick and the lit dot follows; no gamepad connected → an X. Press A to show the left stick X number, B the Y. Use it to check the receiver wiring, the pairing and the directions.

What you need

  • micro:bit V1 or V2 + USB cable
  • PS2 Gamepad Receiver (Universal I2C edition) + 4-pin cable + a PS2 2.4 GHz wireless pad (dongle in the receiver)

Wiring: As in the wiring table above: SDA → P20, SCL → P19, VCC → 3V (or external 5 V), GND → GND.

Download: ex01_stick_dot.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.

Code

// Gamepad receiver PS2 × micro:bit example 1: joystick dot (an LED dot follows the left stick)
// What it does: push the left stick and the lit dot follows; no gamepad connected → an X. Press A to show the left stick X number, B the Y.
// Wiring: receiver SDA → P20, SCL → P19, VCC → 3V (or external 5 V), GND → GND (common ground).
// Setup: pair the gamepad with the receiver first (see the product page). MakeCode new project → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript, paste this whole file → download to the micro:bit.
// You see: a tick at power-on (receiver found); gamepad off → X; stick released → dot in the centre; pushed up-right → dot in the top-right corner.

let busy = false

if (mbc.waitReady(mbc.ADDR_PAD, 3000)) {
    basic.showIcon(IconNames.Yes)
} else {
    basic.showIcon(IconNames.No)                          // no answer: check P19 / P20 and the external power
}

basic.forever(function () {
    if (busy) {
        basic.pause(100)
        return
    }
    if (!mbc.padConnected()) {                            // no gamepad connected
        basic.showIcon(IconNames.No, 0)
        basic.pause(200)
        return
    }
    let sx = mbc.padStick(MbcPadAxis.LeftX)               // −100 (left) .. +100 (right), 0 = centre
    let sy = mbc.padStick(MbcPadAxis.LeftY)               // −100 (down) .. +100 (up)
    let x = Math.round(Math.map(sx, -100, 100, 0, 4))
    let y = Math.round(Math.map(sy, -100, 100, 4, 0))     // up = positive → row 0
    basic.clearScreen()
    led.plot(x, y)
    basic.pause(50)
})

input.onButtonPressed(Button.A, function () {
    busy = true
    basic.showNumber(mbc.padStick(MbcPadAxis.LeftX))
    busy = false
})
input.onButtonPressed(Button.B, function () {
    busy = true
    basic.showNumber(mbc.padStick(MbcPadAxis.LeftY))
    busy = false
})

Step by step

  1. mbc.waitReady(mbc.ADDR_PAD, 3000): tick if the receiver answers, X if not.
  2. forever: !mbc.padConnected() no gamepad → X, check again after 0.2 s.
  3. sx = mbc.padStick(LeftX), sy = mbc.padStick(LeftY): −100..+100.
  4. x = Math.map(sx, −100, 100, 0, 4); y = Math.map(sy, −100, 100, 4, 0): up is positive, so Y is flipped (row 0 is at the top).
  5. basic.clearScreen() then led.plot(x, y) draws one dot.
  6. A / B: basic.showNumber shows the number; busy pauses the dot meanwhile.

What you should see

  • Power-on: tick. Gamepad off: X.
  • Press PS on the gamepad: dot in the centre.
  • Left stick to the top-right: dot in the top-right corner; A shows 100, B shows 100.
  • Half left: dot in the second column; A shows about −50.

FAQ

  • Always an X? → The pad is not connected: check the 2.4 GHz dongle is in the receiver, the pad is switched on and its ANALOG LED is lit.
  • The dot only moves near the centre? → Normal, it reaches the corners only with the stick pushed fully; to magnify use Math.map(sx, −60, 60, 0, 4).
  • An X right at power-on (sensor not found)? → Check SDA = P20 and SCL = P19 are not swapped, that the external power is on, and with crocodile clips that no clip touches the neighbouring pad.
  • All zeros in the simulator? → Normal. The MakeCode simulator has no real I2C, every read is 0; download to the micro:bit to see real values.
  • No sound on a V1? → The V1 has no speaker; connect a buzzer or headphones between P0 and GND.

Example 2: Buttons make pictures and sounds

What this example does: Triangle / circle / cross / square each show a picture and play a note, the d-pad shows arrows. All written with "on gamepad button … pressed" event blocks.

What you need

  • Everything from example 1
  • On a V1 also a buzzer or headphones on P0

Wiring: Same as example 1.

Download: ex02_button_sound.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.

Code

// Gamepad receiver PS2 × micro:bit example 2: buttons make pictures and sounds
// What it does: triangle / circle / cross / square each show a picture and play a note; the d-pad shows arrows.
// Wiring: receiver SDA → P20, SCL → P19, VCC → 3V (or external 5 V), GND → GND (common ground). On a V1 connect a buzzer or headphones to P0 for sound (V2 has a speaker).
// Setup: gamepad paired; extension and paste steps as in example 1.
// You see: triangle → a triangle + C; circle → a target + E; cross → an X + G; square → a square + high C; d-pad → arrows in four directions.

mbc.onPadButton(MbcPadButton.Triangle, MbcPadButtonEvent.Pressed, function () {
    basic.showIcon(IconNames.Triangle, 0)
    music.playTone(Note.C, 200)
})
mbc.onPadButton(MbcPadButton.Circle, MbcPadButtonEvent.Pressed, function () {
    basic.showIcon(IconNames.Target, 0)
    music.playTone(Note.E, 200)
})
mbc.onPadButton(MbcPadButton.Cross, MbcPadButtonEvent.Pressed, function () {
    basic.showIcon(IconNames.No, 0)
    music.playTone(Note.G, 200)
})
mbc.onPadButton(MbcPadButton.Cross, MbcPadButtonEvent.Released, function () {
    basic.clearScreen()
})
mbc.onPadButton(MbcPadButton.Square, MbcPadButtonEvent.Pressed, function () {
    basic.showIcon(IconNames.Square, 0)
    music.playTone(Note.C5, 200)
})
mbc.onPadButton(MbcPadButton.Up, MbcPadButtonEvent.Pressed, function () {
    basic.showArrow(ArrowNames.North, 0)
})
mbc.onPadButton(MbcPadButton.Down, MbcPadButtonEvent.Pressed, function () {
    basic.showArrow(ArrowNames.South, 0)
})
mbc.onPadButton(MbcPadButton.Left, MbcPadButtonEvent.Pressed, function () {
    basic.showArrow(ArrowNames.West, 0)
})
mbc.onPadButton(MbcPadButton.Right, MbcPadButtonEvent.Pressed, function () {
    basic.showArrow(ArrowNames.East, 0)
})

basic.forever(function () {
    if (!mbc.padConnected()) {                            // no gamepad: show a small heart as a reminder
        basic.showIcon(IconNames.SmallHeart, 0)
    }
    basic.pause(500)
})

Step by step

  1. mbc.onPadButton(MbcPadButton.Triangle, Pressed, ...): runs once the moment triangle is pressed → triangle icon, Note.C.
  2. Circle → target icon, Note.E; square → square icon, Note.C5.
  3. Cross pressed → X icon, Note.G; a separate released event for cross clears the screen.
  4. One event each for up / down / left / right → four arrows.
  5. forever: while no pad is connected a small heart is shown every 0.5 s as a reminder.

What you should see

  • Press triangle: triangle, C.
  • Hold cross: X; release: screen clears.
  • Press the d-pad: arrows.

FAQ

  • No reaction? → Run example 1 first to confirm the pad is connected; PS2 pads must be in ANALOG mode (red LED on).
  • Add L1 / R1? → Copy an event and pick MbcPadButton.L1.

Example 3: Triggers control a servo

What this example does: R2 pressed → servo to 180°, L2 pressed → 0°, both released → 90° (PS2 digital pads report only 0 or 255 on the triggers, so it jumps; a DualShock 2 in analog mode sweeps smoothly). The LED bar shows R2. Press A to show the angle.

What you need

  • Everything from example 1
  • one 9 g servo (SG90)
  • a 4.8–6 V battery pack and jumper wires

Wiring: Receiver as in example 1. Servo orange (signal) → P1, red → external 4.8–6 V battery +, brown → battery −, and run a second wire from battery − to micro:bit GND (common ground).

Download: ex03_trigger_servo.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.

Code

// Gamepad receiver PS2 × micro:bit example 3: triggers control a servo
// What it does: the harder you squeeze R2, the further the servo turns right; L2 turns it left; release both and it centres. The LED bar shows the R2 pressure. Press A to show the angle.
// Wiring: receiver SDA → P20, SCL → P19, VCC → 3V (or external 5 V), GND → GND. Servo orange (signal) → P1, red → external 4.8–6 V battery +, brown → battery − joined to micro:bit GND.
// Setup: a 9 g servo (SG90).
// You see: squeeze R2 slowly → the bar grows and the servo turns toward 180°; squeeze L2 → toward 0°; release → 90°.

let angle = 90

basic.forever(function () {
    let r2 = mbc.padTrigger(MbcPadTrigger.R2)             // 0 (released) .. 255 (fully squeezed)
    let l2 = mbc.padTrigger(MbcPadTrigger.L2)
    angle = Math.round(90 + (r2 - l2) * 90 / 255)         // R2 → 90..180, L2 → 90..0
    pins.servoWritePin(AnalogPin.P1, angle)
    led.plotBarGraph(r2, 255)
    basic.pause(30)
})

input.onButtonPressed(Button.A, function () {
    basic.showNumber(angle)
})

Step by step

  1. forever: r2 = mbc.padTrigger(R2), l2 = mbc.padTrigger(L2), each 0..255.
  2. angle = 90 + (r2 − l2) × 90 / 255: R2 only → 90..180; L2 only → 90..0; Math.round makes it an integer.
  3. pins.servoWritePin(AnalogPin.P1, angle) every 30 ms.
  4. led.plotBarGraph(r2, 255): the bar shows the R2 pressure.

What you should see

  • Press R2: bar fully lit, servo to 180°.
  • Press L2: to 0°. Both: back to 90°.
  • Release: 90°, bar dark.

FAQ

  • On a PS2 pad it jumps straight to the end? → PS2 digital pads give only 0 or 255 on L2 / R2, no pressure; for a smooth sweep use the right stick: angle = 90 + padStick(RightX) × 0.9.
  • Servo jitters? → Power the servo from the battery pack with a common ground.

Example 4: Drive with the d-pad

What this example does: Hold a d-pad button and the car moves that way, release to stop: up = forward, down = back, left = spin left, right = spin right. The car stops by itself if the gamepad disconnects.

What you need

  • A two-wheel car: two continuous-rotation servos (e.g. FS90R) as wheels plus a caster
  • the receiver fixed on the car
  • a 4.8–6 V battery pack (servos) and external 5 V (receiver; the same power bank will do)

Wiring: Receiver as in example 1. Left continuous-rotation servo signal → P1, right → P2; both servo red wires to the 4.8–6 V battery pack +, brown to battery −, and battery − also to micro:bit GND.

Download: ex04_dpad_drive.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.

Code

// Gamepad receiver PS2 × micro:bit example 4: drive with the d-pad
// What it does: hold a d-pad button and the car moves that way, release to stop: up = forward, down = back, left = spin left, right = spin right. The car stops by itself if the gamepad disconnects.
// Wiring: receiver SDA → P20, SCL → P19, VCC → 3V (or external 5 V), GND → GND. Left continuous-rotation servo signal → P1, right → P2; servos on a 4.8–6 V battery pack whose − is joined to micro:bit GND.
// Setup: a two-wheel car (two continuous-rotation servos as wheels).
// You see: hold up → the car drives forward with an up arrow; release → stop; switch the gamepad off → X, car stops.

// left servo P1, right servo P2 (continuous rotation); left / right = −100..100, positive = forward
function drive(left: number, right: number): void {
    left = Math.constrain(left, -100, 100)
    right = Math.constrain(right, -100, 100)
    pins.servoWritePin(AnalogPin.P1, Math.round(90 + left * 0.9))    // 90 = stop, 180 = full forward, 0 = full reverse
    pins.servoWritePin(AnalogPin.P2, Math.round(90 - right * 0.9))   // the right servo is mounted mirrored, so subtract
}

basic.forever(function () {
    if (!mbc.padConnected()) {                            // disconnected: stop
        drive(0, 0)
        basic.showIcon(IconNames.No, 0)
    } else if (mbc.padButtonPressed(MbcPadButton.Up)) {
        drive(50, 50)                                     // forward
        basic.showArrow(ArrowNames.North, 0)
    } else if (mbc.padButtonPressed(MbcPadButton.Down)) {
        drive(-50, -50)                                   // back
        basic.showArrow(ArrowNames.South, 0)
    } else if (mbc.padButtonPressed(MbcPadButton.Left)) {
        drive(-30, 30)                                    // left back, right forward = spin left
        basic.showArrow(ArrowNames.West, 0)
    } else if (mbc.padButtonPressed(MbcPadButton.Right)) {
        drive(30, -30)                                    // spin right
        basic.showArrow(ArrowNames.East, 0)
    } else {
        drive(0, 0)                                       // nothing pressed: stop
        basic.clearScreen()
    }
    basic.pause(50)
})

Step by step

  1. function drive(left, right): left wheel P1, right wheel P2, −100..100, positive = forward; the right servo is mirrored so it subtracts.
  2. forever: first !mbc.padConnected() → on disconnect drive(0, 0) and X.
  3. mbc.padButtonPressed(Up) → drive(50, 50) forward, up arrow; Down → drive(−50, −50).
  4. Left → drive(−30, 30): left wheel back, right forward = spin left; Right the opposite.
  5. Nothing pressed → drive(0, 0) and clear the screen. One pass every 50 ms.

What you should see

  • Hold up: forward, up arrow; release: stop.
  • Hold left: spin left.
  • Switch the gamepad off: X, car stops.

FAQ

  • Up drives backwards? → Both servos are mounted the other way: in drive change + left to - left and - right to + right. Only one wheel wrong → change only that line.
  • Up spins on the spot? → Only one side is reversed; flip the sign of that servo only.
  • It creeps when released? → The stop point of a continuous servo is not always 90; adjust its trim screw or use 88..92 instead of 90.

Example 5: Two-stick tank drive + safe start (all together)

What this example does: Left stick drives the left wheel, right stick the right wheel, further = faster, one forward one back spins. Both sticks must be released (centred) once before the car is "armed", and it locks again on disconnect, so the car never shoots off at power-on. Press A to show the battery (always 0 on PS2 pads).

What you need

  • The car from example 4

Wiring: Same as example 4.

Download: ex05_tank_drive.ts (right-click, save) → MakeCode New Project → gear → Extensions → paste https://github.com/contactmbcrobot/pxt-mbc-sensors → switch to JavaScript (top) → select all, delete, paste the file → Download and drag the .hex onto the MICROBIT drive.

Code

// Gamepad receiver PS2 × micro:bit example 5: two-stick tank drive + safe start (all together)
// What it does: left stick drives the left wheel, right stick the right wheel, further = faster. Both sticks must be released (centred) once before the car is 'armed'; it locks again on disconnect. Press A to show the battery (PS2 pads have no battery reading, always 0).
// Wiring: as example 4 (receiver P19 / P20; left wheel P1, right wheel P2, servos on their own battery, common ground).
// Setup: the two-wheel car.
// You see: release both sticks → tick (armed); push both forward → forward; one forward one back → spin; release → stop; gamepad off → X and locked.

const DEAD = 15                                           // dead zone: within ±15 counts as 0
let armed = false

// left servo P1, right servo P2 (continuous rotation); left / right = −100..100, positive = forward
function drive(left: number, right: number): void {
    left = Math.constrain(left, -100, 100)
    right = Math.constrain(right, -100, 100)
    pins.servoWritePin(AnalogPin.P1, Math.round(90 + left * 0.9))    // 90 = stop, 180 = full forward, 0 = full reverse
    pins.servoWritePin(AnalogPin.P2, Math.round(90 - right * 0.9))   // the right servo is mounted mirrored, so subtract
}

function deadZone(v: number): number {                    // treat the stick near centre as 0 so the car really stops
    if (Math.abs(v) < DEAD) {
        return 0
    }
    return v
}

input.onButtonPressed(Button.A, function () {
    basic.showNumber(mbc.padBattery())                    // gamepad battery 0..100 (PS2 pads always 0)
})

basic.forever(function () {
    if (!mbc.padConnected()) {
        drive(0, 0)
        armed = false                                     // must re-arm after a disconnect
        basic.showIcon(IconNames.No, 0)
        basic.pause(200)
        return
    }
    let ly = deadZone(mbc.padStick(MbcPadAxis.LeftY))     // left stick up / down −100..+100
    let ry = deadZone(mbc.padStick(MbcPadAxis.RightY))    // right stick up / down
    if (!armed) {
        if (ly == 0 && ry == 0) {                         // arm only when both sticks are released
            armed = true
            basic.showIcon(IconNames.Yes, 0)
        }
        basic.pause(50)
        return
    }
    drive(ly, ry)                                         // left stick → left wheel, right stick → right wheel
    basic.pause(20)
})

Step by step

  1. DEAD = 15: the dead zone. deadZone(v) returns 0 while the stick is within ±15, so the car really stops when you let go.
  2. forever: disconnected → stop, armed = false, X.
  3. ly, ry = left / right stick up-down after the dead zone.
  4. !armed: only when both are 0 → armed = true, tick; otherwise do nothing.
  5. Once armed drive(ly, ry): the left stick goes straight to the left wheel, the right stick to the right wheel (−100..100 is exactly the drive range).
  6. A shows the battery (PS2 pads have no battery reading, always 0).

What you should see

  • Power-on, pad connected: release both sticks → tick.
  • Both forward: forward, faster the further; left forward right back: spin right; release: stop.
  • Pad off: X; after reconnecting release the sticks again to re-arm.

FAQ

  • Never gets the tick? → A stick does not return to centre (offset). Change DEAD to 25.
  • Too fast to control? → Change drive(ly, ry) to drive(ly / 2, ry / 2).
  • Drive with one stick? → drive(ly + lx, ly − lx) with lx from padStick(LeftX).

Next steps

More questions

Still stuck? Contact us with your software and a screenshot of the program.

Downloads

Sources

  • Host and software list:_build/guides.json
  • Values and commands:_build/protocol/ps2.json (single source of truth)
  • Setup:_build/guides/software/microbit-makecode.en.md、_build/guides/product/ps2/microbit-makecode.en.md
  • Examples:sensors/ps2/microbit-examples.md
Email for a quoteBuying Guide