Write Python scripts and run them on a PSP. This is a port of MicroPython 1.29 to the PSP. It runs on a PSP with custom firmware and in the PPSSPP emulator.
Scripts can read the buttons, the analog stick, the battery, the clock and the memory stick, print text, draw on the screen with Pimoroni's PicoVector at 60 frames a second, play sound, and connect to Wi-Fi. If you'd like to see more, star the repo or open an issue: that decides how much further this goes.
There's no release to download yet, so make the zip yourself:
- Build the EBOOT (see Building from source) and
run
tools/make-release.sh. It writesbuild/release/micropython-psp-<version>.zip. - Copy the
PSPfolder from the zip onto the memory stick, merging it with thePSPfolder that's already there. You should end up withPSP/GAME/MicroPython/EBOOT.PBP. - On the PSP, open Game → Memory Stick → MicroPython.
It has been tested on a PSP-1000 running 6.60 PRO-C, and in PPSSPP 1.20.4. Other models and custom firmwares should work too, but haven't been tried yet.
Put .py files in PSP/GAME/MicroPython/, next to EBOOT.PBP. When
MicroPython starts, it lists them:
| Button | Action |
|---|---|
| Up / Down | choose a script |
| ✕ | run it |
| △ | show its source |
| ○ | back to the list |
| HOME | quit |
If the folder has a main.py, MicroPython runs that instead of showing the
list (after boot.py, if there is one). Modules in a lib/ folder can be
imported.
The zip comes with an example for each part of the port: hello (versions,
clock speed and battery), buttons, stick, clock (the time), notes
(files), tasks (asyncio), colours (text on screen), keys (sound),
wifi (networking) and demos (graphics).
They're in examples/ too.
demos is a tour of PicoVector: 24 short demos from Pimoroni's Tufty 2350
(brushes, blur, gradients, strokes, fill rules, text layout, sprites and a
raycaster), redrawn for the PSP's 480×272 screen. Up and Down change demo,
and START finishes. Each demo is a small module in demos/ with an
update(ticks) function, which makes them a good place to start your own.
import psp
print("Press some buttons. START finishes.")
while True:
for b in psp.pressed():
print("pressed", b, "held:", " ".join(psp.held()))
if b == psp.START:
raise SystemExit
psp.vsync()| Call | Returns |
|---|---|
psp.held() |
buttons held now, for example ['CROSS', 'UP'] |
psp.pressed() |
buttons pressed since the last call |
psp.released() |
buttons released since the last call |
psp.UP, psp.CROSS, ... |
button names: UP DOWN LEFT RIGHT CROSS CIRCLE TRIANGLE SQUARE L R START SELECT |
psp.analog() |
the stick position (x, y), each -127 to 127 |
psp.set_deadzone(n) |
ignore stick movement within n of the centre (default 16) |
psp.vsync() |
waits for the next screen refresh (60 Hz) |
psp.battery() |
battery percent, or None |
psp.battery_minutes() |
minutes left, or None |
psp.charging(), psp.on_ac() |
True while charging, or on mains power |
psp.freq() |
the clock as (cpu, bus) in MHz; psp.freq(222) sets it |
psp.emulator() |
True in PPSSPP |
psp.VERSION |
the port's version |
print() writes to the screen: 68 columns by 34 rows of text, which wraps
at the right edge and scrolls up at the bottom. ANSI codes in the text clear
the screen, move the cursor and set colours, as in a terminal. The ansi
module has them ready to use:
import ansi
ansi.clear() # clears the screen, cursor at the top left
ansi.move(10, 5) # column 10, row 5 (both from 0)
print(ansi.RED + "Game over" + ansi.RESET)
print(ansi.BG_BLUE + ansi.BRIGHT_WHITE + " Score: 100 " + ansi.RESET)In ansi |
Does |
|---|---|
clear(), clear_line() |
clears the screen, or the line from the cursor |
move(col, row) |
moves the cursor |
RED, GREEN, ... |
text colour: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE |
BRIGHT_RED, ... |
the bright versions |
BG_RED, ... |
background colour |
BRIGHT, REVERSE, RESET |
bright text, swapped colours, back to white on black |
COLUMNS, ROWS |
the screen's size in characters: 68 and 34 |
The screen also understands the codes themselves ("\x1b[2J" and so on):
cursor moves up, down, left and right, saving and restoring the cursor, and
the 256-colour codes for the first 16 colours. Codes it doesn't know are
dropped. Characters past ASCII show as ?. During a REPL session over USB,
the same codes work in the terminal on the computer. colours.py shows the
16 colours, and a timer redrawn in place.
Scripts draw with Pimoroni's
PicoVector, the
graphics library of Pimoroni's Badgeware badges, into screen, a 480×272
picovector image that pspdisplay puts on the PSP screen:
import psp
from picovector import color, font, shape, vec2
from pspdisplay import screen, update, WIDTH, HEIGHT # 480, 272
screen.font = font.load("fonts/sins.ppf") # a font file next to the script
x = 0
while psp.START not in psp.pressed():
screen.pen = color.rgb(0, 0, 0)
screen.clear()
screen.pen = color.rgb(255, 0, 0)
screen.circle(vec2(x, HEIGHT // 2), 20)
screen.text("Hello", vec2(8, 8))
update() # shows the frame at the next screen refresh
x = (x + 4) % WIDTHpicovector has shapes (circles, polygons, stars, arcs, lines and strokes),
brushes and colours (RGB, HSV and OKLCH, with alpha), images (load PNG, JPEG
and GIF files, blit and scale them, sprite sheets and filters), transforms,
vector (.af) and pixel (.ppf) fonts, and tweens. On the PSP:
update()waits for the screen refresh, so a drawing loop runs at up to 60 frames a second without any other pacing. The drawing stays in place between frames.- Antialiasing is off until you set
screen.antialias = image.X2orX4. - Fonts load from files:
font.load("fonts/sins.ppf")with a folder in the name opens that file, relative to the script's folder. A name on its own (font.load("sins"), orfont.sins) looks in folders such as/rom/fonts, which the PSP doesn't have. The demos come withfonts/sins.ppf, one of the Badgeware pixel fonts. - Text markup works as on the Tufty:
[pen:r,g,b]inside a string changes colour mid-text, andimage.add_glyph(name, fn)adds codes of your own. - While a script draws,
print()doesn't reach the screen. When the script ends, the launcher takes the screen back with the last frame still showing, and the next script'sscreenstarts black. - On a PSP-1000, a fully saturated colour (such as a pure rainbow) shifting slowly across the whole screen flickers. That's the PSP's LCD, not the drawing.
import audio
music = audio.play("music.mp3", loop=True)
beep = audio.play("beep.wav", volume=0.5)
music.volume = 0.3 # also pause(), resume(), stop(), and .playing
audio.volume(0.8) # master volume
audio.stop() # stop everythingaudio.play()takes WAV files (8 or 16-bit, mono or stereo, any sample rate) and MP3s, which the PSP's hardware decoder streams from the file.- Up to 8 sounds play at once, and at most 2 of them can be MP3s. Starting one more stops the oldest.
audio.Stream(rate=22050, channels=1, bits=16)plays samples made by the script:write(buf)queues them and waits while the queue is full,space()says how many bytes fit without waiting,close()ends it.- When a script ends, the launcher stops its sounds.
import network, requests
wlan = network.WLAN()
wlan.profiles() # [(1, "Home"), ...]: the networks saved in Settings
wlan.connect("Home") # by name or number; waits until connected
print(wlan.ifconfig()) # (ip, netmask, gateway, dns)
r = requests.get("http://example.com/")
print(r.status_code, r.text[:60])
wlan.disconnect()- The PSP connects with the networks saved in its own Settings > Network Settings, so add yours there first, and turn the Wi-Fi switch on. The PSP only knows WEP and WPA. For WPA2, install the wpa2psp plugin (ARK-4 has it built in); that's how it was tested on 6.60 PRO-C.
socketis MicroPython's standard module: TCP and UDP,getaddrinfo, timeouts, non-blocking sockets andselect.poll.requests(from micropython-lib) does plainhttp://only: there's no HTTPS yet.- When a script ends, the launcher disconnects.
For development, MicroPython's >>> prompt can run on the PSP with your
Mac's (or PC's) keyboard, over a USB cable, using
PSPLINK. The PSP's working folder is
then a folder on your computer, so you can edit a module there and import
it straight away.
One-off setup:
- Copy the
psplinkfolder from PSPLINK's release toPSP/GAME/PSPLINK/on the memory stick. - Install the pspdev toolchain, which has
usbhostfs_pcandpspsh, andmpremote.
Each time:
- Connect the PSP by USB, and start PSPLINK from the Game menu (not USB mode).
- In the build folder (
build/psp), which the PSP then sees ashost0:/, runusbhostfs_pcand leave it running. - In another terminal, run
tools/psp-repl.sh. It starts MicroPython in REPL mode (./micropython.prx replinpspsh: PSPLINK starts the build's.prx, not the EBOOT) and opens it inmpremote. Press Enter for a prompt; Ctrl-] leavesmpremote.
To use the copy from the release zip instead, with the scripts on the memory
stick, run usbhostfs_pc in any folder and
tools/psp-repl.sh --prx ms0:/PSP/GAME/MicroPython/micropython.prx. The zip
ships micropython.prx next to EBOOT.PBP for this; delete it if you don't
need the REPL.
The prompt has history, tab completion, paste mode (Ctrl-E) and Ctrl-C to stop
running code; Ctrl-D starts MicroPython afresh. The session shows on the
PSP's screen too. Other mpremote commands work through the same script
(leave out the word mpremote). A path starting with : is on the PSP;
any other path is on the computer:
tools/psp-repl.sh ls # the PSP's working folder
tools/psp-repl.sh run examples/hello.py # run a file from the computer
tools/psp-repl.sh cp examples/hello.py :hello2.py # computer to PSP
tools/psp-repl.sh cp :notes.txt /tmp/ # PSP to computer
tools/psp-repl.sh mount examples exec "import hello"
# the PSP imports from a folder on the computerStarted from the build folder, the PSP's working folder is host0:/; give
the memory stick's files in full (:ms0:/PSP/GAME/MicroPython/notes.txt), or
start the memory stick's copy with --prx.
Works:
- Most of MicroPython's standard library:
os,time,json,re,random,math,struct,collections,asyncio,deflate,hashliband more. MicroPython's own test suite passes (821 tests). - Files on the memory stick, with
open()andos. Scripts run with their own folder as the working directory. timereads the PSP's real-time clock and time zone.- About 12 MB of memory for Python, sized to fit a PSP-1000.
Doesn't work yet:
- The REPL needs a computer. It runs over USB with PSPLINK (see above); on the PSP alone, you run scripts from the launcher.
- No HTTPS, USB or threads.
- Floats are single precision (about 7 significant digits).
os.renamecan't move a file to another folder. The PSP can only rename within a folder, so a move raisesOSError(EXDEV, 18). Copy the file and delete the old one instead.
You need the pspdev toolchain, CMake,
Python 3 and a host C compiler (for mpy-cross).
git clone --recurse-submodules https://github.com/thirdr/micropython-psp.git
cd micropython-psp
psp-cmake -S ports/psp -B build/psp -DCMAKE_BUILD_TYPE=Release
cmake --build build/psp -j8That writes build/psp/EBOOT.PBP. tools/make-release.sh packs it into the
release zip, in build/release/.
The port lives in ports/psp and builds against the
micropython submodule, which isn't modified.
The tests run in PPSSPPHeadless, PPSSPP's command-line build, which you
build from PPSSPP's source. Set PPSSPP_HEADLESS to its path (the default is
../ppsspp/build-headless/PPSSPPHeadless). tools/run-ppsspp.sh runs an EBOOT, shows its
output in the terminal and saves a screenshot:
tools/run-ppsspp.sh --files tests/fs build/psp/EBOOT.PBP 30
tools/run-mp-tests.sh # MicroPython's own test suiteThe folders in tests/ are script sets that each run as a PSP
folder. CI (.github/workflows/build.yml)
builds the EBOOT, runs every test headless, and makes the release zip.
MIT; see LICENSE. MicroPython itself is MIT-licensed too; see
micropython/LICENSE.
The EBOOT also contains Pimoroni's PicoVector (from
picovector-micropython),
with the PNGdec and JPEGDEC decoders (Apache 2.0) and the QR Code generator
library (MIT) it bundles, requests (MIT,
from micropython-lib) and the pspdev
toolchain's pspsdk (BSD), newlib (mostly BSD-style) and pthread-embedded
(LGPL 2.1) libraries. Their licenses are in
ports/psp/release/licenses
and ship in the release zip. The demos, their skull sprite and their
fonts (fonts/sins.ppf, fonts/compass.ppf) are from Pimoroni's
tufty2350 (MIT).
