DEV Community

Cover image for Hacking a Dead Digital Frame Into a Linux Smart Display
Hayes Roach
Hayes Roach

Posted on

Hacking a Dead Digital Frame Into a Linux Smart Display

Our Nixplay digital frame bricked itself and was headed for the trash. I opened it up instead. Now it runs Linux with a custom clock, weather, calendar, daily bible verse, and photo dashboard.


The setup

My family's old Nixplay photo frame died. Power it on and you'd get a backlight, sometimes a red bar, and nothing else. Resets did nothing, power cycles did nothing. Something in the firmware had clearly died.

Corrupted Nixplay screen showing all black

I wanted to see if I could fix it, so I opened it up. Inside was a surprisingly capable little computer:

  • Allwinner A20 (dual-core ARM)
  • 512 MB RAM
  • 18.5" AUO M185XTN01.2 LVDS panel (1366x768)
  • NAND flash storage
  • SD card slot
  • USB port

Nixplay ARM chip

The A20 is old but has great Linux support. This device was never a supported board anywhere, and nothing about its display wiring is documented.

Getting a serial console

Before anything else I needed a way to see what the board was doing. Boards like this usually have a debug serial port (UART), but nothing here was labeled. Just a few bare flat pads near the main chip.

Finding it is a classic trick: probe the pads with a multimeter while the board boots. The transmit pad idles at a steady 3.3V and visibly dips while the board prints boot messages.

Board with uart locations for Tx, Rx, and GND

Once I found TX, RX, and ground, I soldered wires directly to the pads (no through-holes on this board), ran Dupont jumpers to a USB-to-TTL adapter (real FTDI chip, switchable to 3.3V, which is what the A20 needs), and had an instant boot console at 115200 baud.

Connection to from uart pints to laptop

Booting Linux without breaking anything

The A20 can boot from an SD card instead of the onboard NAND flash, which meant I could experiment without touching the original firmware.

The closest supported board in Armbian (a Linux distro for ARM boards) is the Olimex LIME2, which uses a similar chip. I flashed it to an SD card.

It didn't just boot on its own the first time, though. The bootloader stored on NAND grabbed control before the SD card could boot, so I had to spam the spacebar over the serial console to interrupt its autoboot countdown, then kick off the SD card boot manually from the bootloader prompt. Once Armbian was fully installed with its own bootloader on the SD card, the board started booting straight into Linux automatically and everything was working.

Except the screen, which showed solid white.

Solid white display

The white screen problem

Here's the thing about this type of LCD: it's "normally white." Backlight on plus no valid video signal equals a pure white screen. So white means the panel isn't receiving anything it understands.

The Armbian kernel I started with didn't have a working LVDS pipeline for this panel, so I had to build a custom kernel with additional A20 LVDS patches:

  • Forked Armbian's build system (github.com/hayes-roach/build) and added community kernel patches that add A20 LVDS support
  • Built custom kernels in GitHub Actions
  • Hand-edited the device tree (the config file that tells Linux what hardware exists) to describe the panel: resolution, timing, pins
  • Found the backlight control pin by writing a loop that toggled every GPIO until the backlight blinked
  • Even rebuilt the bootloader with display support

After all that, Linux reported a fully working display pipeline. Correct registers, panel driver loaded, backlight on.

Still white.

I then tried everything. Every data format, every timing, both of the chip's LVDS outputs, live register pokes over the serial console. The panel never reacted to anything. Not even garbage on screen. It behaved exactly like an unplugged panel.

Except one fact didn't fit: the corrupted firmware's red bar was a real rendered image. The display path worked. My software just couldn't reach it.

The broken firmware saves the day

Then it hit me: this board has no eMMC. The firmware lives on a NAND flash chip that my Linux install never touched. The original firmware was still sitting there, corrupted OS and all.

