Controller PS4/PS5 × SPIKE × Pybricks

Interactive tutorialSPIKE

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

This page covers Pybricks (Python) only: how to set it up for the Controller PS4/PS5 on SPIKE Prime / Robot Inventor, how to read every value, the commands it supports and examples. Use the switcher above to change product, host or software.

Setup

What you need

  • Sensor: Controller PS4/PS5 (SPIKE edition).
  • Hub: SPIKE Prime or Robot Inventor.
  • Computer: Chrome. Open code.pybricks.com and start writing; nothing to install.
  • Hub firmware: must be switched to Pybricks firmware v4.1 first (the stable code.pybricks.com is v4.1.1; v4.1.0 from the MBC SPIKE Blocks website works too). Every example on this site is written for v4.1.
  • Library: MBC_gamepad_Lib.py (only needed for the "import the library" method). For a new program of your own, import just MBC_sensors.py: one file for every MBC sensor.

Note

The Pybricks 3.6.1 libraries and examples are no longer updated (the 2026 SPIKE Prime hubs cannot run 3.6.1 either). For hubs that have not been upgraded, the old files are under "Legacy (no longer updated)" on the Downloads page.

Install the software

  1. Open code.pybricks.com in Chrome.
  2. Switch the hub to Pybricks firmware. The easiest way is the MBC SPIKE Blocks website: top menu "SPIKE Hub → Install/Update Pybricks firmware", about 3 minutes.
  3. To go back to the official LEGO App later, switch back to the original LEGO firmware with the SPIKE App.

Make the software see the sensor

Pybricks can read the sensor two ways; pick one:

  1. Built-in commands (no library): read the sensor with Pybricks' own sensor commands. No extra file, quickest to start.
  2. Import the library: download MBC_gamepad_Lib.py to get more data, such as the high-resolution position and every channel.

To import the library:

  1. In code.pybricks.com, click "Import a file" in the file panel on the left.
  2. Import both the example file and MBC_gamepad_Lib.py, so they are in the same project list.
  3. Open the example and press ▶ to run.

Important

New projects: use MBC_sensors.py. Old projects: just replace the one file they already use. MBC_sensors.py is one file for every MBC sensor (the EXP6 and Servo6 expander libraries are separate downloads); write from MBC_sensors import MBC_LINE8, with the same class names as in the old files. The old file names (such as MBC_gamepad_Lib.py used by the examples on this page) are kept for existing programs: swap in the new file with the same name and the program needs no changes. But every old-name file is the whole shared library, so importing 3 or more of them in one program goes over the SPIKE program slot limit of about 51 KB; to mix several sensors, import only MBC_sensors.py.

Tip

The file name must match the name in the program's import exactly, or you will get an ImportError. The library files on this site have no version suffix, so you do not need to rename them.

Tip

Most libraries take a multitask argument when you create the object: False is normal mode, one line after another; True is multitask mode, where every call needs await in front so the robot can do several things at once.

Wiring

  1. Plug the sensor into hub port C with the cable (any of A~F works).
  2. Use the same port in the program: built-in commands use Port.APort.F; a library's port argument is usually 16 for A~F.
  3. Latch up, push the plug in gently.

Pair the gamepad (first use or new gamepad)

  1. Connect the receiver and turn the controller on. Do not hold the receiver's button while plugging it in (that enters flashing mode and the receiver does nothing; release the button, unplug and replug).
  2. First use: a receiver that remembers no gamepad opens a 30-second pairing window at power-on (green LED blinking fast). To change gamepads, or if the 30 seconds are over: press the receiver's button (Reset) once; it forgets the old gamepad and opens 30 seconds again.
  3. Within those 30 seconds put the gamepad in pairing mode until its light bar flashes fast: PS4 hold SHARE + PS; PS5 hold Create + PS.
  4. Once connected, the receiver remembers this gamepad and will not connect to anyone else's. From then on, just press the gamepad's PS button after power-on.
