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!

Adjustable snare cajon

I’m a bit of a dabbling musician, and I’ve¬†wanted to make a cajon for a while.

Apart from the snares, which were a cheap purchase from AliExpress, everything else is made from left-over materials I had lying around.

The body (top, bottom and sides) are made from 12mm poplar ply. It’s not the best ply as it has a few voids in the layers, but it looks nice and¬†sounds ok.

The tapa is 4mm 3-ply birch (model ply, or aircraft ply).

Unlike a more traditional cajon, I made the sound hole in the side instead of the back, and attached a second tapa to the back. I plan to attach a drum pedal which strikes the rear tapa so that I can play with my foot while sitting on the cajon and playing guitar.

Usually the rear is¬†thinner than the sides, but not as thin as the tapa (usually it’s about twice as thick). The thinner¬†rear has given my cajon more of a resonant bass tone –¬†a little more like as bongo than a classic bass drum, but it still¬†sounds nice.

The knob on the side allows me to engage or disengage the snares from the tapa.

The body is varnished, and both tapas are oiled.

 

Table-mounted jigsaw

Inspired by Mistry MakeTool on YouTube (videos:¬†table, guide), I decided to make my own jigsaw table. I don’t currently own a¬†bandsaw, or even a scrollsaw, so this is my interim solution.

The¬†top is 400x400mm (~16″ x 16″) and 200mm (8″) high. The box is made from 18mm MDF, with a 4mm hardboard top (smooth side up). Everything is glued (Titebond III) and key parts, such as¬†where the guide attaches to the box, are also screwed.

I’m using a Ryobi 600W jigsaw that I purchased specifically for this project for around US$80. The foot of the jigsaw has been completely removed, and¬†I’ve bolted the jigsaw directly to the underside of the table using the bolt hole for the foot. A couple of blocks either side of the jigsaw are there just to keep it aligned.

I pulled apart a very old Black & Decker jigsaw and used the blade guide wheel from that as the new blade guide for the table.

The build took around 4 hrs (mostly waiting for glue to dry).

Warning

My experience after a¬†bit of use is that this tool is great for cutting thinner (12mm or less) thicknesses of timber, but it feels pretty dangerous when cutting metal. I’ve been cutting 3mm aluminium plate, and I would not recommend using the jigsaw table for that. The blade wants to try and lift the work piece from the table on the up stroke, and¬†I had to hold¬†the work piece down very firmly and be extremely careful when cutting. This doesn’t seem to be as much of a problem with timber.

Secondly, this¬†model of jigsaw (Ryobi 600W 85mm, RJS850-K) allows the blade to twist/rotate quite a bit. My battery jigsaw (Ryobi +one) holds the jigsaw blade absolutely rigid, but due to a different locking mechanism, this particular jigsaw model allows the¬†blade to twist by up to about 30 degrees! If you’re planning to build a table like this, look for a jigsaw that holds the blade absolutely rigid.

Drill lathe

One of my other projects required some wood turning. Not owning a lathe, or having done any sort of turning in the past, I reached out to the local wood turners association, but unfortunately got no response.

So I decided to make my own (very simple) lathe.

I mounted an old, cheap drill I had into a jig. The workpiece was simply bolted tightly to a large bolt and inserted into the chuck.

The American Black Walnut light surrounds turned out beautifully. very happy with my first ever wood turning experience!

Table-mounted belt sander

I needed¬†to do a bit of sanding for a wood turning project (see drill lathe), so¬†I made a jig to mount my belt sander at 90 degrees to it’s own table. This makes a handy belt sanding table¬†– I used it for both wood and aluminium.

The belt sander is held in place with a large clamp so that it can be easily removed. In the future I may look at building a custom clamp for this.

The lines on the surface are guides at 90 and 45 degrees.

I’ve since¬†attached the sanding table to the top of a small cabinet on castors so that I can move it around. It’s one of¬†the most used tools in the shop now!

How to fix old LEGO train wheels

If you¬†had a LEGO train set in the 80s or 90s, chances are that some of the wheels have come off by now, and they just won’t attach to any¬†of the common LEGO axles.

These are the wheels I mean. They have small metal axles and attach to a thick 4×2 block. In my wheels, all the little axles are long since missing, and all that is left is a wheel with a small hole through it.

Lego train wheels

What I did to fix mine is drill the holes out to fit¬†standard LEGO wheel axles, like in the image below (see ‘the plan’).

The plan

Train wheel plan

You’ll need:

  • A drill press*
  • A 3.2mm drill bit (optional**)
  • A 3.5mm drill bit
  • A 4.5mm¬†drill bit

* You could try this without a drill press, but you¬†won’t get consistent results, and you may get some tight wheels and some loose.

** The 3.2mm drill bit is used to set the depth stop to the perfect depth without trial and error. You can do this with a ruler or something else if you need to.

The steps are:

  1. Enlarge the hole all the way through the wheel with the 3.5mm drill bit
  2. Insert the 4.5mm drill bit and set the depth stop using the 3.2mm drill bit
  3. Drill the hole larger part way through the wheel until the depth stop is reached

The wheels should now snap perfectly onto the axle and spin smoothly without being too tight or too loose.

1. Enlarge hole to 3.5mm

Insert the 3.5mm drill bit in the drill press. Place a block of wood on the platform to ensure you don’t drill into the platform itself. Carefully but tightly hold the LEGO train wheel in place with one hand, and very slowly drill through the centre of the wheel all the way through. Because there is already a hole, the drill centres itself and ensures the hole is¬†perfectly vertical.

Drill 3.5mm hole

Tips:

  1. Although I wouldn’t normally recommend just holding the work piece between fingers, if you hold it steady and drill slowly you shouldn’t have a problem. The¬†plastic is soft and shouldn’t snag.

