As front-end engineers, it has become common sense that we write code that only lives in a browser, applets, or Node processes. But is this the limit of our capabilities? This article will take you through the new possibilities of the Web technology stack by adapting a front-end toolchain to a handheld game console with only 32MB of ram and 320×240 resolution.

This time, our goal is to be equipped with only a 400Mhz single-core CPU and 32M memory of domestic nostalgia handheld Miyoo. It is completely unable to compare with the current iOS and Android phones, but it can be very good in a small and delicate volume, to meet the needs of playing small Overlord, GBA, arcade and other classic game platform simulators, the price is also very low. Here’s how it compares to the iPad Mini:

So what counts as porting a front-end stack for it? In my personal understanding, this includes at least the following parts:

  • Build the environment – Apply the build tool chain
  • Runtime – Embedded JS engine
  • Debug environment – IDE or editor support

Below are some of the technical explorations I made to complete these three major migrations. This mainly includes:

  • Build Docker tool chain
  • Go to Hello World
  • Soldering pin row and serial port login
  • Customize Linux kernel drivers
  • Porting THE JS engine
  • Supports VSCode debugger

Let’s rock!

Build Docker tool chain

When we start embedded development, the first thing we should do is to compile the source code for embedded operating system applications. So what’s the operating system on Miyoo? Here’s a story first.

Miyoo is a handheld game console customized by a small domestic company based on The CHIP scheme of Quan Chi F1C500S. Its default operating system is closed source Melis OS, which is sold in foreign countries under the names of Bittboy and Pocket Go, and is quite famous. The closed source system was not suitable for hobbyists, so the community reverse-engineered it. Steward Fu, a Taiwanese predecessor, successfully ported Linux to the console, but has since withdrawn from development for personal reasons. MiyooCFW, an open source system for the console, is based on the original Linux 4.14 kernel ported by Stu and maintained by the community.

Therefore, our target system is neither iOS nor Android, but Linux as it is! How do I compile an application for embedded Linux? We need a toolchain of basic tools like compilers, assemblers, and linkers to build usable ARM binaries.

Setting up a development environment on each operating system is often quite tedious. The popular approach in the open source console community is to use Linux virtual machines such as VirtualBox. This basically solves the cross-platform problem of toolchains, but it’s not as convenient as modern front-end engineering. Therefore, I choose to introduce Docker first to achieve a cross-platform out-of-the-box development environment.

As we know, Docker containers can be understood as lighter virtual machines. We can run the container with a single Docker run command and mount files, networks, and other external resources to it. Obviously, what we need now is a Docker container that can compile embedded Linux applications. This can be done by making a baseline Docker image that is used to launch the container. Docker images are easy to distribute across platforms, so just make and upload the image, and the basic development environment is ready.

So, what should this Docker image contain? This is obviously the tool chain for compiling embedded applications. Stu has provided the community with a set of pre-compiled toolchain packages on Debian 9. Just unpack them to /opt/miyoo and install some common dependencies to complete the image creation. This process can be automated using a Dockerfile file, which looks like this:

