ST7789: Convert Parallel IO to PIO + DMA.

Make display updates around 4x faster. Requires a PIO + SM to run Tufty 2040.
This commit is contained in:
Phil Howard 2022-06-16 17:38:57 +01:00
parent 3cd64747b2
commit 76715e45f8
10 changed files with 388 additions and 21 deletions

View File

@ -4,9 +4,11 @@ add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp)
pico_generate_pio_header(${DRIVER_NAME} ${CMAKE_CURRENT_LIST_DIR}/st7789_parallel.pio)
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(st7789 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib pimoroni_bus hardware_spi hardware_pwm hardware_dma pico_graphics)
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib pimoroni_bus hardware_spi hardware_pwm hardware_pio hardware_dma pico_graphics)

View File

@ -109,6 +109,20 @@ namespace pimoroni {
}
}
void ST7789::cleanup() {
if(spi) return; // SPI mode needs no tear down
if(dma_channel_is_claimed(parallel_dma)) {
dma_channel_abort(parallel_dma);
dma_channel_unclaim(parallel_dma);
}
if(pio_sm_is_claimed(parallel_pio, parallel_sm)) {
pio_sm_set_enabled(parallel_pio, parallel_sm, false);
pio_sm_drain_tx_fifo(parallel_pio, parallel_sm);
pio_sm_unclaim(parallel_pio, parallel_sm);
}
}
void ST7789::configure_display(Rotation rotate) {
bool rotate180 = rotate == ROTATE_180 || rotate == ROTATE_90;
@ -209,16 +223,37 @@ namespace pimoroni {
command(reg::MADCTL, 1, (char *)&madctl);
}
void ST7789::write_blocking_parallel_dma(const uint8_t *src, size_t len) {
while (dma_channel_is_busy(parallel_dma))
;
dma_channel_set_trans_count(parallel_dma, len, false);
dma_channel_set_read_addr(parallel_dma, src, true);
}
void ST7789::write_blocking_parallel(const uint8_t *src, size_t len) {
uint32_t mask = 0xff << d0;
const uint8_t *p = src;
while(len--) {
// Does not byte align correctly
//pio_sm_put_blocking(parallel_pio, parallel_sm, *p);
while (pio_sm_is_tx_fifo_full(parallel_pio, parallel_sm))
;
*(volatile uint8_t*)&parallel_pio->txf[parallel_sm] = *p;
p++;
}
uint32_t sm_stall_mask = 1u << (parallel_sm + PIO_FDEBUG_TXSTALL_LSB);
parallel_pio->fdebug = sm_stall_mask;
while (!(parallel_pio->fdebug & sm_stall_mask))
;
/*uint32_t mask = 0xff << d0;
while(len--) {
gpio_put(wr_sck, false);
uint8_t v = *src++;
gpio_put_masked(mask, v << d0);
asm("nop;");
//asm("nop;");
gpio_put(wr_sck, true);
asm("nop;");
}
}*/
}
void ST7789::command(uint8_t command, size_t len, const char *data) {
@ -266,8 +301,17 @@ namespace pimoroni {
write_blocking_parallel(&cmd, 1);
gpio_put(dc, 1); // data mode
graphics->scanline_convert(PicoGraphics::PEN_RGB565, [this](void *data, size_t length) {
write_blocking_parallel((const uint8_t*)data, length);
int scanline = 0;
graphics->scanline_convert(PicoGraphics::PEN_RGB565, [this, scanline](void *data, size_t length) mutable {
write_blocking_parallel_dma((const uint8_t*)data, length);
// Stall on the last scanline since "data" goes out of scope and is lost
scanline++;
if(scanline == height) {
while (dma_channel_is_busy(parallel_dma))
;
}
});
gpio_put(cs, 1);

View File

@ -1,12 +1,17 @@
#pragma once
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#include "hardware/pwm.h"
#include "common/pimoroni_common.hpp"
#include "common/pimoroni_bus.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "st7789_parallel.pio.h"
#include <algorithm>
@ -31,6 +36,11 @@ namespace pimoroni {
uint d0;
uint bl;
uint vsync = PIN_UNUSED; // only available on some products
uint parallel_sm;
PIO parallel_pio;
uint parallel_offset;
uint parallel_dma;
// The ST7789 requires 16 ns between SPI rising edges.
// 16 ns = 62,500,000 Hz
@ -43,17 +53,47 @@ namespace pimoroni {
DisplayDriver(width, height, rotation),
spi(nullptr), round(false),
cs(pins.cs), dc(pins.dc), wr_sck(pins.wr_sck), rd_sck(pins.rd_sck), d0(pins.d0), bl(pins.bl) {
parallel_pio = pio1;
parallel_sm = pio_claim_unused_sm(parallel_pio, true);
parallel_offset = pio_add_program(parallel_pio, &st7789_parallel_program);
gpio_set_function(wr_sck, GPIO_FUNC_SIO);
gpio_set_dir(wr_sck, GPIO_OUT);
//gpio_init(wr_sck);
//gpio_set_dir(wr_sck, GPIO_OUT);
//gpio_set_function(wr_sck, GPIO_FUNC_SIO);
pio_gpio_init(parallel_pio, wr_sck);
gpio_set_function(rd_sck, GPIO_FUNC_SIO);
gpio_set_dir(rd_sck, GPIO_OUT);
for(auto i = 0u; i < 8; i++) {
gpio_set_function(d0 + i, GPIO_FUNC_SIO);
gpio_set_dir(d0 + i, GPIO_OUT);
//gpio_set_function(d0 + i, GPIO_FUNC_SIO);
//gpio_set_dir(d0 + i, GPIO_OUT);
//gpio_init(d0 + 0); gpio_set_dir(d0 + i, GPIO_OUT);
pio_gpio_init(parallel_pio, d0 + i);
}
pio_sm_set_consecutive_pindirs(parallel_pio, parallel_sm, d0, 8, true);
pio_sm_set_consecutive_pindirs(parallel_pio, parallel_sm, wr_sck, 1, true);
pio_sm_config c = st7789_parallel_program_get_default_config(parallel_offset);
sm_config_set_out_pins(&c, d0, 8);
sm_config_set_sideset_pins(&c, wr_sck);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_out_shift(&c, false, true, 8);
sm_config_set_clkdiv(&c, 4);
pio_sm_init(parallel_pio, parallel_sm, parallel_offset, &c);
pio_sm_set_enabled(parallel_pio, parallel_sm, true);
parallel_dma = dma_claim_unused_channel(true);
dma_channel_config config = dma_channel_get_default_config(parallel_dma);
channel_config_set_transfer_data_size(&config, DMA_SIZE_8);
channel_config_set_bswap(&config, false);
channel_config_set_dreq(&config, pio_get_dreq(parallel_pio, parallel_sm, true));
dma_channel_configure(parallel_dma, &config, &parallel_pio->txf[parallel_sm], NULL, 0, false);
gpio_put(rd_sck, 1);
@ -75,13 +115,14 @@ namespace pimoroni {
common_init();
}
void cleanup() override;
void update(PicoGraphics *graphics) override;
void set_backlight(uint8_t brightness) override;
private:
void common_init();
void configure_display(Rotation rotate);
void write_blocking_parallel_dma(const uint8_t *src, size_t len);
void write_blocking_parallel(const uint8_t *src, size_t len);
void command(uint8_t command, size_t len = 0, const char *data = NULL);
};

View File

@ -0,0 +1,7 @@
.program st7789_parallel
.side_set 1
.wrap_target
out pins, 8 side 0
nop side 1
.wrap

View File

@ -125,6 +125,258 @@ namespace pimoroni {
void deflate(int32_t v);
};
static const RGB565 rgb332_to_rgb565_lut[256] = {
0x0000, 0x0800, 0x1000, 0x1800, 0x0001, 0x0801, 0x1001, 0x1801,
0x0002,
0x0802,
0x1002,
0x1802,
0x0003,
0x0803,
0x1003,
0x1803,
0x0004,
0x0804,
0x1004,
0x1804,
0x0005,
0x0805,
0x1005,
0x1805,
0x0006,
0x0806,
0x1006,
0x1806,
0x0007,
0x0807,
0x1007,
0x1807,
0x0020,
0x0820,
0x1020,
0x1820,
0x0021,
0x0821,
0x1021,
0x1821,
0x0022,
0x0822,
0x1022,
0x1822,
0x0023,
0x0823,
0x1023,
0x1823,
0x0024,
0x0824,
0x1024,
0x1824,
0x0025,
0x0825,
0x1025,
0x1825,
0x0026,
0x0826,
0x1026,
0x1826,
0x0027,
0x0827,
0x1027,
0x1827,
0x0040,
0x0840,
0x1040,
0x1840,
0x0041,
0x0841,
0x1041,
0x1841,
0x0042,
0x0842,
0x1042,
0x1842,
0x0043,
0x0843,
0x1043,
0x1843,
0x0044,
0x0844,
0x1044,
0x1844,
0x0045,
0x0845,
0x1045,
0x1845,
0x0046,
0x0846,
0x1046,
0x1846,
0x0047,
0x0847,
0x1047,
0x1847,
0x0060,
0x0860,
0x1060,
0x1860,
0x0061,
0x0861,
0x1061,
0x1861,
0x0062,
0x0862,
0x1062,
0x1862,
0x0063,
0x0863,
0x1063,
0x1863,
0x0064,
0x0864,
0x1064,
0x1864,
0x0065,
0x0865,
0x1065,
0x1865,
0x0066,
0x0866,
0x1066,
0x1866,
0x0067,
0x0867,
0x1067,
0x1867,
0x0080,
0x0880,
0x1080,
0x1880,
0x0081,
0x0881,
0x1081,
0x1881,
0x0082,
0x0882,
0x1082,
0x1882,
0x0083,
0x0883,
0x1083,
0x1883,
0x0084,
0x0884,
0x1084,
0x1884,
0x0085,
0x0885,
0x1085,
0x1885,
0x0086,
0x0886,
0x1086,
0x1886,
0x0087,
0x0887,
0x1087,
0x1887,
0x00a0,
0x08a0,
0x10a0,
0x18a0,
0x00a1,
0x08a1,
0x10a1,
0x18a1,
0x00a2,
0x08a2,
0x10a2,
0x18a2,
0x00a3,
0x08a3,
0x10a3,
0x18a3,
0x00a4,
0x08a4,
0x10a4,
0x18a4,
0x00a5,
0x08a5,
0x10a5,
0x18a5,
0x00a6,
0x08a6,
0x10a6,
0x18a6,
0x00a7,
0x08a7,
0x10a7,
0x18a7,
0x00c0,
0x08c0,
0x10c0,
0x18c0,
0x00c1,
0x08c1,
0x10c1,
0x18c1,
0x00c2,
0x08c2,
0x10c2,
0x18c2,
0x00c3,
0x08c3,
0x10c3,
0x18c3,
0x00c4,
0x08c4,
0x10c4,
0x18c4,
0x00c5,
0x08c5,
0x10c5,
0x18c5,
0x00c6,
0x08c6,
0x10c6,
0x18c6,
0x00c7,
0x08c7,
0x10c7,
0x18c7,
0x00e0,
0x08e0,
0x10e0,
0x18e0,
0x00e1,
0x08e1,
0x10e1,
0x18e1,
0x00e2,
0x08e2,
0x10e2,
0x18e2,
0x00e3,
0x08e3,
0x10e3,
0x18e3,
0x00e4,
0x08e4,
0x10e4,
0x18e4,
0x00e5,
0x08e5,
0x10e5,
0x18e5,
0x00e6,
0x08e6,
0x10e6,
0x18e6,
0x00e7,
0x08e7,
0x10e7,
0x18e7,
};
class PicoGraphics {
public:
enum PenType {
@ -301,7 +553,6 @@ namespace pimoroni {
class PicoGraphics_PenRGB332 : public PicoGraphics {
public:
RGB332 color;
RGB palette[256];
PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
@ -344,6 +595,7 @@ namespace pimoroni {
virtual void update(PicoGraphics *display) {};
virtual void set_backlight(uint8_t brightness) {};
virtual void cleanup() {};
};
}

View File

@ -1,4 +1,5 @@
#include "pico_graphics.hpp"
#include <string.h>
namespace pimoroni {
PicoGraphics_PenRGB332::PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer)
@ -7,9 +8,6 @@ namespace pimoroni {
if(this->frame_buffer == nullptr) {
this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]);
}
for(auto i = 0u; i < 256; i++) {
palette[i] = RGB((RGB332)i);
}
}
void PicoGraphics_PenRGB332::set_pen(uint c) {
color = c;
@ -86,12 +84,18 @@ namespace pimoroni {
}
void PicoGraphics_PenRGB332::scanline_convert(PenType type, conversion_callback_func callback) {
if(type == PEN_RGB565) {
// Cache the RGB888 palette as RGB565
RGB565 cache[256];
static RGB565 cache[256];
for(auto i = 0u; i < 256; i++) {
cache[i] = palette[i].to_rgb565();
cache[i] = rgb332_to_rgb565_lut[i]; // defined in pico_graphics.hpp
}
// 2ms slower, I swear!
/*
static RGB565 cache[256];
memcpy(cache, rgb332_to_rgb565_lut, 256 * sizeof(RGB565));
*/
// Treat our void* frame_buffer as uint8_t
uint8_t *src = (uint8_t *)frame_buffer;
@ -99,7 +103,8 @@ namespace pimoroni {
uint16_t row_buf[bounds.w];
for(auto y = 0; y < bounds.h; y++) {
for(auto x = 0; x < bounds.w; x++) {
row_buf[x] = cache[src[bounds.w * y + x]];
row_buf[x] = cache[*src];
src++;
}
// Callback to the driver with the row data
callback(row_buf, bounds.w * sizeof(RGB565));

View File

@ -17,6 +17,8 @@ target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/types.cpp
)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/st7789/st7789_parallel.pio)
target_include_directories(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)

View File

@ -46,6 +46,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_get_bounds_obj, ModPicoGraphics_get_bo
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_font_obj, ModPicoGraphics_set_font);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_framebuffer_obj, ModPicoGraphics_set_framebuffer);
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics__del__obj, ModPicoGraphics__del__);
STATIC const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&ModPicoGraphics_pixel_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_pen), MP_ROM_PTR(&ModPicoGraphics_set_pen_obj) },
@ -79,6 +83,8 @@ STATIC const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_get_bounds), MP_ROM_PTR(&ModPicoGraphics_get_bounds_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&ModPicoGraphics_set_font_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_framebuffer), MP_ROM_PTR(&ModPicoGraphics_set_framebuffer_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ModPicoGraphics__del__obj) },
};
STATIC MP_DEFINE_CONST_DICT(ModPicoGraphics_locals_dict, ModPicoGraphics_locals_dict_table);

View File

@ -108,7 +108,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
self = m_new_obj(ModPicoGraphics_obj_t);
self = m_new_obj_with_finaliser(ModPicoGraphics_obj_t);
self->base.type = &ModPicoGraphics_type;
@ -210,6 +210,12 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
return MP_OBJ_FROM_PTR(self);
}
mp_obj_t ModPicoGraphics__del__(mp_obj_t self_in) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->display->cleanup();
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_spritesheet(mp_obj_t self_in, mp_obj_t spritedata) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
if(spritedata == mp_const_none) {

View File

@ -73,4 +73,6 @@ extern mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font);
extern mp_obj_t ModPicoGraphics_get_bounds(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_set_framebuffer(mp_obj_t self_in, mp_obj_t framebuffer);
extern mp_int_t ModPicoGraphics_get_framebuffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
extern mp_int_t ModPicoGraphics_get_framebuffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags);
extern mp_obj_t ModPicoGraphics__del__(mp_obj_t self_in);