Receiver green LED Meaning
Blinking fast Waiting for a gamepad to pair
Steady on No gamepad connected (press the PS button to reconnect)
Off Gamepad connected

Tip

At a competition, pair at your own table so no other team's gamepad is in pairing mode during the 30 seconds. Paired the wrong one? Press Reset and pair again.

Library and example downloads (Pybricks 4.1)

  • Download all (.zip): MBC_sensors.py, MBC_gamepad_Lib.py and the 6 beginner examples
  • Library only: MBC_sensors.py (use this for new projects: one file for every MBC sensor, from MBC_sensors import MBC_GAMEPAD) · MBC_gamepad_Lib.py (for the examples on this page and older programs)

Note

The Pybricks 3.6.1 examples are no longer updated; the old files are under "Legacy (no longer updated)" on the Downloads page. In those old examples a key comes out as a decimal (for example 1.0); it still works for checking whether the key is pressed, and you can wrap it in int() to print a whole number. For Pybricks 4.x use the SPIKE beginner examples or the MBC library.

All buttons as one number: buttons() and buttons_psx()

The library has two ways to pack the 16 buttons into one number. The buttons are the same; each button just has a different value:

Button pad.buttons() (MBC order) pad.buttons_psx() (PSX order)
Up / Right / Down / Left 1 / 2 / 4 / 8 16 / 32 / 64 / 128
▲ / ● / ✕ / ■ 16 / 32 / 64 / 128 4096 / 8192 / 16384 / 32768
L2 / R2 / L1 / R1 256 / 512 / 1024 / 2048 256 / 512 / 1024 / 2048
SHARE (PS5: Create) / L3 / R3 / OPTIONS 4096 / 8192 / 16384 / 32768 1 / 2 / 4 / 8
Check one button pad.pressed('CROSS') pad.pressed_psx('CROSS') or pad.buttons_psx() & PSX_CROSS
  • Beginners: use buttons() / pressed(). The D-pad and shape-button values match the official App's "reflected light"; every example on this page uses them.
  • buttons_psx() uses the PS2 gamepad's native button order. PS2 and PS4 receivers send the same number, and Arduino (I2C) reads the 16 buttons in this order too, so use it when your program shares button values with Arduino code. SPIKE edition only (the 7th value of mode 9).
  • Both are 0 while no gamepad is connected.
from pybricks.parameters import Port
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD, PSX_CROSS

pad = MBC_GAMEPAD(Port.C)
while True:
    pad.update()                 # update once per loop
    n = pad.buttons_psx()        # 16 buttons, PSX order
    if n & PSX_CROSS:            # ✕ pressed
        print('CROSS')
    if pad.pressed_psx('L1'):    # or check by name
        print('L1')
    wait(50)

Important

Pybricks 4.1 or later without the library (built-in ColorSensor blocks): the buttons (reflection(), make it whole first) and LX, LY, RX (ambient()) still work, but RY is lost (on 4.1 ambient() keeps only one decimal). For RY install the MBC library (mode 9, works on old and new firmware); a new-firmware alternative will be added after hardware testing.

First test

  1. Turn on the hub and connect it in code.pybricks.com.
  2. Import and open example 1 on this page, then press ▶.
  3. If the numbers printed in the console change with what is in front of the sensor, everything is connected.

FAQ

  • ImportError? The library was not imported into the same project, or its file name differs from the import name. Rename it to MBC_gamepad_Lib.py and import it again.
  • The example does not run, or a command is unknown? The hub firmware is too old: the examples on this site are written for Pybricks v4.1, so update the hub to v4.1 first. 3.6.1 is no longer updated; the old files are on the Downloads page.
  • What happens if the sensor cable comes out? The program keeps running: the library returns the last value it read and prints one notice in the console (in Chinese and English: MBC: Port C 讀不到感應器,先回傳上一次的值 / no sensor, using the last value); if the sensor was never connected it returns None. To check in your program, call the object's sensor_ok(): True if the last read worked, False if not (add await in multitask mode). Once you plug it back in the library keeps reading; if the sensor was missing from the start and you use multitask mode, run the program again.
  • An example marked "Pybricks only" shows strange numbers? It uses a new mode in sensor firmware from 2026-10 on; older firmware returns different numbers.
  • The official App cannot connect any more? The hub now runs Pybricks firmware: switch back to the original LEGO firmware with the SPIKE App.