FROM debian:9
ADD toolchain.tar.gz /opt
ENV PATH="${PATH}:/opt/miyoo/bin"
ENV ARCH="arm"
ENV CROSS_COMPILE="arm-miyoo-linux-uclibcgnueabi-"
RUNapt-get update && apt-get install -y \ build-essential \ bc \ libncurses5-dev \ libncursesw5-dev \ libssl-dev \ && rm -rf /var/lib/apt/lists/*WORKDIR /root
Copy the code

With the Docker build command, we can create a clean embedded development image using a clean Debian image. So how do you compile files with images next? Assuming we have the Miyoo_SDK image ready, we simply mount the local file system directory to the container launched based on the image. Like this:

docker run -it --rm -v `pwd`:/root miyoo_sdk
Copy the code

In a nutshell, the meaning of this order is as follows:

  • docker runBased on themiyoo_sdkImage start onetemporaryThe container
  • -vMounts the current directory to the container/root
  • -itLet’s use the current terminal to log in to the Shell that operates on the container
  • --rmMake the container throwaway, in addition to changing the current directory,Leave no trace

So, we actually compiled the source code on the Mac file system directly in the container, based on Docker. This has no side effects and requires no additional data passing operations. I believe this is also a solution to the increasingly complex problem of front-end toolchain dependencies that I could write about in a separate article if I had the opportunity.

Go to Hello World

Once the Docker image is ready, we can use a compiler like ARM-Linux-gcc in the container. So how do you compile a Hello World? It’s not time to introduce a JS engine, but let’s write a simple example in C to verify that everything works.

Embedded Linux devices often use SDL libraries to render basic GUIs. The simplest example is shown below, which is similar to the Canvas familiar to front-end students:

#include <stdio.h>
#include <SDL.h>

int main(int argc, char* args[])
{
  printf("Init! \n");
  SDL_Surface* screen;
  screen = SDL_SetVideoMode(320.240.16, SDL_HWSURFACE | SDL_DOUBLEBUF);
  SDL_ShowCursor(0);
  // Fill it with red
  SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0xff.0x00.0x00));
  // Swap buffers once
  SDL_Flip(screen);
  SDL_Delay(10000);
  SDL_Quit();
  return 0;
}
Copy the code

The C source code can be compiled from our Docker environment. But obviously any application of any size should not be built directly by typing GCC’s arguments. It is better to automate this with a Makefile like this (note that indentation must be TAB) :

all:
    arm-linux-gcc main.c -o demo.out -ggdb -lSDL -I/opt/miyoo/arm-miyoo-linux-uclibcgnueabi/sysroot/usr/include/SDL
clean:
    rm -rf demo.out
Copy the code

In addition to logging into the Shell of the Docker container, we can also easily create a “headless” container with the -d argument to help you compile in the background. A command like make to build this Makefile can be done on a Mac terminal with a line like this:

docker run -d --rm -v `pwd`:/root miyoo_sdk make
Copy the code

This will generate the demo.out binary. Copy the 12KB file to the /apps directory on the Miyoo TF card and open it with Miyoo’s built-in installer. You can see something like this:

This shows that the Docker compilation tool chain is working properly! But that’s not enough, and now the key question is, where did our printf go?

Soldering pin row and serial port login

Basic Unix knowledge tells us that the output of a process is written to the stdout standard output file by default. Typically, these outputs are written to the stream buffer and drawn to the terminal. But where is the end of an embedded device? Typically, these logs are written to the so-called Serial Console. The console’s data, on the other hand, can be used to interact with a PC via a very old UART transmitter, which only requires a three-circuit connection.

Therefore, we need to find a way to connect to Miyoo’s UART interface so that we can log in its Shell on the computer. This article on welding UART joints is a very good reference in this regard. I was particularly impressed by one sentence:

Manufacturers are really considerate, especially the GND, UART1 RX, UART1 TX (from top to bottom) pull out, to provide developers a friendly development interface

Disassembly welding to use things, in the big guy’s eyes is actually a friendly development interface… Well, isn’t it just welding? Just learn.

First we take the back cover apart, and then we take the motherboard off. All you need for this step is a standard Phillips screwdriver, making sure you don’t lose any small parts. When finished, it looks like this:

See the three needles in the upper right corner of the motherboard? These are the three interfaces of the UART (I haven’t welded them yet, I just put the pins on them). They are GND, RX and TX from top to bottom. Just weld the pins for them and connect the wires to the UARt-to-USB converter, and you can log into them on your Mac. The join order looks like this:

  • Miyoo GND is connected to the GND of the converter
  • Miyoo’s RX connects to the converter’s TX
  • Miyoo’s TX connects to RX of the converter

Therefore, we need to weld the row of pins first. Welding looks like a lot of trouble, but now it is not difficult to learn, in fact, just press the iron tip on the solder joint, and then put the solder wire on it. For beginners like me, I can also buy some cheap exercise boards and practice with a few diodes before welding the real boards. The finished result is as follows, with three rows of red pins (the solder joints are on the back, so I won’t put the picture if it is very ugly) :

After welding, use a multimeter to measure whether the solder joint is connected. Remember in high school physics how to connect the red and black markers on a multimeter… I forgot all about it anyway. I learned it now. The actual measured resistance values of RX and TX to GND are about 600 ohm, which means that the connection is smooth.

With the adapter, the result is like this:

I finally made a hole in the back cover to fit the machine back in, like this:

After this hardware transformation, how do you implement the software connection? This requires software that can be logged into the serial port. Everything in Unix is a file, so all you have to do is find the serial file in /dev and use the serial communication software to open it. Screen is the built-in command line conversation software for Mac, but it is a bit troublesome to use. Minicom is recommended for Mac users. Once connected, you should see login log output that looks like this:

[1.000000] DEVtMPfs: Mounted [1.010000] Freeing unused kernel memory: 1024K [1.130000] ext4-FS (MMCBLK0P2): Re-mounted. Opts: data=ordered [1.230000] fat-fs (mmcBLK0p4): Volume was not properly unmounted. Some data may be corrupt. [1.250000] Adding 262140k swap on /dev/mmcblk0p3. Priority:-2 extents:1 across:262140k SS Starting logging: OKread-only file system detected... done Starting system message bus: dbus-daemon[72]: Failed to start message bus: Failed to open socket: Fddone
Starting network: ip: socket: Function not implemented
ip: socket: Function not implemented
FAIL

Welcome to Miyoo
miyoo login: 
Copy the code

It looks like we are close to success. Can you login in to see the log? As a result, a bug stopped me: there was no response after all the buttons were pressed, and I could not log in the terminal completely. What should I do?

I’ve never done this level of hardware modification, and I’ve never used the UART serial port. So this problem is pretty tricky for me — it could be a hardware problem or a software problem. But it’s got to be something we can fix.

  • First of all, I repeatedly confirmed the configuration of serial communication software, combed the configuration process of Linux startup, and mounted the EXT4 format rootFS partition on the Mac, confirming it/etc/inittabThe configuration and it starts/etc/mainAll scripts are valid, eliminating software issues on the device side.
  • Then on the hardware, I confirmed that there was no virtual welding in the circuit, and used raspberry PI to communicate with Mac through serial port in the experiment. It was confirmed that the terminal could be used normally and the problems of peripheral hardware were eliminated.
  • Finally, I found that when communicating with raspberry PI, the Mac side button made both the RX and TX lights of the adapter glow. But when connecting to Miyoo, pressing the key only made the TX sender on the Mac side glow, instead of receiving the signal that should have been sent back through the RX. So presumably the problem is the RX line of this interface. After I asked Stuart about the phenomenon in detail, I got the reply that the UART is shared with the headset and the Linux kernel must be recompiled.

Okay, I actually ran into a hardware problem with the physical circuit design. Then go ahead and change the Linux kernel.

Customize Linux kernel drivers

Following a tip from Stu, I tried to block the audio driver from Miyoo’s Linux kernel source code. We all know that Linux is a macro kernel, and a lot of hardware driver source code is all in it. Simply change the driver, in fact, is not a matter of much prestige.

First, we need to be able to at least compile the kernel. Note that the kernel is not equivalent to an embedded Linux system. A complete embedded Linux system should include the following parts:

  • Kernel – Contains the core subsystems of the operating system and the required hardware drivers
  • Rootfs – The root file system, which is basically the heap of binary applications under the root directory
  • UBoot – boot loader, itself equivalent to a very simple operating system

We just want to disable the audio driver, so we just need to recompile the Kernel. Kernel compiles the image named zImage. The user experience of this process is no different from that of a normal C project, which is to configure the compile parameters and environment variables, and then make:

make miyoo_defconfig
make zImage
Copy the code

In my MacBook Pro Docker, it takes about 12 minutes to compile the kernel. Here’s a picture to commemorate the first compilation of a Linux kernel in my career:

Once the build passed, I was happy to go straight to trying to change the kernel driver (note that I didn’t actually test the kernel for the first time, this is foreshadowed). After some research, I discovered that embedded Linux hardware is described in a DSL code called a device tree, which should be modified so that the Kernel does not support certain hardware. So I went to the audio section of the Miyoo device tree, commented it out, and tried to compile a device tree description file that didn’t include audio, and installed it.

Then the screen went black when the machine started up.

It seems that the device tree configuration does not work, I thought of directly modify audio driver C source. It was the kernel project /sound/soc/suniv/miyoo.c, and the C code didn’t look too hard, but I tried at least seven or eight different fixes, but it didn’t compile a normal image: sometimes it solved the problem of the UART not being logged in, sometimes it didn’t, and the black screen was still there. Why the audio driver would affect the video output bothered me so much that I even doubted my toolchain for a while.

Eventually, I came to a shocking conclusion:

This kernel code, even if completely unchanged, will compile with a black screen.

So I switched to the kernel code for the community version, and the screen lit up and the problem was solved.

However, the community kernel is maintained by foreigners, who are used to having opposite definitions of key A and key B (those of you who played the US PSP as A child know what I mean). So I started messing around again, trying to switch A and B.

As A result, I ran into an even weirder problem, which was that whenever I swapped A and B values in the keyboard driver, either it didn’t work, or some other key would always fail and not swap completely.

Therefore, I carefully studied the documentation of the GPIO part of the Linux kernel corresponding to the key driver, checked the behavior of this driver in the init and SCAN phases, and even suspected that the macro definition of the key would affect the result of bit calculation… Turns out there’s no use for eggs. But I did find a debug macro that showed the keystrokes, and I didn’t want to waste compilation time opening it, so I just turned it on and tried again.

As a result, I came to another shocking conclusion:

This code has the wrong name for the variable. The variables of the substitution are not A and B, but A and X.

It seems that I really do not have the talent to write Linux kernel, or honestly go back to porting JS engine.

Porting THE JS engine

With the kernel layer in place, we can easily log into Miyoo’s console. The user name is root and there is no password. After all the twists and turns, it was very exciting when the first landing was successful. Screenshot to commemorate:

Next application layer JS engine transplantation, for me is a familiar road. Here’s our old friend QuickJS engine, which is an ultra-mini embedded JS engine that already has compatibility with many ES2020 features. Since it doesn’t have any third party dependencies, migrating it to Miyoo isn’t really that hard. Add a build configuration of CROSS_PREFIX= ARM-Miyoo-linux-uclibcgnueabi – to the Makefile to compile it with a cross-compiler.

Cross-compiling, of course, is hardly straightforward. The compilation errors I encountered here were all due to a lack of standard library capabilities in an embedded environment. But there are only two things:

  • malloc_usable_sizeNot supported, which would affect the retrieval of in-memory metrics, but JS can still run happily. Incidentally, from the source code, this capability is not supported in WASM either. So someone has compiled QuickJS into WASM and played with JS in JS dolls.
  • fenv.hMissing, this should affect how a pawnpoint number works, but measuring it correctlyMath.ceilMath.floorNo effect. Anyway, it’s not like you can’t use it.

This is a small problem, simple patch related code will be solved. Once compiled, copy it to /usr/bin in the rootfs partition to run JS with QJS in Miyoo’s Shell. This finally cool, watch me back home, crackling write a paragraph JS test:

import { setTimeout } from 'os'

const wait = timeout= >
  new Promise(resolve= > setTimeout(resolve, timeout))

let i = 0
;(async () = > {
  while (true) {
    await wait(2000)
    console.log(`Hello World ${i}! `)
    i++
  }
})()
Copy the code

To prove it, I was actually running in Miyoo:

But how does the result of this JS code run output to the real machine? We know that Linux has the default /dev/console system console and /dev/tty1 virtual terminal, So as long as the console in the startup of the inittab: : respawn: / etc/main into devices tty1: : respawn: / etc/main, can output to the graphical virtual terminal. Like this:

Supports VSCode debugger

JS can run, log can see, what bike? Of course is to support it under the breakpoint ah! I always thought breakpoint debugging would require a heavy engine like V8 in conjunction with Chrome, but to my surprise, the community has implemented a debugger enabled fork for QuickJS, which requires only VSCode as the debugger front end, You can debug QuickJS engine runtime code. With VSCode’s Remote feature, this is a lot to imagine.

This step of support is the easiest in the whole article. Because I only did a validation on the Mac, compiled once and passed, so there’s nothing to talk about. It looks like this:

The VSCode Debugger you see here is not V8, but a proper QuickJS engine. I’ve also debugged Dart and C++ code with VSCode, and it didn’t occur to me at the time that such a debugger could be plugged into a third-party language. A search revealed that Microsoft has even designed a generic debugging Protocol called Debug Adapter Protocol between editors and any third party language, which is instructive. Originally I think very high programming language debugging system, also can use breakpoints, exceptions and other concepts to abstraction and structure, and design a general protocol. The engineering and documentation that Microsoft has built up is just amazing.

Now that I’ve compiled this QuickJS version with VSCode debugging support on Miyoo, I just haven’t done any actual debugging yet — I’m not raising a Flag that it will work, given the lessons I’ve learned from digging holes in my own custom kernel drivers.

So far, the capabilities of this experiment have been basically verified. The corresponding Docker image has also been posted to GitHub, see MiyooSDK. Also welcome everyone to exchange.

Afterword.

This time it was another long article, and the whole work was far from being done in one fell swelter. What I have right now is just a preliminary engineering prototype, and there’s a lot of work to be done. Places like this:

  • It does not support USB communication and SSH login
  • There is no GUI renderer for C implemented for JS
  • There is no upper-layer framework for porting JS

However, as long as you have the passion to continue to dive into technology, the results will not disappoint you. Like the mysterious Linux kernel, there are rules to follow. Even as a JavaScript hobbyist for my day, I can experiment and analyze it using a common scientific methodology, and the process can be as much fun as playing an escape room or a puzzle game — you know the problem can be solved by using logic to find the hidden switch in the room.

I especially want to thank Stu for his great contribution to the development of open source consoles. The most difficult hardware circuit bug was finally solved by him after he provided the key information. Most of the time, what we lack is not a complicated and trivial guide to getting started, but a sentence or two from a higher level person that will make you understand. He is such a respected technician.

Here’s a quick question: There are quite a few stores on Taobao selling consoles under the name of Stu System, which have no connection with him at all. While I still highly recommend buying the $100 + Miyoo console for entertainment or research purposes, I have some feelings. The so-called people all over the world are not sericulture people, probably so.

From building toolchains to soldering circuit boards to customizing Linux kernels and JS engines, the technology itself is a bit of a hurdle. But fun goals can always motivate us to overcome obstacles along the way. I believe that interest and passion are always the best stimulators of the thirst for knowledge, and that insatiable thirst for knowledge is what drives us over and over hill after hill. After all, what was that famous quote about Joe?

Stay Hungry. Stay Foolish.

I’m primarily a front-end developer. If you are interested in Web editors, WebGL rendering, Hybrid architecture design, or computer hobbyist chatter, please follow me 🙂

# inspiration