diff --git a/drivers/st7789/st7789.cmake b/drivers/st7789/st7789.cmake index a08f111c..45d5247c 100644 --- a/drivers/st7789/st7789.cmake +++ b/drivers/st7789/st7789.cmake @@ -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) diff --git a/drivers/st7789/st7789.cpp b/drivers/st7789/st7789.cpp index 688e008f..e3243122 100644 --- a/drivers/st7789/st7789.cpp +++ b/drivers/st7789/st7789.cpp @@ -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*)¶llel_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); diff --git a/drivers/st7789/st7789.hpp b/drivers/st7789/st7789.hpp index 5be438c3..2a7fa36f 100644 --- a/drivers/st7789/st7789.hpp +++ b/drivers/st7789/st7789.hpp @@ -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 @@ -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, ¶llel_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); }; diff --git a/drivers/st7789/st7789_parallel.pio b/drivers/st7789/st7789_parallel.pio new file mode 100644 index 00000000..6438c028 --- /dev/null +++ b/drivers/st7789/st7789_parallel.pio @@ -0,0 +1,7 @@ +.program st7789_parallel +.side_set 1 + +.wrap_target + out pins, 8 side 0 + nop side 1 +.wrap \ No newline at end of file diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 0fddba41..926db4eb 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -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() {}; }; } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index d8389229..9fb7bb1c 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -1,4 +1,5 @@ #include "pico_graphics.hpp" +#include 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)); diff --git a/micropython/modules/picographics/micropython.cmake b/micropython/modules/picographics/micropython.cmake index e3713556..9a1675af 100644 --- a/micropython/modules/picographics/micropython.cmake +++ b/micropython/modules/picographics/micropython.cmake @@ -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} ) diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index 86ec0ee2..63bafca8 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -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); diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 8626e690..abffeb59 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -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) { diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 01a26233..e081ed83 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -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); \ No newline at end of file +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); \ No newline at end of file