From 5f8e7556f053ceb5fbd85686f2356928b0b9fd99 Mon Sep 17 00:00:00 2001 From: Mike Bell Date: Tue, 9 May 2023 22:32:33 +0100 Subject: [PATCH] Begin DV Stick display driver --- drivers/CMakeLists.txt | 2 + drivers/aps6404/CMakeLists.txt | 1 + drivers/aps6404/aps6404.cmake | 12 + drivers/aps6404/aps6404.cpp | 255 ++++++++++++++++++ drivers/aps6404/aps6404.hpp | 65 +++++ drivers/aps6404/aps6404.pio | 222 +++++++++++++++ drivers/dv_display/CMakeLists.txt | 1 + drivers/dv_display/dv_display.cmake | 10 + drivers/dv_display/dv_display.cpp | 102 +++++++ drivers/dv_display/dv_display.hpp | 105 ++++++++ examples/CMakeLists.txt | 1 + examples/dv_stick/CMakeLists.txt | 1 + examples/dv_stick/dv_stick_test.cmake | 14 + examples/dv_stick/dv_stick_test.cpp | 77 ++++++ libraries/pico_graphics/pico_graphics.cmake | 1 + libraries/pico_graphics/pico_graphics.hpp | 30 ++- .../pico_graphics_pen_dv_rgb555.cpp | 29 ++ 17 files changed, 927 insertions(+), 1 deletion(-) create mode 100644 drivers/aps6404/CMakeLists.txt create mode 100644 drivers/aps6404/aps6404.cmake create mode 100644 drivers/aps6404/aps6404.cpp create mode 100644 drivers/aps6404/aps6404.hpp create mode 100644 drivers/aps6404/aps6404.pio create mode 100644 drivers/dv_display/CMakeLists.txt create mode 100644 drivers/dv_display/dv_display.cmake create mode 100644 drivers/dv_display/dv_display.cpp create mode 100644 drivers/dv_display/dv_display.hpp create mode 100644 examples/dv_stick/CMakeLists.txt create mode 100644 examples/dv_stick/dv_stick_test.cmake create mode 100644 examples/dv_stick/dv_stick_test.cpp create mode 100644 libraries/pico_graphics/pico_graphics_pen_dv_rgb555.cpp diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index fb66d2a5..1186c847 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -44,3 +44,5 @@ add_subdirectory(psram_display) add_subdirectory(inky73) add_subdirectory(shiftregister) add_subdirectory(mlx90640) +add_subdirectory(aps6404) +add_subdirectory(dv_display) \ No newline at end of file diff --git a/drivers/aps6404/CMakeLists.txt b/drivers/aps6404/CMakeLists.txt new file mode 100644 index 00000000..794176b9 --- /dev/null +++ b/drivers/aps6404/CMakeLists.txt @@ -0,0 +1 @@ +include(aps6404.cmake) \ No newline at end of file diff --git a/drivers/aps6404/aps6404.cmake b/drivers/aps6404/aps6404.cmake new file mode 100644 index 00000000..62d4517e --- /dev/null +++ b/drivers/aps6404/aps6404.cmake @@ -0,0 +1,12 @@ +set(DRIVER_NAME aps6404) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/aps6404.cpp +) + +pico_generate_pio_header(${DRIVER_NAME} ${CMAKE_CURRENT_LIST_DIR}/aps6404.pio) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_pio hardware_dma hardware_irq) \ No newline at end of file diff --git a/drivers/aps6404/aps6404.cpp b/drivers/aps6404/aps6404.cpp new file mode 100644 index 00000000..e06be353 --- /dev/null +++ b/drivers/aps6404/aps6404.cpp @@ -0,0 +1,255 @@ +#include +#include "aps6404.hpp" +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/sync.h" +#include "hardware/clocks.h" +#include "pico/stdlib.h" +#include "aps6404.pio.h" + +namespace pimoroni { + APS6404::APS6404(uint pin_csn, uint pin_d0, PIO pio) + : pin_csn(pin_csn) + , pin_d0(pin_d0) + , pio(pio) + { + // Initialize data pins + for (int i = 0; i < 4; ++i) { + gpio_init(pin_d0 + i); + gpio_disable_pulls(pin_d0 + i); + } + + pio_prog = &sram_reset_program; + pio_offset = pio_add_program(pio, &sram_reset_program); + pio_sm = pio_claim_unused_sm(pio, true); + + // Claim DMA channels + dma_channel = dma_claim_unused_channel(true); + read_cmd_dma_channel = dma_claim_unused_channel(true); + } + + void APS6404::init() { + pio_sm_set_enabled(pio, pio_sm, false); + pio_remove_program(pio, pio_prog, pio_offset); + + pio_prog = &sram_reset_program; + pio_offset = pio_add_program(pio, &sram_reset_program); + aps6404_reset_program_init(pio, pio_sm, pio_offset, pin_csn, pin_d0); + + sleep_us(200); + pio_sm_put_blocking(pio, pio_sm, 0x00000007u); + pio_sm_put_blocking(pio, pio_sm, 0x66000000u); + pio_sm_put_blocking(pio, pio_sm, 0x00000007u); + pio_sm_put_blocking(pio, pio_sm, 0x99000000u); + pio_sm_put_blocking(pio, pio_sm, 0x00000007u); + pio_sm_put_blocking(pio, pio_sm, 0x35000000u); + sleep_us(500); + + adjust_clock(); + } + + void APS6404::set_qpi() { + pio_sm_set_enabled(pio, pio_sm, false); + pio_remove_program(pio, pio_prog, pio_offset); + + pio_prog = &sram_reset_program; + pio_offset = pio_add_program(pio, &sram_reset_program); + aps6404_reset_program_init(pio, pio_sm, pio_offset, pin_csn, pin_d0); + pio_sm_put_blocking(pio, pio_sm, 0x00000007u); + pio_sm_put_blocking(pio, pio_sm, 0x35000000u); + + while (!pio_sm_is_tx_fifo_empty(pio, pio_sm) || pio->sm[pio_sm].addr != pio_offset); + + adjust_clock(); + } + + void APS6404::set_spi() { + pio_sm_set_enabled(pio, pio_sm, false); + pio_remove_program(pio, pio_prog, pio_offset); + + pio_prog = &sram_reset_qpi_program; + pio_offset = pio_add_program(pio, &sram_reset_qpi_program); + aps6404_program_init(pio, pio_sm, pio_offset, pin_csn, pin_d0, false, false, true); + pio_sm_put_blocking(pio, pio_sm, 0x00000001u); + pio_sm_put_blocking(pio, pio_sm, 0xF5000000u); + } + + void APS6404::adjust_clock() { + pio_sm_set_enabled(pio, pio_sm, false); + pio_remove_program(pio, pio_prog, pio_offset); + + if (clock_get_hz(clk_sys) > 296000000) { + pio_prog = &sram_fast_program; + pio_offset = pio_add_program(pio, &sram_fast_program); + aps6404_program_init(pio, pio_sm, pio_offset, pin_csn, pin_d0, false, true, false); + } + else if (clock_get_hz(clk_sys) < 130000000) { + pio_prog = &sram_slow_program; + pio_offset = pio_add_program(pio, &sram_slow_program); + aps6404_program_init(pio, pio_sm, pio_offset, pin_csn, pin_d0, true, false, false); + } + else { + pio_prog = &sram_program; + pio_offset = pio_add_program(pio, &sram_program); + aps6404_program_init(pio, pio_sm, pio_offset, pin_csn, pin_d0, false, false, false); + } + } + + void APS6404::write(uint32_t addr, uint32_t* data, uint32_t len_in_bytes) { + wait_for_finish_blocking(); + + setup_cmd_buffer_dma(true); + + for (int len = len_in_bytes, page_len = std::min(PAGE_SIZE, len); + len > 0; + addr += page_len, data += page_len >> 2, len -= page_len, page_len = std::min(PAGE_SIZE, len)) + { + wait_for_finish_blocking(); + + pio_sm_put_blocking(pio, pio_sm, (page_len << 1) - 1); + pio_sm_put_blocking(pio, pio_sm, 0x38000000u | addr); + pio_sm_put_blocking(pio, pio_sm, pio_offset + sram_offset_do_write); + + dma_channel_config c = dma_channel_get_default_config(dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_dreq(&c, pio_get_dreq(pio, pio_sm, true)); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_bswap(&c, true); + + dma_channel_configure( + dma_channel, &c, + &pio->txf[pio_sm], + data, + (page_len >> 2) + 1, + true + ); + } + } + + void APS6404::write_repeat(uint32_t addr, uint32_t data, uint32_t len_in_bytes) { + repeat_data = data; + wait_for_finish_blocking(); + + setup_cmd_buffer_dma(true); + + for (int len = len_in_bytes, page_len = std::min(PAGE_SIZE, len); + len > 0; + addr += page_len, len -= page_len, page_len = std::min(PAGE_SIZE, len)) + { + wait_for_finish_blocking(); + + pio_sm_put_blocking(pio, pio_sm, (page_len << 1) - 1); + pio_sm_put_blocking(pio, pio_sm, 0x38000000u | addr); + pio_sm_put_blocking(pio, pio_sm, pio_offset + sram_offset_do_write); + + dma_channel_config c = dma_channel_get_default_config(dma_channel); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, false); + channel_config_set_dreq(&c, pio_get_dreq(pio, pio_sm, true)); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_bswap(&c, true); + + dma_channel_configure( + dma_channel, &c, + &pio->txf[pio_sm], + &repeat_data, + (page_len >> 2) + 1, + true + ); + } + } + + void APS6404::read(uint32_t addr, uint32_t* read_buf, uint32_t len_in_words) { + start_read(read_buf, len_in_words); + + uint32_t first_page_len = (PAGE_SIZE - (addr & (PAGE_SIZE - 1))) >> 2; + + if (first_page_len >= len_in_words) { + pio_sm_put_blocking(pio, pio_sm, (len_in_words * 8) - 4); + pio_sm_put_blocking(pio, pio_sm, 0xeb000000u | addr); + pio_sm_put_blocking(pio, pio_sm, pio_offset + sram_offset_do_read); + } + else { + // Must always reset the cmd buffer DMA because writes use the same DREQ + setup_cmd_buffer_dma(); + uint32_t* cmd_buf = add_read_to_cmd_buffer(multi_read_cmd_buffer, addr, len_in_words); + dma_channel_transfer_from_buffer_now(read_cmd_dma_channel, multi_read_cmd_buffer, cmd_buf - multi_read_cmd_buffer); + } + } + + void APS6404::multi_read(uint32_t* addresses, uint32_t* lengths, uint32_t num_reads, uint32_t* read_buf, int chain_channel) { + uint32_t total_len = 0; + uint32_t* cmd_buf = multi_read_cmd_buffer; + for (uint32_t i = 0; i < num_reads; ++i) { + total_len += lengths[i]; + cmd_buf = add_read_to_cmd_buffer(cmd_buf, addresses[i], lengths[i]); + } + + start_read(read_buf, total_len, chain_channel); + setup_cmd_buffer_dma(); + + dma_channel_transfer_from_buffer_now(read_cmd_dma_channel, multi_read_cmd_buffer, cmd_buf - multi_read_cmd_buffer); + } + + void APS6404::start_read(uint32_t* read_buf, uint32_t total_len_in_words, int chain_channel) { + wait_for_finish_blocking(); + + dma_channel_config c = dma_channel_get_default_config(dma_channel); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_dreq(&c, pio_get_dreq(pio, pio_sm, false)); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_bswap(&c, true); + if (chain_channel >= 0) { + channel_config_set_chain_to(&c, chain_channel); + } + + dma_channel_configure( + dma_channel, &c, + read_buf, + &pio->rxf[pio_sm], + total_len_in_words, + true + ); + } + + void APS6404::setup_cmd_buffer_dma(bool clear) { + dma_channel_config c = dma_channel_get_default_config(read_cmd_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + if (!clear) channel_config_set_dreq(&c, pio_get_dreq(pio, pio_sm, true)); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + + dma_channel_configure( + read_cmd_dma_channel, &c, + &pio->txf[pio_sm], + multi_read_cmd_buffer, + 0, + false + ); + } + + uint32_t* APS6404::add_read_to_cmd_buffer(uint32_t* cmd_buf, uint32_t addr, uint32_t len_in_words) { + uint32_t len = (PAGE_SIZE - (addr & (PAGE_SIZE - 1))) >> 2; + + while (true) { + *cmd_buf++ = (len * 8) - 4; + *cmd_buf++ = 0xeb000000u | addr; + *cmd_buf++ = pio_offset + sram_offset_do_read; + len_in_words -= len; + addr += len << 2; + + if (len_in_words == 0) break; + + len = len_in_words; + if (len > (PAGE_SIZE >> 2)) len = PAGE_SIZE >> 2; + } + + return cmd_buf; + } + + void APS6404::wait_for_finish_blocking() { + dma_channel_wait_for_finish_blocking(dma_channel); + } +} diff --git a/drivers/aps6404/aps6404.hpp b/drivers/aps6404/aps6404.hpp new file mode 100644 index 00000000..0d881bc8 --- /dev/null +++ b/drivers/aps6404/aps6404.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include "hardware/pio.h" + +namespace pimoroni { + class APS6404 { + public: + static constexpr int RAM_SIZE = 8 * 1024 * 1024; + static constexpr int PAGE_SIZE = 1024; + + APS6404(uint pin_csn = 17, uint pin_d0 = 19, PIO pio = pio0); + + void init(); + + void set_qpi(); + void set_spi(); + + // Must be called if the system clock rate is changed after init(). + void adjust_clock(); + + // Start a write, this completes asynchronously, this function blocks if another + // transfer is already in progress + // Writes should always be <= 1KB. + void write(uint32_t addr, uint32_t* data, uint32_t len_in_bytes); + void write_repeat(uint32_t addr, uint32_t data, uint32_t len_in_bytes); + + // Start a read, this completes asynchronously, this function only blocks if another + // transfer is already in progress + void read(uint32_t addr, uint32_t* read_buf, uint32_t len_in_words); + + // Start multiple reads to the same buffer. They completes asynchronously, + // this function only blocks if another transfer is already in progress + void multi_read(uint32_t* addresses, uint32_t* lengths, uint32_t num_addresses, uint32_t* read_buf, int chain_channel = -1); + + // Read and block until completion + void read_blocking(uint32_t addr, uint32_t* read_buf, uint32_t len_in_words) { + read(addr, read_buf, len_in_words); + wait_for_finish_blocking(); + } + + // Block until any outstanding read or write completes + void wait_for_finish_blocking(); + + private: + void start_read(uint32_t* read_buf, uint32_t total_len_in_words, int chain_channel = -1); + void setup_cmd_buffer_dma(bool clear = false); + uint32_t* add_read_to_cmd_buffer(uint32_t* cmd_buf, uint32_t addr, uint32_t len_in_words); + + uint pin_csn; // CSn, SCK must be next pin after CSn + uint pin_d0; // D0, D1, D2, D3 must be consecutive + + PIO pio; + uint16_t pio_sm; + uint16_t pio_offset; + const pio_program* pio_prog; + + uint dma_channel; + uint read_cmd_dma_channel; + + static constexpr int MULTI_READ_MAX_PAGES = 128; + uint32_t multi_read_cmd_buffer[3 * MULTI_READ_MAX_PAGES]; + uint32_t repeat_data; + }; +} diff --git a/drivers/aps6404/aps6404.pio b/drivers/aps6404/aps6404.pio new file mode 100644 index 00000000..02069e09 --- /dev/null +++ b/drivers/aps6404/aps6404.pio @@ -0,0 +1,222 @@ +; Pins: +; - CSn is side-set 0 +; - SCK is side-set 1 +; - D0 is IN/OUT/SET pin 0 +; +; DMA is 32 bit. Stream takes form: +; 32 bits For read: length of read in nibbles, minus 4 +; For write: length of write in nibbles, minus 1 +; Original data length in bits must be a multiple of 32 +; 8 bits AP6404 cmd (0xeb = read, 0x38 = write) +; 24 bits address +; 32 bits sram_offset_do_read or sram_offset_do_write +; For write, data + +; Program for running at high frequencies (130MHz-290MHz RP2040 system clock) +; Waits an extra half cycle on read as the data back from the PSRAM appears a fixed +; amount of *time* after the CLK +.program sram +.side_set 2 +.wrap_target +top: + set x, 6 side 0b01 + out y, 32 side 0b01 + set pindirs, 0xf side 0b01 + +; Write command, followed by data +addr_lp: + out pins, 4 side 0b00 + jmp x--, addr_lp side 0b10 + out pins, 4 side 0b00 + out pc, 32 side 0b10 +PUBLIC do_write: + out pins, 4 side 0b00 + jmp y--, do_write side 0b10 + set pindirs, 0 side 0b00 + out null, 32 side 0b01 + jmp top side 0b01 + +; Read command +PUBLIC do_read: +; Wait for 8.5 clocks (6 + 2.5 for latencies) + set x, 7 side 0b00 +rwt_lp: + set pindirs, 0 side 0b10 + jmp x-- rwt_lp side 0b00 + + set x, 2 side 0b10 +rd_lp: + in pins, 4 side 0b00 + jmp y--, rd_lp side 0b10 + +; Read the remaining 3 nibbles, but no more clocks so we don't read beyond +; the requested length from the SRAM. +rd_rem: + in pins, 4 side 0b01 + jmp x--, rd_rem side 0b01 +.wrap + +; Program for running at low frequencies (<130MHz RP2040 system clock) +.program sram_slow +.side_set 2 +.wrap_target +top: + set x, 6 side 0b01 + out y, 32 side 0b01 + set pindirs, 0xf side 0b01 + +; Write command, followed by data +addr_lp: + out pins, 4 side 0b00 + jmp x--, addr_lp side 0b10 + out pins, 4 side 0b00 + out pc, 32 side 0b10 +PUBLIC do_write: + out pins, 4 side 0b00 + jmp y--, do_write side 0b10 + set pindirs, 0 side 0b00 + out null, 32 side 0b01 + jmp top side 0b01 + +; Read command +PUBLIC do_read: + set pindirs, 0 side 0b00 +; Wait for 8 clocks (6 + 2 for latencies) + set x, 6 side 0b10 +rwt_lp: + nop side 0b00 + jmp x-- rwt_lp side 0b10 + + set x, 2 side 0b00 +rd_lp: + in pins, 4 side 0b10 + jmp y--, rd_lp side 0b00 + +; Read the remaining 3 nibbles, but no more clocks so we don't read beyond +; the requested length from the SRAM. +rd_rem: + in pins, 4 side 0b01 + jmp x--, rd_rem side 0b01 +.wrap + +; Program for running at extreme frequencies (>290MHz RP2040 system clock) +; Runs clock at 1/3rd RP2040 clock with 66% duty cycle. Out of spec but +; then everything we are doing here is! +.program sram_fast +.side_set 2 +.wrap_target +top: + set x, 6 side 0b01 + out y, 32 side 0b01 + set pindirs, 0xf side 0b01 + +; Write command, followed by data +addr_lp: + out pins, 4 side 0b00 + jmp x--, addr_lp [1] side 0b10 + out pins, 4 side 0b00 + out pc, 32 [1] side 0b10 +PUBLIC do_write: + out pins, 4 side 0b00 + jmp y--, do_write [1] side 0b10 + set pindirs, 0 side 0b00 + out null, 32 side 0b01 + jmp top side 0b01 + +; Read command +PUBLIC do_read: + set pindirs, 0 side 0b00 +; Wait for 8 clocks (6 + 2 for latencies) + set x, 6 [1] side 0b10 +rwt_lp: + nop side 0b00 + jmp x-- rwt_lp [1] side 0b10 + + set x, 2 side 0b00 +rd_lp: + nop side 0b10 + in pins, 4 side 0b10 + jmp y--, rd_lp side 0b00 + +; Read the remaining 2 nibbles, but no more clocks so we don't read beyond +; the requested length from the SRAM. +rd_rem: + in pins, 4 [1] side 0b01 + jmp x--, rd_rem side 0b01 +.wrap + +; Write only program that writes a short (<32 bit) command, +; ignoring the rest of the 32 bit input word. +; This is used to write the reset command and enter QPI mode. +.program sram_reset +.side_set 2 +.wrap_target + out y, 32 side 0b01 + pull side 0b01 +wr_lp: + out pins, 1 side 0b00 + jmp y--, wr_lp side 0b10 + out null, 32 [7] side 0b01 ; Ensure large enough gap between commands for reset time + nop [7] side 0b01 +.wrap + +.program sram_reset_qpi +.side_set 2 +.wrap_target + out y, 32 side 0b01 + pull side 0b01 + set pindirs, 0xF side 0b01 +wr_lp: + out pins, 4 side 0b00 + jmp y--, wr_lp side 0b10 + out null, 32 [7] side 0b01 ; Ensure large enough gap between commands for reset time + set pindirs, 0 [7] side 0b01 +.wrap + + +% c-sdk { +void aps6404_reset_program_init(PIO pio, uint sm, uint offset, uint csn, uint mosi) { + uint miso = mosi + 1; + pio_gpio_init(pio, csn); + pio_gpio_init(pio, csn + 1); + pio_gpio_init(pio, mosi); + pio_sm_set_consecutive_pindirs(pio, sm, csn, 2, true); + pio_sm_set_consecutive_pindirs(pio, sm, mosi, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, miso, 1, false); + + pio_sm_config c = sram_reset_program_get_default_config(offset); + sm_config_set_in_pins(&c, miso); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_out_pins(&c, mosi, 1); + sm_config_set_out_shift(&c, false, true, 32); + sm_config_set_sideset_pins(&c, csn); + sm_config_set_clkdiv(&c, 4.f); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +void aps6404_program_init(PIO pio, uint sm, uint offset, uint csn, uint mosi, bool slow, bool fast, bool reset) { + pio_gpio_init(pio, csn); + pio_gpio_init(pio, csn + 1); + pio_gpio_init(pio, mosi); + pio_gpio_init(pio, mosi + 1); + pio_gpio_init(pio, mosi + 2); + pio_gpio_init(pio, mosi + 3); + pio_sm_set_consecutive_pindirs(pio, sm, csn, 2, true); + pio_sm_set_consecutive_pindirs(pio, sm, mosi, 4, false); + + pio_sm_config c = slow ? sram_slow_program_get_default_config(offset) : + fast ? sram_fast_program_get_default_config(offset) : + reset ? sram_reset_qpi_program_get_default_config(offset) : + sram_program_get_default_config(offset); + sm_config_set_in_pins(&c, mosi); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_out_pins(&c, mosi, 4); + sm_config_set_out_shift(&c, false, true, 32); + sm_config_set_set_pins(&c, mosi, 4); + sm_config_set_sideset_pins(&c, csn); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%} diff --git a/drivers/dv_display/CMakeLists.txt b/drivers/dv_display/CMakeLists.txt new file mode 100644 index 00000000..e8543caa --- /dev/null +++ b/drivers/dv_display/CMakeLists.txt @@ -0,0 +1 @@ +include(dv_display.cmake) \ No newline at end of file diff --git a/drivers/dv_display/dv_display.cmake b/drivers/dv_display/dv_display.cmake new file mode 100644 index 00000000..e7121a3e --- /dev/null +++ b/drivers/dv_display/dv_display.cmake @@ -0,0 +1,10 @@ +set(DRIVER_NAME dv_display) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib aps6404 pimoroni_i2c) diff --git a/drivers/dv_display/dv_display.cpp b/drivers/dv_display/dv_display.cpp new file mode 100644 index 00000000..21182ec2 --- /dev/null +++ b/drivers/dv_display/dv_display.cpp @@ -0,0 +1,102 @@ +#include "dv_display.hpp" + +#include +#include +#include + +namespace pimoroni { + + void DVDisplay::init() { + gpio_init(RAM_SEL); + gpio_put(RAM_SEL, 0); + gpio_set_dir(RAM_SEL, GPIO_OUT); + + gpio_init(VSYNC); + gpio_set_dir(VSYNC, GPIO_IN); + + ram.init(); + write_header(0); + + gpio_put(RAM_SEL, 1); + ram.init(); + write_header(1); + + bank = 0; + gpio_put(RAM_SEL, 0); + i2c.reg_write_uint8(I2C_ADDR, I2C_REG_START, 1); + } + + void DVDisplay::flip() { + bank ^= 1; + ram.wait_for_finish_blocking(); + while (gpio_get(VSYNC) == 0); + gpio_put(RAM_SEL, bank); + } + + void DVDisplay::write(uint32_t address, size_t len, const uint16_t *data) + { + if ((uintptr_t)data & 2) { + write(address, 1, *data); + data++; + len--; + address += 2; + } + + if (len > 0) { + ram.write(address, (uint32_t*)data, len << 1); + } + } + + void DVDisplay::write(uint32_t address, size_t len, const uint16_t colour) + { + uint32_t val = colour | ((uint32_t)colour << 16); + + ram.write_repeat(address, val, len << 1); + } + + void DVDisplay::read(uint32_t address, size_t len, uint16_t *data) + { + ram.read_blocking(address, (uint32_t*)data, (len + 1) >> 1); + } + + void DVDisplay::write_pixel(const Point &p, uint16_t colour) + { + write(pointToAddress(p), 1, colour); + } + + void DVDisplay::write_pixel_span(const Point &p, uint l, uint16_t colour) + { + write(pointToAddress(p), l, colour); + } + + void DVDisplay::read_pixel_span(const Point &p, uint l, uint16_t *data) + { + read(pointToAddress(p), l, data); + } + + void DVDisplay::write_header(uint bank) + { + uint32_t buf[8]; + buf[0] = 0x4F434950; + buf[1] = 0x01010101; + buf[2] = (uint32_t)width << 16; + buf[3] = (uint32_t)height << 16; + buf[4] = 0x00000001; + buf[5] = 0x00010000 + height + (bank << 24); + buf[6] = 0x00000000; + ram.write(0, buf, 7 * 4); + ram.wait_for_finish_blocking(); + + uint addr = 4 * 7; + for (int i = 0; i < height; i += 8) { + for (int j = 0; j < 8; ++j) { + buf[j] = 0x91000000 + ((i + j) * width * 2) + base_address; + } + ram.write(addr, buf, 8 * 4); + ram.wait_for_finish_blocking(); + addr += 4 * 8; + } + + sleep_us(100); + } +} diff --git a/drivers/dv_display/dv_display.hpp b/drivers/dv_display/dv_display.hpp new file mode 100644 index 00000000..ea26fb7c --- /dev/null +++ b/drivers/dv_display/dv_display.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "common/pimoroni_common.hpp" +#include "common/pimoroni_i2c.hpp" +#include "drivers/aps6404/aps6404.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" + + +// Pimoroni DV Stick + +namespace pimoroni { + + // This is ARGB1555 only for now + class DVDisplay : public IDirectDisplayDriver { + //-------------------------------------------------- + // Variables + //-------------------------------------------------- + private: + // Ram accessed through the APS6404 driver + APS6404 ram; + + // I2C interface to driver + I2C i2c; + + // interface pins + static constexpr uint CS = 17; + static constexpr uint D0 = 19; + static constexpr uint VSYNC = 16; + static constexpr uint RAM_SEL = 8; + static constexpr uint I2C_SDA = 6; + static constexpr uint I2C_SCL = 7; + + // I2C + static constexpr uint I2C_ADDR = 0x0D; + static constexpr uint I2C_REG_START = 0xF9; + + static constexpr uint32_t base_address = 0x10000; + uint16_t width = 0; + uint16_t height = 0; + uint8_t bank = 0; + + public: + DVDisplay(uint16_t width, uint16_t height) + : ram(CS, D0) + , i2c(I2C_SDA, I2C_SCL) + , width(width), height(height) + {} + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + void test(void){ + char writeBuffer[256]; + char readBuffer[256]; + + uint mb = 8; + + for (uint b = 0; b < 2; ++b) { + gpio_put(RAM_SEL, b); + + for(uint k = 0; k < 1024*mb; k++) { + sprintf(writeBuffer, "%u-%u", b, k); + + write(k*1024, strlen(writeBuffer)+1, (uint16_t *)writeBuffer); + } + } + + for (uint b = 0; b < 2; ++b) { + gpio_put(RAM_SEL, b); + bool bSame = true; + for(uint k = 0; k < 1024*mb && bSame; k++) + { + sprintf(writeBuffer, "%u-%u", b, k); + read(k*1024, strlen(writeBuffer)+1, (uint16_t *)readBuffer); + bSame = strcmp(writeBuffer, readBuffer) == 0; + printf("[%u] %s == %s ? %s\n", k, writeBuffer, readBuffer, bSame ? "Success" : "Failure"); + } + } + } + + void write_pixel(const Point &p, uint16_t colour) override; + void write_pixel_span(const Point &p, uint l, uint16_t colour) override; + void read_pixel_span(const Point &p, uint l, uint16_t *data) override; + + void init(); + void flip(); + + private: + void write(uint32_t address, size_t len, const uint16_t *data); + void write(uint32_t address, size_t len, const uint16_t byte); + void read(uint32_t address, size_t len, uint16_t *data); + + void write_header(uint bank); + + uint32_t pointToAddress(const Point &p) + { + return base_address + ((p.y * (uint32_t)width) + p.x) * 2; + } + }; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d32cc949..eb2ab60a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -62,3 +62,4 @@ add_subdirectory(galactic_unicorn) add_subdirectory(gfx_pack) add_subdirectory(cosmic_unicorn) add_subdirectory(stellar_unicorn) +add_subdirectory(dv_stick) diff --git a/examples/dv_stick/CMakeLists.txt b/examples/dv_stick/CMakeLists.txt new file mode 100644 index 00000000..e48be058 --- /dev/null +++ b/examples/dv_stick/CMakeLists.txt @@ -0,0 +1 @@ +include(dv_stick_test.cmake) \ No newline at end of file diff --git a/examples/dv_stick/dv_stick_test.cmake b/examples/dv_stick/dv_stick_test.cmake new file mode 100644 index 00000000..caea1fd0 --- /dev/null +++ b/examples/dv_stick/dv_stick_test.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME dv_stick_test) + +add_executable( + ${OUTPUT_NAME} + dv_stick_test.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib dv_display hardware_i2c pico_graphics) + +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/dv_stick/dv_stick_test.cpp b/examples/dv_stick/dv_stick_test.cpp new file mode 100644 index 00000000..f3be0650 --- /dev/null +++ b/examples/dv_stick/dv_stick_test.cpp @@ -0,0 +1,77 @@ +#include +#include + +#include +#include "hardware/gpio.h" +#include "drivers/dv_display/dv_display.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" + +using namespace pimoroni; + +#define FRAME_WIDTH 640 +#define FRAME_HEIGHT 480 + +int main() { + stdio_init_all(); + + constexpr uint BUTTON_A = 9; + gpio_init(BUTTON_A); + gpio_set_dir(BUTTON_A, GPIO_IN); + gpio_pull_up(BUTTON_A); + + sleep_ms(5000); + + DVDisplay display(FRAME_WIDTH, FRAME_HEIGHT); + display.init(); + //display.test(); + + PicoGraphics_PenDV_RGB555 graphics(FRAME_WIDTH, FRAME_HEIGHT, display); + + graphics.set_pen(0x001F); + graphics.clear(); + display.flip(); + sleep_ms(2000); + graphics.set_pen(0x7C00); + graphics.clear(); + display.flip(); + + printf("Starting\n"); + + while (true) { + while(gpio_get(BUTTON_A) == 1) { + sleep_ms(10); + } + uint32_t render_start_time = time_us_32(); + + graphics.set_pen(0xFFFF); + graphics.clear(); + + for(int i =0 ; i < 50 ; i++) + { + uint size = 25 + (rand() % 50); + uint x = rand() % graphics.bounds.w; + uint y = rand() % graphics.bounds.h; + + graphics.set_pen(0); + graphics.circle(Point(x, y), size); + + graphics.set_pen(RGB::from_hsv(i * 0.02f, 1.0f, 1.0f).to_rgb555()); + graphics.circle(Point(x, y), size-2); + } + +#if 0 + uint x = 260; //rand() % graphics.bounds.w; + uint y = 468; //rand() % graphics.bounds.h; + printf("Circle at (%d, %d)\n", x, y); + graphics.set_pen(0); + graphics.circle(Point(x, y), 25); + #endif + + uint32_t render_time = time_us_32() - render_start_time; + + uint32_t flip_start_time = time_us_32(); + display.flip(); + uint32_t flip_time = time_us_32() - flip_start_time; + printf("Render: %.3f, flip: %.3f\n", render_time / 1000.f, flip_time / 1000.f); + } +} diff --git a/libraries/pico_graphics/pico_graphics.cmake b/libraries/pico_graphics/pico_graphics.cmake index e069cbef..44be9469 100644 --- a/libraries/pico_graphics/pico_graphics.cmake +++ b/libraries/pico_graphics/pico_graphics.cmake @@ -18,6 +18,7 @@ add_library(pico_graphics ${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_rgb565.cpp ${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_rgb888.cpp ${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_inky7.cpp + ${CMAKE_CURRENT_LIST_DIR}/pico_graphics_pen_dv_rgb555.cpp ) target_include_directories(pico_graphics INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 3b5362b1..4b384136 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -26,6 +26,7 @@ namespace pimoroni { typedef uint8_t RGB332; typedef uint16_t RGB565; + typedef uint16_t RGB555; typedef uint32_t RGB888; @@ -108,6 +109,14 @@ namespace pimoroni { return __builtin_bswap16(p); } + constexpr RGB555 to_rgb555() { + uint16_t p = ((r & 0b11111000) << 7) | + ((g & 0b11111000) << 2) | + ((b & 0b11111000) >> 3); + + return p; + } + constexpr RGB565 to_rgb332() { return (r & 0b11100000) | ((g & 0b11100000) >> 3) | ((b & 0b11000000) >> 6); } @@ -192,7 +201,8 @@ namespace pimoroni { PEN_RGB332, PEN_RGB565, PEN_RGB888, - PEN_INKY7 + PEN_INKY7, + PEN_DV_RGB555 }; void *frame_buffer; @@ -579,4 +589,22 @@ namespace pimoroni { return w * h; } }; + + class PicoGraphics_PenDV_RGB555 : public PicoGraphics { + public: + RGB555 color; + IDirectDisplayDriver &driver; + + PicoGraphics_PenDV_RGB555(uint16_t width, uint16_t height, IDirectDisplayDriver &direct_display_driver); + void set_pen(uint c) override; + void set_pen(uint8_t r, uint8_t g, uint8_t b) override; + int create_pen(uint8_t r, uint8_t g, uint8_t b) override; + int create_pen_hsv(float h, float s, float v) override; + void set_pixel(const Point &p) override; + void set_pixel_span(const Point &p, uint l) override; + + static size_t buffer_size(uint w, uint h) { + return w * h * sizeof(RGB555); + } + }; } diff --git a/libraries/pico_graphics/pico_graphics_pen_dv_rgb555.cpp b/libraries/pico_graphics/pico_graphics_pen_dv_rgb555.cpp new file mode 100644 index 00000000..8e99c0d3 --- /dev/null +++ b/libraries/pico_graphics/pico_graphics_pen_dv_rgb555.cpp @@ -0,0 +1,29 @@ +#include "pico_graphics.hpp" + +namespace pimoroni { + PicoGraphics_PenDV_RGB555::PicoGraphics_PenDV_RGB555(uint16_t width, uint16_t height, IDirectDisplayDriver &direct_display_driver) + : PicoGraphics(width, height, nullptr), + driver(direct_display_driver) + { + this->pen_type = PEN_DV_RGB555; + } + void PicoGraphics_PenDV_RGB555::set_pen(uint c) { + color = c; + } + void PicoGraphics_PenDV_RGB555::set_pen(uint8_t r, uint8_t g, uint8_t b) { + RGB src_color{r, g, b}; + color = src_color.to_rgb555(); + } + int PicoGraphics_PenDV_RGB555::create_pen(uint8_t r, uint8_t g, uint8_t b) { + return RGB(r, g, b).to_rgb555(); + } + int PicoGraphics_PenDV_RGB555::create_pen_hsv(float h, float s, float v) { + return RGB::from_hsv(h, s, v).to_rgb555(); + } + void PicoGraphics_PenDV_RGB555::set_pixel(const Point &p) { + driver.write_pixel(p, color); + } + void PicoGraphics_PenDV_RGB555::set_pixel_span(const Point &p, uint l) { + driver.write_pixel_span(p, l, color); + } +} \ No newline at end of file