Project Overview & Difficulty Rating

Difficulty: Intermediate (3.5/5) | Time: 3-4 hours | Cost: $70 - $85 USD

The Raspberry Pi Hackberry (often styled as HackberryPi) is an open-source, ultra-portable cyberdeck and handheld terminal. Unlike bulky RetroPie builds, the Hackberry focuses on a minimalist footprint, utilizing a custom carrier board, a 2.0-inch IPS display, and a thumb-friendly mechanical switch keyboard. While the hardware assembly is straightforward, getting the custom SPI display and I2C battery fuel gauge to initialize reliably under Raspberry Pi OS (Bookworm) requires precise configuration.

This guide targets the Raspberry Pi Zero 2 W variant of the Hackberry. We will cover the exact hardware pinouts, provide a robust Python initialization script with error handling, and break down the most common boot and bus-lockup errors you will encounter on the bench.

Exact Parts List & Spec Sheet

Do not substitute the display or fuel gauge IC without modifying the initialization code. The Hackberry PCB is routed specifically for these components.

ComponentExact Variant / ModelNotes & Pricing (2026)
Compute ModuleRaspberry Pi Zero 2 W (v1.0)Must be Zero 2 W; original Zero W lacks the RAM/CPU for smooth UI. (~$15 MSRP / $25 street)
Carrier BoardHackberryPi Zero PCB v1.2Includes onboard MAX17048, level shifters, and FPC connectors. (~$35)
Display2.0' 240x320 IPS LCD (ST7789V2)SPI interface, 40-pin FPC ribbon. (~$12)
Battery1000mAh 3.7V LiPo (JST-PH 2.0)Must be < 55mm x 35mm to fit the 3D printed shell. (~$8)
KeyboardHackberry custom matrix PCBUses Kailh Choc v1 low-profile switches. (~$15 for switches + caps)

Pin Mapping & Hardware Assembly

The Hackberry PCB acts as a breakout board, routing the Pi Zero 2 W's GPIOs to the respective peripherals. When seating the FPC ribbon cables for the display and keyboard, ensure the black locking flap is lifted before insertion, and pressed down flush after. A partially seated FPC is the number one cause of SPI bus timeouts.

PeripheralPi Zero 2 W GPIO (BCM)Function / Protocol
ST7789 DisplayGPIO 10 (MOSI), GPIO 11 (SCLK)SPI0 Data & Clock
ST7789 DisplayGPIO 8 (CE0)SPI0 Chip Select
ST7789 DisplayGPIO 25 (DC), GPIO 27 (RST)Data/Command & Reset (Digital Out)
MAX17048 Fuel GaugeGPIO 2 (SDA1), GPIO 3 (SCL1)I2C Bus 1 (Address 0x36)
Keyboard MatrixGPIO 4, 5, 6, 12, 13, 16Multiplexed Input/Output Matrix
Power ControlGPIO 17Soft Latching Power Enable
Bench Tip: The Hackberry PCB v1.2 includes onboard 4.7kΩ pull-up resistors for the I2C bus. Do not add external pull-ups to the MAX17048 lines, or you will pull the bus out of I2C specification and cause ghosting on the SDA line.

Screen & Battery Gauge Initialization Code

The following Python script targets the Raspberry Pi Zero 2 W running Raspberry Pi OS (Bookworm). It uses the Adafruit Blinka libraries to initialize the ST7789 display and read the MAX17048 battery state. It includes robust error handling for bus lockups.

Prerequisites: Run sudo apt install python3-pip and pip3 install adafruit-circuitpython-rgb-display adafruit-circuitpython-max1704x. Ensure SPI and I2C are enabled in sudo raspi-config.

import time
import board
import busio
import digitalio
from adafruit_rgb_display import st7789
import adafruit_max1704x

# --- Pin Definitions (HackberryPi Zero v1.2) ---
SPI_SCK = board.SCLK    # GPIO 11
SPI_MOSI = board.MOSI   # GPIO 10
SPI_CS = board.CE0      # GPIO 8
DISPLAY_DC = digitalio.DigitalInOut(board.D25)
DISPLAY_RST = digitalio.DigitalInOut(board.D27)

# --- Bus Initialization ---
try:
    spi = busio.SPI(clock=SPI_SCK, MOSI=SPI_MOSI)
    # Hackberry traces are short; 24MHz is safe, 40MHz may cause artifacts
    display = st7789.ST7789(spi, cs=SPI_CS, dc=DISPLAY_DC, rst=DISPLAY_RST, 
                            width=240, height=320, rotation=180, baudrate=24000000)
    print('ST7789 Display initialized successfully.')
except RuntimeError as e:
    print(f'Display Init Failed: {e}')
    print('Check FPC ribbon seating and SPI CE0 wiring.')
    exit(1)

try:
    i2c = busio.I2C(board.SCL, board.SDA)
    # MAX17048 default I2C address is 0x36
    max17048 = adafruit_max1704x.MAX17048(i2c)
    print('MAX17048 Fuel Gauge initialized successfully.')
except ValueError as e:
    print(f'I2C Init Failed: {e}')
    print('Verify I2C is enabled in raspi-config and battery is connected.')
    exit(1)

