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:
pip install --userwill fail- System Python must not be modified
- ESP tools should be installed using pipx or a virtual environment
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:
/dev/ttyUSB*(CP210x, CH340)/dev/ttyACM*(native USB, ESP32-S3)
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:
- Board powers on
- Screen lights up
- No
/dev/ttyUSB*or/dev/ttyACM*appears
Fix:
- Use a known data-capable USB cable
- If unsure, test the cable with another data device
👉 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:
cp210xch341cdc_acm
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:
- 4MB (most T-Display boards)
- 8MB (some newer variants)
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:
- Debian-safe Python tooling is installed
- USB enumeration is working
- Serial permissions are correct
- Original firmware is safely backed up
Next steps (not yet performed)
- Erase flash
- Flash MicroPython
- Verify REPL access
- Configure T-Display screen (ST7789)
- Upload first
main.py
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
- Debian 13 (or similar Linux)
- ESP32 T-Display board (1.14" 135x240 ST7789)
- USB data cable (charging-only cables won’t work)
- Python virtual environment for
esptoolandmpremote
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
0x400000= 4MB (TTGO T-Display flash size)
4️⃣ Erase flash
Completely erase the flash so no old scripts remain:
esptool.py --chip esp32 --port /dev/ttyUSB0 erase-flash
- Takes a few seconds.
- Ensures the display will not show leftover firmware messages.
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
- Offset
0x1000is standard for ESP32 MicroPython. - Remaining flash up to 4MB is empty and ready for
main.py.
6️⃣ Connect to MicroPython REPL
mpremote connect /dev/ttyUSB0 repl
- You now have a working MicroPython REPL on the board.
- Use
Ctrl-]orCtrl-xto exit.
7️⃣ Upload main.py with test program
Here’s a working main.py that:
- Draws a 1px RGB border (top-red, bottom-green, left-blue, right-white)
- Prints multiple lines of text with proper margins
- Implements left/right button counters
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
- After reset, your test program runs automatically.
9️⃣ Notes / tips
- RGB border ensures you see the screen limits and confirms pixel drawing works.
- Multiple lines demonstrate text placement and spacing.
- Text area clearing prevents ghosting while counters update.
- Use rotation or adjust
tft_width/tft_heightif you move to a different T-Display variant (S3, 1.9” panel). - You can later modularize the screen into sections using
tft.fill_rectand coordinates.
This guide covers everything from erased flash to working RGB border and button test program.
If you want, the next step could be:
- Automatically detect display size and divide it into 3 sections
- Draw nested RGB borders for each section
- Make a fully modular display layout for multiple modules
Do you want me to make that next?