Gamepad Receiver PS4/PS5: value & command reference
Back to Gamepad Receiver PS4/PS5
Every value and command the Gamepad Receiver PS4/PS5 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] | 2(F0 high byte; ID high byte in SPIKE mode 8 / EV3 mode 5) |
| Model | PS4 |
| 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~100 % | ✓ | — | ✓ | ✓ | — | ✓ |
08 |
L2 analog | 0~255 | — | — | ✓ | ✓ | — | ✓ |
09 |
R2 analog | 0~255 | — | — | ✓ | ✓ | — | ✓ |
0A |
system buttons | 0~3 bit | — | — | ✓ | ✓ | — | ✓ |
0B |
gamepad type | 0~4 | — | — | ✓ | ✓ | — | ✓ |
10~13 |
stick axis 1~4 | -100~+100 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
✓ = available on that host; — = not sent.
Reading each value
01 16 buttons buttons16
| Item | Detail |
|---|---|
| Meaning | all buttons in one 16-bit word (PSX layout): bit0 SELECT(SHARE), 1 L3, 2 R3, 3 START(OPTIONS), 4 up, 5 right, 6 down, 7 left, 8 L2, 9 R2, 10 L1, 11 R1, 12 triangle, 13 circle, 14 cross, 15 square; 1 = pressed; 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 5 v[0] 🔒; mode 0 (edu) byte 6 🔒; mode 6 v[2]; mode 9 v[6] 🔒. Note: Pybricks reflection() 編碼:跟 PS2 同一套 11 鍵 complexXY = SELECT(SHARE)<<10 | R1<<9 | L1<<8 | ■<<7 | ✕<<6 | ●<<5 | ▲<<4 | 左<<3 | 下<<2 | 右<<1 | 上;L2、R2、L3、R3、START(OPTIONS) 讀不到(PS4 .ino:768)。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; PSX 排列(#65 已改,原本是 Bluepad32 排列); PSX 排列; PSX 排列(D3,PS2/PS4 相同;函式庫 buttons_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 bitmBlock
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 bitRelated 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
| Item | Detail |
|---|---|
| Meaning | up 1, right 2, down 4, left 8, added when pressed together (0~15) |
| 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 7 v[0] lo 🔒; mode 9 v[0] lo 🔒. Note: 低 nibble;高 nibble = 形狀鍵; 低 nibble;高 nibble = 形狀鍵; 低 nibble;高 nibble = 形狀鍵; 低 nibble;高 nibble = 形狀鍵
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 1×int8; mode 1 · int8 · IR-SEEK [方向鍵, LX, 形狀鍵, LY, 肩鍵, RX, 功能鍵, RY]; mode 3 · int16 · 低 nibble;高 nibble = 形狀鍵;bit8 = 已連線(+256,#67 新增); 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 bitmBlock
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(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 bit03 shape buttons shapeKeys
| Item | Detail |
|---|---|
| Meaning | triangle 1, circle 2, cross 4, square 8, added when pressed together (0~15) |
| 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 7 v[0] hi 🔒; mode 9 v[0] lo 🔒. Note: 高 nibble; 高 nibble; 高 nibble; 高 nibble
from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port
dev = PUPDevice(Port.A) # sensor on port A
w = dev.read(9)[0]
shapeKeys = (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
shapeKeys = pad.pressed('TRIANGLE')EV3
mode 1 · int8; mode 3 · int16 · 高 nibble(÷16 取整數再 mod 16); 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
shapeKeys = sensor.read(1)[2] # shape buttons:0~15 bit
w = sensor.read(3)[0]
shapeKeys = (w >> 4) & 0x0F # shape buttons (bits 4~7)
shapeKeys = sensor.read(4)[2] # shape buttons:0~15 bit' clev3r (BASIC)
Sensor.SetMode(1, 1)
shapeKeys = Sensor.ReadRawValue(1, 2)
Sensor.SetMode(1, 3)
w = Sensor.ReadRawValue(1, 0)
shapeKeys = Math.Remainder(Math.Floor(w / 16), 16) ' bits 4~7
Sensor.SetMode(1, 4)
shapeKeys = Sensor.ReadRawValue(1, 2)Arduino I2C
Returns MBC_NONE (−32768) when the module does not answer.
int16_t shapeKeys = mbcRead(0x42, 0x03); // shape buttons:0~15 bitmBlock
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 shapeKeys = mbc.padGroup(MbcPadGroup.Shapes) // shape buttons:0~15 bit04 shoulder buttons shoulderKeys
| Item | Detail |
|---|---|
| Meaning | L2 1, R2 2, L1 4, R1 8, added when pressed together (0~15) |
| 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 7 v[1] lo 🔒; mode 9 v[1] lo 🔒; mode 0 (toy) byte 6 🔒; mode 0 (toy) byte 1 🔒. Note: 低 nibble;高 nibble = 功能鍵; 低 nibble;高 nibble = 功能鍵; 低 nibble;高 nibble = 功能鍵; 原始藍 = 肩 | 功能<<4(0~255); 單鍵編號(L2=1 R2=2 L1=3 R1=4 SELECT(SHARE)=5 L3=6 R3=7 START(OPTIONS)=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]
shoulderKeys = 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
shoulderKeys = 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
shoulderKeys = sensor.read(1)[4] # shoulder buttons:0~15 bit' clev3r (BASIC)
Sensor.SetMode(1, 1)
shoulderKeys = Sensor.ReadRawValue(1, 4)Arduino I2C
Returns MBC_NONE (−32768) when the module does not answer.
int16_t shoulderKeys = mbcRead(0x42, 0x04); // shoulder buttons:0~15 bitmBlock
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(G = 肩鍵 | 功能鍵<<4(L2 1 R2 2 L1 4 R1 8、SELECT(SHARE) 16 L3 32 R3 64 START(OPTIONS) 128))
int shoulderKeys = 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 shoulderKeys = mbc.padGroup(MbcPadGroup.Shoulders) // shoulder buttons:0~15 bit05 function buttons funcKeys
| Item | Detail |
|---|---|
| Meaning | SELECT(SHARE) 1, L3 2, R3 4, START(OPTIONS) 8, added when pressed together (0~15) |
| 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 0 (toy) byte 1 🔒; mode 2 byte 0; mode 6 v[1] hi; mode 7 v[1] hi 🔒; mode 9 v[1] lo 🔒; mode 0 (toy) byte 6 🔒. Note: 高 nibble; 單鍵編號 18(掃 功能鍵<<4|肩鍵 的 bit0→7:1 L2 2 R2 3 L1 4 R1 5 SELECT(SHARE) 6 L3 7 R3 8 START(OPTIONS)); 高 nibble; 高 nibble; 原始藍 = 肩 | 功能<<4(0255)
from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port
dev = PUPDevice(Port.A) # sensor on port A
w = dev.read(9)[1]
funcKeys = (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
funcKeys = 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
funcKeys = sensor.read(1)[6] # function buttons:0~15 bit' clev3r (BASIC)
Sensor.SetMode(1, 1)
funcKeys = Sensor.ReadRawValue(1, 6)Arduino I2C
Returns MBC_NONE (−32768) when the module does not answer.
int16_t funcKeys = mbcRead(0x42, 0x05); // function buttons:0~15 bitmBlock
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 funcKeys = mbc.padGroup(MbcPadGroup.Functions) // function buttons:0~15 bit06 connected connected
| Item | Detail |
|---|---|
| Meaning | 1 = gamepad connected and data seen within 300 ms, 0 = not connected (buttons 0, sticks centred while disconnected) |
| Range | 0~1 |
| I2C / mBlock id | 06 |
How to read it
SPIKE
mode 3 byte 0. Note: #67 改:模式 3 = [已連線, 電量, 0](3×int8)
from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port
dev = PUPDevice(Port.A) # sensor on port A
connected = dev.read(3)[0] # connectedMBC 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 · bit8(值 ≥ 256 = 已連線,#67 新增)
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 8Arduino I2C
Returns MBC_NONE (−32768) when the module does not answer.
int16_t connected = mbcRead(0x42, 0x06); // connected:0~1mBlock
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(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~107 battery battery
| Item | Detail |
|---|---|
| Meaning | gamepad battery 0~100 (always 0 on the PS2 receiver) |
| Range | 0~100 % |
| I2C / mBlock id | 07 |
How to read it
SPIKE
mode 3 byte 1
from pybricks.iodevices import PUPDevice
from pybricks.parameters import Port
dev = PUPDevice(Port.A) # sensor on port A
battery = dev.read(3)[1] # batteryMBC 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~100 %mBlock
Block returns battery(0~100 %)。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~100 %08 L2 analog l2Analog
| Item | Detail |
|---|---|
| Meaning | L2 trigger depth 0~255 (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~255mBlock
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~25509 R2 analog r2Analog
| Item | Detail |
|---|---|
| Meaning | R2 trigger depth 0~255 (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~255mBlock
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~2550A system buttons sysKeys
| Item | Detail |
|---|---|
| Meaning | b0 PS button, b1 capture (PS5 mute) button; 0 when not connected (always 0 on PS2) |
| Range | 0~3 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 buttons:0~3 bitmBlock
Block returns system buttons(0~3 bit)。Host sends [0A] → reply [lo][hi][0A ^ lo ^ hi], value = int16 little-endian.
(Controller port [1 v] system buttons :: 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 buttons:0~3 bit0B gamepad type padType
| Item | Detail |
|---|---|
| Meaning | 0 none, 1 PS2 digital, 2 PS2 analog, 3 PS4, 4 PS5 (or other) |
| Range | 0~4 |
| 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~4mBlock
Block returns gamepad type(0~4)。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~410~13 stick axis 1~4 stick
| Item | Detail |
|---|---|
| Meaning | 10 LX, 11 LY, 12 RX, 13 RY: −100 to +100, 0 = centre, right/up positive; 0 when not connected |
| 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: LX×10+LY(各 19,5 = 中間);byte 45 = RX×10+RY; 原始紅 = LX×10+LY、原始綠 = RX×10+RY(各 19,5 = 中間); v0 = LX | LY<<8、v1 = RX | RY<<8(各 19,5 = 中間); LX×1000+LY×100+RX×10+RY(各 19);個位數 RY 在 4.1.x 的 ambient() 讀不到,4.1.x 改從模式 5 reflection() 的小數位讀(見按鍵 buttons16); v[2..5] = LX LY RX RY,19,5 = 中間
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 · LX;LY 在 index 3、RX 5、RY 7(與按鍵交錯);099,50 = 中間(未連線 50); mode 2 · int8 · IR-REMOTE [LX, LY, RX, RY] 099,50 = 中間; mode 4 · int8 · LX;LY 在 index 3(RX、RY 不在模式 4) (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 axis 1~4 (index 1, 3, 5, 7): 0~99 (50 = centre)
stick = sensor.read(2)[0:4] # stick axis 1~4
stick = sensor.read(4)[1::2] # stick axis 1~4 (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 axis 1~4:-100~+100
for (uint8_t i = 0; i < 4; i++) { Serial.print(stick[i]); Serial.print(' '); }
Serial.println();mBlock
Block returns stick axis 14(-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 [axis] :: sensing)
MATRIX
0x22 B~Y.
// A. stock 0x22 blocks:B~Y = stick axis 1~4(B C M Y = LX LY RX RY,0~99,50 = 中間)
int raw = MiniR4.I2C1.MXColor.getColor(B); // group 1micro: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 axis 1~4:-100~+100Write commands
Only Arduino I2C, mBlock and micro:bit can send commands; the SPIKE / EV3 ports are read-only.
E5 rumble rumble
| Item | Detail |
|---|---|
| Action | [strength 0~255], 0 = stop; 200 ms per call (ignored by PS2) |
| 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) // rumbleE6 light bar colour lightbar
| Item | Detail |
|---|---|
| Action | [R][G][B] each 0~255, exactly 3 bytes; remembered until connected; default blue (0,0,255) (ignored by PS2) |
| Parameters | R 0~255(1 byte), G 0~255(1 byte), B 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(R 0~255, G 0~255, B 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 colourStatus 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 (linkActive) |
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 # 2EV3
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 # 2Arduino I2C
int16_t st = mbcRead(0x42, 0xF3);
bool ready = st & (1 << 0); // ready
bool connected = st & (1 << 1); // gamepad connectedmBlock
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) // F3Shared 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 0x08 |
EF |
reboot | [EF][0xA5]; mBlock sends ACK first, then reboots |
Sources and assumptions
Generated by _build/gen-reference.mjs from _build/protocol/ps4.json (single source of truth, transcribed from the 2026-10-21 protocol decisions).
- 值表、命令、旗標與 PS2 完全相同(mBlock 定案「兩款相同」+I2C 定案 §3/§4);只有 hw0 = 2、model = PS4、E5/E6 有作用。
- D3:SPIKE 模式 0 教育版 byte 6~7、模式 6 v[2]、模式 9 v[6] 的 button16 都是 PSX 排列(#65 已把 PS4 改成 PSX,盤點檔寫的 Bluepad32 排列已過時)。
- 0x22 命令 4 同時裝方向鍵(低 nibble)與形狀鍵(高 nibble)、命令 5 裝肩鍵(低)與功能鍵(高);JSON 只能把一個命令掛在一個值上,所以掛在 02/04,03/05 的 ch22 = null(內容寫在 02/04 的 note)。SPIKE 模式 0/1/2/9 的 nibble 也用同樣方式在 note 說明。
- 搖桿:I2C/mBlock 送 ±100;EV3/0x22送 0
99(50 = 中間)、SPIKE 送 19(5 = 中間),不是線性 offset,主機端各自換算(網站範例已是這樣)。 - EV3 模式 3 = 方向鍵 | 形狀鍵<<4 | 已連線<<8(#67 新增 bit8);模式 4 內容不變([方向鍵, LX, 形狀鍵, LY]);模式 5 改送裝置 ID 格式 B [0x42 | 2<<8, 韌體版]。
- SPIKE 模式 3 改 [已連線, 電量, 0](3×int8)、模式 8 改 [0x42 | 2<<8, 協議版 2, 韌體版, 更新序號];其餘空模式 1、2、4、6 維持現狀。
- ESP32 從機不能時脈延展:I2C v2 在 onReceive 就預載從該編號起 32 B;0x22照 MATRIX 定案 §5 #2 預載最新一包,待上機。
- I2C 0A 系統鍵 b1 = 擷取鍵(v1 0x15 放在 b3,v2 改 b1,照 I2C 定案 §4)。