I’ve recently started building the final prototype revision
of
micropad. Since the
last post,
I’ve been able to:
Finish the transition to a USB-C connector
Get the case in good alignment, including other minor mechanical changes
Fix top-plate buckling
.
.
.
Mechanical Fixes
I fixed the top-plate buckling issue, by including 2 different
standoff sizes that the PCB is mounted to. This increased the overall
macropad height, but gives the board enough clearance for a
completely level top-plate mount.
I also changed the revision 1 screw fasteners from M2 to M3. M3
fasteners are much easier to find and purchase, and I wanted these to
be easily repairable if fasteners are ever lost.
Part of what delayed this project’s finalization was waiting for my
Prusa Mini+ 3D printer to arrive:
.
Having a 3D printer at home allowed me to iterate on case tweaks
really fast, and get all the case tolerances worked out. It was also a
fun multi-week assembly that my oldest son helped with, which made for
great fun!
Firmware
On the firmware side: There’s still a few blocks of unsafe Rust in the
firmware. Most of this is caused by needing static mutable references
to peripherals for interrupt handling, but I was able to slim down the
unsafe blocks by wrapping more global peripherals in Mutex<RefCell<T>>
types than before. I ended up not using RTIC, since my firmware size was
already at the upper limit of the onboard 32K flash memory size, and
adding another dependency sent it over the edge. A few unsafe blocks
in a small firmware like this seems worth it to not have to buy a chip
with more onboard memory, or reduce other user-visible functionality.
I’ve made 4 of these micropads in my (really messy) workshop, to give
to friends, and am excited with how they’ve turned out!
I recently completed the first revision prototype of a USB media
controller keyboard “macropad”. I’m not a fan of the touchbar based
media control buttons on my work MacBook Pro, and wanted a way to
control my music and audio volume (especially for video calls) with big chunky, tactile buttons instead.
The result is an Open Source Hardware USB Keyboard I
call micropad.
Experiment with ENIG, gold plated PCB finish, and black PCB solder-mask.
Many of the other existing hardware design ideas were remixes of existing projects
I’ve had success with in the past. I improved the rotary encoder
accuracy by actually including a rotary encoder filter circuit
into the schematic. I used
standard Cherry MX keyboard switches I had laying around from a
previous project for the media control buttons, but was able to skip
the
scan matrix
that is normally required when you have more input buttons than
microcontroller pins.
The most difficult part of the hardware design has been getting the
PCB and case design to fit correctly. I still haven’t found a good
workflow to connect OpenSCAD and KiCAD so I can get exact / precise
alignment with mounting holes and components tolerances. As much as I
appreciate the “CAD as source code” nature of OpenSCAD, getting good
component fits will probably push me to try something different like
FreeCAD with my next hardware design project.
Firmware
Before this project, I built a small proof
of
concept board that used Rust and an ATSAMD51,
so I was pretty confident I could get Rust going on an STM32F042, and
make its USB hardware peripheral work with Rust. My previous keyboard’s
firmware
was written in C++,
and I wanted to see if I could mimic a lot of that existing
functionality in Rust.
I was able to leverage some really spectacular Rust crates, that made
this project so much simpler and more intuitive than working in
C++. I spent weeks in the past getting USB to work with crappy STM32 vendor
examples that I could scour on the internet. Even after getting this C++ implementation
to work, so much of that USB handling code wasn’t portable to other
microcontrollers.
Instead, on this project, I used the usb-device crate,
which provides an easy abstraction over common USB firmware
implementations, including USB handshake handling, device descriptors,
and communications polling.
In addition to the USB HID device that the hardware would expose, I
also implemented
a simple
bidirectional serial communication protocol between
the hardware device and the host computer. This gets exposed as a
USB-CDC serial device, which is essentially a way to send serial data
over USB.
The result is a command-line client that can be used to control
brightness, and other settings on the micropad itself. Right now, the
messages it supports is pretty limited, but I have plans to expand
this to support live changing the keyboard layout from the host computer.
What’s next
So far, I’ve done a first and second revision of the PCB and case. I’m
waiting for
a
Prusa Mini+ 3D printer
to arrive from backorder so I can iterate on the case design much
more quickly, and without spending a bunch of money on externally
printed parts. There is some other top plate buckling issues I plan to
fix as well.
I also had to use a lot of unsafe Rust. This comes with the territory
of embedded firmware, but I’d like to try some different idioms that
give more safety guarantees. RTIC
might be able to help, but I’m not sure if I want a ‘framework’ like
this in the project. We’ll see.
I recently purchased a RG350M, a retro-gaming emuation handheld,
that’s an improved version of
the RG350. It features a 1Ghz,
JZ4770 dual-core 64-bit processor, with 512MB of DDR ram, and a
640x480 LCD display (double the resolution from the RG350). Much to my
surprise, the JZ4770 SOC features a MIPS64 core, rather than the
prevalent ARM core that’s popular in so many portables these days. I
thought MIPS was mostly a thing of the past, but it turns out it’s
still found in many small embedded devices. MIPS processors were also used in many
popular gaming consoles of the past (PS1, N64, and many others).
The RG350M can emulate all your retro gaming systems quite well, all
the way up to many PS1 games.
Software Toolchain
After I got it, I wanted to know how I write software for the thing. The RG350 is based on OpenDingux
Linux:
a stripped down Linux distribution that’s optimized for these small,
portable gaming handhelds. While having a Linux target makes it
significantly easier to target for software development, OpenDingux
has a few design constraints that make it different than your standard
desktop Linux target:
No window manager: No X11 window manager, so you can’t just find your favorite GTK application and port it over. By default, applications are launched using the GMenu2X laucher and run fullscreen.
Only one application can be running at a time (with some exceptions).
While OpenDingux has its own OPK application file format, it has no traditional “package management” system like RPM or APT packages.
First, you’ll want to setup a cross-compilation toolchain. The two
main things you need are:
A C/C++ compiler that can target the MIPS64 ISA, along with a libc. The RG350 stock firmware ships with uclibc, so we’ll want to make sure our toolchain has that available for compilation.
Cross compiled shared libraries that we can link against during development. Most applications for these handhelds end up using SDL2 for graphics rendering.
Luckily, there’s a Linux Buildroot GitHub
repository that Github user
tonyjih put together that will help you bootstrap this entire
toolchain, including common shared libraries that are useful for development.
Development Notes
A few important details on building software for the RG350:
The OPK file format is basically a squashfs filesystem file that contains a special application launcher file inside (.desktop file). The .desktop file has basic application metadata, similar to a Debian package, and also includes a menu icon and application launch command. See the Makefile to see an example for how the squashfs filesystem can be built, and how the .desktop file gets included.
.desktop files are loosely based on GNOME Desktop launcher files, but with different keys and sections for handhelds. See the .desktop file to see a working example.
A useful workflow I’ve found is to compile and test my code on my Linux laptop, and then test on the device once I’m reasonably happy with the desktop behavior. Remember: this is just Linux, with SDL2, so it should be easy to iterate locally. If you need to use other libraries that don’t ship on the device, you will have to either include them compiled in your OPK file, or statically link them into your binary.
A ROM hack binary patcher
I wrote a retro gaming ROM hack binary patcher program that should work on
most OpenDingux handhelds. There
are many great (and awful) community made ROM hacks that I’d like to
play, that require binary patching an original stock game ROM. It’d be
nice to be able to patch these games on the handheld itself. I’m mostly
interested in playing Super Metroid and Super Mario World hacks (Kaizo
Super Metroid anyone?).
The basic application structure is:
A filesystem browser, to select ROM files and patch files.
A basic IPS / BPS patch decoder, to patch the ROMs. IPS, even with it’s several shortcomings, seems to be the favored format for patch distribution in the Super Metroid community. I found BPS patches to be more popular in the Super Mario World community. Both IPS and BPS are simple and easy to implement.
If you’re going to pick an RG350M, I’d recommend getting
one from an Amazon seller. Mine took almost a month to get here,
spending most of it’s time sitting in a local post office in a
“Tendered for Delivery” state.
I still have an old GP2X from South Korea, which a lot of the software for
the RG350M is based on. The hardware has improved dramatically since
then, and this is a fun little handheld that is fun and easy to
hack. I’d recommend it if you’re at all interested in a compact retro
gaming device.