Reading values

Code assumes the sensor is on port A. For the MBC library form, import the library .py into Pybricks too.

16 buttons buttons16

16 buttons
Range Meaning
0~65535 bit 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
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()

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

D-pad dpad

D-pad
Range Meaning
0~15 bit up 1, right 2, down 4, left 8, added when pressed together (0~15)
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')

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 = 形狀鍵

shape buttons shapeKeys

shape buttons
Range Meaning
0~15 bit triangle 1, circle 2, cross 4, square 8, added when pressed together (0~15)
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')

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

shoulder buttons shoulderKeys

shoulder buttons
Range Meaning
0~15 bit L2 1, R2 2, L1 4, R1 8, added when pressed together (0~15)
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')

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;同時按只回編號最小的那個)

function buttons funcKeys

function buttons
Range Meaning
0~15 bit SELECT(SHARE) 1, L3 2, R3 4, START(OPTIONS) 8, added when pressed together (0~15)
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')

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; 單鍵編號 1~8(掃 功能鍵<<4|肩鍵 的 bit0→7:1 L2 2 R2 3 L1 4 R1 5 SELECT(SHARE) 6 L3 7 R3 8 START(OPTIONS)); 高 nibble; 高 nibble; 原始藍 = 肩 | 功能<<4(0~255)

connected connected

Range Meaning
0~1 1 = gamepad connected and data seen within 300 ms, 0 = not connected (buttons 0, sticks centred while disconnected)
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()

mode 3 byte 0. Note: #67 改:模式 3 = [已連線, 電量, 0](3×int8)

battery battery

battery
Range Meaning
0~100 % gamepad battery 0~100 (always 0 on the PS2 receiver)
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()

mode 3 byte 1

stick axis 1~4 stick

stick axis 1~4
Range Meaning
1~9 (5 = centre) SPIKE gives a scaled value, 1~9 (5 = centre). I2C registers: 10 LX, 11 LY, 12 RX, 13 RY: −100 to +100, 0 = centre, right/up positive; 0 when not connected
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')

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(各 1~9,5 = 中間);byte 4~5 = RX×10+RY; 原始紅 = LX×10+LY、原始綠 = RX×10+RY(各 1~9,5 = 中間); v0 = LX | LY<<8、v1 = RX | RY<<8(各 1~9,5 = 中間); LX×1000+LY×100+RX×10+RY(各 1~9);個位數 RY 在 4.1.x 的 ambient() 讀不到,4.1.x 改從模式 5 reflection() 的小數位讀(見按鍵 buttons16); v[2..5] = LX LY RX RY,1~9,5 = 中間

Not readable here:L2 analog, R2 analog, system buttons, gamepad type. Use Arduino / mBlock / micro:bit for these.

Commands

The SPIKE port is read-only, so this software cannot send commands: calibrate with the button on the sensor and change settings over Arduino I2C, mBlock or micro:bit.

Examples

Example 1: Button test

What this example does: the hub display keeps showing the "button number". Press different buttons and the number changes, so you can check that every button works.

What you need

  • 1 SPIKE hub
  • Controller PS4/PS5 receiver + a PS4 or PS5 gamepad (already paired, see above)

How to wire it: plug the receiver into port C.

Download the program

