Index

MicroPython on ESP32 T-Display (Debian 13)

This guide reflects what actually works on Debian 13 (Trixie), including common pitfalls encountered during setup.


1. Debian 13 specifics (important)

Debian 13 enforces PEP 668 – externally managed Python environments.

This means:

Attempting to bypass this with --break-system-packages is not recommended.


2. Required packages

Install base dependencies:

sudo apt update
sudo apt install -y \
  python3-full \
  pipx \
  picocom \
  git

Enable pipx paths:

pipx ensurepath
logout

(Log out and back in – opening a new shell is not enough.)


3. Install ESP tools (Debian-safe way)

Install tools using pipx:

pipx install esptool
pipx install mpremote

Verify:

esptool.py version
mpremote --help

4. USB & serial permissions (critical)

ESP32 serial devices appear as:

Add your user to the dialout group:

sudo usermod -aG dialout $USER
logout

⚠️ You must fully log out and log back in. Opening a new terminal is not sufficient.

Verify:

groups

You should see dialout listed.


5. USB cable warning (very common issue)

⚠️ Many USB cables are charge-only.

Symptoms:

Fix:

👉 This issue alone can block the entire setup.


6. Identify the ESP32 device

Plug in the ESP32 and run:

ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null

Typical output:

/dev/ttyUSB0

If nothing appears, diagnose with:

dmesg -w

Look for:


7. Backup (dump) current ESP32 firmware

Before flashing anything, make a full backup.

7.1 Identify flash size

esptool.py --port /dev/ttyUSB0 flash_id

Typical sizes:


7.2 Dump flash to file

4MB flash:

esptool.py --port /dev/ttyUSB0 \
  read_flash 0x000000 0x400000 esp32_backup.bin

8MB flash:

esptool.py --port /dev/ttyUSB0 \
  read_flash 0x000000 0x800000 esp32_backup.bin

Verify:

ls -lh esp32_backup.bin

8. Current state (checkpoint)

At this point:


Next steps (not yet performed)


Absolutely! Let’s create a full, step-by-step working guide for the TTGO T-Display (ESP32) starting after backing up the firmware, including all the lessons we’ve learned: erasing flash, writing MicroPython, uploading main.py, and drawing the RGB border + text. I’ll structure it clearly.


TTGO T-Display MicroPython Setup & Test Guide

1️⃣ Prerequisites

python3 -m venv ~/esp32-venv
source ~/esp32-venv/bin/activate
pip install --upgrade pip
pip install esptool mpremote

Note: After creating the venv, make sure you log out and back in or source the venv to see USB devices.


2️⃣ Identify your ESP32 device

Plug in the USB cable and check which device is assigned:

ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null

You should see something like /dev/ttyUSB0.


3️⃣ Backup current firmware

If you want to save the original firmware:

mpremote connect /dev/ttyUSB0 fs cp : backup_dir/

Optional: you can also dump the full flash using esptool:

esptool.py --chip esp32 --port /dev/ttyUSB0 read_flash 0x0000 0x400000 backup.bin

4️⃣ Erase flash

Completely erase the flash so no old scripts remain:

esptool.py --chip esp32 --port /dev/ttyUSB0 erase-flash

5️⃣ Flash MicroPython firmware

Download or locate the TTGO T-Display MicroPython firmware (firmware.bin), e.g., from st7789_mpy repo:

esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z 0x1000 firmware.bin

6️⃣ Connect to MicroPython REPL

mpremote connect /dev/ttyUSB0 repl

7️⃣ Upload main.py with test program

Here’s a working main.py that:

from machine import Pin, SPI
import st7789
import vga1_8x16
import time

# SPI config
spi = SPI(1, baudrate=40000000, polarity=1, phase=1, sck=Pin(18), mosi=Pin(19))

# Display size
tft_width = 135
tft_height = 240

tft = st7789.ST7789(
    spi,
    tft_width, tft_height,
    reset=Pin(23, Pin.OUT),
    cs=Pin(5, Pin.OUT),
    dc=Pin(16, Pin.OUT),
    backlight=Pin(4, Pin.OUT),
    rotation=0
)
tft.init()

# --- Function to draw RGB border ---
def draw_rgb_border(tft):
    # Top edge: red
    for x in range(tft_width):
        tft.pixel(x, 0, st7789.RED)
    # Bottom edge: green
    for x in range(tft_width):
        tft.pixel(x, tft_height-1, st7789.GREEN)
    # Left edge: blue
    for y in range(tft_height):
        tft.pixel(0, y, st7789.BLUE)
    # Right edge: white
    for y in range(tft_height):
        tft.pixel(tft_width-1, y, st7789.WHITE)

# Clear screen and draw border
tft.fill(st7789.BLACK)
draw_rgb_border(tft)

# --- Text area setup ---
margin = 4
text_x = margin
text_y = margin
line_h = vga1_8x16.HEIGHT

lines = [
    "0123456789",
    "123456789",
    "23456789",
    "3456789",
    "456789",
    "56789",
    "6789",
    "789",
    "89",
    "9",
    "Hello World!"
]

for i, line in enumerate(lines):
    tft.text(vga1_8x16, line, text_x, text_y + i*line_h, st7789.WHITE)

time.sleep(2)  # hold test pattern

# --- Button test program ---
btn_left = Pin(35, Pin.IN, Pin.PULL_UP)
btn_right = Pin(0, Pin.IN, Pin.PULL_UP)

count_left = 0
count_right = 0

def draw():
    # Clear text area only
    tft.fill_rect(text_x-2, text_y-2, tft_width - 2*margin, 6*line_h, st7789.BLACK)
    tft.text(vga1_8x16, "Hello MicroPython!", text_x, text_y, st7789.WHITE)
    tft.text(vga1_8x16, f"Left: {count_left}", text_x, text_y + line_h*2, st7789.GREEN)
    tft.text(vga1_8x16, f"Right: {count_right}", text_x, text_y + line_h*4, st7789.BLUE)

# Initial display
draw()

while True:
    if not btn_left.value():
        count_left += 1
        draw()
        time.sleep(0.2)

    if not btn_right.value():
        count_right += 1
        draw()
        time.sleep(0.2)

8️⃣ Uploading main.py to ESP32

mpremote connect /dev/ttyUSB0 fs cp main.py :
mpremote connect /dev/ttyUSB0 reset

9️⃣ Notes / tips


This guide covers everything from erased flash to working RGB border and button test program.


If you want, the next step could be:

Do you want me to make that next?