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?).
- LilyGo TTGO T-DISPLAY product page (official)
- TTGO T-Display on AliExpress (official)
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.
- Set-up guide for ESP32 extension in Visual Studio Code (official)
- Video tutorial for setting up the ESP32 extension in VSCode (official)
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!