Pybricks
ex01_button_number.py + library
Pybricks code
# 掌控者PS4 範例 1:按鍵測試(螢幕顯示按鍵數字)
# 接線:掌控者PS4 接收器 → C 埠
from pybricks.hubs import PrimeHub
from pybricks.parameters import Port
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD

hub = PrimeHub()
pad = MBC_GAMEPAD(Port.C)       # 接收器插在 C 埠

while True:
    pad.update()                # 先讀一次手把
    n = pad.buttons()           # 全部按鍵合成一個數字:沒按 = 0,上 = 1、右 = 2、下 = 4、左 = 8…
    hub.display.text(str(n))    # 主機螢幕捲動顯示這個數字
    print(n)                    # 電腦畫面也印出來
    wait(100)                   # 等 0.1 秒再讀下一次

Step by step, what the program does

  1. Repeat forever: the blocks below keep running until you press stop.
  2. Read the buttons: the official App uses "reflected light", which reads the D-pad and the shape buttons; the MBC Blocks website and Pybricks use "all buttons", which reads all 16.
  3. Show it on the hub display: the number scrolls across the display (hold the button and wait for it to finish scrolling).
  4. Wait 0.1 second before reading again.

What you should see when it runs

Holding All three versions show MBC Blocks website and Pybricks only
Nothing 0
Up 1
Right / Down / Left 2 / 4 / 8
▲ / ● / ✕ / ■ 16 / 32 / 64 / 128
Up + ▲ together 17 (1 + 16, pressed together = added up)
L2 / R2 / L1 / R1 Official App still shows 0 (shoulder buttons need the "color" block, used in Example 3) 256 / 512 / 1024 / 2048
SHARE / L3 / R3 / OPTIONS Official App still shows 0 4096 / 8192 / 16384 / 32768

Common questions

  • Always 0, nothing happens no matter what I press? → The gamepad is not connected yet: look at the green light on the receiver. Steady on = press the PS button on the gamepad to reconnect; blinking fast = still waiting to pair, follow "pair the gamepad" above.
  • The gamepad keeps connecting and dropping? → The receiver remembers a different gamepad: press the button (Reset) on the receiver once and pair again.
  • The hub says it cannot find the sensor, or the numbers look strange? → The receiver may have the EV3 firmware. You need the SPIKE version.

Example 2: Joystick value

What this example does: the hub display shows the up-down position of the left stick (1 to 9). Released is 5; pushing up makes it bigger, pulling down makes it smaller.

What you need: same as Example 1.

How to wire it: plug the receiver into port C.

Download the program

Pybricks
ex02_stick_value.py + library
Pybricks code
# 掌控者PS4 範例 2:搖桿數值(螢幕顯示左搖桿上下的位置)
# 接線:掌控者PS4 接收器 → C 埠
from pybricks.hubs import PrimeHub
from pybricks.parameters import Port
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD

hub = PrimeHub()
pad = MBC_GAMEPAD(Port.C)       # 接收器插在 C 埠

while True:
    pad.update()                # 先讀一次手把
    y = pad.stick('LY')         # 左搖桿上下:1(拉到最下)~ 5(中間)~ 9(推到最上)
    hub.display.number(y)       # 主機螢幕顯示這個數字
    print('LX', pad.stick('LX'), 'LY', y, 'RX', pad.stick('RX'), 'RY', pad.stick('RY'))   # 電腦畫面印出 4 個方向
    wait(100)

Step by step, what the program does

  1. Read the left stick up-down:
    • Official App: "raw red" reads X×10+Y (for example 59 = left-right centered at 5, pushed all the way up to 9), so use "remainder of dividing by 10" to pull out the Y (59 → 9).
    • MBC Blocks website: just pick "left stick Y (1~9)".
    • Pybricks: pad.stick('LY').
  2. Show it on the hub display. The MBC Blocks website and Pybricks also print the number on the computer screen (Pybricks prints all 4 directions).
  3. Wait 0.1 second before reading again.

