sdlamp2/docs/rg35xx-plus.md
Michael Smith 8a638acdd8 Skin template system, separate prev sprite, reorganize device scripts
Add tools/gen_skin_template.py to generate a labeled 642x420 PNG template
for creating custom spritesheets. Move rg35xx device scripts from tools/
to device/rg35xx/. Point prev_sprite at its own cell (bottom-center) so
Prev and Next can have distinct icons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 23:47:21 +01:00

182 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Anbernic RG35XX Plus
Device-specific reference for the target hardware. For build instructions see [`TOOLCHAIN.md`](TOOLCHAIN.md).
## Hardware
- **Device:** Anbernic RG35XX Plus
- **SoC:** Allwinner H700 — quad-core ARM Cortex-A53 @ 1.5 GHz (AArch64 / ARMv8-A)
- **GPU:** Mali G31 MP2
- **RAM:** 1 GB LPDDR4
- **Display:** 640×480 IPS
## Software
- **OS:** Ubuntu 22.04 LTS (Jammy Jellyfish) — [modified stock firmware](https://github.com/cbepx-me/Anbernic-H700-RG-xx-StockOS-Modification)
- **Kernel:** Linux 4.9.170 aarch64 (proprietary, no source released by Anbernic)
- **Architecture:** aarch64 / arm64 (confirmed via `dpkg --print-architecture`)
- **glibc:** 2.35
- **Userland:** Debian/Ubuntu-based (`dpkg`, `apt-get`, `systemd`)
## Libraries on Device
All required shared libraries are pre-installed. Most are at `/usr/lib/`, some at `/usr/lib/aarch64-linux-gnu/` (Debian multiarch path):
| Library | Soname | Notes |
| :------------ | :----------------------------- | :---------------------------------- |
| SDL2 | `libSDL2-2.0.so.0.12.0` | SDL 2.0.12 |
| SDL2_image | `libSDL2_image-2.0.so.0.900.0` | SDL2_image 2.0.9, at multiarch path |
| libavcodec | `libavcodec.so.58` | FFmpeg ~4.x |
| libavformat | `libavformat.so.58` | FFmpeg ~4.x |
| libavutil | `libavutil.so.56` | FFmpeg ~4.x |
| libswresample | `libswresample.so.3` | FFmpeg ~4.x |
Shared libraries are already present — no need to bundle or build them. A native aarch64 compile (e.g. inside the arm64 Docker container) produces working binaries. Must link against glibc ≤ 2.35.
## Input Devices
Three input devices are registered via `/proc/bus/input/devices`:
| Device | Handlers | Purpose |
| :------------------- | :-------------- | :----------------------------------- |
| `axp2202-pek` | `event0` | Power button (`KEY_POWER`, code 116) |
| `ANBERNIC-keys` | `event1`, `js0` | Gamepad (d-pad, face buttons) |
| `dierct-keys-polled` | `event2` | Shoulder buttons, menu/function keys |
- **logind**: `HandlePowerKey=ignore` in `/etc/systemd/logind.conf` — systemd does not act on the power button, leaving it free for userspace handling.
- **evtest**: Available at `/usr/bin/evtest` for debugging input events.
## Partition Layout
| Device | Mount point | Filesystem | Contents |
| :--------------- | :------------ | :--------- | :---------------------- |
| `/dev/mmcblk0p5` | `/` | ext4 | Root filesystem |
| `/dev/mmcblk0p6` | `/mnt/vendor` | ext4 | Firmware, apps, scripts |
| `/dev/mmcblk0p7` | `/mnt/data` | ext4 | User data |
| `/dev/mmcblk0p8` | `/mnt/mmc` | vfat | ROMs |
| SD card slot | `/mnt/sdcard` | (varies) | External storage |
## Boot Chain
The device uses systemd but boots its UI through a SysV init script that systemd auto-wraps via `systemd-sysv-generator`. The chain is intentionally layered: `launcher.sh` runs before partitions are mounted (hardware setup), `loadapp.sh` runs after mounts (filesystem/network setup + app restart loop), and `dmenu_ln` is the app dispatcher.
```
systemd (graphical.target)
└─ launcher.service (SysV → auto-generated by systemd-sysv-generator)
└─ /etc/init.d/launcher.sh start
├── mounts /mnt/vendor
├── starts brightCtrl.bin (backlight daemon) &
├── starts cexpert &
└── starts loadapp.sh &
├── resizes root partition (first boot only)
├── mounts /mnt/data, /mnt/mmc
├── charging mode: if boot_mode==1 → charger UI → poweroff
├── normal mode: starts NetworkManager, bluetooth, wifi
├── syncs RTC, loads kernel modules, mounts SD card
├── runs /mnt/mod/ctrl/autostart (if exists)
└── RESTART LOOP: runs /mnt/vendor/ctrl/dmenu_ln repeatedly
└─ /mnt/vendor/ctrl/dmenu_ln
├── selects binary (default: dmenu.bin)
├── config overrides: muos.ini → muos.bin, vpRun.ini → ...
├── runs binary via app_scheduling()
└── on exit: executes /tmp/.next (app dispatch for menu)
```
### Why the indirection?
- **`launcher.sh`** — Pre-mount hardware setup: mounts `/mnt/vendor`, starts backlight and system daemons. Runs as a SysV init script so it integrates with systemd's boot ordering.
- **`loadapp.sh`** — Post-mount filesystem/network setup: mounts data partitions, handles charging mode, starts networking, then enters the restart loop. Runs in the background (`&`) so `launcher.sh` can return.
- **`dmenu_ln`** — App dispatcher: selects which binary to run based on config files, wraps execution in `app_scheduling()` (which sleeps 30s on crash before retrying), and supports `/tmp/.next` for menu-driven app switching.
### `app_scheduling` behavior
The `app_scheduling` function in `dmenu_ln` runs the selected binary. If it exits with a non-zero status, it sleeps 30 seconds before returning, which prevents crash loops from consuming all CPU. The outer `while true` loop in `loadapp.sh` then re-invokes `dmenu_ln`, restarting the application.
## Framebuffer
| Property | Value |
| :------------- | :------------------- |
| Device | `/dev/fb0` |
| Visible size | 640x480 |
| Virtual size | 640x960 (double-buf) |
| Bits per pixel | 32 (BGRA) |
| Stride | 2560 bytes (640 * 4) |
No standard framebuffer image tools (`fbv`, `fbi`, `psplash`) are installed. To display a PNG on the framebuffer, decode with Python3+PIL and write raw BGRA pixels to `/dev/fb0`.
## Backlight Control
There is no sysfs backlight interface (`/sys/class/backlight/` is empty) and the stock `brightCtrl.bin` daemon does not expose a usable control mechanism. The backlight is controlled via the Allwinner `/dev/disp` ioctl interface:
| Ioctl | Number | Description |
| :------------------ | :------- | :----------------------------------- |
| `DISP_SET_BRIGHTNESS` | `0x102` | Set backlight brightness (0255) |
| `DISP_GET_BRIGHTNESS` | `0x103` | Get current backlight brightness |
Both ioctls take an argument buffer of 4 unsigned longs (`struct { unsigned long args[4]; }`):
- `args[0]` = screen index (always 0)
- `args[1]` = brightness value (for SET; ignored for GET)
- Return value (for GET) is in `args[0]` after the ioctl call
Setting brightness to 0 turns the screen off completely. The original value (typically ~50) can be restored to turn it back on. This is used by `rg35xx-screen-monitor.py` for idle timeout and power button toggle.
## Stock Firmware Assets
Shutdown-related assets in `/mnt/vendor/res1/shutdown/`:
| File | Description | Size |
| :------------- | :------------------------------ | :------ |
| `goodbye.png` | Shutdown screen (RGB, 640x480) | Matches display |
| `lowpower.png` | Low battery warning | — |
The boot logo is at `/mnt/vendor/res1/boot/logo.png`.
## Deploying sdlamp2
### Overview
The `dmenu_ln` script already supports switching the startup binary via config files (e.g. `muos.ini``muos.bin`). We follow the same pattern to launch sdlamp2 instead of the default menu.
### Setup
1. **Copy the binary, wrapper, and screen monitor** to the device:
```sh
scp build/sdlamp2 root@rg35xx:/mnt/vendor/bin/sdlamp2
scp device/rg35xx/rg35xx-wrapper.sh root@rg35xx:/mnt/vendor/bin/rg35xx-wrapper.sh
scp device/rg35xx/rg35xx-screen-monitor.py root@rg35xx:/mnt/vendor/bin/rg35xx-screen-monitor.py
```
2. **Add the config check** to `/mnt/vendor/ctrl/dmenu_ln`. In the section where `CMD` overrides are checked (after the existing `muos.ini` / `vpRun.ini` checks, before the `app_scheduling` call), add:
```bash
if [ -f "/mnt/vendor/sdlamp2.ini" ];then
CMD="/mnt/vendor/bin/rg35xx-wrapper.sh"
fi
```
The wrapper script handles device-specific concerns (WiFi hotspot, power button monitoring) and launches sdlamp2 as its main foreground process. See `device/rg35xx/rg35xx-wrapper.sh` for details.
3. **Enable sdlamp2 on boot:**
```sh
touch /mnt/vendor/sdlamp2.ini
```
4. **Disable** (revert to normal menu):
```sh
rm /mnt/vendor/sdlamp2.ini
```
### What's preserved
Everything else in the boot chain continues to work:
- **Charging mode** — handled in `loadapp.sh` before the restart loop
- **LED/backlight control** — `brightCtrl.bin` started by `launcher.sh`
- **Clean shutdown** — sdlamp2 handles SIGTERM/SIGINT, saving position and volume before exit. The wrapper displays the stock `goodbye.png` on the framebuffer and calls `poweroff`
- **Restart on exit** — if sdlamp2 exits cleanly (status 0), the restart loop in `loadapp.sh` re-launches it immediately
- **Crash recovery** — if sdlamp2 crashes (non-zero exit), `app_scheduling` sleeps 30s then the loop retries
- **Easy revert** — removing `sdlamp2.ini` restores the stock menu on next boot