micropong: a tiny videogame running Rust on STM32

I wanted to learn embedded programming with Rust, so I made this little project. It's a battery powered pocket sized video game. The idea of the game is to be ridiculously small. It's played by two players each grabbing one end of the board. I’ve programmed two games for it: pong and tetris.

Diving into embedded Rust

I started by skimming the Embedded Rust Book: It explains the basics, and contains a nice template for getting a project started. I tried out the tutorials with the STM32F3DISCOVERY board. As everything seemed to work fine, I then moved on to the smaller and lower-power NUCLEO-F042K6 (recommended to me by my colleague Joonas), powered by the stm32f042k6 microcontroller.

Nucleo-based prototype (v0.1)

I built and programmed the first prototype of the Pong game on a whim one Sunday evening, using the NUCLEO-F042K6 board. It uses the SSD1306 128x32 pixel I2C OLED display I had picked up earlier, and some microswitches as controllers for the two players.

Programming the microcontroller was very easy. The NUCLEO-F042K6 contains another microcontroller that can be used for programming via USB. I followed the instructions in the embedded rust book to use OpenOCD and GDB to program the chip.

Prototype with separate components (v0.2)

As the pong prototype proved to be quite fun, I decided I wanted to try to assemble my own board. I examined the user manual for the NUCLEO-F042K6, and was able to decipher the schematic, and figure out how it was powered.

After some searching I picked up the MCP1640 regulator to convert the 1.5V voltage from a AAA battery to the 3.3V needed. I soldered it and a stm32f042k6 on breakout boards (from Adafruit). I assembled the ICs, along with the capacitors, resistors and inductor needed (as described in the MCP1640 datasheet reference circuit) on a breadboard.

In order to program the chip, I followed the instructions in the STM32F3DISCOVERY datasheet. I removed the ST-LINK jumper, and connected my custom device to the SWD header pins. This way I could use the programmer on the STM32F3DISCOVERY board to program my device.

PCB version (v1.0)

Now that I had proved that it all works, I wanted to finalize the project by bringing it all into one PCB.

Designing the board

I looked at different PCB design packages and settled on KiCad, since it’s free. Digi-Key has created this very nice and thorough series of tutorials for designing PCBs in KiCad, which I followed more or less. The steps are: 1. Designing a schematic, 2. Finding symbols and footprints for the necessary parts, 3. Designing the PCB layout (routing the traces)

Designing in KiCad proved to be a rather painful experience. Resizing the board (shrinking it) meant I needed to reroute everything multiple times pretty much from scratch. In the end I managed to design something I was fairly happy with, so I sent the designs to OSH Park. It’s a great service that’s fast, cheap, and accepts the KiCad design files as input directly!

Soldering the board

I hadn’t attempted soldering components this small before, so I got a training kit to learn surface mount component soldering. With my new Hakko FX888D soldering iron, some flux, thin solder and help from some youtube tutorials, I was able to solder all the components.

While soldering the board, I realized I had ran the traces for the switches (with four pads) the wrong way, so I had to solder them on diagonally. I fixed this in v1.1 of the board.

Final version (v1.2)

For the final version I swapped the 1.5V AAA for a 3V CR2032 coin cell. I had realized earlier that the AAA lasts for way longer than I would ever need, so why not go for a slimmer form factor.

In order to further reduce the size, I replaced the programming/debug header pins with the SOICbite footprint, and filed down a SOIC-8 test clip to attach to it. This works ok, but the connection can be a bit finicky, and fails every now and then.

I also added four more buttons. This enabled me to implement the tetris game, or whatever future game I would like to create.

Programming the software

Programming the games was fairly straightforward. I used the stm32f0xx-hal crate to access the peripherals (GPIO for the buttons and I2C for the display), ssd1306 crate to interface with the display itself, and the embedded-graphics crate to do the drawing. As this is a “no-std” environment (no access to the Rust standard library), some things were a little trickier. In order to use a `Vec` I used the arrayvec crate that provides a fixed-capacity `Vec` backed by an array. For random number generation I used the wyhash crate.

Microcontroller peripheral configuration is not something I really know much about, but it normally involves writing values to certain registers. Due to the limitations of the hardware itself, only certain configurations are possible. E.g. only certain pins can be configured for certain functions. The Rust crates that implement the HAL (such as stm32f0xx-hal) seem to have taken the approach of a typestate pattern, where the type of variable representing a peripheral would limit its use in different contexts. I think this is very helpful, and exactly the reason why I’m doing this stuff in Rust and not C.

The toughest bug I had was a software bug. The device wouldn’t boot when switched on, but would start if the reset pin was held low. I read datasheets very carefully, and tried hard to find what I had missed in the hardware design, only to eventually realize that a short delay between power on and initializing the display was needed. I was so focused on the thing I didn’t know much about (hardware), that I forgot to look for a simple software fix.

Conclusion

Embedded programming in Rust is fun and easy, you should give it a go as well :)

Source