ST7789 LCD with Touch (Part II)
The first post is here.
In this one I need to explore the memory layout a bit, before continuing with the countdown timer.
Memory Data Access Control
It turns out that there’s one bit called “MV”. It controls the page/column mode, and can be set (or cleared) using the MADCTL command. I like this command, I read it “mad control”.
Experimenting will explain this better. The “pattern” I’m going to use is four pixels on, four pixels off, with both width and height divisible by 8 (but not the same value).
MADCTL has 6 bits. I can just flip them one at a time, I don’t need to check all 64 options.
- MY is page address order (0 = top-to-bottom)
- MX is column address order (0 = left-to-right)
- MV is page/column order (0 = normal mode)
- ML is line address order (0 = LCD refresh top-to-bottom)
- RGB is RGB/BGR order (0 = RGB)
- MH is display data latch order (0 = LCD refresh left-to-right)
Default value is 0000h. I probably don’t need to touch ML/MH. RGB may be slightly interesting, but not excessively. The other bits are more interesting.
The code:
st7789_set_viewport(32, 64, 32, 64);
st7789_fill_pattern(RED, 32 * 64);
-
With defaults:
The pattern is “sideways”, top right corner. That’s what I expect, as the right-hand edge of the screen (in “widescreen” mode) is really the top of the screen (it’s a 240x320 display).
-
With MY=“1”:
I expect the pattern to be “sideways”, but top left corner (which is really the bottom of the screen). And indeed that’s the case
-
With MX=“1”:
I expect the pattern to be “sideways”, but bottom right corner (which is really the right side of the screen). And indeed that’s the case.
-
With MV=“1”:
This is the interesting bit. The display still has the same number of pixels, but now it has 320 columns and 240 rows (in which direction? We’ll find out). So I expect the pattern to be “upright”, maybe still top right corner? Not sure.
Yes, it’s upright, top right corner, seems to be 32 pixels from the right edge (which is really the top of the display), and 64 pixels from the top edge (which is really the left side of the display).
Now the challenge: I want it to be upright, top left corner. For that I definitely need MV=“1”. The description (section 8.12) says that that in this mode, the Y-address increments after each byte. When it reaches the end, it starts from the beginning and the X increments to address the next column. So maybe bottom-to-top is what I want - MY=“1”.
And indeed, that worked. So MV=“1”, MY=“1” gives me “widescreen” mode, left to right.
Now we can start using the font.
The Font
TTF2BMH converts a TTF font to a C array where the first W bytes are the first “byte-row”, the next W bytes are the second “byte-row” etc.
This sounds very convoluted, but it makes sense - the width is variable, the height is a multiple of 8. So it makes sense to divide the height into bytes. Yes, maybe one column at a time would have made sense, but in the end it doesn’t really matter (it’s one or the other, using “byte-rows” makes sense).
Using MV=“1”, I don’t need to worry about transposition. And using MY=“1”, I don’t need to worry about back-to-front. So it should be fairly straightforward to draw a character instead of the pattern I’ve been using.
void st7789_draw_large_digit(char ch, int x, int y, const colour_t colour,
const colour_t background)
{
if (ch < '0' || ch > ':') {
return;
}
ch -= '0';
char width = char_width[ch];
const char *bitmap = char_addr[ch];
uint8_t *buffer = large_digit_buffer;
for (int pixel_row = 0; pixel_row < LARGE_DIGIT_HEIGHT; pixel_row++) {
for (uint16_t mask = 0x01; mask != 0x100; mask <<= 1) {
const char *row = bitmap;
for (int col = 0; col < width; col++) {
uint8_t byte = *row++;
if (byte & mask) {
*buffer++ = colour[0];
*buffer++ = colour[1];
} else {
*buffer++ = background[0];
*buffer++ = background[1];
}
}
}
bitmap += width;
}
st7789_set_viewport(x, y, width, LARGE_DIGIT_HEIGHT * 8);
blit(large_digit_buffer, width * (LARGE_DIGIT_HEIGHT * 8) * BYTES_PER_PIXEL);
}
Not much to say. I don’t need this code to be blazing fast, so it’s all good. I get the digit exactly where I want it (x and y are in pixels from the top left corner when the display is “widescreen”).
Next I need to really understand the colours, and why I need to invert the screen to make it work.
Colours
Every pixel is represented by 2 bytes, using RGB565 as the pixel format (5 bits red, 6 bits green, 5 bits blue).
Now something like “full red”, which is 0xf8 0x00, well what would that be if I got the byte order wrong? It would be 0x00 0xf8 which is 3 bits green and 2 bits blue. Not sure what colour that would be.
So to make sure that I’m getting the byte order right, I’ll use 0x07 0x00. If it’s the right order, it should just be green (and quite bright). But if it’s the wrong order, it should be just blue (and quite dull).
Well, I got green. And if I don’t invert I get strong purple instead. That also makes sense - purple = blue + red (and I should have a bit of dull green in there as well). Now I’ll try to flip the bytes. That would be blue, and inverted would be green + red = mud. But I get light blue. So I have no idea what’s going on.
No, there is no doubt - it must be inverted. I tried using colour bands with one bit on per pixel (from 0x8000 to 0x0001). I got red/green/blue bands (from light to dark) when inversion was on, and weird bright colours (close to white - most bits are on) when inversion was off. So it must be inverted, and the byte order is correct (high then low).
You learn something new every day. And with knowledge I can now finally program a simple countdown clock. It’s not the end, because I still want to have some interaction, but good to get out of the way.
Diversion
Nothing is simple. What I really want is massive digits. But the fonts only go to 64 pixels. The display is 240x320. Having a 70x120 font would be lovely, but I don’t have that.
Also, the font is a 1-bit font. We could have a 2-bit or a 3-bit font (or even 5-bit font), because anti-aliasing is great. The font could also be compressed a bit (using RLE).
In other words, it’s time to take a little diversion and create a font tool.