This page has moved into the new guide: Controller 2.4G

PS2 Gamepad Receiver: value & command reference

Back to PS2 Gamepad Receiver

Every value and command the PS2 Gamepad Receiver reports to each host: what it means, its range, and how to read it on SPIKE / EV3 / Arduino I2C / mBlock / MATRIX / micro:bit. Append ?host=spike (or ev3, arduino, mblock, matrix, microbit) to the URL to expand that host by default.

SPIKEEV3Arduino I2CmBlockMATRIXmicro:bit

Item Detail
Factory I2C address 0x42
hw[0] 1(F0 high byte; ID high byte in SPIKE mode 8 / EV3 mode 5)
Model PS2
Protocol version 2
Hosts SPIKE, EV3, Arduino I2C, mBlock, MATRIX, micro:bit

Values at a glance

Id Value Range SPIKE EV3 Arduino I2C mBlock MATRIX micro:bit
01 16 buttons 0~65535 bit ✓ — ✓ ✓ — ✓
02 d-pad 0~15 bit ✓ ✓ ✓ ✓ ✓ ✓
03 shape buttons 0~15 bit ✓ ✓ ✓ ✓ — ✓
04 shoulder buttons 0~15 bit ✓ ✓ ✓ ✓ ✓ ✓
05 function buttons 0~15 bit ✓ ✓ ✓ ✓ — ✓
06 connected 0~1 ✓ ✓ ✓ ✓ ✓ ✓
07 battery 0 % ✓ — ✓ ✓ — ✓
08 L2 analog 0~255 — — ✓ ✓ — ✓
09 R2 analog 0~255 — — ✓ ✓ — ✓
0A system keys 0 bit — — ✓ ✓ — ✓
0B gamepad type 0~2 — — ✓ ✓ — ✓
10~13 stick LX / LY / RX / RY -100~+100 ✓ ✓ ✓ ✓ ✓ ✓

✓ = available on that host; — = not sent.

Reading each value

01 16 buttons buttons16

16 buttons
Item Detail
Meaning 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
Range 0~65535 bit
I2C / mBlock id 01
Locked 🔒 LEGO mode layout will not change

How to read it

SPIKE

mode 0 (edu) byte 6 🔒; mode 5 v[0] 🔒; mode 6 v[2]; mode 9 v[6] 🔒. Note: App 原始藍 = button16(.ino:201-211); Pybricks reflection() 編碼:11 鍵 complexXY = SELECT<<10 | R1<<9 | L1<<8 | ■<<7 | ✕<<6 | ●<<5 | ▲<<4 | 左<<3 | 下<<2 | 右<<1 | 上,a = /25、b = (餘+1)×10−1,3 個 int16 = a×256+b → reflection() = complexXY;L2、R2、L3、R3、START 讀不到(.ino:334-358)。2026-10-30 起(方案 A,使用者定案):模式 5 總和 S = ceil((10×complexXY + RY)×3.072),R = G = S/3、B = S−2R → Pybricks 4.1.x int(reflection()) = complexXY(不變)、round(reflection()×10) % 10 = RY(1~9);3.6.1/4.0.0 的 reflection() 照舊 = complexXY; v2 = button16(PSX); v6 = button16(PSX)

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

w = dev.read(9)[6]
buttons16 = w & 0xFFFF   # 16 buttons (int16 back to 0~65535; negative while ■ is held)

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
buttons16 = pad.buttons_psx()
EV3

Not sent in any EV3 mode (the locked EV3 modes stay as they are). Use Arduino I2C / mBlock if you need it.

Arduino I2C

This value can itself be −32768 (top bit set, e.g. 0x8000), the same as MBC_NONE for no answer, so do not test for −32768: use mbcReadMany(0x42, 0x01, 1, &buttons16) instead; only a false return means no answer.

int16_t buttons16 = mbcRead(0x42, 0x01);   // 16 buttons:0~65535 bit
mBlock

Block returns 16 buttons(0~65535 bit)。Host sends [01] → reply [lo][hi][01 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] 16 buttons :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(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 () {
})