What you should see when it runs

  • Stick released: 5.
  • Slowly push up: 6, 7, 8, 9.
  • Slowly pull down: 4, 3, 2, 1.

Common questions

  • Always 5, even when I push the stick? → The sticks also read 5 while no gamepad is connected: check whether the green light on the receiver has gone off (off means connected).
  • Want the right stick, or the left-right direction? → MBC Blocks website: change the dropdown to "right stick Y" or "left stick X"; official App: use "raw green" for the right stick, and "whole part of dividing by 10" for left-right; Pybricks: change 'LY' to 'RY', 'LX' or 'RX'.

Example 3: Buttons control the light and sound

What this example does: press ▲ and the hub light turns green, ● red, ✕ blue, ■ pink (the same colors as the symbols on the PS4 gamepad); with nothing pressed the light turns off. Press L1 for a low beep, R1 for a high beep.

What you need: same as Example 1.

How to wire it: plug the receiver into port C.

Download the program

Pybricks
ex03_light_sound.py + library
Pybricks code
# 掌控者PS4 範例 3:按鍵控制燈和聲音
# 接線:掌控者PS4 接收器 → C 埠
# ▲ 綠燈、● 紅燈、✕ 藍燈、■ 粉紅燈(跟手把上的顏色一樣),都沒按就關燈;L1 低音嗶、R1 高音嗶
from pybricks.hubs import PrimeHub
from pybricks.parameters import Color, Port
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD

hub = PrimeHub()
pad = MBC_GAMEPAD(Port.C)               # 接收器插在 C 埠

while True:
    pad.update()                        # 先讀一次手把
    if pad.pressed('TRIANGLE'):         # ▲
        hub.light.on(Color.GREEN)
    elif pad.pressed('CIRCLE'):         # ●
        hub.light.on(Color.RED)
    elif pad.pressed('CROSS'):          # ✕
        hub.light.on(Color.BLUE)
    elif pad.pressed('SQUARE'):         # ■
        hub.light.on(Color.MAGENTA)
    else:                               # 4 個都沒按
        hub.light.off()

    if pad.pressed('L1'):
        hub.speaker.beep(262, 100)      # 低音 Do(262 Hz),響 0.1 秒
    if pad.pressed('R1'):
        hub.speaker.beep(523, 100)      # 高音 Do(523 Hz)
    wait(50)

Step by step, what the program does

  1. First read the buttons into a "variable" (a variable = a box with a name that can hold one number): the official App uses two boxes, "buttons" holds reflected light (D-pad + shape buttons) and "shoulder" holds color (L1, R1, ...). The MBC Blocks website and Pybricks do not need to store anything; they just ask "is this button pressed?".
  2. If ▲ is pressed → green light.
  3. Else, if ● is pressed → red light; else if ✕ → blue light; else if ■ → pink light.
  4. None of them → light off.
  5. Two more "if"s: L1 pressed → low beep (note 60 in the official App, 262 Hz in the other two; both are a "Do"); R1 pressed → high beep (note 72 / 523 Hz, the Do one octave up).
  6. How the official App checks "▲ is pressed": whole part of (buttons ÷ 16), then remainder of dividing by 2 = 1. For ● use 32, ✕ 64, ■ 128; L1 is (shoulder ÷ 4), R1 is (shoulder ÷ 8).

What you should see when it runs

  • Hold ▲: green light; release: light off.
  • Press ▲ and ● together: green light (the first "if" that is true wins, the ones below are not checked).
  • Hold L1: it keeps beeping the low note.

Common questions

  • No sound for L1, R1 in the official App? → The shoulder buttons are read with the "color" block (this page was put together from the firmware and the official examples; it has not yet been checked one by one on real hardware). First run Example 1 to confirm the D-pad and shape buttons work; then change "reflected light" in Example 1 to "color", and pressing L1 should show 4, R1 should show 8.
  • How do I change the light color? → Official App: click the color dot on the "set Power Button light to" block; MBC Blocks website: click the dropdown on the "set hub light to" block; Pybricks: change Color.GREEN to something like Color.YELLOW.

