How to use a 1.77 inch TFT display with Python? | Myrtle Thai

How to use a 1.77 inch TFT display with Python?

You can drive a 1.77 inch TFT display with Python by connecting it to a Raspberry Pi or any Linux-based single-board computer via SPI, using the Adafruit CircuitPython library or the luma.oled library (though the latter is more for OLED, TFT variants like ST7735 need specific forks). The most common controller for these small displays is the ST7735S, which runs at 262K colors and supports a 128x160 pixel resolution. To get started, you need to wire the display’s SPI pins: CS (chip select) to GPIO 8, DC (data/command) to GPIO 25, RESET to GPIO 27, MOSI to GPIO 10, SCLK to GPIO 11, and power it with 3.3V and GND. Some modules also have a backlight pin (LED) that you can connect to a 3.3V pin through a 100-ohm resistor to avoid burning out the LED. The SPI clock speed should be set to around 8 MHz for stable operation—faster than that can cause glitches on longer wires. If you’re using a 1.77 inch 128x160 tft display with a pre-soldered header, wiring is straightforward; otherwise, you’ll need to solder a 0.1-inch pitch header to the breakout board.

The Python library you choose depends on your board. For Raspberry Pi, the most reliable is the `Adafruit_CircuitPython_ST7735R` library, which is part of the CircuitPython ecosystem. You install it via pip: `pip3 install adafruit-circuitpython-st7735r`. But you also need the `RPi.GPIO` and `spidev` libraries for hardware SPI access. If you’re on a non-Raspberry Pi board like an Orange Pi or a Jetson Nano, you’ll need to use `libgpiod` or `sysfs` GPIO, and the SPI device node might be `/dev/spidev0.0` or `/dev/spidev1.0` depending on the board’s pinout. The ST7735S controller has a specific initialization sequence that must be sent over SPI before any drawing commands. This sequence includes commands like `SLPOUT` (sleep out), `COLMOD` (set color mode to 16-bit), `DISPON` (display on), and a gamma correction set. If you skip this, the display will show garbage or remain blank. The Adafruit library handles this automatically, but if you’re writing a raw driver, you need to send about 30 bytes of initialization data.

Let’s talk about the actual Python code. After installing the library, you instantiate the display object like this:

```python
import board
import busio
import adafruit_st7735r
import digitalio
spi = busio.SPI(clock=board.SCLK, MOSI=board.MOSI)
cs = digitalio.DigitalInOut(board.CE0)
dc = digitalio.DigitalInOut(board.D25)
rst = digitalio.DigitalInOut(board.D27)
display = adafruit_st7735r.ST7735R(spi, cs, dc, rst, baudrate=8000000)
```

Note that the `board` module in CircuitPython uses predefined pin mappings for Raspberry Pi. If you’re on a different board, you’ll need to specify the pin numbers manually using `busio.SPI(11, 10)` for SCLK and MOSI, and `digitalio.DigitalInOut(8)` for CS, etc. The `baudrate` parameter is critical: set it to 8,000,000 (8 MHz) for reliable operation. Some displays can handle 16 MHz, but I’ve seen data corruption on longer ribbon cables. The display object exposes a `displayio` group, which is a framebuffer that you can draw shapes, text, and bitmaps onto. For example, to fill the screen with red:

```python
import displayio
bitmap = displayio.Bitmap(128, 160, 65535)
palette = displayio.Palette(1)
palette[0] = 0xFF0000
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)
group = displayio.Group()
group.append(tile_grid)
display.show(group)
```

This creates a 16-bit color bitmap (65535 colors) and assigns a red palette. The color format is 5-6-5 RGB: 5 bits for red, 6 for green, 5 for blue. So red is `0xF800`, green is `0x07E0`, blue is `0x001F`. The `display.show()` call pushes the group to the display. If you want to draw text, you need a font. The `adafruit_display_text` library provides bitmap fonts like `terminalio.FONT`, but they’re small (5x7 pixels). For larger fonts, you can load a `.bdf` font file using `bitmap_font.load_font()`. But be careful: the 128x160 resolution is tiny, so text larger than 12 points will be truncated. A common use case is to display sensor data: temperature, humidity, or a simple graph. For example, you can draw a line graph by plotting points on the bitmap using `bitmap[x, y] = color_index`. However, the `displayio` system is not optimized for pixel-by-pixel updates; it’s better to use a framebuffer approach with `adafruit_imageload` for images or `adafruit_display_shapes` for rectangles and circles.

