Category Archives: Coding

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_LCD_BK_LIGHT_ON_LEVEL  1
#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) {
        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]
    x+=8;
    y+=8;
    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;
        prev_frame=frame;
    }
    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!

Compiling Rive-Tizen (Rive and ThorVG) on windows

I’ve been an avid Flash and Actionscript developer for a long time. With the decline of Flash there was nothing really suitable to replace the scalable vector graphics that was the major benefit of Flash. But now, along comes Rive to the rescue, providing a fantastic, open, vector creation tool and format, and ThorVG, providing a small, powerful vector drawing engine with no external dependencies.

Both of these projects are still in early stages of active development, but both look very promising, and have dedicated and passionate contributors behind them.

Rive-Tizen is a small project that connects the two, allowing you to render Rive animations using ThorVG. Based on my experimentation, I’ve jotted down some tips for compiling the rive-tizen project on windows using Visual Studio. Note that you’ll get the best results using the clang-cl compiler rather than the built-in msvc compiler.

I run Visual Studio 2019 Community Edition on Windows 11, but it may work for other combinations as well.

Install prerequisite tools (ninja, meson, clang-cl)
  • Install clang-cl as a Visual Studio component using these instructions
  • Install both meson and ninja build systems. There is an msi installer that does both provided by meson.
Clone and prepare the code
  • Clone the rive-tizen repo to your machine
    • Install the submodule dependency (rive) using your favorite git tool (I use GitKraken. and it is awesome), or use the git command line and enter git submodule update --init --recursive
  • Clone the thorvg repo to your machine
Edit the rive-tizen meson build file

There are some edits required to rive-tizen/meson.build to support windows. Here is what I did:

  • Add the compiler flag _USE_MATH_DEFINES so that math related defines like M_PI are available.
  • Change how meson looks for the thorvg dependency by specifying the path to the ThorVG source files instead. Make sure you change the path to match where you cloned the thorvg repo!

This is what meson.build file looks like now. Be careful, depending on when you read this it may be out of date, so make sure to just take the changes you need.

project('rive_tizen',
    'cpp',
    default_options : ['cpp_std=c++17'],
    version : '0.1.0',
    license : 'MIT')

add_project_arguments('-DRIVE_FILE_DIR="@0@/example/resources/"'.format(meson.current_source_dir()), language : 'cpp')

# changes start
thorvg_dep = dependency('thorvg', required : false)
if thorvg_dep.found() != true
    thorvg_dep = declare_dependency(include_directories : include_directories('../thorvg/inc'))
    if thorvg_dep.found() != true
        error('ThorVG dependency not found. Looking for ../thorvg')
    endif
endif
if host_machine.system() == 'windows'
    add_project_arguments('-D_USE_MATH_DEFINES', language: 'cpp')
endif
# changes end

# ... rest of file
Run and configure meson
  • This works best using the VS command prompt, so open a visual studio command prompt (instructions).
    • I do this by typing command in the windows search bar, and selecting the result called x64 Native Tools Command Prompt for VS 2019.
  • Change directory to rive-tizen folder. For example, mine is here:
    cd C:\Projects\rive-tizen
  • Change the compiler to clang-cl
    set CXX=clang++
  • Run meson
    meson build
  • Enter the build folder
    cd build
  • Change some configuration options to build a static lib and specify that it’s a release build. The last option suppresses a warning about “non-virtual destructor”. because there are a LOT of these.
meson configure -Ddefault_library=static -Dbuildtype=release -Doptimization=2 -Dcpp_args=['-Wno-non-virtual-dtor']
  • Hint: There are many more things you can tweak. To see all the config options available, type
    meson configure
Compile the static library
  • Still in the command prompt, run ninja
    ninja -C .
  • Now you should have a compiled static library!