Compiling for the Miyoo A30 using SDL2
I picked up the Miyoo A30 off AliExpress for about $32 a few weeks ago. It took a little while to arrive in the US. This small handheld runs Linux. I actually ordered the unit without an SDCard, so there is not really any software that comes with it. I picked up a 128 GB SD Card separately from the unit.
You can download spruceOS which allows for customization of the device once loaded on an SD card. I did that by following this guide. You can use the "Advanced Settings" application to start an SSH server once you have spruceOS loaded. You can connect in from any machine on the same network as the WiFi. Once I did this you get dropped into the shell of a system based off BusyBox. I quickly checked to see what kind of software this is running
# uname -a Linux MIYOO282 3.4.39 #1075 SMP PREEMPT Sat Jul 13 02:15:57 UTC 2024 armv7l GNU/Linux /mnt/SDCARD # cat /proc/cpuinfo Processor : ARMv7 Processor rev 5 (v7l) processor : 0 BogoMIPS : 4799.99 processor : 1 BogoMIPS : 4799.99 processor : 2 BogoMIPS : 4799.99 processor : 3 BogoMIPS : 4799.99 Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4 idiva idivt CPU implementer : 0x41 CPU architecture: 7 CPU variant : 0x0 CPU part : 0xc07 CPU revision : 5 Hardware : sun8i Revision : 0000 Serial : 872a885c12431f9778f7 Chipid : 000000001f9778f7885c12430461872a /mnt/SDCARD #
So this is running a Linux 3.4.x kernel. Not exactly modern, but I don't think I am missing anything here by not being on 6.x. The obvious next step was to run code on the device. My initial attempt was to use arm-linux-gnueabihf-gcc-14
to compile a hello world program and copy it on to the A30. I use Ubuntu on my personal laptop, so you can install just about any standard compiler toolchain on any host architecture. This works to produce a program, but the version of the C library that this is compiled against is too new for the program to work on the A30. So I was able to compile by doing something like arm-linux-gnueabihf-gcc-14 -static -o hello_world hello_world.c
. This creates a statically linked executable. This does run on the A30! But realistically there isn't much we can do here. All I can really interact with this way is the console, which is only reachable over SSH. We need whatver libraries are on the system to do anything remotely interesting.
I poked around the device and found /usr/miyoo/lib/sdl2/libSDL2-2.0.so.0
. So someone compiled SDL 2 for this device. The thing that tipped me off to this is the free game Dino Jump has been ported to this device and comes loaded with spruceOS. It seems to use SDL 2, so this all makes sense. I actually have no idea where spruceOS got these SDL2 libraries from, as the compiled shared libraries are committed directly into the Git repository. It looks like other projects have the exact same set of files present. From the debugging symbols I can see these were compiled by an individual that goes by Ninoh Fox.
Setting up a compilation environment
I'm fairly familiar with SDL, so actually writing some code to interact with SDL 2 shouldn't be too difficuilt. I started from some example code I found on the internet. The main obstacle here is producing a dynamically linked executable that I can then copy to the A30. I poked around at the Dino Jump game and I'm not even entirely sure how that is compiled. But it did mention a project called miyoomini-buildroot. The Miyoo Mini is a vaguely similar device from the same manufacture that probably runs similar hardware.
After looking at how that works they apparently use a Dockerfile to setup a cross compilation environment based on Debian Buster. It appears that Debian Buster is old enough that the libc version is close enough to work. I promptly took everything I could from that project and set it up in my own project. This works, but I still need to link against the libraries on the A30 if I want do something useful. What I did was use tar
and netcat
over SSH to copy the entire filesystem to my laptop from the A30. When I tried to do this all at once the SSH session died for some reason. So I did it in groups of /etc
, /bin
, /config
, lib
, /sbin
, and /usr
. Looking at that I found the following paths
/usr/miyoo/lib/libSDL2_mixer-2.0.so.0 /usr/miyoo/lib/libSDL2_image-2.0.so.0 /usr/miyoo/lib/libSDL2_gfx-1.0.so.0 /usr/miyoo/lib/libSDL2-2.0.so.0 /usr/miyoo/lib/libSDL2_ttf-2.0.so.0 /usr/miyoo/lib/sdl2/libSDL2-2.0.so.0
So it looks like SDL2 was compiled with several different extensions. The additional files in /usr/miyoo/lib/sdl2/
appear to have been compiled with debugging info
$ file usr/miyoo/lib/libSDL2-2.0.so.0 usr/miyoo/lib/libSDL2-2.0.so.0: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, stripped $ file usr/miyoo/lib/sdl2/libSDL2-2.0.so.0 usr/miyoo/lib/sdl2/libSDL2-2.0.so.0: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, with debug_info, not stripped
So I just decided the ones I needed to link against are in /usr/miyoo/lib
. So I setup the Docker file to copy the libraries into to the contianer. I invoked the cross compiler to run and I added a compiler switch of -lsdl2 -L/workspace/miyooa30/usr/miyooa/lib
. This still didn't link. If you pass a compile option of -Wl,-verbose
the linker runs in verbose mode. The linker was looking for files named things like /usr/miyoo/lib/libSDL2.so
. I just added symbolic links from where it was searching to the actual files. Nothing ever actually runs in this docker container anyways, so I just need to take whatever steps are needed to get the compiler to put out a working program. This actually worked!
Drawing with SDL2
The program I compiled using SDL 2 just tries to render an image on the screen of the A30.
So this works, but the image is not upright. I'm not entirelty sure why this happens, but my guess is that while the screen is advertised as 640x480, it's actually a 480x640 display. There's a bunch of header files and other libraries on the device, but none of them seem to have any obvious way to request the device change its display mode or anything like that. I guess there is never a hardware problem that can't be fixed in software?
The first thing I did was change my window request to SDL 2 to look like this
SDL_CreateWindow("Hello World SDL2", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_HEIGHT, WINDOW_WIDTH, SDL_WINDOW_SHOWN| SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL);
Normally the width is given first & the height next. But I inverted those, asking for the "real" dimensions of the physical display. At this point I figured I can just rotate my image! You can call this function
SDL_RenderCopyEx(renderer, texture, NULL, NULL, -90.0, NULL, SDL_FLIP_NONE);
The argument of -90.0
implies a 90 degree rotation anti-clockwise. That gave me this.
Success, or at least a partial success. So at this point I realized what I would have to do is
- rotate all assets
- invert my entire usage of coordinate system
There's probably a couple more steps I'm not thinking of here, because I figured out this would be a horrible idea. With SDL2 you can render to basically anything you want. So I allocated a texture that is in the actual 640x480 dimensions. I then set that as my render target and did all rendering there. This gave me the ability to render in normal, sane coordinates. All I have to do is then flip that texture to the actual screen with the -90.0
rotation. This worked, but gave me a very distorted image. I eventualy realized SDL 2 was trying to scale my 640x480 texture into a 480x640 texture. I thought at first I could somehow turn this off, but that doesn't appear to be possible.
SDL 2 supports render scaling already in case you want to render low resolution graphics onto a high resolution display. So I ended up doing this
SDL_RenderSetScale(renderer, 1.0, (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT);
Basically all this does is has the renderer "scale" the graphics by ratio of the width / height. This means that the final image isn't scaled at all and is exactly what we would expect. I have no idea of the performance implications of this, but it at least allows images to be drawn without trying to think in some bizaree coordinate system. This got me to something I considered usable.
What SDL2 version are we using anwyays?
At this point I was able to use the basic SDL2 features, but the actual device seemed to have several SDL 2 extensions at well. This lead me to the question of what version of SDL 2 to compile against. I put together this program
#include <stdio.h> #include <SDL.h> const SDL_version * Mix_Linked_Version(void); const SDL_version * TTF_Linked_Version(void); //const SDL_version * GFX_Linked_Version(void); const SDL_version * IMG_Linked_Version(void); int main() { SDL_version sdlVer; SDL_GetVersion(&sdlVer); printf("sdl version: %u.%u.%u\n", sdlVer.major, sdlVer.minor, sdlVer.patch); SDL_version const * v = Mix_Linked_Version(); printf("mix version: %u.%u.%u\n", v->major, v->minor, v->patch); v = TTF_Linked_Version(); printf("ttf version: %u.%u.%u\n", v->major, v->minor, v->patch); /** v = GFX_Linked_Version(); printf("gfx version: %u.%u.%u\n", v->major, v->minor, v->patch); **/ v = IMG_Linked_Version(); printf("img version: %u.%u.%u\n", v->major, v->minor, v->patch); return 0; }
This asks the running library that the program is linked against to report its version. Apparently SDL2_gfx is some non-standard extension that doesn't support this. After figuring out how to link against the right set of libraries, I got this program to produce the following when run from the SSH console
# ./show_sdl_versions sdl version: 2.26.1 mix version: 2.0.1 ttf version: 2.0.13 img version: 2.0.1
So, we're running old but still usable versions of SDL 2. As far as an SDK goes, this is certainly better than nothing. For SDL2 GFX I eventually just guessed that version 1.0.4 must be correct. With this I can download the original source files and compile against the headers from those projects.
Where to go from here?
The obvious next step here is to create some kind of really fun video game appropriate for the form factor. I don't have really time for that at present. The next best thing is putting together something that shows how to use all the different components. I'd need to do this anyways and perhaps someone else can create or port an interesting video game to the device.
So what I did was found a python application that seems to be doing the same kind of thing. This was helpful because some of the kep mappings are stuff like the letter "t" for the R1 key which I have no idea how I would figure that out on my own. I made up an application in C that responds to all of the keys by translating the shown image, displaying the pressed button, or playing some sound. I also drew a basic triangle using the SDL2 GFX extension, although I think that may not be very useful for any sort of sprite based game.
Source code
You can get the complete source code for this project along with instructions on how to build it and add it to your Miyoo A30 here on codeberg.