Example 4: Drive with the D-pad

What this example does: hold a D-pad button and the car drives that way; release and it stops. Up = forward, Down = backward, Left = turn left in place, Right = turn right in place.

What you need

  • Everything from Example 1, plus
  • A two-wheel car (two motors as the wheels)

How to wire it

  • Plug the receiver into port C
  • Left wheel motor on E, right wheel motor on F

Download the program

Pybricks
ex04_dpad_drive.py + library
Pybricks code
# 掌控者PS4 範例 4:方向鍵開車(按住才走,放開就停)
# 接線:掌控者PS4 接收器 → C 埠;左輪馬達 → E 埠;右輪馬達 → F 埠
from pybricks.parameters import Direction, Port
from pybricks.pupdevices import Motor
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD

pad = MBC_GAMEPAD(Port.C)                          # 接收器插在 C 埠
left = Motor(Port.E, Direction.COUNTERCLOCKWISE)   # 左輪:反過來裝,所以設成逆時針才是往前
right = Motor(Port.F)                              # 右輪

while True:
    pad.update()                    # 先讀一次手把
    if pad.pressed('UP'):           # 上 → 前進(功率 50%)
        left.dc(50)
        right.dc(50)
    elif pad.pressed('DOWN'):       # 下 → 後退
        left.dc(-50)
        right.dc(-50)
    elif pad.pressed('LEFT'):       # 左 → 原地左轉:左輪往後、右輪往前
        left.dc(-30)
        right.dc(30)
    elif pad.pressed('RIGHT'):      # 右 → 原地右轉
        left.dc(30)
        right.dc(-30)
    else:                           # 都沒按 → 停
        left.dc(0)
        right.dc(0)
    wait(50)

Step by step, what the program does

  1. Tell the hub how the wheels are mounted: the two motors face each other, so the left wheel has to "spin the other way" to go forward.
    • MBC Blocks website: "E is a LEGO motor, forward = counterclockwise" (F is clockwise); Pybricks: Direction.COUNTERCLOCKWISE. Once this is set, giving both wheels a positive number means forward.
    • The official App's motor blocks have no such setting, so the left wheel E gets a minus sign: forward is E -50, F 50.
  2. Repeat forever: read the buttons.
  3. If Up is pressed → both wheels forward (50% power).
  4. Else if Down → both wheels backward.
  5. Else if Left → left wheel backward, right wheel forward → turn left in place (30% power; turning slower is easier to control).
  6. Else if Right → turn right in place.
  7. Nothing pressed → both wheels power 0, stop.
Holding MBC Blocks website, Pybricks (E, F)
Up 50, 50
Down -50, -50
Left -30, 30
Right 30, -30
Nothing 0, 0

What you should see when it runs: hold Up and the car drives forward; release and the car stops.

Common questions

  • Pressing Up drives backward? → The motors are mounted the opposite way from the standard LEGO car: MBC Blocks website: swap the "forward" directions of E and F; Pybricks: change the left wheel to Direction.CLOCKWISE and the right wheel to Motor(Port.F, Direction.COUNTERCLOCKWISE); official App: flip the sign of every number.
  • Pressing Up spins in place? → Only one side is reversed: flip the sign (or forward direction) of that one motor only.
  • The gamepad ran out of battery or went too far and lost the connection? → If the receiver hears nothing from the gamepad for more than 0.3 seconds, it releases all buttons and returns the sticks to center, so the car stops by itself.

Example 5: Tank drive with two joysticks

What this example does: drive it like a tank: the left stick controls the left wheel, the right stick controls the right wheel. Push both forward = forward, pull both back = backward, one forward and one back = turn in place. The further you push, the faster it goes; release and it stops.

What you need: same as Example 4.