Performance is a key consideration. The SPI bus on a Raspberry Pi 4 can push about 2 MB/s, which means a full 128x160 frame (40,960 bytes at 16-bit color) takes about 20 ms to transfer. But the initialization and refresh rate are limited by the ST7735S’s internal timing. The display’s refresh rate is typically 60 Hz, but with SPI, you’ll be lucky to get 30 fps for full-screen updates. Partial updates are faster: if you only update a 64x64 region, it takes about 5 ms. The library supports `display.refresh()` for manual updates, but the default is auto-refresh. If you’re doing animations, you should disable auto-refresh and call `refresh()` only when the framebuffer changes. This reduces CPU load and prevents tearing. Another trick is to use the display’s vertical scrolling mode: you can set the scroll start address using the `ST7735S` command `0x37` (VSCRSADD) to scroll a portion of the screen without redrawing. This is useful for a scrolling text ticker.

Power consumption matters for battery-powered projects. The 1.77-inch display draws about 30 mA with the backlight on full brightness, and 15 mA with the backlight off (the LCD panel itself still draws current). You can control the backlight via PWM on a GPIO pin. For example, connect the LED pin to GPIO 18 (which supports hardware PWM) and set the duty cycle to 50% to halve the brightness. The Python code uses `RPi.GPIO` or `pigpio` for PWM. But note that the backlight LED has a forward voltage of about 3.0V, so a series resistor is mandatory. Without it, the LED will draw over 100 mA and burn out in seconds. The datasheet for the ST7735S specifies a maximum supply voltage of 3.6V, so don’t power it from 5V. Use a 3.3V regulator if your board outputs 5V.

There are several pitfalls to avoid. First, the SPI mode must be Mode 0 (CPOL=0, CPHA=0). The Adafruit library sets this automatically, but if you’re using raw `spidev`, you must set it explicitly: `spi.mode = 0b00`. Second, the display’s reset pin must be held low for at least 10 ms after power-up, then released. The library does this, but if you’re using a separate microcontroller like an ESP32, you need to add a delay. Third, the ST7735S has a built-in voltage generator that requires a capacitor on the VCC pin (typically 1 µF). Most breakout boards have this, but cheap modules might omit it, causing flickering. Fourth, the display’s driver IC supports 12-bit, 16-bit, and 18-bit color modes. The 16-bit mode (5-6-5) is the most common because it balances color depth and memory usage. If you accidentally set the color mode to 12-bit, the colors will be posterized. The initialization sequence in the Adafruit library sets it to 16-bit, but if you’re writing your own driver, you must send the `COLMOD` command (`0x3A`) with parameter `0x05` for 16-bit.

For advanced users, you can interface the display with a Python script that runs on a headless Raspberry Pi and serves a web UI. Use Flask to create a simple HTTP server that accepts drawing commands and sends them to the display. This is useful for a remote dashboard. The display’s small size makes it ideal for a mini status monitor: show CPU temperature, RAM usage, and network speed. The Python script reads `/sys/class/thermal/thermal_zone0/temp` for CPU temperature, `/proc/meminfo` for RAM, and `/proc/net/dev` for network stats. You can update the display every second using a timer. The `displayio` system supports multiple layers, so you can have a background image and overlay text. But the framebuffer is limited to 40 KB, so you can’t store high-resolution images. Use 128x160 JPEGs compressed to about 10 KB, or use indexed PNGs with a 256-color palette. The `adafruit_imageload` library can load BMP, PPM, and GIF files, but not JPEG natively. You’ll need to convert images to BMP offline using ImageMagick.

Another angle is using the display with a Python library that supports hardware acceleration via the GPU. The Raspberry Pi’s VideoCore GPU can render directly to the SPI display using the `fbtft` kernel driver. This is a Linux framebuffer driver that treats the display as a `/dev/fb1` device. You can then use `pygame` or `Pillow` to draw on the framebuffer. The `fbtft` driver is included in the Raspberry Pi kernel since version 4.9, but you need to enable it in `/boot/config.txt` by adding `dtoverlay=adafruit18,rotate=90`. This overlays the ST7735S driver. Once enabled, you can write to `/dev/fb1` directly. For example, using `Pillow`:

