2021-03-24 12:18:56 +00:00
|
|
|
#include "st7789.hpp"
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
namespace pimoroni {
|
|
|
|
uint8_t madctl;
|
|
|
|
uint16_t caset[2] = {0, 0};
|
|
|
|
uint16_t raset[2] = {0, 0};
|
|
|
|
|
2021-04-23 13:22:00 +01:00
|
|
|
enum MADCTL : uint8_t {
|
|
|
|
ROW_ORDER = 0b10000000,
|
|
|
|
COL_ORDER = 0b01000000,
|
|
|
|
SWAP_XY = 0b00100000, // AKA "MV"
|
|
|
|
SCAN_ORDER = 0b00010000,
|
|
|
|
RGB = 0b00001000,
|
|
|
|
HORIZ_ORDER = 0b00000100
|
|
|
|
};
|
|
|
|
|
|
|
|
enum reg {
|
|
|
|
SWRESET = 0x01,
|
2021-05-06 14:21:12 +01:00
|
|
|
TEOFF = 0x34,
|
2021-04-23 13:22:00 +01:00
|
|
|
TEON = 0x35,
|
|
|
|
MADCTL = 0x36,
|
|
|
|
COLMOD = 0x3A,
|
|
|
|
GCTRL = 0xB7,
|
|
|
|
VCOMS = 0xBB,
|
|
|
|
LCMCTRL = 0xC0,
|
|
|
|
VDVVRHEN = 0xC2,
|
|
|
|
VRHS = 0xC3,
|
|
|
|
VDVS = 0xC4,
|
|
|
|
FRCTRL2 = 0xC6,
|
2021-07-20 15:22:31 +01:00
|
|
|
PWCTRL1 = 0xD0,
|
|
|
|
PORCTRL = 0xB2,
|
2021-04-23 13:22:00 +01:00
|
|
|
GMCTRP1 = 0xE0,
|
|
|
|
GMCTRN1 = 0xE1,
|
|
|
|
INVOFF = 0x20,
|
|
|
|
SLPOUT = 0x11,
|
|
|
|
DISPON = 0x29,
|
|
|
|
GAMSET = 0x26,
|
|
|
|
DISPOFF = 0x28,
|
|
|
|
RAMWR = 0x2C,
|
|
|
|
INVON = 0x21,
|
|
|
|
CASET = 0x2A,
|
2021-06-25 11:35:23 +01:00
|
|
|
RASET = 0x2B,
|
|
|
|
PWMFRSEL = 0xCC
|
2021-04-23 13:22:00 +01:00
|
|
|
};
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void ST7789::common_init() {
|
|
|
|
gpio_set_function(dc, GPIO_FUNC_SIO);
|
|
|
|
gpio_set_dir(dc, GPIO_OUT);
|
|
|
|
|
|
|
|
gpio_set_function(cs, GPIO_FUNC_SIO);
|
|
|
|
gpio_set_dir(cs, GPIO_OUT);
|
|
|
|
|
|
|
|
// if a backlight pin is provided then set it up for
|
|
|
|
// pwm control
|
|
|
|
if(bl != PIN_UNUSED) {
|
|
|
|
pwm_config cfg = pwm_get_default_config();
|
|
|
|
pwm_set_wrap(pwm_gpio_to_slice_num(bl), 65535);
|
|
|
|
pwm_init(pwm_gpio_to_slice_num(bl), &cfg, true);
|
|
|
|
gpio_set_function(bl, GPIO_FUNC_PWM);
|
|
|
|
set_backlight(0); // Turn backlight off initially to avoid nasty surprises
|
|
|
|
}
|
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
command(reg::SWRESET);
|
2022-05-12 12:04:55 +01:00
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
sleep_ms(150);
|
2021-03-24 12:18:56 +00:00
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
// Common init
|
|
|
|
command(reg::TEON); // enable frame sync signal if used
|
|
|
|
command(reg::COLMOD, 1, "\x05"); // 16 bits per pixel
|
2021-03-24 12:18:56 +00:00
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
command(reg::PORCTRL, 5, "\x0c\x0c\x00\x33\x33");
|
|
|
|
command(reg::LCMCTRL, 1, "\x2c");
|
|
|
|
command(reg::VDVVRHEN, 1, "\x01");
|
|
|
|
command(reg::VRHS, 1, "\x12");
|
|
|
|
command(reg::VDVS, 1, "\x20");
|
|
|
|
command(reg::PWCTRL1, 2, "\xa4\xa1");
|
|
|
|
command(reg::FRCTRL2, 1, "\x0f");
|
2021-03-24 12:18:56 +00:00
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
if(width == 240 && height == 240) {
|
|
|
|
command(reg::GCTRL, 1, "\x14");
|
|
|
|
command(reg::VCOMS, 1, "\x37");
|
|
|
|
command(reg::GMCTRP1, 14, "\xD0\x04\x0D\x11\x13\x2B\x3F\x54\x4C\x18\x0D\x0B\x1F\x23");
|
|
|
|
command(reg::GMCTRN1, 14, "\xD0\x04\x0C\x11\x13\x2C\x3F\x44\x51\x2F\x1F\x1F\x20\x23");
|
2021-03-24 12:18:56 +00:00
|
|
|
}
|
|
|
|
|
2022-05-28 01:00:56 +01:00
|
|
|
if(width == 320 && height == 240) {
|
2022-05-12 18:40:37 +01:00
|
|
|
command(reg::GCTRL, 1, "\x35");
|
|
|
|
command(reg::VCOMS, 1, "\x1f");
|
|
|
|
command(0xd6, 1, "\xa1"); // ???
|
|
|
|
command(reg::GMCTRP1, 14, "\xD0\x08\x11\x08\x0C\x15\x39\x33\x50\x36\x13\x14\x29\x2D");
|
|
|
|
command(reg::GMCTRN1, 14, "\xD0\x08\x10\x08\x06\x06\x39\x44\x51\x0B\x16\x14\x2F\x31");
|
2021-03-24 12:18:56 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
command(reg::INVON); // set inversion mode
|
|
|
|
command(reg::SLPOUT); // leave sleep mode
|
|
|
|
command(reg::DISPON); // turn display on
|
2021-03-24 12:18:56 +00:00
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
sleep_ms(100);
|
2021-05-06 14:21:12 +01:00
|
|
|
|
2022-05-28 01:00:56 +01:00
|
|
|
configure_display(rotation);
|
2021-06-14 15:26:58 +01:00
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
if(bl != PIN_UNUSED) {
|
2022-05-25 17:22:20 +01:00
|
|
|
//update(); // Send the new buffer to the display to clear any previous content
|
2022-05-12 18:40:37 +01:00
|
|
|
sleep_ms(50); // Wait for the update to apply
|
|
|
|
set_backlight(255); // Turn backlight on now surprises have passed
|
2021-03-24 12:18:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-28 01:00:56 +01:00
|
|
|
void ST7789::configure_display(Rotation rotate) {
|
|
|
|
|
|
|
|
bool rotate180 = rotate == ROTATE_180 || rotate == ROTATE_90;
|
|
|
|
|
|
|
|
if(rotate == ROTATE_90 || rotate == ROTATE_270) {
|
|
|
|
std::swap(width, height);
|
|
|
|
}
|
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
// 240x240 Square and Round LCD Breakouts
|
2022-05-12 12:04:55 +01:00
|
|
|
if(width == 240 && height == 240) {
|
2022-06-08 13:01:08 +01:00
|
|
|
int row_offset = round ? 40 : 80;
|
|
|
|
int col_offset = 0;
|
|
|
|
|
|
|
|
switch(rotate) {
|
|
|
|
case ROTATE_0:
|
|
|
|
if (!round) row_offset = 0;
|
|
|
|
caset[0] = col_offset;
|
|
|
|
caset[1] = width + col_offset - 1;
|
|
|
|
raset[0] = row_offset;
|
|
|
|
raset[1] = width + row_offset - 1;
|
|
|
|
|
|
|
|
madctl = MADCTL::HORIZ_ORDER;
|
|
|
|
break;
|
|
|
|
case ROTATE_90:
|
|
|
|
if (!round) row_offset = 0;
|
|
|
|
caset[0] = row_offset;
|
|
|
|
caset[1] = width + row_offset - 1;
|
|
|
|
raset[0] = col_offset;
|
|
|
|
raset[1] = width + col_offset - 1;
|
|
|
|
|
|
|
|
madctl = MADCTL::HORIZ_ORDER | MADCTL::COL_ORDER | MADCTL::SWAP_XY;
|
|
|
|
break;
|
|
|
|
case ROTATE_180:
|
|
|
|
caset[0] = col_offset;
|
|
|
|
caset[1] = width + col_offset - 1;
|
|
|
|
raset[0] = row_offset;
|
|
|
|
raset[1] = width + row_offset - 1;
|
|
|
|
|
|
|
|
madctl = MADCTL::HORIZ_ORDER | MADCTL::COL_ORDER | MADCTL::ROW_ORDER;
|
|
|
|
break;
|
|
|
|
case ROTATE_270:
|
|
|
|
caset[0] = row_offset;
|
|
|
|
caset[1] = width + row_offset - 1;
|
|
|
|
raset[0] = col_offset;
|
|
|
|
raset[1] = width + col_offset - 1;
|
|
|
|
|
|
|
|
madctl = MADCTL::ROW_ORDER | MADCTL::SWAP_XY;
|
|
|
|
break;
|
2022-05-12 18:40:37 +01:00
|
|
|
}
|
2022-05-12 12:04:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Pico Display
|
|
|
|
if(width == 240 && height == 135) {
|
|
|
|
caset[0] = 40; // 240 cols
|
|
|
|
caset[1] = 279;
|
|
|
|
raset[0] = 53; // 135 rows
|
|
|
|
raset[1] = 187;
|
|
|
|
madctl = rotate180 ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER;
|
|
|
|
madctl |= MADCTL::SWAP_XY | MADCTL::SCAN_ORDER;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pico Display at 90 degree rotation
|
|
|
|
if(width == 135 && height == 240) {
|
|
|
|
caset[0] = 52; // 135 cols
|
|
|
|
caset[1] = 186;
|
|
|
|
raset[0] = 40; // 240 rows
|
|
|
|
raset[1] = 279;
|
|
|
|
madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pico Display 2.0
|
|
|
|
if(width == 320 && height == 240) {
|
|
|
|
caset[0] = 0;
|
|
|
|
caset[1] = 319;
|
|
|
|
raset[0] = 0;
|
|
|
|
raset[1] = 239;
|
|
|
|
madctl = rotate180 ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER;
|
|
|
|
madctl |= MADCTL::SWAP_XY | MADCTL::SCAN_ORDER;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pico Display 2.0 at 90 degree rotation
|
|
|
|
if(width == 240 && height == 320) {
|
|
|
|
caset[0] = 0;
|
|
|
|
caset[1] = 239;
|
|
|
|
raset[0] = 0;
|
|
|
|
raset[1] = 319;
|
|
|
|
madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Byte swap the 16bit rows/cols values
|
|
|
|
caset[0] = __builtin_bswap16(caset[0]);
|
|
|
|
caset[1] = __builtin_bswap16(caset[1]);
|
|
|
|
raset[0] = __builtin_bswap16(raset[0]);
|
|
|
|
raset[1] = __builtin_bswap16(raset[1]);
|
|
|
|
|
2022-05-12 18:40:37 +01:00
|
|
|
command(reg::CASET, 4, (char *)caset);
|
|
|
|
command(reg::RASET, 4, (char *)raset);
|
|
|
|
command(reg::MADCTL, 1, (char *)&madctl);
|
2022-05-12 12:04:55 +01:00
|
|
|
}
|
|
|
|
|
2022-05-25 10:44:14 +01:00
|
|
|
void ST7789::write_blocking_parallel(const uint8_t *src, size_t len) {
|
|
|
|
uint32_t mask = 0xff << d0;
|
|
|
|
while(len--) {
|
|
|
|
gpio_put(wr_sck, false);
|
|
|
|
uint8_t v = *src++;
|
|
|
|
gpio_put_masked(mask, v << d0);
|
|
|
|
asm("nop;");
|
|
|
|
gpio_put(wr_sck, true);
|
|
|
|
asm("nop;");
|
|
|
|
}
|
|
|
|
}
|
2021-03-24 12:18:56 +00:00
|
|
|
|
2022-05-25 10:44:14 +01:00
|
|
|
void ST7789::command(uint8_t command, size_t len, const char *data) {
|
2021-03-24 12:18:56 +00:00
|
|
|
gpio_put(dc, 0); // command mode
|
2022-05-25 10:44:14 +01:00
|
|
|
|
|
|
|
gpio_put(cs, 0);
|
|
|
|
|
|
|
|
if(spi) {
|
|
|
|
spi_write_blocking(spi, &command, 1);
|
|
|
|
} else {
|
|
|
|
write_blocking_parallel(&command, 1);
|
|
|
|
}
|
2021-03-24 12:18:56 +00:00
|
|
|
|
|
|
|
if(data) {
|
|
|
|
gpio_put(dc, 1); // data mode
|
2022-05-25 10:44:14 +01:00
|
|
|
if(spi) {
|
|
|
|
spi_write_blocking(spi, (const uint8_t*)data, len);
|
|
|
|
} else {
|
|
|
|
write_blocking_parallel((const uint8_t*)data, len);
|
|
|
|
}
|
2021-03-24 12:18:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gpio_put(cs, 1);
|
|
|
|
}
|
2022-06-06 17:54:15 +01:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
void ST7789::update(PicoGraphics *graphics) {
|
|
|
|
if(graphics->pen_type == PicoGraphics::PEN_RGB565) {
|
|
|
|
command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)graphics->get_data());
|
2022-06-06 17:54:15 +01:00
|
|
|
} else {
|
2022-06-07 16:37:06 +01:00
|
|
|
uint8_t command = reg::RAMWR;
|
2022-06-06 17:54:15 +01:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
gpio_put(dc, 0); // command mode
|
2022-06-06 17:54:15 +01:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
gpio_put(cs, 0);
|
2022-06-06 17:54:15 +01:00
|
|
|
|
|
|
|
if(spi) {
|
2022-06-07 16:37:06 +01:00
|
|
|
spi_write_blocking(spi, &command, 1);
|
2022-06-06 17:54:15 +01:00
|
|
|
} else {
|
2022-06-07 16:37:06 +01:00
|
|
|
write_blocking_parallel(&command, 1);
|
2022-06-06 17:54:15 +01:00
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
gpio_put(dc, 1); // data mode
|
2022-06-06 17:54:15 +01:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
uint16_t row_buf[width];
|
2022-06-06 17:54:15 +01:00
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
for(auto y = 0u; y < height; y++) {
|
|
|
|
graphics->get_data(y, &row_buf);
|
|
|
|
// TODO: Add DMA->SPI / PIO while we prep the next row
|
|
|
|
if(spi) {
|
|
|
|
spi_write_blocking(spi, (const uint8_t*)row_buf, width * sizeof(uint16_t));
|
|
|
|
} else {
|
|
|
|
write_blocking_parallel((const uint8_t*)row_buf, width * sizeof(uint16_t));
|
|
|
|
}
|
2022-06-06 17:54:15 +01:00
|
|
|
}
|
|
|
|
|
2022-06-07 16:37:06 +01:00
|
|
|
gpio_put(cs, 1);
|
2022-05-28 01:00:56 +01:00
|
|
|
}
|
2021-03-24 12:18:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ST7789::set_backlight(uint8_t brightness) {
|
|
|
|
// gamma correct the provided 0-255 brightness value onto a
|
|
|
|
// 0-65535 range for the pwm counter
|
|
|
|
float gamma = 2.8;
|
|
|
|
uint16_t value = (uint16_t)(pow((float)(brightness) / 255.0f, gamma) * 65535.0f + 0.5f);
|
|
|
|
pwm_set_gpio_level(bl, value);
|
|
|
|
}
|
|
|
|
}
|