How to wire it: plug the receiver into port C; left wheel motor on E, right wheel motor on F.

Download the program

Pybricks
ex05_tank_drive.py + library
Pybricks code
# 掌控者PS4 範例 5:雙搖桿坦克開車(左搖桿管左輪、右搖桿管右輪)
# 接線:掌控者PS4 接收器 → C 埠;左輪馬達 → E 埠;右輪馬達 → F 埠
from pybricks.hubs import PrimeHub
from pybricks.parameters import Color, Direction, Port
from pybricks.pupdevices import Motor
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD

hub = PrimeHub()
pad = MBC_GAMEPAD(Port.C)                          # 接收器插在 C 埠
left = Motor(Port.E, Direction.COUNTERCLOCKWISE)   # 左輪:反過來裝,所以設成逆時針才是往前
right = Motor(Port.F)                              # 右輪

# 開始前的保護:等兩支搖桿都在中間(4~6)才開始,車子才不會一開機就衝出去
while True:
    pad.update()
    if 4 <= pad.stick('LY') <= 6 and 4 <= pad.stick('RY') <= 6:
        break
    wait(20)
hub.light.on(Color.GREEN)                          # 綠燈 = 可以開了

while True:
    pad.update()                                   # 先讀一次手把
    ly = pad.stick('LY')                           # 左搖桿上下 1~9
    ry = pad.stick('RY')                           # 右搖桿上下 1~9
    # 死區:搖桿在中間附近(4~6)就當成 0,放開手車子就停
    if 4 <= ly <= 6:
        left_power = 0
    else:
        left_power = (ly - 5) * 25                 # 7 → 50%、9 → 100%、3 → -50%、1 → -100%
    if 4 <= ry <= 6:
        right_power = 0
    else:
        right_power = (ry - 5) * 25
    left.dc(left_power)
    right.dc(right_power)
    wait(20)

Step by step, what the program does

  1. Safety check before starting: first "wait until" both sticks are centered (4 to 6), then turn the light green. This way the car will not suddenly shoot off if a stick happens to get bumped right at startup.
  2. Repeat forever: read the left stick up-down (Y) and the right stick up-down (Y). In the official App that is "raw red" and "raw green", remainder of dividing by 10.
  3. Dead zone: if the stick is between 4 and 6, set the power to 0. When you let go, the stick does not always land exactly on 5; the dead zone is what makes the car stop properly.
  4. Else: power = (stick − 5) × 25. Pushed to 7 is 50%, all the way to 9 is 100%; pulled to 3 is -50%, all the way to 1 is -100%.
  5. Send the power to the motors: left wheel E uses the left stick's power, right wheel F uses the right stick's power. In the official App the left wheel is × -1 (same reason as Example 4).

What you should see when it runs

  • Right after the program starts: release the sticks and the light turns green; the car does not move.
  • Push both sticks forward together: it drives forward, faster the further you push.
  • Left stick forward, right stick back: it turns right in place.
  • Release: the car stops.

Common questions

  • I pressed run but the green light never comes on? → The sticks are not centered: release both sticks and the green light comes on and it starts.
  • A small push does not move the car? → That is the dead zone; it only moves at 7 or more (or 3 or less). For more sensitivity you can change "4 to 6" to "5 to 5", but the car may creep slowly when you let go.
  • Too fast to control? → Change "× 25" to "× 15" (full push becomes 60%).
  • Forward and backward are swapped, or it spins in place? → See the common questions in Example 4.

Example 6 (advanced): Connection status and battery level

Note

This example has a Pybricks version only. The "connection status" and "battery level" it uses live in the receiver's mode 3, which the official LEGO App and the MBC Blocks website cannot read. The receiver firmware must be a version from 2026-10 or later; older firmware returns different numbers there. The code is the example itself, so there is no screenshot.