2. Set up 4.5mm drill bit depth

Insert the 4.5mm drill bit in the drill press. Now lay the 3.2mm drill bit on the block of wood directly under the inserted bit and lower the drill press until the inserted bit sits on the 3.2mm drill bit. Now set the depth stop so that the drill press will not drill any deeper than that.

Setting the depth stop

Tips:

  1. A depth of 3.2mm gives the perfect depth for the lego axles to click in place and allow the LEGO train wheel to freely spin. 3.5mm results in a tight wheel and 3mm allows the train wheel to move (too much slop).
  2. If you don’t have a 3.2mm drill bit, use the 3.5mm drill bit from the previous step, and then carefully lower the drill press a tiny bit more before setting the depth stop. It is best to drill the hole too shallow than too deep. You can always drill it a tad deeper later if the wheel is too tight.

3. Drill the 4.5mm hole

Finally, drill the 4.5mm hole in each wheel, lowering the drill press until the depth stop is reached. Remember to drill from the front of the train wheel towards the back!

Drill the larger hole

Tips:

  1. Make sure you drill from the front of the LEGO train wheel to the back, like in the photo.
  2. Hold the¬†LEGO train wheel securely, and drill slowly. You do NOT want the plastic shavings to snag and pull the train wheel upwards, or you’ll get a hole all the way through¬†the wheel and it will be ruined.
  3. Drill down to the depth stop, not all the way through the wheel!

Done!

Hopefully your wheels now snap onto the axles and spin nice and freely! Have fun.

Caravan fun

I haven’t posted in a while – mostly because I’ve been busy on all my other projects – so here is a very quick update of progress with some pictures ūüôā

First job was installing the roof.  Carbonised bamboo on either side, with a panel of Lawson Cypress in the middle and remote-controlled 12v LED down lights.

Roof raised ready for work on ceiling
Roof raised ready for work on ceiling

Working on the electrics for the downlights
Working on the electrics for the downlights

New ceiling is in!
New ceiling is in!

Roof raised under carport to work on ceiling
Roof raised under carport to work on ceiling

After a few camping trips, Wife and I were ready for a better bed. We decided to put in a a permanent (almost) full-size double bed to replace the folding table and squabs.  The mattress was made to order by a local bed manufacturer and is only 3cm shorter and the same width as a full double.  We also removed the wardrobe to get extra room for the bed.

The bed folds upwards to allow access to all the storage room underneath.

The kitchen was removed because we never used it and we always cook in the awning. ¬†This allowed room for the bed, too, and I’m making new cabinets to fit the smaller space.

Out with the old table and bed
Out with the old table and bed

Half-way through making the new bed
Half-way through making the new bed

The next job were a few interior modifications. I added a shelf at one end¬†for baskets (clothes etc) that folds up against the wall. We’re removing the wardrobe, so we need extra storage. Another shelf is planned for the other end, but hasn’t been done yet. ¬†Also added a side for one of the beds – our girl was only 1yr old at this stage and we didn’t want her falling out!

Folding shelf - up
Folding shelf – up

Folding shelf - down, with baskets
Folding shelf – down, with baskets

Side on bed for baby
Side on bed for baby

Next up was a new floor to match the ceiling.  The old lino floor looked pretty horrible compared to the ceiling!

Out with the old floor
Out with the old floor

New floor cut to shape
New floor cut to shape

New floor glued down
New floor glued down

New floor finished
New floor finished

Currently I’m building the¬†new¬†cabinets to replace the kitchen that was removed (kept the fridge) and prepping the interior for painting!

 

 

Repairing a loose knot

Unfortunately the thicknesser knocked a large knot right out of the timber. The hole is not just unsightly, but may let water and dust enter the caravan from the ceiling space.
Large knot missing

Rather than try to fill the hole with bog, I made a fake knot to fill the hole, and then fixed that in place. ¬†The first step was to cut a small piece of timber from the same plank. ¬†I also removed the loose bark and other material from the hole – note that I didn’t shape the hole in any way – to keep it looking natural.

Cutting the blank

I placed the blank over the hole and roughly marked the shape (oversize!) with a pencil.  Then using a saw, wood file and bastard file the blank was shaped to fit the hole.  The process involved cutting/filing a bit, returning to the hole and marking more pencil, then returning to the vice for more cutting and filing.  Eventually the new fake knot was almost the right size.

Almost the right size

After some final filing the knot fit perfectly into the space left by the loose knot Рnot too loose and not too tight.  The edges of the knot are not vertical Рthey are sloped at about 30 degrees so be careful to account for this if you are replicating this process.

Perfect fit

The fact that there is a little space around the edge of the knot doesn’t matter – it makes it look more authentically like a knot ūüôā ¬†The final step was to use wood coloured putty to fill all the gaps around it. ¬†On hindsight I should have glued it in place first before using the putty, but I’m hoping the putty will be strong enough to keep the new fake knot in place!

This whole process took maybe 20 minutes.

More middle bit

After tear-out problems preparing the ceiling panels with the thicknesser, I changed the router blades. The difference is remarkable – the finish is now so smooth it almost doesn’t require sanding. I couldn’t be happier.

The three central sections of ceiling panel were run through the thicknesser and honed down to 8mm. I then used the table router to create a sort of shiplap of about 15mm width along the edge of the sections. This allows the sections to overlap when glued instead of simply butting up against each other and triples the area to be glued for a better hold.

The central section is now completely glued up ready for cutting to length and cutting the circular holes for the spotlights.

panel1

 

photo showing the routed overlap. The boards are 8mm thick, the overlap is 4mm x 15mm.