Giorgio's Blog

Homebrew with the Miyoo Mini Plus

I love video games. Growing up, I received a Super Nintendo from my parents one year, and I've never strayed far from the hobby since. In fact, it's what inspired me to become a software engineer in the first place. The thought at the time was to venture into game development, but I quickly gravitated towards the web instead. That being said, I still find game development fascinating and maybe some day I'll build something cool 😉

For today though, I'd like to touch on a development of the last 20 or so years. Handheld devices that emulate the game consoles of yesteryear. Behold, the Miyoo Mini Plus:

miyoo mini plus

This little GameBoy-inspired device has enough oomph to run everything up to PlayStation 1 games (via emulation, of course). It's linux-based and has a reasonably powerful dual-core ARM Coretex A7 cpu and a whopping 128 megabytes of ram!

Now, you may be thinking, "that's laughably low-spec." You'd be right! But the appeal of something like this is in the limitations: it has a very specific niche (4:3 aspect ratio gaming) and it executes on that well enough. Furthermore, it's cheap! I picked this one up off of Amazon for about 80 dollars and threw in some custom buttons from Etsy for another 20.

A brief history of retro handhelds

This isn't the first device of its kind, mind you. Rewind the clock about 20 years and there was a GameBoy Advance 'competitor' called the GP32 which the homebrew community embraced. I myself had a Dingoo A320 at one point, and kickstarted a device called the GCW Zero. These weren't amazing devices, but they were cheap, portable and enabled the playback of games libraries in a way that the bigger console manufacturers would eventually adopt.

gcw zero This might still work, but I can't be bothered to find a 5-pin mini-usb cable in my nest of wires.

What is different about these devices compared to the Nintendo Switch you can find at Best Buy is just how open they are. For instance, the photo of the Miyoo above isn't of the stock firmware but instead a community project called Onion. Not only do these projects add a slew of new features and software support (there's a library of open source games ported to the miyoo), but being an open device that runs linux it's relatively easy to write your own software for it!

On developing for embedded devices

You are still limited by the hardware of the device of course. No graphics acceleration in the Miyoo certainly makes writing games for it complicated. There's a patched version of SDL available for it, and a toolchain is available.

So, how difficult is it to take those two pieces and apply them to get a Hello World going? Not very! Continue on for a hello world how to.

Hello World Miyoo

If you clone the repository above and run the docker incantations prescribed in the README, you'll have a functional cross-compiler to begin building for the Miyoo. For the sake of demonstration, I've prepared a silly little SDL application at https://github.com/zbtnot/hello-miyoo for us to step through.

Clone the project into your workspace folder. Now from your toolchain shell, build the project with TARGET=miyoo make. If everything went correctly, you should have a bin directory containing the project's assets and a Miyoo-friendly binary. Copy bin to your device's SD card. You can now execute the application using Onion's build-in file explorer.

Pressing the A button will play a sound effect, and pressing the Menu button quits the program. Neat!

miyoo demo

There are certainly more robust tutorials on SDL, so I won't duplicate their effort here. That said, what's going on when we run this program? Let's dive into src/main.cpp to find out!

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_ttf.h>
#include <SDL/SDL_mixer.h>

// Screen formatting
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int BITS_PER_PIXEL = 32;

// Font formatting
const int FONT_SIZE = 64;

const int AUDIO_CHUNK_SIZE = 512;

// Button Helpers
const SDLKey BTN_A = SDLK_SPACE;
const SDLKey BTN_MENU = SDLK_ESCAPE;

// Resource paths
const char *imagePath = "assets/img/battleback8.png";
const char *fontPath = "assets/font/MMXSNES.ttf";
const char *bgmPath = "assets/bgm/Mars.wav";
const char *sfxPath = "assets/sfx/hop.wav";

const SDL_Color COLOR_WHITE = {255, 255, 255};
const SDL_Color COLOR_BLACK = {0, 0, 0};

const int LOOP_MUSIC = 1;

//...continued below

At the top here we have our includes for the SDL libraries. As you might have guessed, we are leveraging the base SDL library as well as libraries for images, sound and fonts. All things necessary for a robust game! Below the includes are a number of constants to keep things readable. The Miyoo's native resolution is 640x480 and of course we have strings for all of our resources.

// ...continued from above

