Skip to main content

pocketterm35 batocera setup

PocketTerm35 + Raspberry Pi 5 + Batocera

Handheld retro-gaming terminal: Raspberry Pi 5 (4GB) in the Waveshare PocketTerm35 housing (3.5" 640×480 DPI touchscreen + QWERTY keyboard with gaming keys), a 500GB NVMe SSD on the PCIe TO M.2 Board (E), running Batocera 43.1.

This page documents the full configuration as set up on 2026-08-22, so it can be reproduced from scratch.

Hardware

Component Detail
Board Raspberry Pi 5 Model B Rev 1.1, 4GB RAM
Housing Waveshare PocketTerm35 (3.5" 640×480 DPI display, Goodix capacitive touch, Pico-based USB keyboard)
Storage 500GB NVMe via Waveshare PCIe TO M.2 Board (E), PCIe gen 3
OS Batocera 43.1 "Glasswing" (2026-05-29)

Display (boot partition: /boot/config.txt)

The display uses Waveshare's custom DPI overlays. These .dtbo files are not part of Batocera — they come from Waveshare's 3.5HDMI_E_DTBO.zip (software guide) and must be extracted to /boot/overlays/:

dtparam=i2c_arm=on
dtoverlay=waveshare-35dpi-4b
dtoverlay=waveshare-35dpi-5b
dtoverlay=dwc2,dr_mode=host      # required for the keyboard (USB OTG host)
dtparam=pciex1_gen=3             # NVMe at gen 3 speed

In batocera.conf, keep EmulationStation at panel resolution: es.resolution=max-640x480.

Important: a Batocera version upgrade rewrites the boot partition and deletes the custom overlays. Backups of waveshare-35dpi-4b.dtbo, waveshare-35dpi-5b.dtbo and config.txt live in /userdata/system/pocketterm-backup/ on the device (survives upgrades) and on the PC under ~/backups/pocketterm35/. After an upgrade, copy them back to /boot//boot/overlays (remount rw: mount -o remount,rw /boot).

Keyboard gaming keys

The keyboard enumerates as a USB HID device ("My Custom Pico Keyboard"). The gaming keys emit ordinary keyboard keycodes matching their printed labels (verified by capturing /dev/input/event0):

Physical key Keycode sent
D-pad ▲▼◀▶ arrow keys
A / B / X / Y letters a / b / x / y
L / R letters l / r
Select (PrtSc) KEY_SYSRQ (print screen)
Start (Pause) KEY_PAUSE
Esc, F1–F12 Fn layer: Fn+Q = Esc, Fn+1…0 = F1–F10

This clashes with RetroArch's default keyboard layout (retropad A on key x, B on z, …) and with default letter hotkeys (r = rewind, l = hold fast-forward) — out of the box the buttons do the wrong things. Fix via /userdata/system/batocera.conf (update-proof, injected into every generated RetroArch config):

## POCKETTERM35 KEYBOARD BLOCK START
global.retroarch.input_player1_up=up
global.retroarch.input_player1_down=down
global.retroarch.input_player1_left=left
global.retroarch.input_player1_right=right
global.retroarch.input_player1_a=a
global.retroarch.input_player1_b=b
global.retroarch.input_player1_x=x
global.retroarch.input_player1_y=y
global.retroarch.input_player1_l=l
global.retroarch.input_player1_r=r
global.retroarch.input_player1_l2=q
global.retroarch.input_player1_r2=w
global.retroarch.input_player1_start=pause
global.retroarch.input_player1_select=print_screen
# default letter hotkeys collide with the gaming keys -> move/disable
global.retroarch.input_rewind=f6
global.retroarch.input_toggle_fast_forward=f7
global.retroarch.input_hold_fast_forward=nul
global.retroarch.input_reset=nul
global.retroarch.input_frame_advance=nul
global.retroarch.input_hold_slowmotion=nul
global.retroarch.input_toggle_slowmotion=nul
# Batocera generates input_enable_hotkey=shift which forces Shift+hotkey; disable
global.retroarch.input_enable_hotkey=nul
# N64 games are analog-driven -> map the stick onto the arrows too
n64.retroarch.input_player1_l_x_minus=left
n64.retroarch.input_player1_l_x_plus=right
n64.retroarch.input_player1_l_y_minus=up
n64.retroarch.input_player1_l_y_plus=down
## POCKETTERM35 KEYBOARD BLOCK END

The input_enable_hotkey=nul line matters: without it Batocera requires holding Shift together with every keyboard hotkey, so Esc alone never exits a game.

In-game controls after this config

Keys Function
gaming keys exactly as labeled (SNES-style layout)
Pause / PrtSc Start / Select
Q / W L2 / R2
Fn+Q (Esc) quit game
Fn+1 (F1) RetroArch menu
Fn+2 / Fn+4 save / load state
Fn+6 / Fn+7 rewind / fast-forward

Limitation: since the gaming keys emit plain letters, keyboard-centric systems (DOS, Amiga, ScummVM) see them as typed letters — play those with the keyboard itself.

Performance & quality-of-life (batocera.conf)

system.cpu.governor=performance
kodi.enabled=0            # keyboard 'x' in ES could trigger Kodi launch
global.rewind=0           # rewind costs CPU each frame
global.autosave=1         # quit = snapshot, relaunch = resume (handheld style)
# one frame less input lag on light cores:
nes.runahead=1
famicom.runahead=1
snes.runahead=1
megadrive.runahead=1
mastersystem.runahead=1
gamegear.runahead=1
gb.runahead=1
gbc.runahead=1
gba.runahead=1
pcengine.runahead=1

Power: the Waveshare-recommended EEPROM setting PSU_MAX_CURRENT=5000 was already present (check with rpi-eeprom-config); vcgencmd get_throttled reports 0x0. Overclocking is not recommended in this housing — the Pi already reaches ~60°C under sustained load at stock clocks.

Quieter fan curve

The M.2 Board (E) has a 30×30×7mm 5V blower (8000 RPM, PWM + tach) on the Pi 5 fan header — audible even at 30% duty. Firmware fan curve in /boot/config.txt: silent below 55°C, gentle midrange, full speed only near the 85°C throttle point:

dtparam=fan_temp0=55000,fan_temp0_hyst=5000,fan_temp0_speed=60
dtparam=fan_temp1=63000,fan_temp1_hyst=5000,fan_temp1_speed=100
dtparam=fan_temp2=70000,fan_temp2_hyst=5000,fan_temp2_speed=160
dtparam=fan_temp3=76000,fan_temp3_hyst=5000,fan_temp3_speed=255

Monitor with cat /sys/class/hwmon/hwmon2/fan1_input (RPM) and vcgencmd measure_temp. Drop-in replacement if the blower develops bearing rattle: a 3007 5V blower with 4-pin JST plug (e.g. GMBYLBY 3007); pinout on the Pi 5 header is GND, 5V, PWM, tach.

WiFi

In batocera.conf:

wifi.enabled=1
wifi.ssid=<ssid>
wifi.key=<password>

Apply with batocera-wifi enable or reboot. Batocera's connman runs with SingleConnectedTechnology=true and prefers ethernet — WiFi connects automatically only when no cable is plugged in, and the IP changes when it switches.

ROM library

Source: a 1TB Batocera share image (BobbyCera_Pi4_1TB_V42_C.L.A.M, 882GB of content) — too big for the 463GB NVMe, so a curated subset (~390GB) was synced. Selection principle: systems that emulate well on a Pi 5 and play well on a 3.5" screen with keyboard controls.

  • Included: full PSX (CHD set), N64, NDS (touchscreen makes it playable), Saturn, all 8/16-bit consoles and handhelds, arcade (FBNeo, MAME, Neo Geo, Naomi, Atomiswave), Sega CD, PC Engine CD, Amiga/C64/Amstrad/Atari ST (great with the physical keyboard), OpenBOR, Daphne, all small systems.
  • Excluded: 3DS (poor emulation on Pi 5), PSP and Dreamcast (analog-stick-dependent, 267GB together), 3DO, Neo Geo CD, MSU-1 sets (duplicates).

Procedure from a WSL/Linux PC (image lives on a NAS SMB share):

sudo mount -t cifs //NAS/stash /mnt/stash -o username=USER,uid=$(id -u)
sudo losetup -P /dev/loop0 /mnt/stash/games/<image>.img
sudo mount /dev/loop0p2 /mnt/imgshare        # ext4 "share" partition
rsync -a --partial --info=progress2 /mnt/imgshare/bios/ root@<pi>:/userdata/bios/
rsync -a --partial --info=progress2 \
  --exclude=/3ds --exclude=/psp --exclude=/dreamcast --exclude=/snes-msu1 \
  --exclude=/msu-md --exclude=/3do --exclude=/neogeocd \
  /mnt/imgshare/roms/ root@<pi>:/userdata/roms/

(Batocera SSH: user root, default password linux. After the sync, refresh game lists: ES menu → Game settings → Update gamelists, or batocera-es-swissknife --restart.)

Backups

  • On-device: /userdata/system/pocketterm-backup/ (custom overlays + config.txt) — survives upgrades.
  • On PC: ~/backups/pocketterm35/ (overlays, config.txt, batocera.conf incl. pre-change original).
  • batocera.conf contains the WiFi password in plain text — handle backups accordingly.