Tag Archives: esp32

Getting TTGO ESP32-based T-DISPLAY to work

I’m a fan of microcontroller boards, and have quite the selection now – mostly Arduino clones and (my favorite) Teensy boards.

However, I recently picked up a cheap ESP32-based development board from AliExpress – the TTGO T-DISPLAY. It comes with a 135 x 240 pixel ST7789 LCD display and two built-in buttons, all in a package about the same footprint as my thumb (should that be thumbprint?).

Installing the development environment

There are a couple of IDEs you can use to develop for ESP32, one of them being the Arduinio IDE with the ESP32 extensions installed. However, I prefer VSCode (Visual Studio Code), and the team at Espressif have developed a nice VS extension which took me no time at all to get working.

Creating the LCD display tutorial

Once the ESP32 extensions and tools are installed in Visual Studio you have access to a huge number of examples. To access these, go to view > command palette and start typing show ESP examples, then select ESP-IDF: Show Examples Projects from the list.

The example you want is peripherals > lcd > tjpgd. Go through the process of creating the project.

Setting the correct pins and display size

You should have an example project now with several files in it. The main one is lcd_tjpgd_example_main.c

First of all, set up the #defines to the correct pins for the TTGO board. I’ve only listed the ones that you need to change:

// STEP 1 of 3
// Change the following defines for compatibility with the TTGO T-DISPLAY

#define EXAMPLE_PIN_NUM_DATA0          19
#define EXAMPLE_PIN_NUM_PCLK           18
#define EXAMPLE_PIN_NUM_CS             5
#define EXAMPLE_PIN_NUM_DC             16
#define EXAMPLE_PIN_NUM_RST            23
#define EXAMPLE_PIN_NUM_BK_LIGHT       4

#define EXAMPLE_LCD_H_RES              135
#define EXAMPLE_LCD_V_RES              240

Unfortunately , the ST7789 display that ships with the TTGO T-DISPLAY is a special beast. It has a non-standard display size of 135×240. As well as the general setup, it requires additional code changes to work.

Setting the correct display RAM offsets

The display RAM for the first pixel (and all subsequent pixels, of course) is offset by a certain amount.

Important! The offsets below (52,40) work for the display in it’s ‘normal’ vertical orientation, but they change if the display is rotated (using hardware rotate). So be wary of this. The best place to look for answers is the Adafruit ST7899 driver source code 🙂

// Step 2 of 3
// Change the "display_pretty_colors" near line 60 with the following code

static uint16_t *s_lines[2];
static int xoffset = 52; // EDIT: Add this
static int yoffset = 40; // EDIT: Add this
static void display_pretty_colors(esp_lcd_panel_handle_t panel_handle)
    int frame = 0;
    // Indexes of the line currently being sent to the LCD and the line we're calculating
    int sending_line = 0;
    int calc_line = 0;

    // After ROTATE_FRAME frames, the image will be rotated
    while (frame <= ROTATE_FRAME) {
        for (int y = 0; y < EXAMPLE_LCD_V_RES; y += PARALLEL_LINES) {
            // Calculate a line
            pretty_effect_calc_lines(s_lines[calc_line], y, frame, PARALLEL_LINES);
            sending_line = calc_line;
            calc_line = !calc_line;
            // Send the calculated data. EDIT: The line below is the only one that has changed
            esp_lcd_panel_draw_bitmap(panel_handle, xoffset + 0, yoffset + y, xoffset +  + EXAMPLE_LCD_H_RES, yoffset + y + PARALLEL_LINES, s_lines[sending_line]);

Updating the “pretty effect” code with correct screen size

If you try to build/flash/run this, you’ll notice that the display seems to work, but is full of garbage. There’s still one more thing to change.

In the source file pretty_effect.c there are a whole lot of values hardcoded to 320 (width) and 240 (height). Open the file and change all occurrences of 320 to 135. I added defines at the top of the file for width and height, and my file now looks like this:

// Step 3 of 3
// Update the width and height to match the display 

#include <math.h>
#include "pretty_effect.h"
#include "sdkconfig.h"
#include "decode_image.h"

#define PIX_WIDTH 135
#define PIX_HEIGHT 240

uint16_t **pixels;

//Grab a rgb16 pixel from the esp32_tiles image
static inline uint16_t get_bgnd_pixel(int x, int y)
    //Image has an 8x8 pixel margin, so we can also resolve e.g. [-3, 243]
    return pixels[y][x];

//This variable is used to detect the next frame.
static int prev_frame=-1;

//Instead of calculating the offsets for each pixel we grab, we pre-calculate the valueswhenever a frame changes, then re-use
//these as we go through all the pixels in the frame. This is much, much faster.
static int8_t xofs[PIX_WIDTH], yofs[PIX_HEIGHT];
static int8_t xcomp[PIX_WIDTH], ycomp[PIX_HEIGHT];

//Calculate the pixel data for a set of lines (with implied line size of 320). Pixels go in dest, line is the Y-coordinate of the
//first line to be calculated, linect is the amount of lines to calculate. Frame increases by one every time the entire image
//is displayed; this is used to go to the next frame of animation.
void pretty_effect_calc_lines(uint16_t *dest, int line, int frame, int linect)
    if (frame!=prev_frame) {
        //We need to calculate a new set of offset coefficients. Take some random sines as offsets to make everything
        //look pretty and fluid-y.
        for (int x=0; x<PIX_WIDTH; x++) xofs[x]=sin(frame*0.15+x*0.06)*4;
        for (int y=0; y<PIX_HEIGHT; y++) yofs[y]=sin(frame*0.1+y*0.05)*4;
        for (int x=0; x<PIX_WIDTH; x++) xcomp[x]=sin(frame*0.11+x*0.12)*4;
        for (int y=0; y<PIX_HEIGHT; y++) ycomp[y]=sin(frame*0.07+y*0.15)*4;
    for (int y=line; y<line+linect; y++) {
        for (int x=0; x<PIX_WIDTH; x++) {
            *dest++=get_bgnd_pixel(x+yofs[y]+xcomp[x], y+xofs[x]+ycomp[y]);

esp_err_t pretty_effect_init(void)
    return decode_image(&pixels);

Build, flash and run

You can now build the code, flash it to the board and run it, and you should see the correct result. Note that the display is much smaller than the image it is trying to display, so you’ll only see a partial image, but at least it works!