diff --git a/.gitmodules b/.gitmodules index b1f258d23..292b442fe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,6 @@ [submodule "drivers/mlx90640/src"] path = drivers/mlx90640/src url = https://github.com/melexis/mlx90640-library +[submodule "examples/pico_plus_2_psram/lwmem"] + path = examples/pico_plus_2_psram/lwmem + url = https://github.com/MaJerle/lwmem diff --git a/CMakeLists.txt b/CMakeLists.txt index 5718ada79..cc98efee5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.12) - + # Pull in PICO SDK (must be before project) include(pico_sdk_import.cmake) diff --git a/drivers/st7789/st7789.cpp b/drivers/st7789/st7789.cpp index ed34447c5..094bbeedc 100644 --- a/drivers/st7789/st7789.cpp +++ b/drivers/st7789/st7789.cpp @@ -3,7 +3,11 @@ #include #include + namespace pimoroni { + + DMAInterruptHandler *DMAInterruptHandler::dma_interrupt_handlers[2] = {nullptr, nullptr}; + uint8_t madctl; uint16_t caset[2] = {0, 0}; uint16_t raset[2] = {0, 0}; @@ -46,6 +50,7 @@ namespace pimoroni { PWMFRSEL = 0xCC }; + void ST7789::common_init() { gpio_set_function(dc, GPIO_FUNC_SIO); gpio_set_dir(dc, GPIO_OUT); @@ -118,10 +123,20 @@ namespace pimoroni { } void ST7789::cleanup() { - if(dma_channel_is_claimed(st_dma)) { - dma_channel_abort(st_dma); - dma_channel_unclaim(st_dma); + if(dma_channel_is_claimed(st_dma_data)) { + dma_channel_abort(st_dma_data); + dma_channel_unclaim(st_dma_data); + } + +#if !USE_ASYNC_INTERRUPTS + if(use_async_dma && dma_channel_is_claimed(st_dma_control_chain)) { + dma_channel_abort(st_dma_control_chain); + dma_channel_unclaim(st_dma_control_chain); } + + delete[] dma_control_chain_blocks; +#endif + if(spi) return; // SPI mode needs no further tear down if(pio_sm_is_claimed(parallel_pio, parallel_sm)) { @@ -133,9 +148,15 @@ namespace pimoroni { 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); } + + // setup full and current regions + full_screen_region = {0, 0, width, height}; + current_update_region = {0, 0, width, height}; // 240x240 Square and Round LCD Breakouts if(width == 240 && height == 240) { @@ -182,31 +203,29 @@ namespace pimoroni { // Pico Display if(width == 240 && height == 135) { - caset[0] = 40; // 240 cols - caset[1] = 40 + width - 1; - raset[0] = 52; // 135 rows - raset[1] = 52 + height - 1; - if (rotate == ROTATE_0) { - raset[0] += 1; - raset[1] += 1; - } - madctl = rotate == ROTATE_180 ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER; + int col_offset = 40; + int row_offset = 53-rotate180; // something a little weird here, needs offsetting by -1 if rotateing 180! + + caset[0] = col_offset; + caset[1] = width + col_offset - 1; + raset[0] = row_offset; + raset[1] = height + row_offset - 1; + + 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] = 52 + width - 1; - raset[0] = 40; // 240 rows - raset[1] = 40 + height - 1; - madctl = 0; - if (rotate == ROTATE_90) { - caset[0] += 1; - caset[1] += 1; - madctl = MADCTL::COL_ORDER | MADCTL::ROW_ORDER; - } - madctl = rotate == ROTATE_90 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0; + int col_offset = 52+rotate180; // something a little weird here, needs offsetting by +1 if rotateing 180! + int row_offset = 40; + + caset[0] = col_offset; + caset[1] = width + col_offset - 1; + raset[0] = row_offset; + raset[1] = height + row_offset - 1; + + madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0; } // Pico Display 2.0 @@ -229,26 +248,29 @@ namespace pimoroni { } // 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]); + uint16_t scaset[2] = {0, 0}; + uint16_t sraset[2] = {0, 0}; - command(reg::CASET, 4, (char *)caset); - command(reg::RASET, 4, (char *)raset); + scaset[0] = __builtin_bswap16(caset[0]); + scaset[1] = __builtin_bswap16(caset[1]); + sraset[0] = __builtin_bswap16(raset[0]); + sraset[1] = __builtin_bswap16(raset[1]); + + command(reg::CASET, 4, (char *)scaset); + command(reg::RASET, 4, (char *)sraset); command(reg::MADCTL, 1, (char *)&madctl); } void ST7789::write_blocking_dma(const uint8_t *src, size_t len) { - while (dma_channel_is_busy(st_dma)) + while (dma_channel_is_busy(st_dma_data)) ; - dma_channel_set_trans_count(st_dma, len, false); - dma_channel_set_read_addr(st_dma, src, true); + dma_channel_set_trans_count(st_dma_data, len, false); + dma_channel_set_read_addr(st_dma_data, src, true); } void ST7789::write_blocking_parallel(const uint8_t *src, size_t len) { write_blocking_dma(src, len); - dma_channel_wait_for_finish_blocking(st_dma); + dma_channel_wait_for_finish_blocking(st_dma_data); // This may cause a race between PIO and the // subsequent chipselect deassert for the last pixel @@ -256,7 +278,7 @@ namespace pimoroni { ; } - void ST7789::command(uint8_t command, size_t len, const char *data) { + void ST7789::command(uint8_t command, size_t len, const char *data, bool use_async_dma) { gpio_put(dc, 0); // command mode gpio_put(cs, 0); @@ -270,20 +292,112 @@ namespace pimoroni { if(data) { gpio_put(dc, 1); // data mode if(spi) { - spi_write_blocking(spi, (const uint8_t*)data, len); + if(use_async_dma) { + write_blocking_dma((const uint8_t*)data, len); + } + else { + spi_write_blocking(spi, (const uint8_t*)data, len); + } } else { write_blocking_parallel((const uint8_t*)data, len); } } - gpio_put(cs, 1); + if(!use_async_dma) + gpio_put(cs, 1); } + void ST7789::partial_update(PicoGraphics *display, Rect region) { + // check sanity flag + if(in_dma_update) { + panic("When use_async_dma is set you must call wait_for_update_to_finish() between updates"); + } + else { + in_dma_update = use_async_dma; + } + + uint8_t cmd = reg::RAMWR; + if(set_update_region(region)){ + // select and enter command mode + gpio_put(cs, 0); + gpio_put(dc, 0); + + // send RAMWR command + if(spi) { // SPI Bus + spi_write_blocking(spi, &cmd, 1); + } else { // Parallel Bus + write_blocking_parallel(&cmd, 1); + } + + // enter data mode + gpio_put(dc, 1); // data mode + + if(display->pen_type == PicoGraphics::PEN_RGB565) { // Display buffer is screen native + uint16_t* framePtr = (uint16_t*)display->frame_buffer + region.x + (region.y * width); + + if(use_async_dma) { +#if USE_ASYNC_INTERRUPTS + enable_dma_interrupt(true); + dma_stride.count = region.h; + dma_stride.data = (uint8_t*)framePtr; + dma_stride.size = region.w * sizeof(uint16_t); + dma_stride.width = width * sizeof(uint16_t); + start_dma_interrupt(); +#else + // Setup chained dma channels + enable_dma_control_chain(true); + + for(int32_t control_idx = 0; control_idx < region.h; control_idx++) { + dma_control_chain_blocks[control_idx] = { region.w * sizeof(uint16_t), (uint8_t*)framePtr }; + framePtr+=(width); + } + dma_control_chain_blocks[region.h] = { 0, 0 }; + start_dma_control_chain(); +#endif + } + else { + for(int32_t row = region.y; row < region.y + region.h; row++) { + spi_write_blocking(spi, (uint8_t*)framePtr, region.w * sizeof(uint16_t)); + framePtr+=(width); + } + } + } + else + { + // use rect_convert to convert to 565 + display->rect_convert(PicoGraphics::PEN_RGB565, region, [this](void *data, size_t length) { + if (length > 0) { + write_blocking_dma((const uint8_t*)data, length); + } + else { + dma_channel_wait_for_finish_blocking(st_dma_data); + } + }); + } + + // if we are using async dma leave CS alone + if(!use_async_dma) { + gpio_put(cs, 1); + } + } + } + void ST7789::update(PicoGraphics *graphics) { + // check sanity flag + if(in_dma_update) { + panic("When use_async_dma is set you must call wait_for_update_to_finish() between updates"); + } + else { + in_dma_update = use_async_dma; + } + + // set update rect to the full screen + set_update_region(full_screen_region); + uint8_t cmd = reg::RAMWR; if(graphics->pen_type == PicoGraphics::PEN_RGB565) { // Display buffer is screen native - command(cmd, width * height * sizeof(uint16_t), (const char*)graphics->frame_buffer); + command(cmd, width * height * sizeof(uint16_t), (const char*)graphics->frame_buffer, use_async_dma); } else { gpio_put(dc, 0); // command mode gpio_put(cs, 0); @@ -300,7 +414,7 @@ namespace pimoroni { write_blocking_dma((const uint8_t*)data, length); } else { - dma_channel_wait_for_finish_blocking(st_dma); + dma_channel_wait_for_finish_blocking(st_dma_data); } }); @@ -315,4 +429,116 @@ namespace pimoroni { uint16_t value = (uint16_t)(pow((float)(brightness) / 255.0f, gamma) * 65535.0f + 0.5f); pwm_set_gpio_level(bl, value); } + + void ST7789::setup_dma_control_chain_if_needed() { + if(use_async_dma) { + dma_control_chain_blocks = new DMAControlBlock[height+1]; + st_dma_control_chain = dma_claim_unused_channel(true); + + // config to write 32 bit registers in spi dma + dma_control_config = dma_channel_get_default_config(st_dma_control_chain); + channel_config_set_transfer_data_size(&dma_control_config, DMA_SIZE_32); + channel_config_set_read_increment(&dma_control_config, true); + channel_config_set_write_increment(&dma_control_config, true); + channel_config_set_ring(&dma_control_config, true, 3); // wrap at 8 bytes to repeatedly write the count and address registers + + // configure to write to count and address registers from our dma_control_chain_blocks array, write two 32 bit values for len and addr + dma_channel_configure(st_dma_control_chain, &dma_control_config, &dma_hw->ch[st_dma_data].al3_transfer_count, &dma_control_chain_blocks[0], 2, false); + } + } + + + void ST7789::enable_dma_interrupt(bool enable) { + if(use_async_dma) { + if(dma_interrupts_is_enabled != enable){ + dma_interrupts_is_enabled = enable; + + if(dma_interrupts_is_enabled) { + enable_dma_irq(this, st_dma_data, use_dma_interrupt); + } else + { + disable_dma_irq(this, st_dma_data, use_dma_interrupt); + } + } + } + } + + void ST7789::start_dma_interrupt() { + if(use_async_dma) { + if(dma_stride.count && dma_interrupts_is_enabled) + { + dma_channel_configure(st_dma_data, &dma_data_config, &spi_get_hw(spi)->dr, dma_stride.data, dma_stride.size, false); + + dma_hw->ints0 = 1u << st_dma_data; + dma_start_channel_mask(1u << st_dma_data); + + dma_stride.count--; + dma_stride.data += dma_stride.width; + } + else + in_dma_update = false; // Mark as completed + } + } + + + void ST7789::enable_dma_control_chain(bool enable) { + if(use_async_dma) { + if(dma_control_chain_is_enabled != enable){ + dma_control_chain_is_enabled = enable; + if(dma_control_chain_is_enabled) { + // enable dma control chain, chain to control dma and only set irq at end of chain + channel_config_set_chain_to(&dma_data_config, st_dma_control_chain); + channel_config_set_irq_quiet(&dma_data_config, true); + } + else { + // disable dma control chain, chain to data dma and set irq at end of transfer + channel_config_set_chain_to(&dma_data_config, st_dma_data); + channel_config_set_irq_quiet(&dma_data_config, false); + } + + // configure the data dma + if(spi) { + dma_channel_configure(st_dma_data, &dma_data_config, &spi_get_hw(spi)->dr, NULL, 0, false); + } + else { + dma_channel_configure(st_dma_data, &dma_data_config, ¶llel_pio->txf[parallel_sm], NULL, 0, false); + } + + // configure control chain dma + dma_channel_configure(st_dma_control_chain, &dma_control_config, &dma_hw->ch[st_dma_data].al3_transfer_count, &dma_control_chain_blocks[0], 2, false); + } + } + } + + void ST7789::start_dma_control_chain() { + if(use_async_dma && dma_control_chain_is_enabled) { + dma_hw->ints0 = 1u << st_dma_data; + dma_start_channel_mask(1u << st_dma_control_chain); + } + } + + + bool ST7789::set_update_region(Rect& update_region){ + // find intersection with display + update_region = Rect(0,0,width,height).intersection(update_region); + + // check we have a valid region + bool valid_region = !update_region.empty(); + + // if we have a valid region and it is different to the current update rect + if(valid_region && !update_region.equals(current_update_region)) { + // offset the rect + uint16_t scaset[2] = {__builtin_bswap16(caset[0]+update_region.x), __builtin_bswap16(caset[0]+update_region.x+update_region.w-1)}; + uint16_t sraset[2] = {__builtin_bswap16(raset[0]+update_region.y), __builtin_bswap16(raset[0]+update_region.y+update_region.h-1)}; + + // update display row and column + command(reg::CASET, 4, (char *)scaset); + command(reg::RASET, 4, (char *)sraset); + + // update our current region + current_update_region = update_region; + } + + return valid_region; + } } diff --git a/drivers/st7789/st7789.hpp b/drivers/st7789/st7789.hpp index e4ad2ee6f..bac9789e1 100644 --- a/drivers/st7789/st7789.hpp +++ b/drivers/st7789/st7789.hpp @@ -17,10 +17,50 @@ #include +// set USE_ASYNC_INTERRUPTS to 1 to use interrupts rather than control chain for python. +#define USE_ASYNC_INTERRUPTS 0 namespace pimoroni { + + class DMAInterruptHandler { + + protected: + static DMAInterruptHandler *dma_interrupt_handlers[2]; + + static void __isr dma_handler_irq() { + dma_interrupt_handlers[0]->handle_dma_irq(); + } + + static void enable_dma_irq(DMAInterruptHandler *pObject, uint channel, uint dma_irq) + { + dma_channel_set_irq0_enabled(channel, true); + irq_set_exclusive_handler(dma_irq, dma_handler_irq); + dma_interrupt_handlers[dma_irq-DMA_IRQ_0] = pObject; + irq_set_enabled(dma_irq, true); + } + + static void disable_dma_irq(DMAInterruptHandler *pObject, uint channel, uint dma_irq) + { + dma_channel_set_irq0_enabled(channel, false); + irq_remove_handler(dma_irq, dma_handler_irq); + dma_interrupt_handlers[dma_irq-DMA_IRQ_0] = nullptr; + irq_set_enabled(dma_irq, false); + } + + bool is_channel_interrupt_set(uint channel) + { + return dma_hw->ints0 & (1u << channel); + } - class ST7789 : public DisplayDriver { + void clear_dma_channel_interrupt(uint channel) + { + dma_hw->ints0 = (1u << channel); // clear irq flag + } + + virtual void handle_dma_irq(void) = 0; + }; + + class ST7789 : public DisplayDriver, public DMAInterruptHandler { spi_inst_t *spi = PIMORONI_SPI_DEFAULT_INSTANCE; public: @@ -42,20 +82,57 @@ namespace pimoroni { uint parallel_sm; PIO parallel_pio; uint parallel_offset; - uint st_dma; + uint st_dma_data; // The ST7789 requires 16 ns between SPI rising edges. // 16 ns = 62,500,000 Hz + // 2350 doesn't support 62,500,000 so use 75,000,000 seems to work. +#if !PICO_RP2350 static const uint32_t SPI_BAUD = 62'500'000; +#else + static const uint32_t SPI_BAUD = 75'000'000; +#endif + + // current update rect and full screen rect + Rect full_screen_region; + Rect current_update_region; + + // dma control blocks used for async partial updates + struct DMAControlBlock + { + uint32_t len; + uint8_t* data; + }; + + struct DMAStride + { + uint32_t size = 0; + uint32_t count = 0; + uint32_t width = 0; + uint8_t* data = nullptr; + }; + + uint st_dma_control_chain; + DMAStride dma_stride; + DMAControlBlock* dma_control_chain_blocks = nullptr; + dma_channel_config dma_data_config; + dma_channel_config dma_control_config; + bool use_async_dma = false; + u_char use_dma_interrupt; + bool dma_control_chain_is_enabled = false; + bool dma_interrupts_is_enabled = false; + + // sanity flag for dma updates + bool in_dma_update = false; public: // Parallel init - ST7789(uint16_t width, uint16_t height, Rotation rotation, ParallelPins pins) : + ST7789(uint16_t width, uint16_t height, Rotation rotation, ParallelPins pins, bool use_async_dma = false, uint use_dma_interrupt = DMA_IRQ_0) : 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) { + cs(pins.cs), dc(pins.dc), wr_sck(pins.wr_sck), rd_sck(pins.rd_sck), d0(pins.d0), bl(pins.bl), use_async_dma(use_async_dma), use_dma_interrupt(use_dma_interrupt) { parallel_pio = pio1; parallel_sm = pio_claim_unused_sm(parallel_pio, true); @@ -96,23 +173,27 @@ namespace pimoroni { pio_sm_set_enabled(parallel_pio, parallel_sm, true); - st_dma = dma_claim_unused_channel(true); - dma_channel_config config = dma_channel_get_default_config(st_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(st_dma, &config, ¶llel_pio->txf[parallel_sm], NULL, 0, false); + st_dma_data = dma_claim_unused_channel(true); + dma_data_config = dma_channel_get_default_config(st_dma_data); + channel_config_set_transfer_data_size(&dma_data_config, DMA_SIZE_8); + channel_config_set_bswap(&dma_data_config, false); + channel_config_set_dreq(&dma_data_config, pio_get_dreq(parallel_pio, parallel_sm, true)); + dma_channel_configure(st_dma_data, &dma_data_config, ¶llel_pio->txf[parallel_sm], NULL, 0, false); gpio_put(rd_sck, 1); +#if !USE_ASYNC_INTERRUPTS + setup_dma_control_chain_if_needed(); +#endif + common_init(); } // Serial init - ST7789(uint16_t width, uint16_t height, Rotation rotation, bool round, SPIPins pins) : + ST7789(uint16_t width, uint16_t height, Rotation rotation, bool round, SPIPins pins, bool use_async_dma = false, uint use_dma_interrupt = DMA_IRQ_0) : DisplayDriver(width, height, rotation), spi(pins.spi), round(round), - cs(pins.cs), dc(pins.dc), wr_sck(pins.sck), d0(pins.mosi), bl(pins.bl) { + cs(pins.cs), dc(pins.dc), wr_sck(pins.sck), d0(pins.mosi), bl(pins.bl), use_async_dma(use_async_dma), use_dma_interrupt(use_dma_interrupt) { // configure spi interface and pins spi_init(spi, SPI_BAUD); @@ -120,26 +201,97 @@ namespace pimoroni { gpio_set_function(wr_sck, GPIO_FUNC_SPI); gpio_set_function(d0, GPIO_FUNC_SPI); - st_dma = dma_claim_unused_channel(true); - dma_channel_config config = dma_channel_get_default_config(st_dma); - channel_config_set_transfer_data_size(&config, DMA_SIZE_8); - channel_config_set_bswap(&config, false); - channel_config_set_dreq(&config, spi_get_dreq(spi, true)); - dma_channel_configure(st_dma, &config, &spi_get_hw(spi)->dr, NULL, 0, false); - + st_dma_data = dma_claim_unused_channel(true); + dma_data_config = dma_channel_get_default_config(st_dma_data); + channel_config_set_transfer_data_size(&dma_data_config, DMA_SIZE_8); + channel_config_set_bswap(&dma_data_config, false); + channel_config_set_dreq(&dma_data_config, spi_get_dreq(spi, true)); + dma_channel_configure(st_dma_data, &dma_data_config, &spi_get_hw(spi)->dr, NULL, 0, false); + +#if !USE_ASYNC_INTERRUPTS + setup_dma_control_chain_if_needed(); +#endif common_init(); } + + virtual ~ST7789() + { + cleanup(); + } void cleanup() override; void update(PicoGraphics *graphics) override; + void partial_update(PicoGraphics *display, Rect region) override; void set_backlight(uint8_t brightness) override; + + bool is_busy() override + { +#if USE_ASYNC_INTERRUPTS + if(use_async_dma && dma_interrupts_is_enabled) { + return in_dma_update; + } +#else + if(use_async_dma && dma_control_chain_is_enabled) { + return !(dma_hw->intr & 1u << st_dma_data); + } +#endif + else { + return dma_channel_is_busy(st_dma_data); + } + } + + void wait_for_update_to_finish() + { +#if USE_ASYNC_INTERRUPTS + if(use_async_dma && dma_interrupts_is_enabled) { + while(in_dma_update) { + tight_loop_contents(); + } + + enable_dma_interrupt(false); + } +#else + if(use_async_dma && dma_control_chain_is_enabled) { + while (!(dma_hw->intr & 1u << st_dma_data)) { + tight_loop_contents(); + } + + // disable control chain dma + enable_dma_control_chain(false); + } +#endif + else { + dma_channel_wait_for_finish_blocking(st_dma_data); + } + + // deselect + gpio_put(cs, 1); + + // set sanity flag + in_dma_update = false; + } + private: void common_init(); void configure_display(Rotation rotate); void write_blocking_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); - }; + void command(uint8_t command, size_t len = 0, const char *data = NULL, bool use_async_dma = false); + void setup_dma_control_chain_if_needed(); + void enable_dma_control_chain(bool enable); + void start_dma_control_chain(); + void enable_dma_interrupt(bool enable); + void start_dma_interrupt(); + bool set_update_region(Rect& update_rect); + void handle_dma_irq(void) override + { + if(is_channel_interrupt_set(st_dma_data)) + { + clear_dma_channel_interrupt(st_dma_data); + start_dma_interrupt(); + } + } + }; } diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c1f910d78..9bea8df7d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -63,3 +63,5 @@ add_subdirectory(galactic_unicorn) add_subdirectory(gfx_pack) add_subdirectory(cosmic_unicorn) add_subdirectory(stellar_unicorn) + +add_subdirectory(pico_plus_2_psram) diff --git a/examples/pico_display/CMakeLists.txt b/examples/pico_display/CMakeLists.txt index d6f1847a6..ca3884e1b 100644 --- a/examples/pico_display/CMakeLists.txt +++ b/examples/pico_display/CMakeLists.txt @@ -8,4 +8,16 @@ add_executable( target_link_libraries(pico_display_demo pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled pico_display pico_graphics st7789) # create map/bin/hex file etc. -pico_add_extra_outputs(pico_display_demo) \ No newline at end of file +pico_add_extra_outputs(pico_display_demo) + + +add_executable( + pico_display_async_region + pico_display_async_region.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(pico_display_async_region pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled pico_display pico_graphics st7789 button) + +# create map/bin/hex file etc. +pico_add_extra_outputs(pico_display_async_region) \ No newline at end of file diff --git a/examples/pico_display/pico_display_async_region.cpp b/examples/pico_display/pico_display_async_region.cpp new file mode 100644 index 000000000..bdfbcf3bb --- /dev/null +++ b/examples/pico_display/pico_display_async_region.cpp @@ -0,0 +1,318 @@ +// Example for testing Async DMA and Partial Updates +// +// Button A: Cycles arround screen rotations +// Button B: Cycles around pen (display) format - PenRGB565, PenRGB332, Pen* and Pen4 +// Button C: Turns Async DMA on and off +// Button D: Cycles around Partial Region updates: None, Half, Bounce and Full +// +// Timings are logged to the screen and also the console +// I = The pen format, Async status and region. +// C = The time taken in ms for calculations +// D = The remaining free DMA time in ms, so the amount of free time you have for more calculations. +// R = The time to render the frame in ms. +// U = The time blocked in updating the display in ms. +// T = The total time in ms. +// +// Async Updates +// ------------- +// Async updates will only have an effect if you are using the native pen format 565 +// If this is turned on then when you call Update() it will return immediately after setting up the DMA +// At this point you can use the processor as usual apart from you must not send anything to the spi +// or to the display buffer. In this example we are doing the calculations for the bouncing pixels here. +// Async updates also work for region updates using 565. +// +// So when this example first starts dma is off, in a release build here we see these timings: +// C=8.92, D=0.00, R=7.67, U=9.86, T=26.46 +// If we turn Async DMA on with the X button we see: +// C=8.92, D=0.92, R=7.65, U=0.02, T=17.52 +// +// So we can see the time we take to do the calculations (C) stays the same. +// The time to render stays (R) the same +// The time to update the display has dropped from 9.86ms to 0.02ms though +// What is happening is that the display is now being updated via DMA +// So now we are doing our cacluations whilst this is happening. +// D is now set to .92ms, this is the time remaining till the display is ready (not using the dma) +// if we add C, D and U we get 9.86ms which is the time taken when without async we were blocked updating the display. +// so now with Async on we can use that previously blocked time for our calculations. +// We can see the total time drop from 26.46ms to 17.52ms, a nice little speedup. +// +// Partial Updates +// =============== +// +// There may be cases where you don't want to update the whole display. +// If you know you have only updated a small part of the framebuffer then you can use Partial Updates to only send that part to the display. +// +// This example allows you to test partial_update() +// Using Button Y you can cycle around different modes: +// None: Partial updates turned off. +// Half: The left half of the display is updated. +// Bounce: A quarter of the display is updated with this region bouncing around. For testing clipping. +// Full: The full display is updated as a partial area, Currently this can speed things up for some pens +// For example P4 drops from around 34.3ms to 26.1ms when using partial updates for the full screen. + +#include +#include +#include +#include + +#include "pico_display.hpp" +#include "drivers/st7789/st7789.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" +#include "rgbled.hpp" +#include "button.hpp" + +class ElapsedUs +{ +public: + ElapsedUs() + { + last_time = time_us_64(); + } + + uint64_t elapsed(void) + { + uint64_t time_now = time_us_64(); + uint64_t elapsed = time_now - last_time; + last_time = time_now; + return elapsed; + } + +private: + uint64_t last_time; +}; + +using namespace pimoroni; + + +Button button_a(PicoDisplay::A); +Button button_b(PicoDisplay::B); +Button button_x(PicoDisplay::X); +Button button_y(PicoDisplay::Y); + +enum UsePen{up565, up332, up8, up4, upCount}; +enum UseRegion {urNone, urHalf, urBounce, urFull, urCount}; + +static const char* pen_strings[] = {"565", "332", "P8", "P4"}; +static const char* region_strings[] = {"None", "Half", "Bounce", "Full"}; + + +int main() { + stdio_init_all(); + + // default to async dma off + bool use_async_dma = false; + + // default to no rotation + Rotation rotation = ROTATE_0; + + // default to 565 + UsePen use_pen = up565; + + // default to no region + UseRegion use_region = urNone; + Rect region; + Point bounce = {0, 0}; + Point bounce_inc = {1, 1}; + + + ST7789* st7789 = nullptr; + PicoGraphics* graphics = nullptr; + + char log_buffer[64]; + + // turn the led off + RGBLED led(PicoDisplay::LED_R, PicoDisplay::LED_G, PicoDisplay::LED_B); + led.set_rgb(0, 0, 0); + + + struct pt { + float x; + float y; + float dx; + float dy; + uint16_t use_pen; + }; + + + Pen black_pen, white_pen; + + std::vector pixels; + uint update_time = 0; + uint calc_time = 0; + uint dma_time = 0; + uint render_time = 0; + uint total_time = 0; + + + while(true) { + ElapsedUs total_timer; + ElapsedUs timer; + + bool change_rotation = button_a.read(); + bool change_pen = button_b.read(); + bool change_async = button_x.read(); + bool change_region = button_y.read(); + + // cycle arounf regions + if(change_region){ + use_region = (UseRegion)((use_region+1) % urCount); + } + + if(st7789 == nullptr || change_rotation || change_pen || change_async) { + // switch async DMA mode + if(change_async) { + use_async_dma = ! use_async_dma; + delete st7789; + st7789 = nullptr; + } + + // cycle around rotations + if(change_rotation) { + rotation = (Rotation)((rotation + 90) % 360); + delete st7789; + st7789 = nullptr; + } + + // cycle around pens (graphics mode) + if(change_pen) { + use_pen = (UsePen)((use_pen + 1) % upCount); + } + + if(st7789 == nullptr) + st7789 = new ST7789(PicoDisplay::WIDTH, PicoDisplay::HEIGHT, rotation, false, get_spi_pins(BG_SPI_FRONT), use_async_dma); + + delete graphics; + switch(use_pen) + { + case up565 : graphics = new PicoGraphics_PenRGB565(st7789->width, st7789->height, nullptr); break; + case up332 : graphics = new PicoGraphics_PenRGB332(st7789->width, st7789->height, nullptr); break; + case up8 : graphics = new PicoGraphics_PenP8(st7789->width, st7789->height, nullptr); break; + case up4 : graphics = new PicoGraphics_PenP4(st7789->width, st7789->height, nullptr); break; + default: break; + } + + st7789->set_backlight(150); + black_pen = graphics->create_pen(0, 0, 0); + white_pen = graphics->create_pen(255, 255, 255); + + graphics->set_font("bitmap8"); + + pixels.clear(); + for(int i = 0; i < 3000; i++) { + pt pixel; + pixel.x = rand() % graphics->bounds.w; + pixel.y = rand() % graphics->bounds.h; + pixel.dx = float(rand() % 255) / 128.0f; + pixel.dy = float(rand() % 255) / 128.0f; + pixel.use_pen = graphics->create_pen(rand() % 255, rand() % 255, rand() % 255); + pixels.push_back(pixel); + } + } + + switch (use_region) + { + case urFull : region = Rect(0, 0, st7789->width, st7789->height); break; + case urHalf : region = Rect(0, 0, st7789->width / 2, st7789->height); break; + case urBounce : region = Rect(bounce.x - (st7789->width / 4), bounce.y - (st7789->height / 4), st7789->width / 2, st7789->height / 2); break; + default: break; + } + + // update data + for(auto &pixel : pixels) { + pixel.x += pixel.dx; + pixel.y += pixel.dy; + if(pixel.x < 0) pixel.dx *= -1; + if(pixel.x >= graphics->bounds.w) pixel.dx *= -1; + if(pixel.y < 0) pixel.dy *= -1; + if(pixel.y >= graphics->bounds.h) pixel.dy *= -1; + } + + bounce.x += bounce_inc.x; + bounce.y += bounce_inc.y; + if(bounce.x < 0) bounce_inc.x = 1; + if(bounce.x >= graphics->bounds.w) bounce_inc.x = -1; + if(bounce.y < 0) bounce_inc.y = 1; + if(bounce.y >= graphics->bounds.h) bounce_inc.y = -1; + + + calc_time = timer.elapsed(); + + // if async wait for last update to finish before rendering + if(use_async_dma) { + st7789->wait_for_update_to_finish(); + } + + dma_time = timer.elapsed(); + + // render + graphics->set_pen(black_pen); + graphics->clear(); + + for(auto &pixel : pixels) { + graphics->set_pen(pixel.use_pen); + graphics->pixel(Point(pixel.x, pixel.y)); + } + + graphics->set_pen(white_pen); + + graphics->line(Point(0,0), Point(graphics->bounds.w-1, 0)); + graphics->line(Point(0,0), Point(0, graphics->bounds.h-1)); + + graphics->line(Point(graphics->bounds.w-1,0), Point(graphics->bounds.w-1, graphics->bounds.h-1)); + graphics->line(Point(0,graphics->bounds.h-1), Point(graphics->bounds.w-1, graphics->bounds.h-1)); + + graphics->set_pen(white_pen); + + uint spacing = 20; + float scale = 2; + int y = -(spacing/2); + sprintf(log_buffer,"I=%s, %s, %s", pen_strings[use_pen], use_async_dma ? "A" : "S", region_strings[use_region]); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"C=%u.%.2u", calc_time/1000, (calc_time - ((calc_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"D=%u.%.2u", dma_time/1000, (dma_time - ((dma_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"R=%u.%.2u", render_time/1000, (render_time - ((render_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"U=%u.%.2u", update_time/1000, (update_time - ((update_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"T=%u.%.2u", total_time/1000, (total_time - ((total_time/1000)*1000)) / 10); + printf("%s\n", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + if(use_region == urBounce) + { + graphics->set_pen(white_pen); + graphics->line(Point(region.x+1, region.y+1), Point(region.x+1+region.w-3, region.y+1)); + graphics->line(Point(region.x+1+region.w-3, region.y+1), Point(region.x+1+region.w-3, region.y+1+region.h-3)); + graphics->line(Point(region.x+1+region.w-3, region.y+1+region.h-3), Point(region.x+1, region.y+1+region.h-3)); + graphics->line(Point(region.x+1, region.y+1+region.h-3), Point(region.x+1, region.y+1)); + } + + render_time = timer.elapsed(); + + // now update the display + if(use_region != urNone){ + st7789->partial_update(graphics, region); + } + else { + st7789->update(graphics); + } + + update_time = timer.elapsed(); + total_time = total_timer.elapsed(); + + } + + return 0; +} diff --git a/examples/pico_display_2/CMakeLists.txt b/examples/pico_display_2/CMakeLists.txt index d40b117e7..6f24b2cbc 100644 --- a/examples/pico_display_2/CMakeLists.txt +++ b/examples/pico_display_2/CMakeLists.txt @@ -3,12 +3,24 @@ add_subdirectory(mandelbrot) set(OUTPUT_NAME pico_display2_demo) add_executable( - ${OUTPUT_NAME} + pico_display2_demo pico_display_2_demo.cpp ) # Pull in pico libraries that we need -target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics) +target_link_libraries(pico_display2_demo pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics) # create map/bin/hex file etc. -pico_add_extra_outputs(${OUTPUT_NAME}) \ No newline at end of file +pico_add_extra_outputs(pico_display2_demo) + + +add_executable( + pico_display2_async_region + pico_display_2_async_region.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(pico_display2_async_region pico_stdlib hardware_spi hardware_pwm hardware_dma rgbled button pico_display_2 st7789 pico_graphics) + +# create map/bin/hex file etc. +pico_add_extra_outputs(pico_display2_async_region) \ No newline at end of file diff --git a/examples/pico_display_2/pico_display_2_async_region.cpp b/examples/pico_display_2/pico_display_2_async_region.cpp new file mode 100644 index 000000000..f408f5b04 --- /dev/null +++ b/examples/pico_display_2/pico_display_2_async_region.cpp @@ -0,0 +1,326 @@ +// Example for testing Async DMA and Partial Updates +// +// Button A: Cycles arround screen rotations +// Button B: Cycles around pen (display) format - PenRGB565, PenRGB332, Pen* and Pen4 +// Button C: Turns Async DMA on and off +// Button D: Cycles around Partial Region updates: None, Half, Bounce and Full +// +// Timings are logged to the screen and also the console +// I = The pen format, Async status and region. +// C = The time taken in ms for calculations +// D = The remaining free DMA time in ms, so the amount of free time you have for more calculations. +// R = The time to render the frame in ms. +// U = The time blocked in updating the display in ms. +// T = The total time in ms. +// +// Async Updates +// ------------- +// Async updates will only have an effect if you are using the native pen format 565 +// If this is turned on then when you call Update() it will return immediately after setting up the DMA +// At this point you can use the processor as usual apart from you must not send anything to the spi +// or to the display buffer. In this example we are doing the calculations for the bouncing pixels here. +// Async updates also work for region updates using 565. +// +// So when this example first starts dma is off, in a release build here we see these timings: +// C=8.91, D=0.00, R=9.21, U=23.36, T=41.49 +// If we turn Async DMA on with the X button we see: +// C=8.92, D=14.42, R=9.24, U=0.02, T=32.59 +// +// So we can see the time we take to do the calculations (C) stays the same. +// The time to render stays (R) the same +// The time to update the display has dropped from 23.36ms to 0.01ms though +// What is happening is that the display is now being updated via DMA +// So now we are doing our cacluations whilst this is happening. +// D is now set to 14.42ms, this is the time remaining till the display is ready (not using the dma) +// if we add C, D and U we get 23.36ms which is the time taken when without async we were blocked updating the display. +// so now with Async on we can use that previously blocked time for our calculations. +// We can see the total time drop from 41.49ms to 32.59ms, a nice little speedup. +// We also have a spare 14.42 ms to do more processing so the speedup for other applications could be much higher. +// +// Partial Updates +// =============== +// +// There may be cases where you don't want to update the whole display. +// If you know you have only updated a small part of the framebuffer then you can use Partial Updates to only send that part to the display. +// +// This example allows you to test partial_update() +// Using Button Y you can cycle around different modes: +// None: Partial updates turned off. +// Half: The left half of the display is updated. +// Bounce: A quarter of the display is updated with this region bouncing around. For testing clipping. +// Full: The full display is updated as a partial area, Currently this can speed things up for some pens +// For example P4 drops from around 34.3ms to 26.1ms when using partial updates for the full screen. + +#include +#include +#include +#include + +#include "pico_display_2.hpp" +#include "drivers/st7789/st7789.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" +#include "rgbled.hpp" +#include "button.hpp" + +#define NUM_PIXELS (4000) + + +class ElapsedUs +{ +public: + ElapsedUs() + { + last_time = time_us_64(); + } + + uint64_t elapsed(void) + { + uint64_t time_now = time_us_64(); + uint64_t elapsed = time_now - last_time; + last_time = time_now; + return elapsed; + } + +private: + uint64_t last_time; +}; + +using namespace pimoroni; + + +Button button_a(PicoDisplay2::A); +Button button_b(PicoDisplay2::B); +Button button_x(PicoDisplay2::X); +Button button_y(PicoDisplay2::Y); + +enum UsePen{up565, up332, up8, up4, upCount}; +enum UseRegion {urNone, urHalf, urBounce, urFull, urCount}; + +static const char* pen_strings[] = {"565", "332", "P8", "P4"}; +static const char* region_strings[] = {"None", "Half", "Bounce", "Full"}; + +// allocate our own framebuffer to share between pens/formats +// this stops any memory fragmentation +uint8_t framebuffer[PicoDisplay2::WIDTH * PicoDisplay2::HEIGHT * 2]; + + +int main() { + stdio_init_all(); + + // default to async dma off + bool use_async_dma = false; + + // default to no rotation + Rotation rotation = ROTATE_0; + + // default to 565 + UsePen use_pen = up565; + + // default to no region + UseRegion use_region = urNone; + Rect region; + Point bounce = {0, 0}; + Point bounce_inc = {1, 1}; + + + ST7789* st7789 = nullptr; + PicoGraphics* graphics = nullptr; + + char log_buffer[64]; + + // turn the led off + RGBLED led(PicoDisplay2::LED_R, PicoDisplay2::LED_G, PicoDisplay2::LED_B); + led.set_rgb(0, 0, 0); + + + struct pt { + float x; + float y; + float dx; + float dy; + uint16_t use_pen; + }; + + + Pen black_pen, white_pen; + + std::vector pixels(NUM_PIXELS); + uint update_time = 0; + uint calc_time = 0; + uint dma_time = 0; + uint render_time = 0; + uint total_time = 0; + + + while(true) { + ElapsedUs total_timer; + ElapsedUs timer; + + bool change_rotation = button_a.read(); + bool change_pen = button_b.read(); + bool change_async = button_x.read(); + bool change_region = button_y.read(); + + // cycle arounf regions + if(change_region){ + use_region = (UseRegion)((use_region+1) % urCount); + } + + if(st7789 == nullptr || change_rotation || change_pen || change_async) { + // switch async DMA mode + if(change_async) { + use_async_dma = ! use_async_dma; + delete st7789; + st7789 = nullptr; + } + + // cycle around rotations + if(change_rotation) { + rotation = (Rotation)((rotation + 90) % 360); + delete st7789; + st7789 = nullptr; + } + + // cycle around pens (graphics mode) + if(change_pen) { + use_pen = (UsePen)((use_pen + 1) % upCount); + } + + if(st7789 == nullptr) + st7789 = new ST7789(PicoDisplay2::WIDTH, PicoDisplay2::HEIGHT, rotation, false, get_spi_pins(BG_SPI_FRONT), use_async_dma); + + delete graphics; + switch(use_pen) + { + case up565 : graphics = new PicoGraphics_PenRGB565(st7789->width, st7789->height, framebuffer); break; + case up332 : graphics = new PicoGraphics_PenRGB332(st7789->width, st7789->height, framebuffer); break; + case up8 : graphics = new PicoGraphics_PenP8(st7789->width, st7789->height, framebuffer); break; + case up4 : graphics = new PicoGraphics_PenP4(st7789->width, st7789->height, framebuffer); break; + default: break; + } + + st7789->set_backlight(150); + black_pen = graphics->create_pen(0, 0, 0); + white_pen = graphics->create_pen(255, 255, 255); + + graphics->set_font("bitmap8"); + + pixels.clear(); + for(int i = 0; i < 3000; i++) { + pt pixel; + pixel.x = rand() % graphics->bounds.w; + pixel.y = rand() % graphics->bounds.h; + pixel.dx = float(rand() % 255) / 128.0f; + pixel.dy = float(rand() % 255) / 128.0f; + pixel.use_pen = graphics->create_pen(rand() % 255, rand() % 255, rand() % 255); + pixels.push_back(pixel); + } + } + + switch (use_region) + { + case urFull : region = Rect(0, 0, st7789->width, st7789->height); break; + case urHalf : region = Rect(0, 0, st7789->width / 2, st7789->height); break; + case urBounce : region = Rect(bounce.x - (st7789->width / 4), bounce.y - (st7789->height / 4), st7789->width / 2, st7789->height / 2); break; + default: break; + } + + // update data + for(auto &pixel : pixels) { + pixel.x += pixel.dx; + pixel.y += pixel.dy; + if(pixel.x < 0) pixel.dx *= -1; + if(pixel.x >= graphics->bounds.w) pixel.dx *= -1; + if(pixel.y < 0) pixel.dy *= -1; + if(pixel.y >= graphics->bounds.h) pixel.dy *= -1; + } + + bounce.x += bounce_inc.x; + bounce.y += bounce_inc.y; + if(bounce.x < 0) bounce_inc.x = 1; + if(bounce.x >= graphics->bounds.w) bounce_inc.x = -1; + if(bounce.y < 0) bounce_inc.y = 1; + if(bounce.y >= graphics->bounds.h) bounce_inc.y = -1; + + + calc_time = timer.elapsed(); + + // if async wait for last update to finish before rendering + if(use_async_dma) { + st7789->wait_for_update_to_finish(); + } + + dma_time = timer.elapsed(); + + // render + graphics->set_pen(black_pen); + graphics->clear(); + + for(auto &pixel : pixels) { + graphics->set_pen(pixel.use_pen); + graphics->pixel(Point(pixel.x, pixel.y)); + } + + graphics->set_pen(white_pen); + + graphics->line(Point(0,0), Point(graphics->bounds.w-1, 0)); + graphics->line(Point(0,0), Point(0, graphics->bounds.h-1)); + + graphics->line(Point(graphics->bounds.w-1,0), Point(graphics->bounds.w-1, graphics->bounds.h-1)); + graphics->line(Point(0,graphics->bounds.h-1), Point(graphics->bounds.w-1, graphics->bounds.h-1)); + + graphics->set_pen(white_pen); + + uint spacing = 20; + float scale = 2; + int y = -(spacing/2); + sprintf(log_buffer,"I=%s, %s, %s", pen_strings[use_pen], use_async_dma ? "A" : "S", region_strings[use_region]); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"C=%u.%.2u", calc_time/1000, (calc_time - ((calc_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"D=%u.%.2u", dma_time/1000, (dma_time - ((dma_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"R=%u.%.2u", render_time/1000, (render_time - ((render_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"U=%u.%.2u", update_time/1000, (update_time - ((update_time/1000)*1000)) / 10); + printf("%s, ", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + sprintf(log_buffer,"T=%u.%.2u", total_time/1000, (total_time - ((total_time/1000)*1000)) / 10); + printf("%s\n", log_buffer); + graphics->text(std::string(log_buffer), Point(10, y+=spacing), graphics->bounds.w, scale); + + if(use_region == urBounce) + { + graphics->set_pen(white_pen); + graphics->line(Point(region.x+1, region.y+1), Point(region.x+1+region.w-3, region.y+1)); + graphics->line(Point(region.x+1+region.w-3, region.y+1), Point(region.x+1+region.w-3, region.y+1+region.h-3)); + graphics->line(Point(region.x+1+region.w-3, region.y+1+region.h-3), Point(region.x+1, region.y+1+region.h-3)); + graphics->line(Point(region.x+1, region.y+1+region.h-3), Point(region.x+1, region.y+1)); + } + + render_time = timer.elapsed(); + + // now update the display + if(use_region != urNone){ + st7789->partial_update(graphics, region); + } + else { + st7789->update(graphics); + } + + update_time = timer.elapsed(); + total_time = total_timer.elapsed(); + + } + + return 0; +} diff --git a/examples/pico_plus_2_psram/CMakeLists.txt b/examples/pico_plus_2_psram/CMakeLists.txt new file mode 100644 index 000000000..995007b56 --- /dev/null +++ b/examples/pico_plus_2_psram/CMakeLists.txt @@ -0,0 +1,14 @@ +set(OUTPUT_NAME pico_plus_2_psram) + +add_executable( + ${OUTPUT_NAME} + pico_plus_2_psram.cpp + rp2_psram.cpp +) + +add_subdirectory(lwmem) +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib lwmem) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/pico_plus_2_psram/LwmemAllocator.h b/examples/pico_plus_2_psram/LwmemAllocator.h new file mode 100644 index 000000000..df9463494 --- /dev/null +++ b/examples/pico_plus_2_psram/LwmemAllocator.h @@ -0,0 +1,33 @@ +#include "lwmem/lwmem.hpp" + +template +struct LwmemAllocator +{ + typedef T value_type; + + LwmemAllocator() = default; + + template + constexpr LwmemAllocator(const LwmemAllocator &) noexcept {} + + [[nodiscard]] T* allocate(std::size_t n) + { + if (auto p = static_cast(lwmem_malloc(n * sizeof(T)))) + return p; + else + return nullptr; + } + + void deallocate(T* p, std::size_t n) noexcept + { + lwmem_free(p); + } +private: +}; + +template +bool operator==(const LwmemAllocator &, const LwmemAllocator &) { return true; } + +template +bool operator!=(const LwmemAllocator &, const LwmemAllocator &) { return false; } + diff --git a/examples/pico_plus_2_psram/README.md b/examples/pico_plus_2_psram/README.md new file mode 100644 index 000000000..7e19ca689 --- /dev/null +++ b/examples/pico_plus_2_psram/README.md @@ -0,0 +1,9 @@ +# Example code for using the psram + +note: The c++ allocation and new stuff should really be thowing exceptions, but this seems to be disabled by default for the pico builds. + +`rp2_psram.cpp` and `rp2_psram.h` are from: https://github.com/pimoroni/micropython + +`lwmem` from: https://github.com/MaJerle/lwmem and is a submodule so make sure you fetch that. + + diff --git a/examples/pico_plus_2_psram/TestClass.h b/examples/pico_plus_2_psram/TestClass.h new file mode 100644 index 000000000..4aa88953e --- /dev/null +++ b/examples/pico_plus_2_psram/TestClass.h @@ -0,0 +1,26 @@ +#include "lwmem/lwmem.hpp" + +class TestClass +{ +public: + TestClass(uint32_t uVal) + : m_uVal(uVal) + {}; + + ~TestClass(void) + { + } + + void *operator new(size_t size) + { + void * p = lwmem_malloc(size); + return p; + } + + void operator delete(void *ptr) + { + lwmem_free(ptr); + } +private: + uint32_t m_uVal; +}; \ No newline at end of file diff --git a/examples/pico_plus_2_psram/lwmem b/examples/pico_plus_2_psram/lwmem new file mode 160000 index 000000000..2b083176a --- /dev/null +++ b/examples/pico_plus_2_psram/lwmem @@ -0,0 +1 @@ +Subproject commit 2b083176a0dcbc2a5a81c8165c7738a3e443a84d diff --git a/examples/pico_plus_2_psram/pico_plus_2_psram.cpp b/examples/pico_plus_2_psram/pico_plus_2_psram.cpp new file mode 100644 index 000000000..45a65aeb6 --- /dev/null +++ b/examples/pico_plus_2_psram/pico_plus_2_psram.cpp @@ -0,0 +1,362 @@ +#include "stdio.h" +#include +#include "pico/stdlib.h" +#include "boards/pimoroni_pico_plus2_rp2350.h" + +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#include "hardware/structs/xip_ctrl.h" +#include "hardware/clocks.h" +#include "hardware/sync.h" +#include "rp2_psram.h" +#include "TestClass.h" +#include "lwmem/lwmem.hpp" + +#include +#include "lwmemAllocator.h" + +// 128 kb + +// #define TEST_SPEED_SIZE (16384) +// #define TEST_SPEED_ITTERATIONS 128 +// #define TEST_SPEED_SIZE (16384/8) +// #define TEST_SPEED_ITTERATIONS (128*8) + +#define TEST_MEM_ITTERATIONS 1 + +#pragma GCC push_options +#pragma GCC optimize ("-O2") + + +class ElapsedUs +{ +public: + ElapsedUs() + { + last_time = time_us_64(); + } + + float elapsed(void) + { + uint64_t time_now = time_us_64(); + uint64_t elapsed = time_now - last_time; + last_time = time_now; + return (float)elapsed/1000.0f; + } + +private: + uint64_t last_time; +}; + +float CalcMbPerSec(uint32_t uSize, float fMsecs) +{ + float fBytesMS = (float)uSize/fMsecs; + float fBytesSec = fBytesMS * 1000; + float fSpeedMBS = fBytesSec/1024/1024; + + return fSpeedMBS; +} + + +absolute_time_t abs_start_time; +uint64_t microsecs; +double secs; +float fMySecs; +int *xi; + +void start_time() +{ + abs_start_time = get_absolute_time(); +} + +void end_time() +{ + microsecs = absolute_time_diff_us(abs_start_time, get_absolute_time()); + secs = microsecs * 1e-6; +} + +int testI(int jmax, int arraymax) +{ + int i, j; + int toti; + volatile int* xvi = xi; + + ElapsedUs timer; + start_time(); + + for (j=0; j> v; + v.push_back(1); + v.push_back(2); + + + if(tc && v[0] == 1 && v[1] == 2) + { + printf("C++ test passed\n"); + delete tc; + } + else + printf("C++ test failed\n"); + +} + +void TestMem(volatile uint32_t *pMem, uint32_t uSize, uint32_t uIterations, const char *str) +{ + uint32_t uTotalSize = uSize * uIterations; + printf("%s Block Size = %lu bytes, Iterations = %lu, Total size for = %lu bytes, using addr %p\n", str, uSize*sizeof(uint32_t), uIterations, uTotalSize, pMem); + ElapsedUs timer; + + float fWriteTime = 0.0f; + float fReadTime = 0.0f; + + for(uint32_t i = 0; i < uIterations; i++) + { + volatile uint32_t *p = pMem; + + for(uint32_t u = 0; u < uSize/32; u++) + { + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + *p++ = u; + } + + fWriteTime += timer.elapsed(); + + for(uint32_t u = 0; u < uSize/32; u++) + { + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + (void)*p++; + } + fReadTime += timer.elapsed(); + } + printf(" Read: %fMB/Sec, Write %fMB/Sec\n\n", CalcMbPerSec(uTotalSize*4, fReadTime), CalcMbPerSec(uTotalSize*4, fWriteTime)); + // printf("Write took %fms %fMB/Sec\n", fWriteTime, CalcMbPerSec(uTotalSize, fWriteTime)); + // printf("Read took %fms %fMB/Sec (%u)\n", fReadTime, CalcMbPerSec(uTotalSize, fReadTime), bReadOk); +} + +void TestSpeed(uint32_t uSize, uint32_t uIterations) +{ + volatile uint32_t *pNormalRam = (uint32_t *)malloc(uSize * sizeof(uint32_t)); + if(pNormalRam) + { + TestMem(pNormalRam, uSize, uIterations, "RAM:"); + free((uint32_t *)pNormalRam); + } + + volatile uint32_t *pPsram = (uint32_t *)lwmem_malloc(uSize * sizeof(uint32_t)); + if(pPsram) + { + TestMem(pPsram, uSize, uIterations, "PSRAM:"); + lwmem_free((uint32_t *)pPsram); + } +} + +void TestXI(void) +{ + uint32_t uXiTestSize = (1536); + xi = (int *)lwmem_malloc(uXiTestSize * sizeof(int)); + testI(1000, uXiTestSize); + uint32_t uTotalMem = uXiTestSize * 4 * 1000; + float fBytesSecs = (float)uTotalMem/secs; + float fSpeedMBS = fBytesSecs/1024/1024; + printf("XI Test 6k = %f, %f, %f\n", secs, fMySecs, fSpeedMBS); +} + +int main() { + size_t uRamSize = psram_init(PIMORONI_PICO_PLUS2_PSRAM_CS_PIN); + + stdio_init_all(); + + + printf("Pico Plus 2 PSRAM tests\n\n"); + + + printf("Psram installed = %u bytes, %u KB, %u MB\n", uRamSize, uRamSize/1024, uRamSize/1024/1024); + + if(uRamSize) + { + TestMem(uRamSize); + + InitHeap(uRamSize); + + TestCpp(); + + TestXI(); + + TestSpeed(1024,1024); + TestSpeed(1024*2,1024/2); + TestSpeed(1024*4,1024/4); + TestSpeed(1024*8,1024/8); + TestSpeed(1024*16,1024/16); + TestSpeed(1024*32,1024/32); + } + else + printf("No ram found, tests not run\n"); + + return 0; +} + +#pragma GCC pop_options \ No newline at end of file diff --git a/examples/pico_plus_2_psram/rp2_psram.cpp b/examples/pico_plus_2_psram/rp2_psram.cpp new file mode 100644 index 000000000..364c17569 --- /dev/null +++ b/examples/pico_plus_2_psram/rp2_psram.cpp @@ -0,0 +1,166 @@ +#include "hardware/structs/ioqspi.h" +#include "hardware/structs/qmi.h" +#include "hardware/structs/xip_ctrl.h" +#include "hardware/clocks.h" +#include "hardware/sync.h" +#include "rp2_psram.h" + + +size_t __no_inline_not_in_flash_func(psram_detect)() { + int psram_size = 0; + + uint32_t intr_stash = save_and_disable_interrupts(); + + // Try and read the PSRAM ID via direct_csr. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | QMI_DIRECT_CSR_EN_BITS; + + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + // Exit out of QMI in case we've inited already + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + + // Transmit as quad. + qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | 0xf5; + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + (void)qmi_hw->direct_rx; + + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + + // Read the id + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + uint8_t kgd = 0; + uint8_t eid = 0; + + for (size_t i = 0; i < 7; i++) + { + if (i == 0) { + qmi_hw->direct_tx = 0x9f; + } else { + qmi_hw->direct_tx = 0xff; + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { + } + + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + if (i == 5) { + kgd = qmi_hw->direct_rx; + } else if (i == 6) { + eid = qmi_hw->direct_rx; + } else { + (void)qmi_hw->direct_rx; + } + } + + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + if (kgd == 0x5D) { + psram_size = 1024 * 1024; // 1 MiB + uint8_t size_id = eid >> 5; + if (eid == 0x26 || size_id == 2) { + psram_size *= 8; // 8 MiB + } else if (size_id == 0) { + psram_size *= 2; // 2 MiB + } else if (size_id == 1) { + psram_size *= 4; // 4 MiB + } + } + + restore_interrupts(intr_stash); + return psram_size; +} + +size_t __no_inline_not_in_flash_func(psram_init)(uint cs_pin) { + gpio_set_function(cs_pin, GPIO_FUNC_XIP_CS1); + + size_t psram_size = psram_detect(); + + if (!psram_size) { + return 0; + } + + // Enable direct mode, PSRAM CS, clkdiv of 10. + qmi_hw->direct_csr = 10 << QMI_DIRECT_CSR_CLKDIV_LSB | \ + QMI_DIRECT_CSR_EN_BITS | \ + QMI_DIRECT_CSR_AUTO_CS1N_BITS; + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + // Enable QPI mode on the PSRAM + const uint CMD_QPI_EN = 0x35; + qmi_hw->direct_tx = QMI_DIRECT_TX_NOPUSH_BITS | CMD_QPI_EN; + + while (qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) { + ; + } + + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133MHz. + // So: don't allow running at divisor 1 above 100MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100MHz (i.e. sys clock > 200MHz). + const int max_psram_freq = 133000000; + const int clock_hz = clock_get_hz(clk_sys); + int divisor = (clock_hz + max_psram_freq - 1) / max_psram_freq; + if (divisor == 1 && clock_hz > 100000000) { + divisor = 2; + } + int rxdelay = divisor; + if (clock_hz / divisor > 100000000) { + rxdelay += 1; + } + + // - Max select must be <= 8us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + const int clock_period_fs = 1000000000000000ll / clock_hz; + const int max_select = (125 * 1000000) / clock_period_fs; // 125 = 8000ns / 64 + const int min_deselect = (18 * 1000000 + (clock_period_fs - 1)) / clock_period_fs - (divisor + 1) / 2; + + qmi_hw->m[1].timing = 1 << QMI_M1_TIMING_COOLDOWN_LSB | + QMI_M1_TIMING_PAGEBREAK_VALUE_1024 << QMI_M1_TIMING_PAGEBREAK_LSB | + max_select << QMI_M1_TIMING_MAX_SELECT_LSB | + min_deselect << QMI_M1_TIMING_MIN_DESELECT_LSB | + rxdelay << QMI_M1_TIMING_RXDELAY_LSB | + divisor << QMI_M1_TIMING_CLKDIV_LSB; + + // Set PSRAM commands and formats + qmi_hw->m[1].rfmt = + QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | \ + QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | \ + QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | \ + 6 << QMI_M0_RFMT_DUMMY_LEN_LSB; + + qmi_hw->m[1].rcmd = 0xEB; + + qmi_hw->m[1].wfmt = + QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | \ + QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | \ + QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | \ + QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | \ + QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | \ + QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB; + + qmi_hw->m[1].wcmd = 0x38; + + // Disable direct mode + qmi_hw->direct_csr = 0; + + // Enable writes to PSRAM + hw_set_bits(&xip_ctrl_hw->ctrl, XIP_CTRL_WRITABLE_M1_BITS); + + return psram_size; +} \ No newline at end of file diff --git a/examples/pico_plus_2_psram/rp2_psram.h b/examples/pico_plus_2_psram/rp2_psram.h new file mode 100644 index 000000000..c7119d384 --- /dev/null +++ b/examples/pico_plus_2_psram/rp2_psram.h @@ -0,0 +1,10 @@ +#include "pico/stdlib.h" + +#ifndef MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H +#define MICROPY_INCLUDED_RP2_MACHINE_PSRAM_H + +#define PSRAM_LOCATION _u(0x11000000) + +extern size_t psram_init(uint cs_pin); + +#endif \ No newline at end of file diff --git a/libraries/pico_graphics/README.md b/libraries/pico_graphics/README.md index 633ef8cdf..4483125d9 100644 --- a/libraries/pico_graphics/README.md +++ b/libraries/pico_graphics/README.md @@ -131,6 +131,28 @@ Rect b(15, 10, 10, 10); a.intersects(b) == true ``` +##### Rect.equals + +```c++ +bool Rect::equals(const Rect &r); +``` + +`equals` allows you to check if a `Rect` is equal to another `Rect`, so the rectangles are the same: + +```c++ +Rect a(10, 10, 10, 10); +Rect b(30, 10, 10, 10); +a.equals(b) == false +``` + +And these do: + +```c++ +Rect a(10, 10, 10, 10); +Rect b(10, 10, 10, 10); +a.equals(b) == true +``` + ##### Rect.intersection ```c++ diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index 4e32e4ec3..9bafb3535 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -13,6 +13,7 @@ namespace pimoroni { void PicoGraphics::set_pixel_dither(const Point &p, const RGB565 &c) {}; void PicoGraphics::set_pixel_dither(const Point &p, const uint8_t &c) {}; void PicoGraphics::frame_convert(PenType type, conversion_callback_func callback) {}; + void PicoGraphics::rect_convert(PenType type, Rect rect, conversion_callback_func callback) {}; void PicoGraphics::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) {}; int PicoGraphics::get_palette_size() {return 0;} @@ -429,6 +430,29 @@ namespace pimoroni { callback(row_buf[buf_idx], 0); } + void PicoGraphics::rect_convert_rgb565(Rect rect, conversion_callback_func callback, next_scanline_func get_next_scanline) + { + // Allocate two temporary buffers, as the callback may transfer by DMA + // while we're preparing the next part of the row + uint16_t row_buf[2][rect.w]; + int buf_idx = 0; + for(auto i = 0; i < rect.h; i++) { + get_next_scanline(row_buf[buf_idx]); + + // Transfer a filled buffer and swap to the next one + callback(row_buf[buf_idx], rect.w * sizeof(RGB565)); + buf_idx ^= 1; + } + + // Callback with zero length to ensure previous buffer is fully written + callback(row_buf[buf_idx], 0); + } + + void PicoGraphics::create_owned_frame_buffer(size_t size_in_bytes) { + frame_buffer = (void*)new uint8_t[size_in_bytes]; + owned_frame_buffer = true; + } + // Common function for frame buffer conversion to 565 pixel format void PicoGraphics::frame_convert_rgb888(conversion_callback_func callback, next_pixel_func_rgb888 get_next_pixel) { diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 7445c5342..04e24b993 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -175,6 +175,7 @@ namespace pimoroni { bool contains(const Point &p) const; bool contains(const Rect &p) const; bool intersects(const Rect &r) const; + bool equals(const Rect &r) const; Rect intersection(const Rect &r) const; void inflate(int32_t v); @@ -228,7 +229,9 @@ namespace pimoroni { typedef std::function conversion_callback_func; typedef std::function next_pixel_func; + typedef std::function next_scanline_func; typedef std::function next_pixel_func_rgb888; + //typedef std::function scanline_interrupt_func; //scanline_interrupt_func scanline_interrupt = nullptr; @@ -236,6 +239,8 @@ namespace pimoroni { const bitmap::font_t *bitmap_font; const hershey::font_t *hershey_font; + bool owned_frame_buffer = false; + static constexpr RGB332 rgb_to_rgb332(uint8_t r, uint8_t g, uint8_t b) { return RGB(r, g, b).to_rgb332(); } @@ -272,6 +277,12 @@ namespace pimoroni { set_font(&font6); }; + virtual ~PicoGraphics() + { + if(owned_frame_buffer) + delete (uint8_t*)frame_buffer; + }; + virtual void set_pen(uint c) = 0; virtual void set_pen(uint8_t r, uint8_t g, uint8_t b) = 0; virtual void set_pixel(const Point &p) = 0; @@ -291,6 +302,7 @@ namespace pimoroni { virtual void set_pixel_dither(const Point &p, const uint8_t &c); virtual void set_pixel_alpha(const Point &p, const uint8_t a); virtual void frame_convert(PenType type, conversion_callback_func callback); + virtual void rect_convert(PenType type, Rect rect, conversion_callback_func callback); virtual void sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent); virtual bool render_pico_vector_tile(const Rect &bounds, uint8_t* alpha_data, uint32_t stride, uint8_t alpha_type) { return false; } @@ -323,13 +335,15 @@ namespace pimoroni { protected: void frame_convert_rgb565(conversion_callback_func callback, next_pixel_func get_next_pixel); + void rect_convert_rgb565(Rect rect, conversion_callback_func callback, next_scanline_func get_next_scanline); + void create_owned_frame_buffer(size_t size_in_bytes); void frame_convert_rgb888(conversion_callback_func callback, next_pixel_func_rgb888 get_next_pixel); }; class PicoGraphics_Pen1Bit : public PicoGraphics { public: uint8_t color; - + PicoGraphics_Pen1Bit(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; @@ -404,6 +418,7 @@ namespace pimoroni { void set_pixel_dither(const Point &p, const RGB &c) override; void frame_convert(PenType type, conversion_callback_func callback) override; + void rect_convert(PenType type, Rect rect, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return (w * h / 8) * 3; } @@ -437,6 +452,7 @@ namespace pimoroni { void set_pixel_dither(const Point &p, const RGB &c) override; void frame_convert(PenType type, conversion_callback_func callback) override; + void rect_convert(PenType type, Rect rect, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return w * h / 2; } @@ -470,6 +486,7 @@ namespace pimoroni { void set_pixel_dither(const Point &p, const RGB &c) override; void frame_convert(PenType type, conversion_callback_func callback) override; + void rect_convert(PenType type, Rect rect, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return w * h; } @@ -494,6 +511,7 @@ namespace pimoroni { void sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) override; void frame_convert(PenType type, conversion_callback_func callback) override; + void rect_convert(PenType type, Rect rect, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return w * h; } @@ -541,6 +559,11 @@ namespace pimoroni { DisplayDriver(uint16_t width, uint16_t height, Rotation rotation) : width(width), height(height), rotation(rotation) {}; + virtual ~DisplayDriver() + { + cleanup(); + } + virtual void update(PicoGraphics *display) {}; virtual void partial_update(PicoGraphics *display, Rect region) {}; virtual bool set_update_speed(int update_speed) {return false;}; diff --git a/libraries/pico_graphics/pico_graphics_pen_1bit.cpp b/libraries/pico_graphics/pico_graphics_pen_1bit.cpp index 30fb1e53b..b0cd7c338 100644 --- a/libraries/pico_graphics/pico_graphics_pen_1bit.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_1bit.cpp @@ -6,7 +6,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_1BIT; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } } diff --git a/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp b/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp index 6fc2cb8c2..69c2b6e68 100644 --- a/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_1bitY.cpp @@ -6,7 +6,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_1BIT; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } } diff --git a/libraries/pico_graphics/pico_graphics_pen_3bit.cpp b/libraries/pico_graphics/pico_graphics_pen_3bit.cpp index 5faba1cdc..ce2788838 100644 --- a/libraries/pico_graphics/pico_graphics_pen_3bit.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_3bit.cpp @@ -6,7 +6,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_3BIT; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } cache_built = false; } @@ -133,4 +133,6 @@ namespace pimoroni { } } } + void PicoGraphics_Pen3Bit::rect_convert(PenType type, Rect rect, conversion_callback_func callback) { + }; } \ No newline at end of file diff --git a/libraries/pico_graphics/pico_graphics_pen_p4.cpp b/libraries/pico_graphics/pico_graphics_pen_p4.cpp index 68b3d62c2..9f7a58f37 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p4.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p4.cpp @@ -6,7 +6,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_P4; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } for(auto i = 0u; i < palette_size; i++) { palette[i] = { @@ -156,4 +156,32 @@ namespace pimoroni { }); } } + void PicoGraphics_PenP4::rect_convert(PenType type, Rect rect, conversion_callback_func callback) { + if(type == PEN_RGB565) { + // Cache the RGB888 palette as RGB565 + RGB565 cache[palette_size]; + for(auto i = 0u; i < palette_size; i++) { + cache[i] = palette[i].to_rgb565(); + } + + uint scan_line = 0; + rect_convert_rgb565(rect, callback, [&](RGB565 *data) { + uint8_t *src = (uint8_t *)frame_buffer + (rect.x + ((rect.y + scan_line) * bounds.w)) / 2; + uint8_t o = (rect.x % 2 == 1) ? 0 : 4; + + for(int32_t i= 0; i < rect.w; i++) { + uint8_t c = *src; + uint8_t b = (c >> o) & 0xf; // bit value shifted to position + + // Increment to next 4-bit entry + o ^= 4; + if (o != 0) ++src; + + data[i] = cache[b]; + } + + scan_line++; + }); + } + } } diff --git a/libraries/pico_graphics/pico_graphics_pen_p8.cpp b/libraries/pico_graphics/pico_graphics_pen_p8.cpp index f7d0be73d..a4bc26731 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p8.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p8.cpp @@ -5,7 +5,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_P8; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } for(auto i = 0u; i < palette_size; i++) { palette[i] = {uint8_t(i), uint8_t(i), uint8_t(i)}; @@ -125,4 +125,24 @@ namespace pimoroni { }); } } + + void PicoGraphics_PenP8::rect_convert(PenType type, Rect rect, conversion_callback_func callback) { + if(type == PEN_RGB565) { + // Cache the RGB888 palette as RGB565 + RGB565 cache[palette_size]; + for(auto i = 0u; i < palette_size; i++) { + cache[i] = palette[i].to_rgb565(); + } + + // Treat our void* frame_buffer as uint8_t + uint8_t *src = (uint8_t *)frame_buffer + rect.x + (rect.y * bounds.w); + rect_convert_rgb565(rect, callback, [&](RGB565 *data) { + for(int32_t i= 0; i < rect.w; i++) { + data[i] = cache[*src++]; + } + src+=bounds.w - rect.w; + }); + } + } + } diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp index 1bce808fc..6e42cc8cc 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb332.cpp @@ -6,7 +6,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_RGB332; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } } void PicoGraphics_PenRGB332::set_pen(uint c) { @@ -101,6 +101,17 @@ namespace pimoroni { }); } } + void PicoGraphics_PenRGB332::rect_convert(PenType type, Rect rect, conversion_callback_func callback) { + if(type == PEN_RGB565) { + // Treat our void* frame_buffer as uint8_t + uint8_t *src = (uint8_t *)frame_buffer + rect.x + (rect.y * bounds.w); + rect_convert_rgb565(rect, callback, [&](RGB565 *data) { + for(int32_t i= 0; i < rect.w; i++) + data[i] = rgb332_to_rgb565_lut[*src++]; + src += bounds.w - rect.w; + }); + } + } void PicoGraphics_PenRGB332::sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) { //int sprite_x = (sprite & 0x0f) << 3; //int sprite_y = (sprite & 0xf0) >> 1; diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp index f17c6be47..bf978fe1e 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb565.cpp @@ -5,7 +5,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_RGB565; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } } void PicoGraphics_PenRGB565::set_pen(uint c) { diff --git a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp index 6cc9d2adf..4f935b4dd 100644 --- a/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_rgb888.cpp @@ -5,7 +5,7 @@ namespace pimoroni { : PicoGraphics(width, height, frame_buffer) { this->pen_type = PEN_RGB888; if(this->frame_buffer == nullptr) { - this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); + create_owned_frame_buffer(buffer_size(width, height)); } } void PicoGraphics_PenRGB888::set_pen(uint c) { diff --git a/libraries/pico_graphics/types.cpp b/libraries/pico_graphics/types.cpp index 53dac95ed..aa4273389 100644 --- a/libraries/pico_graphics/types.cpp +++ b/libraries/pico_graphics/types.cpp @@ -28,6 +28,10 @@ namespace pimoroni { return !(x > r.x + r.w || x + w < r.x || y > r.y + r.h || y + h < r.y); } + bool Rect::equals(const Rect &r) const { + return r.x == x && r.y == y && r.w == w && r.h == h; + } + Rect Rect::intersection(const Rect &r) const { return Rect(std::max(x, r.x), std::max(y, r.y), diff --git a/micropython/modules/qrcode b/micropython/modules/qrcode index 1a7c2d67a..9371aca7a 160000 --- a/micropython/modules/qrcode +++ b/micropython/modules/qrcode @@ -1 +1 @@ -Subproject commit 1a7c2d67a8cf24848b92acb6379328ad6842ab77 +Subproject commit 9371aca7abdf4e17526dcb3893168ca5900dc79f diff --git a/micropython/modules/ulab b/micropython/modules/ulab index 65c941a80..e68bb707b 160000 --- a/micropython/modules/ulab +++ b/micropython/modules/ulab @@ -1 +1 @@ -Subproject commit 65c941a8059afe1cfd6f4c2b15d0ade798dc24f2 +Subproject commit e68bb707b20ee326d84ab75fc9fb35f2e85b87e3