17 minutes
Open DMG Display (ODD)
TL;DR
- Fully open-source (GPL licensed) and open hardware (CC-BY-SA licensed)
- Custom PCB
- All files available (C code, KiCAD and OpenSCAD)
- Based on RP2350B or RP235xB when available
- Features
- Dimming
- Palette switching
- Automatic game detection with palette presets
- etc.
- Approx. 20 EUR in parts per unit (for quantities of 5 pcs)
- Requires minimal modifications to the case
Introduction
I’ve been thinking about the toys that played an important role during my childhood. Among many others, I had a constant companion that made family reunions bearable: my trusty Game Boy. Unfortunately, I sold my original unit years ago and never used one again. Recently, driven by nostalgia, I bought a model with a broken display on eBay, with the goal of fixing it.
I switched it on, and while there were some lines on the screen, the much bigger issue was that I instantly remembered how poor the LCD was—even back in the day. I did a quick search on how to replace the already broken display with something a bit more modern. While it’s easy to find IPS mods for a DMG, the pricing didn’t seem reasonable.
So, I did the obvious: I started building my own version of such a mod. After spending more money than I would have on any of the available IPS kits, and more time than I originally estimated, I now have exactly what I wanted. To make it easier for others, I’m publishing this project here for anyone who might be interested.
- Features of the Open DMG Display (further called ODD)
- RP235xB Microcontroller – Affordable, accessible alternative to FPGA solutions.
- Custom PCB – Minimal case mod needed.
- 2-Layer PCB – Cost-effective (ENIG recommended).
- 3.2" TFT Display – Pixel-perfect, used area matches original size.
- Backlight Control – Flicker-free dimming (no PWM).
- Multifunction Switch – Replaces contrast wheel; handles UI navigation.
- Battery Monitoring – LED, battery warning icon, adjustable settings.
- On-Screen Menu
- Display Modes - Scanlines and Pixels(experimental)
- Game Detection – CRC32-based title screen detection via DMA for auto palette selection
- PCB design supports different displays and fast modification.
- 3D-Printed Frame for the TFT module
What to Expect – and What Not To
This is an open project, not a finalized product. All data and materials are provided as is. If you have questions, I’m happy to help when I have the time — however, support is not guaranteed and responses may be delayed. Bugs in the code and potential hardware issues are to be expected. If you encounter any problems, I’d appreciate it if you report them so the project can continue to improve. If you’re able to fix an issue yourself, contributions are very welcome!
User interface
The user interface is controlled using the multiswitch, which replaces the contrast wheel of the original LCD board. It can be moved up, moved down, and pressed. There are three different modes:
- Brightness Adjustment Mode
This is the default mode and allows you to set the display brightness. Simply move the wheel up or down to adjust the brightness level. - Palette Selection Mode
To enter this mode, press the multiswitch briefly. Use the up and down movements to switch between different palettes. - Menu Mode
To access the menu, press and hold the multiswitch (long press). The menu provides access to more advanced features, which will be explained in the following sections.
Menu
- BATT LED DEFAULT
- ON: The LED will be on when the battery is good
- OFF: The LED will be off when the battery is good
- BATT LED MODE
- OFF: LED will always be off
- INVERT: Invert the state of the LED when the battery is empty (depending on the BATT LED DEFAULT setting)
- PULSE: The LED will start pulsing when empty
- BLINK: The LED will start blinking when empty
- BATT LED LEVEL
- Battery LED brightness (adjustable in three steps)
- BATT WARN ON TFT
- Show an empty battery symbol on the screen when the battery gets empty
- BATT TYPE
- Set battery type dependent thresholds for battery empty detection
- AUTO PALETTE
- Use automatic game detection and set the palette accordingly
- DISPLAY MODE (experimental)
- NORMAL: Use four pixels to display one pixel of the game image
- LINES: Fill every second line with black (creating a scanline effect)
- PIXELS: Only use one pixel for the game image and set the rest to black (creating a pixelated look)
- DISPLAY VSYNC
- GB: Use the driver mode that lets the Game Boy drive the VSYNC (frame timing)
- SELF: Use the driver mode that creates the frametiming itself
- WARNING! You currently need to restart the device in order to apply the new setting
- CUSTOM LOGO
- If ON: Use the custom logo from NV-Memory. If no valid logo was found, use the demo logo.
- SHOW DEBUG INFO
- Show some data on the screen like battery voltage and the game identifier (see Game detection)
- REBOOT TO BOOTLOADER
- This will reset the device and start it into the bootloader (same as pressing the USB boot button on startup).
Implementation details
I think some aspects of the ODD might be interesting to know, so I explain them briefly.
Capturing the image data
I wrote a separate blog post about this a while ago. You will find this at https://www.embedded-ideas.de/posts/241209_dmg_display_capture/
Game detection
While searching for suitable color palettes, I came across a repository of palettes for the Super Game Boy. These palettes introduce special 4-color palettes optimized for some of the more popular games. For example, there are custom palettes for Mario games and for Tetris.
The Super Game Boy was able to detect the game and choose the appropriate palette. This was done using the game identification data stored in the game ROM. Unfortunately, the ODD doesn’t have access to any memory, so game detection can only be done using the display data.
I came up with the following solution: The DMA controller of the RP235x allows the generation of a CRC32 checksum through its debugging function block (see dma_sniffer functions). This makes it possible to generate a checksum for each captured frame without using any CPU resources. The checksum is constantly monitored until the logo screen’s checksum is detected for the first time. We then look for a checksum that is neither the logo screen’s nor an empty frame (checksum == 0), and voilà, we have the checksum of the game’s title screen.
I tested this method with each game I have on hand, and it looks like this simple approach works like a charm. The final step is to search for the checksum in a table of palettes and activate the corresponding one.
This approach will work for every game as long as it always has the same title screen.
TFT driver and VSYNC handling
The TFT is driven using a parallel RGB interface with 16-bit color (RGB565). This means the signal directly translates to the image on the screen — there’s no frame buffering in RAM like with the more common MCU8080, SPI, or similar interfaces.
Pros and Cons
This setup brings some nice advantages, but also a few challenges.
Let’s start with the downsides: Signal generation on the RP2350 is a bit tricky. So far, I haven’t found a clean or elegant solution for a driver. I’ve implemented one which supports two modes of operation (which I’ll explain below), but honestly, I’m not super happy with it in terms of code readability and maintainability. That said, it’s working quite well in practice.
Another downside is display initialization. With the RGB interface, it took me some time to get the settings just right to avoid artifacts and flickering. I found this article that helped explain some of the strange behavior I was seeing. It was a useful starting point — though I’ll admit, I’m far from an expert in this field.
The Upside: Full Display Control - The real advantage of this approach is that you have complete control over the display timing. As long as you stay within acceptable signal margins, the source (here the Game Boy) can effectively control the frame timing of the target display.
Driver mode 1: Game Boy Synchronized
The goal of this mode is to match the Game Boy’s frame timing as closely as possible, without skipping a single frame.
Here’s how it works:
- The driver is triggered by the capture module after it has captured half of the frame.
- It starts a new frame at that moment, transferring the captured data while the rest of the frame gets captured.
- This introduces a delay of about 6ms, which I think is acceptable — especially when you compare it to the original Game Boy LCD’s refresh behavior.
The Game Boy’s signal isn’t always perfectly consistent. A game can skip clocks to produce visual effects, resulting in frame jitter (longer or shorter frames). Depending on the TFT controller, this can cause flickering.
In my tests, when I stretched the interval between frames to allow for maximum signal jitter from the Game Boy’s signal, I did see flicker — so I had to reduce the interval. This worked fine for the displays I used, but I can’t guarantee it will work for every game. Some titles might push the frame length so far that the TFT driver overflows and skips a frame, resulting in visible flicker. (If you find a game that does this, please let me know!)
Another issue: the Game Boy sometimes has blank periods where no frame data is transmitted. The TFT really doesn’t like that. To compensate, the driver detects timeouts and continues displaying the last valid frame while the GB signal is “asleep.”
Driver mode 2: Constant self-driven
For good measure, I also added a second mode that runs independently of the Game Boy’s timing. This one simply generates frames at a fixed ~60Hz, always showing the most recently captured GB frame.
This approach ignores GB timing completely, so we lose a frame here and there as we’re sampling a ~55 Hz signal at 60 Hz. You guessed it — Nyquist and Shannon don’t approve Sampling Theorem. Still, for most users and most games, this works just fine.
And if you do run into a compatibility issue or timing problem with the first mode, this can be a solid fallback.
Image overlay (Statusbar, Menu)
The menu and other on-screen graphics are implemented as a kind of canvas layered above the actual game image. This canvas uses the lightest (background) and the darkest color of the active palette. It also supports defining areas as either transparent or opaque. The resolution matches the game graphics, ensuring that the interface blends seamlessly with the gameplay. This approach maintains immersion — the additional functionality feels native to the device rather than imposed on top.
I chose this method over using modern graphics libraries like LVGL, etc., as it provides a simpler and more integrated solution. Another benefit is that the canvas can be combined with the game image using basic binary logic operations, making it both lightweight and efficient:
for (ui=0; ui < MAIN_WORDS_PER_LINE; ui++) // Overlay image insertion (for menus etc.)
{
((uint32_t*)pucLineData)[ui] &= odd_display_aulOverlayMask[uiLine * MAIN_WORDS_PER_LINE + ui]; // Clear DMG screen where mask is set to 0
((uint32_t*)pucLineData)[ui] |= odd_display_aulOverlay[uiLine * MAIN_WORDS_PER_LINE + ui]; // Add overlay image data
}
Design choices
Some parts of the design might seem a bit odd and need some explanation. I will therefore tell a bit about why I chose to do it this way. Maybe you have a better idea — so we can use this text as a basis for discussion ;)
Display selection
It took me quite a while to find a reasonably sized display for this project. The original aspect ratio of the Game Boy isn’t very common these days—especially when you’re aiming for a specific resolution that enables pixel-perfect rendering of the DMG’s 160×144px image.
I initially explored various 2.x" displays that closely matched the physical dimensions of the original LCD. Unfortunately, most of them came with a resolution of 160×128px.
Eventually, I abandoned the idea of a landscape-oriented display and started searching for a portrait-oriented alternative. This approach made the search much easier. I settled on using 3.2" screens, which almost perfectly match the original screen size when mounted vertically. The chosen resolution is 320×480px—ideal for mapping the Game Boy’s 160px width perfectly when using 4 pixels per pixel. As for the vertical resolution, we do end up with 192 unused pixels, but that’s a trade-off I’m willing to accept.
These types of displays aren’t extremely common, but they’re not hard to find either. In the final design, I added support for two different displays. Both use the HIMAX HX8357-A01 chipset and share the same resolution and physical dimensions. However, they differ in backlight technology, TFT type, and connector.
Why go through all this effort?
- Sourcing stability: I believe the display is the most likely component to become difficult to source over time. Supporting two display options increases longevity and flexibility.
- Flexible routing: The two connector variants allowed me to create a PCB layout that can be easily adapted to support other displays in the future.
- Backlight versatility:
There are two common approaches to LED backlighting:- Parallel configuration is simpler and often runs off the logic supply (typically 3.3V), which makes it easy to implement. However, it can lead to uneven illumination.
- Series configuration eliminates uneven lighting but requires a higher voltage and a more complex boost circuit.
To accommodate both types, the board includes support for both backlight circuits: a step-down and a step-up converter, both capable of dimming without using PWM (to avoid backlight flicker). This means you can adapt the board to future display modules without needing a complete redesign.
Currently the board supports the following physical displays
-
JJY3202-05-Z-A
- 3.2 inch IPS TFT
- 6 LED backlight (series) ~19.2V
- 45 pin .3 pitch connector
- 45.12mm x 67.68mm active area
- Ecyberspaces via AliExpress (https://de.aliexpress.com/item/32632665396.html)
-
TT320LHN10A
- WARNING! The init code for this display isn’t ready yet. There might be flicker and/or artefacts when using it. I would currently recommend using the JJY3202-05-Z-A.
- 3.2 inch IPS TFT
- 6 LED backlight (parallel) ~3.2V
- 40 pin .5 pitch connector
- 45.12mm x 67.45mm
- Sources
- Spring Trend Limited http://stldisplays.com/ (OEM)
- Shenzhen Toppop Electronic Co.,Ltd https://toppoplcd.com/productdetails_5252259.html
PCB
I have chosen to use a 2-layer PCB to keep the cost down. If you don’t opt for a special surface finish, you could order 5 boards for around 7 EUR. With ENIG (which I recommend for the button contacts), the cost increases to about 23 EUR for 5 pieces. A 4-layer design would at least double these numbers.
The chosen thickness of 1.6mm is required to provide enough clearance for the multi-purpose switch on the left side. The multi-purpose switch would not have fit with the original 1mm PCB thickness of the genuine display.
USB connector placement
The USB connector is not intended for regular use, nor is it meant to be accessed through a cutout in the case. It is designed solely to provide a way to upload the firmware onto the device after it has been built, functioning like an internal flash programmer. If you really want to, you might be able to add a cutout to the DMG shell and use a cable with a long tip.
1.25mm pitch FFC connector to the gameboy
Flat flex cable connectors with a 1.25mm pitch are not very common these days. You can easily find 0.3mm, 0.5mm, and 1.0mm pitch connectors, but 1.25mm connectors are rarely available at a reasonable price point. Therefore, I’ve added solder pads that allow for directly soldering a 1.25mm pitch FFC onto the board. This method is not intended for mass production and is more time-consuming compared to soldering a simple connector. If you’re willing to invest, you might consider designing a flex PCB optimized for this use case.
In addition to the solder pads, there is also an extra footprint for a 0.5mm pitch connector, which includes all the relevant signals. This is intended for use with a custom flex PCB adapter that I may design in the future. This connector is currently untested.
Assembly
You will need the following parts:
- PCB and stencil (JLCPCB, PCBWay, multi-cb, etc.)
- Components according to BOM (DigiKey, Mouser, LCSC, etc.)
- 1.25mm pitch FFC cable (AliExpress or eBay)
- TFT display (see display selection)
- 4x M2 screws with 5mm length (for the display frame)
- Display frame top and bottom (3D-printed)
- Consider buying an after market Game Boy case in order to not modify your original one (AliExpress, eBay)
PCB assembly
You’ll need some soldering experience, but it’s not as difficult as you might think. I cover the process of assembling the critical components with basic tools in this post: Manual Assembly of Modern PCBs Using Low-Cost Tools.
If you’ve ever wanted to get into micro soldering, this project is a great place to start.
I recommend using the provided interactive BOM during assembly — it really helps streamline the process.
FFC Cable Soldering
In addition to assembling the PCB, you’ll need to solder the FFC cable to the board. This part is a bit messy. As mentioned, getting connectors with the right pitch can be tricky, so we’ll need to improvise.
You’ll need:
- A 1.25 mm pitch FFC cable (100 mm length; shorter may also work)
- Some tape (e.g., Kapton tape)
- A hot air tool
- Patience
Steps:
- Remove the stiffener from one side of the FFC using hot air (~100 °C).
- Carefully lift each individual trace with a knife or sharp tweezers. Doing this on a hotplate makes it easier.
Be careful not to burn yourself and use a safe working temperature. - Once all traces are lifted, trim the excess plastic beneath the traces.
- Bend the traces downward slightly so they apply some pressure against the board.
- Align the traces with the pads on the board and fix them in place with tape.
- Solder the traces, and optionally add strain relief with additional tape.
Quick & Dirty Method (Not Recommended)
If you’re looking to speed things up — and don’t mind the smell of burning mystery plastic — you can skip most of the above and align the FFC directly after removing the stiffener.
Warning: This method will probably burn part of the FFC. Proceed at your own risk.
Display frame
The frame is 3D printed. I used a standard layer height of 0.2 mm with a 0.4 mm nozzle. PETG is a good material choice for printing the frame, though a flexible PLA blend could also work well.
The frame is attached to the PCB using four 5 mm M2 screws. If you have appropriately sized self-tapping screws, those can be a suitable alternative.
Flashing the firmware
Connect the ODD to a PC using a USB cable. While holding down the USB BOOT button, apply power to the board. There are two ways to do this:
- Recommended: Close jumper JP1 to power the ODD via the USB cable.
Important: If you choose this method, never switch on the Game Boy while the USB cable is attached! - Leave JP1 open and power the board via the Game Boy mainboard instead.
Once connected, a virtual drive will appear on your computer. Simply copy the supplied UF2 file onto the drive — and you’re done!
Case preparation and board installation
Next, remove the protective film from the TFT. Carefully lower the board into the case, making sure the speaker is seated correctly. Pay close attention to the routing of the speaker cables. Use the Phillips screws to secure the board in place.
Note: The ODD PCB is slightly thicker (0.6 mm) than the original PCB. As a result, the back shell may not close perfectly. You’ll need to file down some of the board rests on the back shell. Take your time and check the fit frequently. You may also want to use a caliper to measure your progress for accuracy.
Unused circuitry
There are some unused, or DNP (Do Not Populate), components on the board. Here’s an explanation of what to do with them:
- Connector J3
- Can be used to design a flex PCB as a replacement for the 1.25 mm FFC cable.
- UNTESTED!
- Op-Amp U4
- If you’re aiming for the most precise battery measurement in the history of the DMG—or if you want to overclock the ADC—this one’s for you.
- Resistors R22, R23
- I initially thought the negative rail might need some load. That doesn’t seem to be the case, so I decided not to use them.
- Capacitors C37, C39, C38, C40
- If you want better compliance, or if you experience noise, you can experiment with these values. They’re intended for decoupling the ODD supply from the Game Boy mainboard.
- So far, the regulator caps in combination with the larger electrolytic capacitor have worked fine for me.
Thanks
I used several resources that really helped speed things up:
- DMG LCD scans
- bit9 on chipmusic.org
- https://chipmusic.org/forums/post/215957/#p215957
- Schematics
- Color Palettes
- TheWolfBunny64 (https://thewolfbunny64.itch.io/game-boy-custom-palettes)
- AdvancedFan2020 (https://www.deviantart.com/advancedfan2020)
- Font
- vyznev on fontstruct.com
- Pascal Stang via glcd library
- PIO
- A big thank you to the Raspberry Pi team for the PIO units. I believe peripherals like this should have been available years ago!
Files
Sourcecode and release binaries are hosted on codeberg.org
-
Hardware
-
Software
3525 Words
2025-04-17 18:00