# --- Main Loop ---
try:
    display.fill((0, 0, 0)) # Clear screen to black
    while True:
        cell_voltage = max17048.cell_voltage
        cell_percent = max17048.cell_percent
        print(f'Battery: {cell_voltage:.2f}V | Charge: {cell_percent:.1f}%')
        # In a full build, you would render this text to the display framebuffer here
        time.sleep(5.0)
except KeyboardInterrupt:
    print('Shutting down gracefully.')
    display.fill((0, 0, 0))

Debugging Common Boot & Hardware Errors

When building embedded handhelds, the hardware-software boundary is where 90% of failures occur. If your script crashes or the screen stays white, check these exact error strings.

1. 'ValueError: No I2C device at address: 0x36'

Ranked Causes:

  1. Dead or Disconnected LiPo: The MAX17048 is powered directly from the battery's VBAT line, not the Pi's 3.3V rail. If the battery is unplugged or deeply discharged (< 2.5V), the IC has no power and will not ACK on the I2C bus.
  2. I2C Disabled in OS: You forgot to enable the I2C ARM interface in raspi-config (Interface Options -> I2C).
  3. Cold Solder Joint: The MAX17048 is a tiny QFN package on the Hackberry PCB. If you hand-soldered a replacement board, the thermal pad might be bridging SDA to GND.

2. 'RuntimeError: SPI display did not respond to initialization'

Ranked Causes:

  1. FPC Latch Not Seated: The 40-pin ribbon cable for the ST7789 is slightly misaligned. Open the black flap, reseat the cable perfectly flush, and lock it.
  2. Backlight Pin Floating: Some ST7789 modules require the BLK (Backlight) pin pulled HIGH. The Hackberry PCB handles this via a MOSFET, but if your specific PCB revision requires manual GPIO control, you must set the BLK GPIO high before sending SPI data.
  3. SPI Bus Contention: Another process (like a rogue framebuffer driver) has claimed /dev/spidev0.0. Run sudo lsof | grep spi to check.
The First Three Things to Check When It Fails:
1. Run lsmod | grep i2c and lsmod | grep spi to verify kernel modules loaded.
2. Measure the JST-PH battery connector with a multimeter; it must read > 3.2V.
3. Inspect the Pi Zero 2 W header pins for a solder bridge between 3.3V and 5V, which will instantly fry the MAX17048.

Extending or Simplifying the Build

The Hackberry is highly modular. Depending on your use case, you can scale the complexity up or down.

  • Simplify (Headless Cyberdeck): If you only need a pocket Linux terminal, omit the ST7789 display entirely. Connect to the Pi Zero 2 W via USB-C Ethernet gadget mode or SSH over WiFi. This reduces current draw from ~350mA to ~120mA, pushing the 1000mAh battery life past 6 hours.
  • Extend (Bluetooth Audio & HID): The Pi Zero 2 W lacks a DAC. To add audio, route I2S to a MAX98357A amplifier breakout board wired to GPIO 18 (PCM_CLK), 19 (PCM_FS), and 21 (PCM_DOUT). Alternatively, use a USB-C hub with a cheap Realtek ALC3286 dongle for wired headsets.
  • Extend (External Antenna): The onboard PCB trace antenna struggles when enclosed in a carbon-fiber filament case. Desolder the 0-ohm resistor bridging the internal antenna and solder a U.FL to SMA pigtail for an external 2.4GHz whip.

Frequently Asked Questions

Can I use a Raspberry Pi Zero W (first gen) instead of the Zero 2 W for the Hackberry?

Physically, yes. The pinout is identical. However, the original Zero W has a single-core 1GHz ARM11 CPU and only 512MB of RAM. While it can drive the ST7789 display via SPI, running a modern terminal emulator, WiFi stack, and Python keyboard matrix polling simultaneously will cause severe UI stutter and watchdog resets. The Zero 2 W's quad-core Cortex-A53 is practically mandatory for a usable experience.

Why does the HackberryPi battery percentage jump from 40% to 0% suddenly?

This is a classic LiPo voltage curve issue combined with the MAX17048's default compensation model. LiPo cells hold ~3.7V for 80% of their discharge cycle, then drop off a cliff below 3.4V. If your Python script doesn't implement a moving average filter on the cell_percent readings, a sudden CPU load spike (like launching a compiler) will cause a voltage sag, tricking the fuel gauge into reporting a lower percentage. Implement a 5-sample rolling average in your code to smooth out transient sags.

How do I route audio to a Bluetooth headset since the Pi Zero 2 W has no DAC?

Bluetooth audio on the Pi Zero 2 W requires configuring PipeWire (the default in Raspberry Pi OS Bookworm). Install the Bluetooth stack via sudo apt install pipewire-pulse bluez, pair your headset using bluetoothctl, and set the profile to A2DP Sink. Be aware that Bluetooth polling can occasionally interfere with the 2.4GHz WiFi stack on the Zero 2 W due to internal antenna coupling; if you experience audio dropouts, switch your WiFi to a 5GHz network (if your router supports it and you are close to the AP) or use a USB Bluetooth dongle on a short extension cable.

For more details on the official hardware schematics, refer to the HackberryPi GitHub repository. For I2C and SPI bus configuration, consult the official Raspberry Pi configuration documentation, and for fuel gauge theory, review the Adafruit MAX17048 guide.