int main(int argc, char **argv) {
    // init
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    IMG_Init(IMG_INIT_PNG);
    TTF_Init();
    Mix_Init(MIX_INIT_OGG);
    Mix_OpenAudio(
        MIX_DEFAULT_FREQUENCY,
        MIX_DEFAULT_FORMAT,
        MIX_DEFAULT_CHANNELS,
        AUDIO_CHUNK_SIZE
    );

    Mix_Chunk *sfx = Mix_LoadWAV(sfxPath);
    Mix_Chunk *bgm = Mix_LoadWAV(bgmPath);

    SDL_Surface *video = SDL_SetVideoMode(
            SCREEN_WIDTH,
            SCREEN_HEIGHT,
            BITS_PER_PIXEL,
            SDL_HWSURFACE | SDL_DOUBLEBUF
    );
    SDL_Surface *screen = SDL_CreateRGBSurface(
            SDL_HWSURFACE,
            SCREEN_WIDTH,
            SCREEN_HEIGHT,
            BITS_PER_PIXEL,
            0, 0, 0, 0
    );
    bool done = false;

    // load resources
    TTF_Font *font = TTF_OpenFont(fontPath, FONT_SIZE);
    SDL_Surface *text = TTF_RenderUTF8_Shaded(font, "Hello world", COLOR_BLACK, COLOR_WHITE);
    SDL_Surface *background = IMG_Load(imagePath);

    // play music
    int bgmChannel = Mix_PlayChannel(-1, bgm, -1);
    
    //...continued below

Here we have the start of our main entrypoint This is pretty large! A real application wouldn't simply dump everything into main, but for the sake of brevity let's pretend this is fine 😅 In this section we have our SDL initialization calls as well as resource loading. One interesting note is that our sound resources are WAV files. SDL_Mixer does support OGG-format but this does not appear to be functional on the Miyoo.

Let's keep going. After initialization, we start the game loop:

    // ...continued from above
    while (!done) {
        // poll for input
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                done = true;
                continue;
            }

            if (event.type == SDL_KEYUP) {
                switch (event.key.keysym.sym) {
                    case BTN_A:
                        Mix_PlayChannel(-1, sfx, 0);
                        break;
                    case BTN_MENU:
                        done = true;
                        continue;
                }
            }
        }

        // clear screen
        SDL_FillRect(screen, nullptr, SDL_MapRGB(screen->format, COLOR_WHITE.r, COLOR_WHITE.g, COLOR_WHITE.b));
        // draw image to screen
        SDL_BlitSurface(background, nullptr, screen, nullptr);

        // to center the text we need to know how wide the text is
        SDL_Rect textCentered = {
                (Sint16)((SCREEN_WIDTH - text->w) / 2),
                (Sint16)((SCREEN_HEIGHT - text->h) / 2),
                0,
                0
        };
        // TTF_RenderUTF8_Shaded puts a nice outlined box around our text, but it has too much height.
        // This clips it down to a nice size.
        SDL_Rect textClipped = {
                0,
                12,
                (Uint16)text->w,
                (Uint16)text->h
        };
        SDL_BlitSurface(text, &textClipped, screen, &textCentered);

        // draw screen to vram
        SDL_BlitSurface(screen, nullptr, video, nullptr);
        SDL_Flip(video);
    }
    
    //...continued below

There's a few things happening here. First, we handle system events and user input, and then turn our attention to updating the screen. Should the user press the A or Menu buttons (instances of SDL_Key technically) we'll respond appropriately by playing our sound effect or ending the loop.

The graphics-handling code is next. This is the real meat and potatoes of the program. First the program clears the screen surface with a call to SDL_FillRect, and then draws our background resource on top of that. What follows is our Hello world text. There are some calculations made so the text renders nicely in the center of the screen. This then gets drawn on the screen surface as well.

Lastly we draw the screen surface to our actual video device surface, and ask SDL to flip the video surface. We do that last part for the sake of reducing tearing, which isn't particularly useful for static imagery, but good hygiene.

When the game loop ends, we have to free the resources allocated. This last section of our main function does just that:

    // ...continued from above
    Mix_HaltChannel(-1);

    Mix_FreeChunk(sfx);
    Mix_FreeChunk(bgm);

    TTF_CloseFont(font);

    SDL_FreeSurface(text);
    SDL_FreeSurface(background);
    SDL_FreeSurface(screen);
    SDL_FreeSurface(video);

    // shutdown
    Mix_Quit();
    TTF_Quit();
    IMG_Quit();
    SDL_Quit();

    return 0;
}

That's really all there is to it! Having an incredible library such as SDL available to abstract away the hardware makes this program entirely portable, too. Uploading files to the Miyoo is possible using SCP or other protocols but for the sake of iteration is a bit slow. Fortunately, it's trivial to spin up a virtual machine with the same libraries.

In fact, I accounted for this in the project's Makefile. Just run make without the TARGET=miyoo prefix and a native copy should build on an Ubuntu VM with the equivalent libraries installed. Here's the same code running on my Mac via Ubuntu:

miyoo demo

What's next

So while the journey itself is interesting, the end result isn't much of a game. The next step would be to turn it into one! There's no shortage of simple yet fun games you could build just with the tools covered in this post.