```python
from PIL import Image, ImageDraw
import numpy as np
fb = open('/dev/fb1', 'wb')
img = Image.new('RGB', (128, 160), 'black')
draw = ImageDraw.Draw(img)
draw.text((10, 10), 'Hello', fill='white')
fb.write(img.tobytes())
fb.close()
```

This method is faster than the CircuitPython library because it bypasses the Python interpreter for the pixel transfer. However, the `fbtft` driver has limited support for rotation and gamma correction. You might need to set the rotation in the device tree overlay. The `rotate` parameter accepts 0, 90, 180, 270, but the physical orientation of the display might require a different value. Also, the `fbtft` driver uses a different initialization sequence than the Adafruit library, so you might see color inversion if the display’s controller variant is slightly different. For example, some ST7735S displays have a green tab (the PCB extension) that requires a different command set. The Adafruit library has a `ST7735R` class that handles this, but the kernel driver might not. If you encounter color issues, you can try adding `fbtft_custom=1` to the overlay parameters.

Let’s talk about real-world data. The 1.77-inch display has a pixel pitch of 0.22 mm, which gives a PPI (pixels per inch) of about 115. This is lower than a smartphone (326 PPI), but acceptable for text and icons. The viewing angle is typically 60 degrees horizontal and 40 degrees vertical, due to the TN (Twisted Nematic) panel. The contrast ratio is around 500:1, and the brightness is about 250 cd/m² with the backlight on. The response time is 10 ms, which is fine for static images but could cause ghosting for fast animations. The operating temperature range is -20°C to 70°C, so it’s suitable for outdoor use in moderate climates. The SPI bus is susceptible to noise on long wires, so keep the connections under 10 cm. If you need longer cables, use shielded twisted pairs and lower the SPI speed to 1 MHz.

For those who want to use Python with a microcontroller like the ESP32 or Pico, the process is similar but the libraries differ. On MicroPython, you can use the `st7735` module from the `micropython-st7735` repository. The wiring is the same, but you need to set up the SPI object manually. For example, on an ESP32:

```python
import machine
import st7735
spi = machine.SPI(1, baudrate=8000000, polarity=0, phase=0, sck=machine.Pin(18), mosi=machine.Pin(23))
cs = machine.Pin(5, machine.Pin.OUT)
dc = machine.Pin(4, machine.Pin.OUT)
rst = machine.Pin(2, machine.Pin.OUT)
display = st7735.ST7735(spi, cs, dc, rst)
display.fill(0xFFFF)
display.text('Hello', 0, 0, 0x0000)
display.show()
```

MicroPython’s `st7735` library is lightweight and uses a simple framebuffer stored in RAM. The ESP32 has 512 KB of RAM, so a 40 KB framebuffer is fine. But the Pico has only 264 KB, so you might need to use a partial framebuffer or disable the backlight to save power. The library supports only 16-bit color, and you can draw text using the built-in 5x7 font. For custom fonts, you need to convert them to a bitmap array. The performance on an ESP32 at 80 MHz is about 10 fps for full-screen updates, which is acceptable for a clock or weather display. You can also use the display’s sleep mode to save power: send the `SLPIN` command (0x10) to put the display into low-power mode, drawing only 5 µA. Wake it up with `SLPOUT` (0x11).

One common mistake is using the wrong SPI pins. The ST7735S requires a 4-wire SPI interface (CS, DC, MOSI, SCLK) plus a reset pin. Some modules have a separate MISO pin, but it’s not used for the display—it’s only for reading the display’s framebuffer, which is rarely implemented. So you can leave MISO unconnected. Another mistake is not initializing the display’s memory access control. The `MADCTL` command (0x36) controls the orientation and color order. If you don’t set it, the display might show the image upside down or with swapped red and blue channels. The typical value for portrait mode is `0xC0` (top-to-bottom, left-to-right, RGB order). For landscape mode, use `0x60` (swap X and Y). The Adafruit library sets this based on the `rotation` parameter in the constructor. If you’re using the kernel driver, you set the rotation via the overlay parameter.

Finally, if you’re building a product, consider the display’s longevity. The ST7735S has a typical lifetime of 50,000 hours (about 5.7 years of continuous use) at 25°C. The backlight LED has a half-life of 30,000 hours. The polarizer can degrade under UV light, so avoid direct sunlight. The SPI interface is not hot-pluggable; always power down before connecting or disconnecting the display. With these factors in mind, the 1.77-inch TFT display is a solid choice for Python-based projects that need a small, colorful display with minimal wiring.