What this example does: the light turns red when the program starts, then it waits until the gamepad connects before turning green, beeping once, and printing the receiver type and firmware version; after that the display keeps showing the gamepad battery level (0 to 100, for example 80). Turn the gamepad off, or walk too far and lose the connection, and the light goes back to red and the display shows "--".

What you need: same as Example 1 (hub + receiver + an already paired PS4 or PS5 gamepad).

How to wire it: plug the receiver into port C.

Download the program

Pybricks
ex06_connected_battery.py + library
Pybricks code
# 掌控者PS4 範例 6:連線狀態與電量(等手把連上才開始,螢幕顯示電量)
# 接線:掌控者PS4 接收器 → C 埠
# 只有 Pybricks 版:連線狀態和電量放在接收器的「模式 3」,官方 App 和積木網站讀不到;接收器韌體要 2026-10 以後
from pybricks.hubs import PrimeHub
from pybricks.parameters import Color, Port
from pybricks.tools import wait
from MBC_gamepad_Lib import MBC_GAMEPAD

hub = PrimeHub()
pad = MBC_GAMEPAD(Port.C)                    # 接收器插在 C 埠

hub.light.on(Color.RED)                      # 紅燈 = 手把還沒連上
while not pad.connected():                   # 等到手把連上(True)才往下
    wait(200)
hub.light.on(Color.GREEN)                    # 綠燈 = 連上了
hub.speaker.beep()
print('接收器種類', pad.kind(), ' 韌體', pad.firmware_version())   # 例如 PS4 (1, 0)

while True:
    if pad.connected():
        battery = pad.battery()              # 手把電量 0~100
        hub.display.number(min(battery, 99)) # 螢幕最多只能顯示兩位數
        hub.light.on(Color.GREEN)
        print('已連線  電量', battery, '%')
    else:                                    # 手把關機或離太遠 → 斷線
        hub.display.text('--')
        hub.light.on(Color.RED)
        print('斷線了')
    wait(500)

Step by step, what the program does

  1. pad.connected(): whether a gamepad is connected to the receiver (True / False). At the start, "wait until connected" replaces Example 5's "wait until the sticks are centered", which is more reliable.
  2. pad.kind(), pad.firmware_version(): whether the receiver is the PS2 or the PS4 model, and which firmware version it has (read from the sensor's mode 8).
  3. Repeat forever: while connected, show pad.battery() and a green light; when disconnected, red light and "--". The display can only show two digits, so 100 shows as 99.
  4. This example never calls pad.update(), because it does not read any buttons. For a program that reads buttons, follow Examples 1 to 5, and call connected() and battery() outside the loop or only now and then (each call makes the receiver switch modes, which costs a few tens of ms).

What you should see when it runs: gamepad not connected yet: red light. Press the PS button on the gamepad, and the moment it connects: green light + beep, display shows the battery level (for example 80). Turn the gamepad off: red light, "--".

Common questions

  • The battery number does not change? → The gamepad only reports its battery level every so often, and it does not always update right away while charging. That is normal.
  • Red light all the time? → The gamepad is not paired (see "pair the gamepad" above), or the receiver firmware is too old (before 2026-10) and has no connection status. Please update the firmware.
  • Can I combine this with Example 5? → Yes: replace "wait until the sticks are centered" at the start of Example 5 with "wait until pad.connected()". You could also turn the light orange as a warning when the battery drops below 20.

More questions

  • The hub does not detect the sensor? Unplug and replug, wait 2 s, try another port. The official app shows it as a colour sensor — that is expected.
  • Can I use the official app and Pybricks on the same hub? One firmware at a time: Pybricks and the MBC Blocks website need Pybricks firmware, the official app needs LEGO firmware; you can switch back any time with the SPIKE app.

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/ps4.json (single source of truth)
  • Setup:_build/guides/software/spike-pybricks.en.md、_build/guides/product/ps4/spike-pybricks.en.md
  • Examples:sensors/ps4/spike-examples.md
Email for a quoteBuying Guide