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>
182 lines
9.4 KiB
Markdown
182 lines
9.4 KiB
Markdown
# 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 (0–255) |
|
||
| `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
|