Blake Smith

create. code. learn.

»

Micropad: Final Prototype Update

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

Micropad Final Prototype 1.

Micropad Final Prototype 2.

Micropad Final Prototype 3.

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:

Prusa Mini+ 3D Printer.

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!

PCB Assembly.

Workshop.

Micropad Final Prototype 4.

As always, the full source for micropad is available from GitHub.


»

Hardware Hacking: Building a Custom USB Media Controller

UPDATE: Click here for part 2.

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.

Its specifications include:

  • A custom circuit and PCB designed in KiCAD.
  • A custom keyboard case and top plate designed in OpenSCAD.
  • Custom firmware, written in Rust for an STM32F042 microcontroller.
  • A command line USB-CDC command line client to control the device via a serial port protocol while the device is plugged into the host computer.
  • A chunky rotary encoder knob, for volume control. Forward, Backward, Play / Pause buttons to control media (mostly Spotify).

Finished Prototype Shop

Hardware Design

Finished Prototype Shot

While I’ve built my own full-sized USB keyboard before, I wanted to try a few new things hardware wise with this project.

  • USB-C: I’ve done plenty of USB Micro projects, but I wanted to support a USB-C connector for power and data for this project.
  • Replace the standard 10-pin 1.27mm pitch JTAG pin header I use on most projects with something that requires no additional pin header for firmware programming. I settled on these SKEDD connector cables that I really like. Check out the adapter board on GitHub.
  • Experiment with ENIG, gold plated PCB finish, and black PCB solder-mask.

KiCAD PCB Screenshot

USB C connector

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.

PCB with components assembled

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.

Micropad Case, PCB and Top plate

OpenSCAD 3D model

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.

unsafe {
    let bus_allocator = {
        USB_BUS_ALLOC = Some(UsbBus::new(usb));
        USB_BUS_ALLOC.as_ref().unwrap()
    };
    *USB_KEYBOARD.borrow(cs).borrow_mut() = Some(KeyboardHidClass::new(&bus_allocator));
    *USB_SERIAL.borrow(cs).borrow_mut() = Some(SerialPort::new(&bus_allocator));
    *USB_DEV.borrow(cs).borrow_mut() = Some(
        UsbDeviceBuilder::new(&bus_allocator, UsbVidPid(0xb38, 0x0003))
            .manufacturer("micropad")
            .product("micropad")
            .serial_number("MP00X")
            .max_packet_size_0(64)
            .build(),
    );

    core.NVIC.set_priority(Interrupt::USB, 1);
    NVIC::unmask(Interrupt::USB);
}

The STM32F042 microcontroller I used has a great Rust crate, that has well-integrated implementations of the usb-device interfaces, allowing me to interface with the USB peripheral in a relatively high-level, and microcontroller agnostic way.

Serial Control Interface

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.

Command Line Client Interface

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.

In the meantime, check out the full source code, along with the Bill of Materials on Github!


»

Writing Software for the RG350M Gaming Handheld

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.

Picture of a RG350M running rombp

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:

  1. 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.
  2. 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?).

rombp desktop screenshot

The basic application structure is:

  1. A filesystem browser, to select ROM files and patch files.
  2. 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.

You can get the source for rombp on the GitHub project page. If you have an OpenDingux compatible handheld, you can grab the compiled OPK files and give it a try.

Interested?

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.


» all posts