May 07, 2020
Game Programming on an ESP32: Motivation (Part 0)
I’ve been looking for a side project to work on outside of my day job to distract me from the state of the world. My
primary interest is game programming but I also enjoy working with embedded systems. My current job is at a game
company but most of my jobs before that were working with microcontrollers. While I eventually decided to change
gears and join the game industry, I still enjoy playing with microcontrollers.
Why not both?
I have an Odroid Go hanging around that might be fun to play around
with. It’s core is an ESP32 which is a very popular microcontroller that has common MCU functionality (SPI, I2C, GPIO,
timers, etc) but also has WiFi and Bluetooth which makes it appealing to those building IoT applications.
The Odroid Go bundles the ESP32 with a bunch of peripherals to turn it into a portable game machine that is reminiscent
of a Gameboy Color: LCD, speaker, D-pad, two main buttons, four auxiliary buttons, a battery, and an SD card reader.
The primary reason people buy the Odroid Go is to run emulators for
old 8-bit systems, so if this thing can emulate old games then it should be able to run a native game created
specifically for it.
The display is only 320x240 so we are very limited in how much we can have on screen at one time. We’ll have to think
carefully about what sort of game to make and what sort of assets to use.
The display only supports 16 bits of color for every pixel: 5 bits for red, 6 bits for green, and 5 bits for blue. This
scheme is commonly called RGB565 for obvious reasons. Green gets one more bit than red and blue because the human
eye is better at picking up gradations of green than blue or red.
16 bits of color means we only have access to 65K colors. Compare that to the typical 24 bits of color (8 bits for
each color) which provides 16 million colors.
Without a GPU we won’t be able to use an API like OpenGL. The common way to do a 2D game these days is to use the same
GPU you’d use for a 3D game but to simply draw quads and then map texture bitmaps to them. Without a GPU we will instead
need to rasterize every pixel directly with the CPU which is slower but simpler.
With a screen resolution of 320x240 and 16-bit color, the total framebuffer size is 153,600 bytes. Meaning at least
thirty times per second we need to send 153,600 bytes to the display. It may end up being an issue and we’ll instead
have to get clever about how we draw the screen. We may choose to do indexed color into a palette, for example, so
for every pixel we store one byte which acts as an index into a palette of 256 colors.
The ESP32 has 520KB of built in RAM and the Odroid Go adds an extra 4MB of external RAM. But not all of that
memory is available to us because it’s in use by the ESP32 SDK (more on that later). After disabling all
extraneous features that I could and then entering my main function, the ESP32 reports 4,494,848 bytes free for
our use. If we need more memory later we’ll revisit ways of trimming the fat.
The CPU is configurable at three speeds: 80MHz, 160MHz, and 240MHz. Even the max of 240MHz is a far cry from the 3GHz+
machines that we’re used to working with. We’re going to start at 80MHz and see how far that gets us. If we want to be
able to run this game on battery then we need to keep the power consumption low. Lowering the clock is a good way to do
There are ways to use debuggers with embedded devices (JTAG) but unfortunately the Odroid Go does not expose the
necessary pins so we can’t step through the code in a debugger like we would normally be able to do. That means that
debugging will be difficult and we’ll need to make judicious use of on-screen debugging (by using colors and text) and
printing information to the debug console (which thankfully is easily accessible over USB UART).
Why try making a game for this middling device with all of the aforementioned limitations instead of just creating one
on your desktop PC? Two reasons:
Limitations breed creativity
When you’re working with a system with a specific set of hardware, each with its own
limitations, it forces you to think about how best to take advantage of those limitations. It’s the closest we can come
to how it must have felt like making a game for something like the Super Nintendo (but still much easier than that).
Low-level is fun
Writing a game from scratch on a normal desktop would require us to tackle typical low-level game engine concepts like
rendering, physics, and collision detection. But doing those things on an embedded device means we also need to deal
with low-level computer concepts like writing an LCD driver.
When it comes to going low-level and rolling your own code, you have to draw the line somewhere. If we were trying to
make a game without libraries on a desktop, that line would probably be the operating system or a cross-platform API
like SDL. For our purposes, I’m drawing the line at writing things like SPI drivers and bootloaders. That’s more pain
So we’re going to use the ESP-IDF which is basically an SDK for the ESP32. We can think of it as providing some of the
utilities that an operating system would normally provide, but the ESP32 is not running an operating system.
Technically it uses FreeRTOS which is a real-time operating system, but it’s not a true operating system. It’s just
a scheduler. We likely won’t be interacting with it much but the ESP-IDF uses it at its core.
The ESP-IDF will give us an API for the ESP32’s peripherals like SPI, I2C, and UART, as well as the C Runtime Library
so that when we call something like printf, it actually sends the bytes out over UART to be displayed in the serial
monitor. It also handles all of the startup code that is required to get the machine ready before it calls the entry
point into our game.
These posts will be a sort of development log where I cover things that I’ve done that I find interesting, or where I
want to explain things that were especially painful to figure out. I have no plan and will likely make many mistakes.
It’s just for fun.