02 d-pad dpad

d-pad
Item Detail
Meaning Up 1, Right 2, Down 4, Left 8 (added when pressed together), 0~15; 0 when not connected
Range 0~15 bit
I2C / mBlock id 02
Locked 🔒 LEGO mode layout will not change

How to read it

SPIKE

mode 0 (edu) byte 1 🔒; mode 0 (toy) byte 0 🔒; mode 1 byte 0; mode 6 v[0] lo; mode 9 v[0] 🔒. Note: byte1 = 方向 | 形狀<<4(App 反射光):低 4 bit; byte0 = 方向 | 形狀<<4; 低 4 bit(高 4 bit = 形狀); v0 = 方向 | 形狀<<4(bit0-3)

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

w = dev.read(9)[0]
dpad = w & 0x0F   # d-pad (bits 0~3)

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
dpad = pad.pressed('UP')
EV3

mode 0 · int8 · IR-PROX = 方向鍵(.ino:130-132); mode 1 · int8 · IR-SEEK [方向, LX, 形狀, LY, 肩, RX, 功能, RY](按鍵與搖桿交錯); mode 3 · int16 · #67 改:IR-REM-A 1×int16 = 方向 | 形狀<<4 | 已連線<<8(bit0-3 = 方向); mode 4 · int8 · IR-S-ALT [方向, LX, 形狀, LY] (locked mode: firmware and hosts will not change it)

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

dpad = sensor.read(0)[0]   # d-pad:0~15 bit
dpad = sensor.read(1)[0]   # d-pad:0~15 bit
w = sensor.read(3)[0]
dpad = w & 0x0F   # d-pad (bits 0~3)
dpad = sensor.read(4)[0]   # d-pad:0~15 bit
' clev3r (BASIC)
Sensor.SetMode(1, 0)
dpad = Sensor.ReadRawValue(1, 0)
Sensor.SetMode(1, 1)
dpad = Sensor.ReadRawValue(1, 0)
Sensor.SetMode(1, 3)
w = Sensor.ReadRawValue(1, 0)
dpad = Math.Remainder(w, 16)   ' bits 0~3
Sensor.SetMode(1, 4)
dpad = Sensor.ReadRawValue(1, 0)
Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t dpad = mbcRead(0x42, 0x02);   // d-pad:0~15 bit
mBlock

Block returns d-pad(0~15 bit)。Host sends [02] → reply [lo][hi][02 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] d-pad :: sensing)
MATRIX

0x22 R.

// A. stock 0x22 blocks:R = d-pad(#67 新增:R = 方向鍵 | 形狀鍵<<4(上 1、右 2、下 4、左 8、▲16、●32、✕64、■128))
int dpad = MiniR4.I2C1.MXColor.getColor(R);
micro:bit

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

