Skip to content

Commit

Permalink
Merge pull request #327 from pimoroni/feature/merged-st7789
Browse files Browse the repository at this point in the history
ST7789: Create generic display driver
  • Loading branch information
Gadgetoid authored May 18, 2022
2 parents b41b52e + 8a68015 commit db60553
Show file tree
Hide file tree
Showing 46 changed files with 1,286 additions and 570 deletions.
257 changes: 94 additions & 163 deletions drivers/st7789/st7789.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
#include <cstdlib>
#include <math.h>

#include "hardware/dma.h"
#include "hardware/pwm.h"

namespace pimoroni {
uint8_t madctl;
uint16_t caset[2] = {0, 0};
Expand All @@ -20,11 +17,6 @@ namespace pimoroni {
HORIZ_ORDER = 0b00000100
};

#define ROT_240_240_0 0
#define ROT_240_240_90 MADCTL::SWAP_XY | MADCTL::HORIZ_ORDER | MADCTL::COL_ORDER
#define ROT_240_240_180 MADCTL::SCAN_ORDER | MADCTL::HORIZ_ORDER | MADCTL::COL_ORDER | MADCTL::ROW_ORDER
#define ROT_240_240_270 MADCTL::SWAP_XY | MADCTL::HORIZ_ORDER | MADCTL::ROW_ORDER

enum reg {
SWRESET = 0x01,
TEOFF = 0x34,
Expand Down Expand Up @@ -54,155 +46,118 @@ namespace pimoroni {
PWMFRSEL = 0xCC
};

void ST7789::init(bool auto_init_sequence, bool round, uint32_t spi_baud) {
// configure spi interface and pins
spi_init(spi, spi_baud);
void ST7789::init() {
command(reg::SWRESET);

gpio_set_function(dc, GPIO_FUNC_SIO);
gpio_set_dir(dc, GPIO_OUT);
sleep_ms(150);

gpio_set_function(cs, GPIO_FUNC_SIO);
gpio_set_dir(cs, GPIO_OUT);
// Common init
command(reg::TEON); // enable frame sync signal if used
command(reg::COLMOD, 1, "\x05"); // 16 bits per pixel

gpio_set_function(sck, GPIO_FUNC_SPI);
gpio_set_function(mosi, GPIO_FUNC_SPI);
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");

if(miso != PIN_UNUSED) {
gpio_set_function(miso, GPIO_FUNC_SPI);
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");
}

// if supported by the display then the vsync pin is
// toggled high during vertical blanking period
if(vsync != PIN_UNUSED) {
gpio_set_function(vsync, GPIO_FUNC_SIO);
gpio_set_dir(vsync, GPIO_IN);
gpio_set_pulls(vsync, false, true);
if((width == 320 && height == 240)
|| (width == 240 && height == 320)) {
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");
}

// 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
}
command(reg::INVON); // set inversion mode
command(reg::SLPOUT); // leave sleep mode
command(reg::DISPON); // turn display on

// if auto_init_sequence then send initialisation sequence
// for our standard displays based on the width and height
if(auto_init_sequence) {
command(reg::SWRESET);

sleep_ms(150);

command(reg::TEON); // enable frame sync signal if used
command(reg::COLMOD, 1, "\x05"); // 16 bits per pixel

if(width == 240 && height == 240) {
command(reg::PORCTRL, 5, "\x0c\x0c\x00\x33\x33");
command(reg::GCTRL, 1, "\x14");
command(reg::VCOMS, 1, "\x37");
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");
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");
}
sleep_ms(100);

if(width == 320 && height == 240) {
command(reg::PORCTRL, 5, "\x0c\x0c\x00\x33\x33");
command(reg::GCTRL, 1, "\x35");
command(reg::VCOMS, 1, "\x1f");
command(reg::LCMCTRL, 1, "\x2c");
command(reg::VDVVRHEN, 1, "\x01");
command(reg::VRHS, 1, "\x12");
command(reg::VDVS, 1, "\x20");
command(reg::FRCTRL2, 1, "\x0f");
command(reg::PWCTRL1, 2, "\xa4\xa1");
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");
}

command(reg::INVON); // set inversion mode
command(reg::SLPOUT); // leave sleep mode
command(reg::DISPON); // turn display on
configure_display(false);

sleep_ms(100);

// setup correct addressing window
if(width == 240 && height == 240) {
caset[0] = 0;
caset[1] = 239;
raset[0] = round ? 40 : 0;
raset[1] = round ? 279 : 239;
madctl = MADCTL::HORIZ_ORDER;
}

if(width == 240 && height == 135) {
caset[0] = 40; // 240 cols
caset[1] = 279;
raset[0] = 53; // 135 rows
raset[1] = 187;
madctl = MADCTL::COL_ORDER | MADCTL::SWAP_XY | MADCTL::SCAN_ORDER;
}
if(bl != PIN_UNUSED) {
update(); // Send the new buffer to the display to clear any previous content
sleep_ms(50); // Wait for the update to apply
set_backlight(255); // Turn backlight on now surprises have passed
}
}

if(width == 135 && height == 240) {
caset[0] = 52; // 135 cols
caset[1] = 186;
raset[0] = 40; // 240 rows
void ST7789::configure_display(bool rotate180) {
// 240x240 Square and Round LCD Breakouts
// TODO: How can we support 90 degree rotations here?
if(width == 240 && height == 240) {
caset[0] = 0;
caset[1] = 239;
if(round) {
raset[0] = 40;
raset[1] = 279;
madctl = 0;
} else {
raset[0] = rotate180 ? 80 : 0;
raset[1] = rotate180 ? 329 : 239;
}
madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
madctl |= MADCTL::HORIZ_ORDER;
}

if(width == 320 && height == 240) {
caset[0] = 0;
caset[1] = 319;
raset[0] = 0;
raset[1] = 239;
madctl = 0x70;
}
// 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;
}

// 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]);
// 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;
}

command(reg::CASET, 4, (char *)caset);
command(reg::RASET, 4, (char *)raset);
command(reg::MADCTL, 1, (char *)&madctl);
// 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;
}

if(bl != PIN_UNUSED) {
update(); // Send the new buffer to the display to clear any previous content
sleep_ms(50); // Wait for the update to apply
set_backlight(255); // Turn backlight on now surprises have passed
}
// 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;
}

// the dma transfer works but without vsync it's not that useful as you could
// be updating the framebuffer during transfer...
//
// this could be avoided by creating another buffer to draw into and flip
// buffers (but costs another ~100kb of ram)
//
// it's probably not worth it for this particular usecase but will consider it
// some more...

// setup spi for 16-bit transfers
// spi_set_format(spi, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);

// initialise dma channel for transmitting pixel data to screen
// dma_channel = dma_claim_unused_channel(true);
// dma_channel_config config = dma_channel_get_default_config(dma_channel);
// channel_config_set_transfer_data_size(&config, DMA_SIZE_16);
// channel_config_set_dreq(&config, spi_get_index(spi) ? DREQ_SPI1_TX : DREQ_SPI0_TX);
// dma_channel_configure(
// dma_channel, &config, &spi_get_hw(spi)->dr, frame_buffer, width * height, false);
// 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]);

command(reg::CASET, 4, (char *)caset);
command(reg::RASET, 4, (char *)raset);
command(reg::MADCTL, 1, (char *)&madctl);
}

spi_inst_t* ST7789::get_spi() const {
Expand Down Expand Up @@ -230,8 +185,6 @@ namespace pimoroni {
}

void ST7789::command(uint8_t command, size_t len, const char *data) {
//dma_channel_wait_for_finish_blocking(dma_channel);

gpio_put(cs, 0);

gpio_put(dc, 0); // command mode
Expand All @@ -245,25 +198,8 @@ namespace pimoroni {
gpio_put(cs, 1);
}

void ST7789::update(bool dont_block) {
void ST7789::update() {
command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)frame_buffer);

/*if(dma_channel_is_busy(dma_channel) && dont_block) {
return;
}
dma_channel_wait_for_finish_blocking(dma_channel);
uint8_t r = reg::RAMWR;
gpio_put(cs, 0);
gpio_put(dc, 0); // command mode
spi_write_blocking(spi, &r, 1);
gpio_put(dc, 1); // data mode
dma_channel_set_read_addr(dma_channel, frame_buffer, true);*/
}

void ST7789::set_backlight(uint8_t brightness) {
Expand All @@ -274,12 +210,7 @@ namespace pimoroni {
pwm_set_gpio_level(bl, value);
}

void ST7789::vsync_callback(gpio_irq_callback_t callback) {
gpio_set_irq_enabled_with_callback(vsync, GPIO_IRQ_EDGE_RISE, true, callback);
}

void ST7789::flip(){
madctl ^= MADCTL::ROW_ORDER | MADCTL::COL_ORDER;
command(reg::MADCTL, 1, (char *)&madctl);
configure_display(true);
}
}
Loading

0 comments on commit db60553

Please sign in to comment.