So I pulled the SD card and let the board boot from NAND, watching over serial. The logs basically explained the whole mystery. The OS partitions threw read flash error (there's the reason the frame died), but the early boot stuff loaded fine, and the stock bootloader brought up the display.

The panel went black with the backlight on.
Black is good. With the backlight on, a black screen means the panel electronics are alive and responding to a video signal. So the hardware worked, and now I had something I never had before: a working display setup I could poke at over serial and compare against.

Allwinner devices store board configuration in a binary file called script.bin. The bootloader loads it into RAM during startup. From the bootloader prompt I dumped memory until I found the display section and could see exactly how the original firmware configured the panel.

And there it was, the actual config this panel wants: 24-bit LVDS (I'd been sending 18-bit this entire time), a 75 MHz pixel clock, and the exact timing values. Not the numbers from the datasheet. Not the numbers from community configs. The real ones.

I copied everything over to my Linux setup and rebooted.
Still white.

One GPIO pin

At this point I had a working display sitting live at the old bootloader prompt, so I figured: let's just start flipping GPIO pins and see what the panel actually depends on. I toggled each Port H bit individually from the bootloader prompt and watched for any visible change on the panel.

bit 8 low  -> white screen
bit 8 high -> panel works
Enter fullscreen mode Exit fullscreen mode

Pin PH8 turned out to control panel power. The original firmware turns it on through its config file. Nothing in my stack, not the kernel, not the bootloader, had ever touched it.
The panel's electronics had been switched off the whole time. Days of debugging display timings while the panel electronics were effectively disabled.

Back on the Linux side, one command to set PH8 high and the white screen collapsed to black. I wrote random data to the framebuffer and the screen filled with static. Alive. After days of chasing LVDS timings, the fix turned out to be a single GPIO bit.

One last gotcha: I tried letting the Linux panel driver manage PH8, and everything broke again. Every display mode change toggled the pin off and back on faster than the panel's required 500 ms power-up time. The fix was a Device tree (a hardware description Linux uses to know what peripherals exist and how they're wired) "gpio-hog," which pins PH8 permanently on from the moment the board boots, plus disabling display power management so the video pipeline never cycles.

Armbian

The final recipe

Piece Value Found via
Panel enable GPIO PH8, always on GPIO fuzzing at the stock bootloader
Backlight GPIO PH7 + PWM GPIO sweep in Linux
LVDS format 24-bit (vesa-24), 75 MHz script.bin dumped from NAND
Kernel Armbian + A20 LVDS patches my fork

Building the dashboard

With the display working, I turned it into a wall dashboard: clock, Google Calendar, weather, rotating photos, a daily Bible verse, running 24/7.

On 512 MB of RAM, heavyweight options like MagicMirror don't fit comfortably. So the whole dashboard is one HTML file with vanilla JavaScript, served by Python's built-in web server and displayed by Firefox in kiosk mode. The board boots straight into it: auto-login, X starts, Firefox launches fullscreen.

The interesting bits:

  • Firefox, not Chromium. The Chromium packages available for this platform rendered every page as a blank white window, while Firefox ESR worked reliably.
  • Calendar without OAuth. Google Calendar has a "secret iCal address" that works for private calendars. A cron job downloads it every 15 minutes and the page parses it in JavaScript into a week view.
  • Weather from Open-Meteo, which is free and needs no API key. Current conditions plus the next 10 hours.
  • Photos come from my NAS, mounted as a network share on the board. The page reads the web server's own directory listing to discover new files automatically.
  • A watchdog restarts Firefox if it crashes, and the backlight dims on a schedule at night.

Total memory footprint: around 200-300 MB. Comfortable.

Memory

What I learned

  1. A white screen on a "normally white" panel means the panel isn't in the game at all. Stop tuning video settings and go hunt for power and enable pins.
  2. Never erase the original firmware, even a broken one. The corrupted NAND couldn't boot its own OS, but its intact hardware config and working display init cracked the whole case. The firmware that killed the frame saved the project.
  3. Vendor-specific enable GPIOs are often more important than display timings. Datasheets and device trees will never tell you about them. Brute-forcing a GPIO bank at a bootloader prompt takes five minutes and would have saved me days.
  4. Get serial access first. Multimeter, bare pads, soldering iron. Every breakthrough in this project happened at that console.

Working smart display running linux that shows time, weather, calendar, and photos

Not bad for a device that was about to be thrown away.


Hardware: Nixplay W18A (Allwinner A20, 512 MB), AUO M185XTN01.2 panel. Software: Armbian, Firefox ESR kiosk, single-file HTML dashboard. Patches and build workflow: https://github.com/hayes-roach/build

Github: https://github.com/hayes-roach

Top comments (0)