(gamepad [d-pad v] buttons \(0-15\) :: #00A4E4)
let dpad = mbc.padGroup(MbcPadGroup.Dpad)   // d-pad:0~15 bit

03 shape buttons shape

shape buttons
Item Detail
Meaning Triangle 1, Circle 2, Cross 4, Square 8 (added when pressed together), 0~15; 0 when not connected; high nibble of 0x22 'R'
Range 0~15 bit
I2C / mBlock id 03
Locked 🔒 LEGO mode layout will not change

How to read it

SPIKE

mode 0 (edu) byte 1 🔒; mode 0 (toy) byte 0 🔒; mode 1 byte 0; mode 6 v[0] hi; mode 9 v[0] 🔒. Note: 高 4 bit; 高 4 bit; 高 4 bit; v0 bit4-7

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

w = dev.read(9)[0]
shape = (w >> 4) & 0x0F   # shape buttons (bits 4~7)

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
shape = pad.pressed('TRIANGLE')
EV3

mode 1 · int8; mode 3 · int16 · #67 改:bit4-7 = 形狀(同一個 int16); mode 4 · int8 (locked mode: firmware and hosts will not change it)

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

shape = sensor.read(1)[2]   # shape buttons:0~15 bit
w = sensor.read(3)[0]
shape = (w >> 4) & 0x0F   # shape buttons (bits 4~7)
shape = sensor.read(4)[2]   # shape buttons:0~15 bit
' clev3r (BASIC)
Sensor.SetMode(1, 1)
shape = Sensor.ReadRawValue(1, 2)
Sensor.SetMode(1, 3)
w = Sensor.ReadRawValue(1, 0)
shape = Math.Remainder(Math.Floor(w / 16), 16)   ' bits 4~7
Sensor.SetMode(1, 4)
shape = Sensor.ReadRawValue(1, 2)
Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t shape = mbcRead(0x42, 0x03);   // shape buttons:0~15 bit
mBlock

Block returns shape buttons(0~15 bit)。Host sends [03] → reply [lo][hi][03 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] shape buttons :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(gamepad [shapes v] buttons \(0-15\) :: #00A4E4)
let shape = mbc.padGroup(MbcPadGroup.Shapes)   // shape buttons:0~15 bit

04 shoulder buttons shoulder

shoulder buttons
Item Detail
Meaning L2 1, R2 2, L1 4, R1 8 (added when pressed together), 0~15; 0 when not connected
Range 0~15 bit
I2C / mBlock id 04
Locked 🔒 LEGO mode layout will not change

How to read it

SPIKE

mode 0 (edu) byte 0 🔒; mode 2 byte 0; mode 6 v[1] lo; mode 9 v[1] 🔒; mode 0 (toy) byte 6 🔒; mode 0 (toy) byte 1 🔒. Note: byte0 = 肩 | 功能<<4(App 顏色,START = −128):低 4 bit; 低 4 bit(高 4 bit = 功能); v1 = 肩 | 功能<<4(bit0-3); 原始藍 = 肩 | 功能<<4(0~255); 單鍵編號(L2=1 R2=2 L1=3 R1=4 SELECT=5 L3=6 R3=7 START=8,沒按 0;同時按只回編號最小的那個)

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

w = dev.read(9)[1]
shoulder = w & 0x0F   # shoulder buttons (bits 0~3)

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
shoulder = pad.pressed('L1')
EV3

mode 1 · int8 (locked mode: firmware and hosts will not change it)

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

shoulder = sensor.read(1)[4]   # shoulder buttons:0~15 bit
' clev3r (BASIC)
Sensor.SetMode(1, 1)
shoulder = Sensor.ReadRawValue(1, 4)
Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t shoulder = mbcRead(0x42, 0x04);   // shoulder buttons:0~15 bit
mBlock

Block returns shoulder buttons(0~15 bit)。Host sends [04] → reply [lo][hi][04 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] shoulder buttons :: sensing)
MATRIX

0x22 G.

// A. stock 0x22 blocks:G = shoulder buttons(#67 新增:G = 肩鍵 | 功能鍵<<4(L2 1、R2 2、L1 4、R1 8、SELECT 16、L3 32、R3 64、START 128))
int shoulder = MiniR4.I2C1.MXColor.getColor(G);
micro:bit

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

(gamepad [shoulders v] buttons \(0-15\) :: #00A4E4)
let shoulder = mbc.padGroup(MbcPadGroup.Shoulders)   // shoulder buttons:0~15 bit

05 function buttons func

function buttons
Item Detail
Meaning SELECT 1, L3 2, R3 4, START 8 (added when pressed together), 0~15; 0 when not connected; high nibble of 0x22 'G'
Range 0~15 bit
I2C / mBlock id 05
Locked 🔒 LEGO mode layout will not change

How to read it

SPIKE

mode 0 (edu) byte 0 🔒; mode 2 byte 0; mode 6 v[1] hi; mode 9 v[1] 🔒; mode 0 (toy) byte 6 🔒; mode 0 (toy) byte 1 🔒. Note: 高 4 bit; 高 4 bit; v1 bit4-7; 原始藍 = 肩 | 功能<<4(0~255); 單鍵編號(L2=1 R2=2 L1=3 R1=4 SELECT=5 L3=6 R3=7 START=8,沒按 0;同時按只回編號最小的那個)

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

w = dev.read(9)[1]
func = (w >> 4) & 0x0F   # function buttons (bits 4~7)

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
func = pad.pressed('START')
EV3

mode 1 · int8 (locked mode: firmware and hosts will not change it)

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

func = sensor.read(1)[6]   # function buttons:0~15 bit
' clev3r (BASIC)
Sensor.SetMode(1, 1)
func = Sensor.ReadRawValue(1, 6)
Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t func = mbcRead(0x42, 0x05);   // function buttons:0~15 bit
mBlock

Block returns function buttons(0~15 bit)。Host sends [05] → reply [lo][hi][05 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] function buttons :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(gamepad [functions v] buttons \(0-15\) :: #00A4E4)
let func = mbc.padGroup(MbcPadGroup.Functions)   // function buttons:0~15 bit

06 connected connected

Item Detail
Meaning 1 = gamepad connected (psx.read() succeeds), 0 = not; while disconnected all buttons read 0 and sticks are centred
Range 0~1
I2C / mBlock id 06

How to read it

SPIKE

mode 3 byte 0. Note: #67 改:LIGHT 3×int8 = [已連線, 電量, 0](原本 = 4 組按鍵)

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

connected = dev.read(3)[0]   # connected

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
connected = pad.connected()
EV3

mode 3 · int16 · #67 改:bit8 = 已連線(+256)

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

w = sensor.read(3)[0]
connected = (w >> 8) & 1   # connected (bit 8)
' clev3r (BASIC)
Sensor.SetMode(1, 3)
w = Sensor.ReadRawValue(1, 0)
connected = Math.Remainder(Math.Floor(w / 256), 2)   ' bit 8
Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t connected = mbcRead(0x42, 0x06);   // connected:0~1
mBlock

Block returns connected(0~1)。Host sends [06] → reply [lo][hi][06 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] gamepad connected? :: sensing)
MATRIX

0x22 K.

// A. stock 0x22 blocks:K = connected(#67 新增:K = 已連線 1/0)
int connected = MiniR4.I2C1.MXColor.getColor(K);
micro:bit

Returns false when the sensor does not answer.

let connected = mbc.padConnected()   // connected:0~1

07 battery battery

Item Detail
Meaning 0~100; PS2 pads report no battery → always 0 (PS4 only)
Range 0 %
I2C / mBlock id 07

How to read it

SPIKE

mode 3 byte 1. Note: #67 改:PS2 送 0

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

battery = dev.read(3)[1]   # battery

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
battery = pad.battery()
EV3

Not sent in any EV3 mode (the locked EV3 modes stay as they are). Use Arduino I2C / mBlock if you need it.

Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t battery = mbcRead(0x42, 0x07);   // battery:0 %
mBlock

Block returns battery(0 %)。Host sends [07] → reply [lo][hi][07 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] battery :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(gamepad battery percent :: #00A4E4)
let battery = mbc.padBattery()   // battery:0 %

08 L2 analog l2Analog

Item Detail
Meaning L2 pressure 0~255 (only DualShock 2 in analog mode reports pressure; otherwise pressed = 255, released = 0); 0 when not connected
Range 0~255
I2C / mBlock id 08

How to read it

SPIKE

Not sent in any SPIKE mode (locked modes stay as they are). Use Arduino I2C / mBlock if you need it.

EV3

Not sent in any EV3 mode (the locked EV3 modes stay as they are). Use Arduino I2C / mBlock if you need it.

Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t l2Analog = mbcRead(0x42, 0x08);   // L2 analog:0~255
mBlock

Block returns L2 analog(0~255)。Host sends [08] → reply [lo][hi][08 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] L2 analog :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(gamepad trigger [L2 v] analog :: #00A4E4)
let l2Analog = mbc.padTrigger(MbcPadTrigger.L2)   // L2 analog:0~255

09 R2 analog r2Analog

Item Detail
Meaning R2 pressure 0~255 (same rule as 08); 0 when not connected
Range 0~255
I2C / mBlock id 09

How to read it

SPIKE

Not sent in any SPIKE mode (locked modes stay as they are). Use Arduino I2C / mBlock if you need it.

EV3

Not sent in any EV3 mode (the locked EV3 modes stay as they are). Use Arduino I2C / mBlock if you need it.

Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t r2Analog = mbcRead(0x42, 0x09);   // R2 analog:0~255
mBlock

Block returns R2 analog(0~255)。Host sends [09] → reply [lo][hi][09 ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] R2 analog :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(gamepad trigger [R2 v] analog :: #00A4E4)
let r2Analog = mbc.padTrigger(MbcPadTrigger.R2)   // R2 analog:0~255

0A system keys sysKeys

Item Detail
Meaning b0 PS button, b1 capture button; PS2 pads have none → always 0 (PS4 only)
Range 0 bit
I2C / mBlock id 0A

How to read it

SPIKE

Not sent in any SPIKE mode (locked modes stay as they are). Use Arduino I2C / mBlock if you need it.

EV3

Not sent in any EV3 mode (the locked EV3 modes stay as they are). Use Arduino I2C / mBlock if you need it.

Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t sysKeys = mbcRead(0x42, 0x0A);   // system keys:0 bit
mBlock

Block returns system keys(0 bit)。Host sends [0A] → reply [lo][hi][0A ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] system keys :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

0B gamepad type padType

Item Detail
Meaning 0 none, 1 PS2 digital mode, 2 PS2 analog mode (3 PS4 / 4 PS5 only appear on the PS4 receiver)
Range 0~2
I2C / mBlock id 0B

How to read it

SPIKE

Not sent in any SPIKE mode (locked modes stay as they are). Use Arduino I2C / mBlock if you need it.

EV3

Not sent in any EV3 mode (the locked EV3 modes stay as they are). Use Arduino I2C / mBlock if you need it.

Arduino I2C

Returns MBC_NONE (−32768) when the module does not answer.

int16_t padType = mbcRead(0x42, 0x0B);   // gamepad type:0~2
mBlock

Block returns gamepad type(0~2)。Host sends [0B] → reply [lo][hi][0B ^ lo ^ hi], value = int16 little-endian.

(Controller port [1 v] gamepad type :: sensing)
MATRIX

Not available with the stock MATRIX 0x22 blocks.

micro:bit

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

(gamepad type :: #00A4E4)
let padType = mbc.padType()   // gamepad type:0~2

10~13 stick LX / LY / RX / RY stick

stick LX / LY / RX / RY
Item Detail
Meaning 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
Range -100~+100
I2C / mBlock id 10~13
Locked 🔒 LEGO mode layout will not change

How to read it

SPIKE

mode 0 (edu) byte 2 🔒; mode 0 (toy) byte 2 🔒; mode 4 v[0]; mode 7 v[2] 🔒; mode 9 v[2] 🔒. Note: byte23 = LX×10+LY、byte45 = RX×10+RY(1199,中心 55;App 原始紅/綠); 原始紅 = LX×10+LY、原始綠 = RX×10+RY(各 19,5 = 中間); v0 = LX | LY<<8、v1 = RX | RY<<8(各 19,5 = 中間); Pybricks ambient() 用:v2 = LX×1000 + LY×100 + RX×10 + RY(11119999,中心 5555);v0、v1 同模式 6;個位數 RY 在 4.1.x 的 ambient() 讀不到,4.1.x 改從模式 5 reflection() 的小數位讀(見按鍵 buttons16); v2v5 = LX、LY、RX、RY(099 ÷10,0 補 1)

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

stick = list(dev.read(9)[2:6])   # [LX, LY, RX, RY]

MBC library

from pybricks.parameters import Port
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.A)
pad.update()   # refresh once per loop
stick = pad.stick('LX')
EV3

mode 1 · int8 · 交錯:index 1 = LX、3 = LY、5 = RX、7 = RY(偶數 index 是按鍵); mode 2 · int8 · IR-REMOTE [LX, LY, RX, RY](描述表宣告 010,實送 099); mode 4 · int8 · index 1 = LX、3 = LY(只到左搖桿) (locked mode: firmware and hosts will not change it)

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

stick = sensor.read(1)[1::2]   # stick LX / LY / RX / RY (index 1, 3, 5, 7): 0~99 (50 = centre)
stick = sensor.read(2)[0:4]   # stick LX / LY / RX / RY
stick = sensor.read(4)[1::2]   # stick LX / LY / RX / RY (index 1, 3): 0~99 (50 = centre)
' clev3r (BASIC)
Sensor.SetMode(1, 1)
stick1 = Sensor.ReadRawValue(1, 1)   ' first; the N-th is at index 1 + 2×(N − 1) (1, 3, 5, 7)
Sensor.SetMode(1, 2)
stick1 = Sensor.ReadRawValue(1, 0)   ' first; for the N-th use 0 + N - 1
Sensor.SetMode(1, 4)
stick1 = Sensor.ReadRawValue(1, 1)   ' first; the N-th is at index 1 + 2×(N − 1) (1, 3)
Arduino I2C

Ids 0x10~0x13 are consecutive: one mbcReadMany call returns them from the same 10 ms snapshot.

int16_t stick[4];
mbcReadMany(0x42, 0x10, 4, stick);   // stick LX / LY / RX / RY:-100~+100
for (uint8_t i = 0; i < 4; i++) { Serial.print(stick[i]); Serial.print(' '); }
Serial.println();
mBlock

Block returns stick LX / LY / RX / RY(-100~+100)。Host sends [10] → reply [lo][hi][10 ^ lo ^ hi], value = int16 little-endian; channel N uses id 0x10 + (N − 1).

(Controller port [1 v] stick [LX/LY/RX/RY] :: sensing)
MATRIX

0x22 B~Y.

// A. stock 0x22 blocks:B~Y = stick LX / LY / RX / RY(#67 新增:B、C、M、Y = LX、LY、RX、RY)
int raw = MiniR4.I2C1.MXColor.getColor(B);   // group 1
micro:bit

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

(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

Write commands

Only Arduino I2C, mBlock and micro:bit can send commands; the SPIKE / EV3 ports are read-only.

E5 rumble vibrate

Item Detail
Action [strength 0~255], 0 = stop; the PS2 receiver ACKs and ignores it (pad rumble disabled since 250614), effective on PS4 only
Parameters strength 0~255(1 byte)
Busy flag none (immediate)

How to send it

SPIKE

The SPIKE port carries no write commands: calibrate with the button on the sensor, change settings over Arduino I2C / mBlock.

EV3

The EV3 port carries no write commands: calibrate with the button on the sensor, change settings over Arduino I2C / mBlock.

Arduino I2C
mbcWrite(0x42, 0xE5, 128);   // rumble(strength 0~255)
mBlock

Host sends [E5][params…] → reply [00][00][E5] (ack), then it runs.

Controller port [1 v] rumble (128) :: sensing
MATRIX

Not available with the stock MATRIX 0x22 blocks.

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

E6 light bar colour setLed

Item Detail
Action [R][G][B] 0~255 each; PS2 pads have no light bar, the receiver ACKs and ignores it, effective on PS4 only
Parameters red 0~255(1 byte), green 0~255(1 byte), blue 0~255(1 byte)
Busy flag none (immediate)

How to send it

SPIKE

The SPIKE port carries no write commands: calibrate with the button on the sensor, change settings over Arduino I2C / mBlock.

EV3

The EV3 port carries no write commands: calibrate with the button on the sensor, change settings over Arduino I2C / mBlock.

Arduino I2C
mbcWrite(0x42, 0xE6, 128, 128, 128);   // light bar colour(red 0~255, green 0~255, blue 0~255)
mBlock

Host sends [E6][params…] → reply [00][00][E6] (ack), then it runs.

Controller port [1 v] light bar colour (128) (128) (128) :: sensing
MATRIX

Not available with the stock MATRIX 0x22 blocks.

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

Status flags F3

One status per bit of F3; read directly over I2C, mBlock, micro:bit.

bit Name Meaning
b0 ready (ready) always 1
b1 gamepad connected (connected) same as value 06
SPIKE

SPIKE has no F3 flags; mode 8 returns ID / version. The official app cannot read mode 8.

from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port

dev = PUPDevice(Port.A)   # sensor on port A

id_word, proto, fw, seq = dev.read(8)   # mode 8: [ID | hw0<<8, protocol 2, major×100+minor, counter]
addr = id_word & 0xFF   # 0x42
hw0 = id_word >> 8      # 1
EV3

EV3 has no F3 flags; mode 5 returns the device ID and firmware version (ID format B).

from pybricks.iodevices import LUMPDevice
from pybricks.parameters import Port

sensor = LUMPDevice(Port.S1)   # sensor on port 1

id_word, fw = sensor.read(5)   # mode 5: [ID | hw0<<8, major×100+minor]
addr = id_word & 0xFF             # 0x42
hw0 = id_word >> 8                # 1
Arduino I2C
int16_t st = mbcRead(0x42, 0xF3);
bool ready = st & (1 << 0);   // ready
bool connected = st & (1 << 1);   // gamepad connected
mBlock

The extension splits status flags F3 into boolean blocks (ready, calibrating, …).

MATRIX

The status flags are not available with the stock MATRIX 0x22 blocks.

micro:bit
let ready = mbc.deviceReady(0x42)   // F3 bit0; false when nothing answers
let st = mbc.status(0x42)   // F3

Shared registers and commands

Identical on every product, documented once in the universal I2C protocol:

Id Name Meaning
F0 device ID low byte = factory I2C address (unchanged after re-addressing), high byte = hw[0] (channels / sensors / variant)
F1 protocol version 2 for this release (same number reported in SPIKE mode 8 v[1])
F2 firmware version major×100 + minor (e.g. 102 = 1.2)
F3 status flags b0 = ready; other bits per product (see flags)
F4 update counter +1 every 10 ms snapshot, wraps 0~255 (2.56 s per cycle, not usable for reboot detection; frozen = module busy)
F5 uptime seconds seconds since boot, saturates at 32767 (~9 h); a drop means the module rebooted
F8 change I2C address [F8][new 0x080x77][new], saved to EEPROM, takes effect after reboot; [F8][0x00][0xFF] = back to factory address
EF reboot [EF][0xA5]; mBlock sends ACK first, then reboots

Sources and assumptions

Generated by _build/gen-reference.mjs from _build/protocol/ps2.json (single source of truth, transcribed from the 2026-10-21 protocol decisions).

  • hw[0] = 1(PS2)、電量 07 恆 0、系統鍵 0A 恆 0、手把類型 0B 只會出現 0/1/2(I2C 定案 §2、§4)。
  • 08/09 L2/R2 類比照定案寫 0~255:DS2 類比模式有壓力值;沒有壓力值時按下 = 255、放開 = 0(沿用 v1 0x14 的做法,盤點 .ino:598-603)。
  • E5 震動、E6 燈條照 I2C 定案 §3 列在 PS2/PS4 共用命令表;PS2 收到回 ACK 後忽略(跟 v1 0x10/0x23 一樣)。
  • 搖桿 1013 在 I2C/mBlock 是 ±100(由 raw (v−128)×100/128、Y 上為正);EV3/0x22 維持 099(50 中間)、SPIKE 維持 1~9(5 中間)照盤點,不改鎖住模式。
  • EV3:模式 0、1、2、4 鎖住(照盤點);模式 3 改 方向|形狀<<4|已連線<<8(原本沒有 bit8);模式 5 改成 ID 格式 B(原本 = 按鍵/搖桿合併版)。
  • SPIKE:0/5/7/9 鎖住,1、2、4、6 維持不動;模式 3 改 [已連線, 電量(0), 0](原本 = 4 組按鍵);模式 8 改 v[0] = ID|hw[0]<<8(原本 = 搖桿)。模式 9 v[6] button16 = PSX 排列(D3)。
  • SPIKE 模式 5 只容得下 11 鍵(L2、R2、L3、R3、START 讀不到),鎖住不改。
Email for a quoteBuying Guide