diff --git a/common/pimoroni_common.hpp b/common/pimoroni_common.hpp index 568ce3d8..2103c73d 100644 --- a/common/pimoroni_common.hpp +++ b/common/pimoroni_common.hpp @@ -96,6 +96,24 @@ namespace pimoroni { 191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255}; + inline constexpr uint16_t GAMMA_12BIT[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 47, 48, 50, + 52, 53, 55, 57, 59, 61, 63, 65, 68, 70, 72, 75, 78, 80, 83, 86, + 89, 92, 95, 98, 102, 105, 109, 112, 116, 120, 124, 128, 132, 137, 141, 146, + 150, 155, 160, 165, 170, 175, 181, 186, 192, 198, 204, 210, 216, 222, 228, 235, + 242, 249, 256, 263, 270, 277, 285, 293, 301, 309, 317, 325, 334, 342, 351, 360, + 369, 379, 388, 398, 408, 418, 428, 438, 449, 459, 470, 481, 493, 504, 516, 527, + 539, 552, 564, 576, 589, 602, 615, 629, 642, 656, 670, 684, 698, 713, 727, 742, + 758, 773, 789, 804, 820, 837, 853, 870, 887, 904, 921, 939, 957, 975, 993, 1011, + 1030, 1049, 1068, 1088, 1107, 1127, 1147, 1168, 1189, 1209, 1231, 1252, 1274, 1296, 1318, 1340, + 1363, 1386, 1409, 1432, 1456, 1480, 1504, 1529, 1554, 1579, 1604, 1630, 1656, 1682, 1708, 1735, + 1762, 1789, 1817, 1845, 1873, 1901, 1930, 1959, 1988, 2018, 2048, 2078, 2109, 2139, 2171, 2202, + 2234, 2266, 2298, 2331, 2364, 2397, 2430, 2464, 2498, 2533, 2568, 2603, 2638, 2674, 2710, 2747, + 2784, 2821, 2858, 2896, 2934, 2973, 3011, 3050, 3090, 3130, 3170, 3210, 3251, 3292, 3334, 3376, + 3418, 3461, 3504, 3547, 3591, 3635, 3679, 3724, 3769, 3814, 3860, 3906, 3953, 4000, 4047, 4095}; + /* Moved from pico_unicorn.cpp v = (uint16_t)(powf((float)(n) / 255.0f, 2.2) * 16383.0f + 0.5f) */ inline constexpr uint16_t GAMMA_14BIT[256] = { @@ -147,4 +165,4 @@ namespace pimoroni { bool_pair() : first(false), second(false) {} bool_pair(bool first, bool second) : first(first), second(second) {} }; -} \ No newline at end of file +} diff --git a/examples/pico_unicorn/demo.cpp b/examples/pico_unicorn/demo.cpp index 0e5f67c7..bf0789e1 100644 --- a/examples/pico_unicorn/demo.cpp +++ b/examples/pico_unicorn/demo.cpp @@ -7,7 +7,7 @@ using namespace pimoroni; -PicoUnicorn pico_unicorn; +PicoUnicorn<14,1,0, uint16_t, pimoroni::GAMMA_14BIT> pico_unicorn; int main() { bool a_pressed = false; diff --git a/examples/pico_unicorn_plasma/demo.cpp b/examples/pico_unicorn_plasma/demo.cpp index c45435af..ca8f406c 100644 --- a/examples/pico_unicorn_plasma/demo.cpp +++ b/examples/pico_unicorn_plasma/demo.cpp @@ -22,7 +22,7 @@ BSD License using namespace pimoroni; -PicoUnicorn pico_unicorn; +PicoUnicorn<14,1,0, uint16_t, pimoroni::GAMMA_14BIT> pico_unicorn; // Sine table to speed up execution static const int8_t sinetab[256] = { @@ -95,7 +95,7 @@ void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) { int main() { stdio_init_all(); - pico_unicorn.init(); + // pico_unicorn.init(); pico_unicorn.clear(); diff --git a/libraries/pico_unicorn/pico_unicorn.cpp b/libraries/pico_unicorn/pico_unicorn.cpp index ea17a546..0d6e3f08 100644 --- a/libraries/pico_unicorn/pico_unicorn.cpp +++ b/libraries/pico_unicorn/pico_unicorn.cpp @@ -1,349 +1,5 @@ -#include "hardware/dma.h" -#include "hardware/irq.h" -#include "common/pimoroni_common.hpp" - -#ifndef NO_QSTR -#include "pico_unicorn.pio.h" -#endif #include "pico_unicorn.hpp" -// pixel data is stored as a stream of bits delivered in the -// order the PIO needs to manage the shift registers, row -// selects, delays, and latching/blanking -// -// the data consists of 7 rows each of which has 14 frames of -// bcd timing data -// -// each row looks like this: -// -// 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # pixel data -// 0b00000000, # dummy byte to 32-bit align the frame (could be used to extend row select in future) -// 0b01111111, # row 0 select (7-bit row address, 1-bit dummy data) -// 0b00001111, 0b11111111, # bcd tick count (0-65536) -// -// .. next BCD frame for this row (repeat for 14 frames) -// -// .. next row (repeat for 7 rows) -// -// pixels are encoded as 4 bits: r, g, b, dummy to conveniently -// pack them into nibbles - -enum pin { - LED_DATA = 8, - LED_CLOCK = 9, - LED_LATCH = 10, - LED_BLANK = 11, - ROW_0 = 22, - ROW_1 = 21, - ROW_2 = 20, - ROW_3 = 19, - ROW_4 = 18, - ROW_5 = 17, - ROW_6 = 16, - A = 12, - B = 13, - X = 14, - Y = 15, -}; - -static uint32_t dma_channel; -static uint32_t dma_ctrl_channel; - -namespace pimoroni { - PicoUnicorn* PicoUnicorn::unicorn = nullptr; - PIO PicoUnicorn::bitstream_pio = pio0; - uint PicoUnicorn::bitstream_sm = 0; - uint PicoUnicorn::bitstream_sm_offset = 0; - - PicoUnicorn::~PicoUnicorn() { - if(unicorn == this) { - partial_teardown(); - - dma_channel_unclaim(dma_ctrl_channel); // This works now the teardown behaves correctly - dma_channel_unclaim(dma_channel); // This works now the teardown behaves correctly - pio_sm_unclaim(bitstream_pio, bitstream_sm); - pio_remove_program(bitstream_pio, &unicorn_program, bitstream_sm_offset); - - unicorn = nullptr; - } - } - - void PicoUnicorn::partial_teardown() { - // Stop the bitstream SM - pio_sm_set_enabled(bitstream_pio, bitstream_sm, false); - - // Make sure the display is off and switch it to an invisible row, to be safe - const uint pins_to_set = 0b1111111 << ROW_6; - pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set); - - dma_hw->ch[dma_ctrl_channel].al1_ctrl = (dma_hw->ch[dma_ctrl_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_ctrl_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); - dma_hw->ch[dma_channel].al1_ctrl = (dma_hw->ch[dma_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); - // Abort any in-progress DMA transfer - dma_safe_abort(dma_ctrl_channel); - dma_safe_abort(dma_channel); - } - - [[deprecated("Handled by constructor.")]] - void PicoUnicorn::init() { - return; - } - - PicoUnicorn::PicoUnicorn() { - if(unicorn != nullptr) { - partial_teardown(); - } - - // setup pins - gpio_init(pin::LED_DATA); gpio_set_dir(pin::LED_DATA, GPIO_OUT); - gpio_init(pin::LED_CLOCK); gpio_set_dir(pin::LED_CLOCK, GPIO_OUT); - gpio_init(pin::LED_LATCH); gpio_set_dir(pin::LED_LATCH, GPIO_OUT); - gpio_init(pin::LED_BLANK); gpio_set_dir(pin::LED_BLANK, GPIO_OUT); - - gpio_init(pin::ROW_0); gpio_set_dir(pin::ROW_0, GPIO_OUT); - gpio_init(pin::ROW_1); gpio_set_dir(pin::ROW_1, GPIO_OUT); - gpio_init(pin::ROW_2); gpio_set_dir(pin::ROW_2, GPIO_OUT); - gpio_init(pin::ROW_3); gpio_set_dir(pin::ROW_3, GPIO_OUT); - gpio_init(pin::ROW_4); gpio_set_dir(pin::ROW_4, GPIO_OUT); - gpio_init(pin::ROW_5); gpio_set_dir(pin::ROW_5, GPIO_OUT); - gpio_init(pin::ROW_6); gpio_set_dir(pin::ROW_6, GPIO_OUT); - - // initialise the bcd timing values and row selects in the bitstream - for(uint8_t row = 0; row < HEIGHT; row++) { - for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) { - // determine offset in the buffer for this row/frame - uint16_t offset = (row * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame); - - uint16_t row_select_offset = offset + 9; - uint16_t bcd_offset = offset + 10; - - // the last bcd frame is used to allow the fets to discharge to avoid ghosting - if(frame == BCD_FRAMES - 1) { - bitstream[row_select_offset] = 0b11111111; - - uint16_t bcd_ticks = 65535; - bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8; - bitstream[bcd_offset] = (bcd_ticks & 0xff); - - for(uint8_t col = 0; col < 6; col++) { - bitstream[offset + col] = 0xff; - } - }else{ - uint8_t row_select_mask = ~(1 << (7 - row)); - bitstream[row_select_offset] = row_select_mask; - - uint16_t bcd_ticks = 1 << frame; - bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8; - bitstream[bcd_offset] = (bcd_ticks & 0xff); - } - } - } - - // setup button inputs - gpio_set_function(pin::A, GPIO_FUNC_SIO); gpio_set_dir(pin::A, GPIO_IN); gpio_pull_up(pin::A); - gpio_set_function(pin::B, GPIO_FUNC_SIO); gpio_set_dir(pin::B, GPIO_IN); gpio_pull_up(pin::B); - gpio_set_function(pin::X, GPIO_FUNC_SIO); gpio_set_dir(pin::X, GPIO_IN); gpio_pull_up(pin::X); - gpio_set_function(pin::Y, GPIO_FUNC_SIO); gpio_set_dir(pin::Y, GPIO_IN); gpio_pull_up(pin::Y); - - // setup the pio - bitstream_pio = pio0; - if(unicorn == nullptr) { - bitstream_sm = pio_claim_unused_sm(bitstream_pio, true); - bitstream_sm_offset = pio_add_program(bitstream_pio, &unicorn_program); - } - - pio_gpio_init(bitstream_pio, pin::LED_DATA); - pio_gpio_init(bitstream_pio, pin::LED_CLOCK); - pio_gpio_init(bitstream_pio, pin::LED_LATCH); - pio_gpio_init(bitstream_pio, pin::LED_BLANK); - pio_gpio_init(bitstream_pio, pin::ROW_0); - pio_gpio_init(bitstream_pio, pin::ROW_1); - pio_gpio_init(bitstream_pio, pin::ROW_2); - pio_gpio_init(bitstream_pio, pin::ROW_3); - pio_gpio_init(bitstream_pio, pin::ROW_4); - pio_gpio_init(bitstream_pio, pin::ROW_5); - pio_gpio_init(bitstream_pio, pin::ROW_6); - - pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::LED_DATA, 4, true); - pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::ROW_6, 7, true); - - pio_sm_config c = unicorn_program_get_default_config(bitstream_sm_offset); - - // osr shifts right, autopull on, autopull threshold 8 - sm_config_set_out_shift(&c, true, false, 32); - - // configure out, set, and sideset pins - sm_config_set_out_pins(&c, pin::ROW_6, 7); - sm_config_set_sideset_pins(&c, pin::LED_CLOCK); - sm_config_set_set_pins(&c, pin::LED_DATA, 4); - - // join fifos as only tx needed (gives 8 deep fifo instead of 4) - sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); - - // setup chained dma transfer for pixel data to the pio - dma_channel = dma_claim_unused_channel(true); - dma_ctrl_channel = dma_claim_unused_channel(true); - - dma_channel_config ctrl_config = dma_channel_get_default_config(dma_ctrl_channel); - channel_config_set_transfer_data_size(&ctrl_config, DMA_SIZE_32); - channel_config_set_read_increment(&ctrl_config, false); - channel_config_set_write_increment(&ctrl_config, false); - channel_config_set_chain_to(&ctrl_config, dma_channel); - - dma_channel_configure( - dma_ctrl_channel, - &ctrl_config, - &dma_hw->ch[dma_channel].read_addr, - &bitstream_addr, - 1, - false - ); - - dma_channel_config config = dma_channel_get_default_config(dma_channel); - channel_config_set_transfer_data_size(&config, DMA_SIZE_32); - channel_config_set_bswap(&config, false); // byte swap to reverse little endian - channel_config_set_dreq(&config, pio_get_dreq(bitstream_pio, bitstream_sm, true)); - channel_config_set_chain_to(&config, dma_ctrl_channel); - - dma_channel_configure( - dma_channel, - &config, - &bitstream_pio->txf[bitstream_sm], - NULL, - BITSTREAM_LENGTH / 4, - false); - - pio_sm_init(bitstream_pio, bitstream_sm, bitstream_sm_offset, &c); - pio_sm_set_enabled(bitstream_pio, bitstream_sm, true); - - // start the control channel - dma_start_channel_mask(1u << dma_ctrl_channel); - - unicorn = this; - } - - void PicoUnicorn::clear() { - for(uint8_t y = 0; y < HEIGHT; y++) { - for(uint8_t x = 0; x < WIDTH; x++) { - set_pixel(x, y, 0); - } - } - } - - void PicoUnicorn::set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { - if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return; - - // make those coordinates sane - x = (WIDTH - 1) - x; - - // work out the byte offset of this pixel - uint8_t byte_offset = x / 2; - - // check if it's the high or low nibble and create mask and shift value - uint8_t shift = x % 2 == 0 ? 0 : 4; - uint8_t nibble_mask = 0b00001111 << shift; - - uint16_t gr = pimoroni::GAMMA_14BIT[r]; - uint16_t gg = pimoroni::GAMMA_14BIT[g]; - uint16_t gb = pimoroni::GAMMA_14BIT[b]; - - // set the appropriate bits in the separate bcd frames - for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) { - // determine offset in the buffer for this row/frame - uint16_t offset = (y * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame); - - uint8_t rgbd = ((gr & 0b1) << 1) | ((gg & 0b1) << 3) | ((gb & 0b1) << 2); - - // shift to correct nibble - rgbd <<= shift; - - // clear existing data - bitstream[offset + byte_offset] &= ~nibble_mask; - - // set new data - bitstream[offset + byte_offset] |= rgbd; - - gr >>= 1; - gg >>= 1; - gb >>= 1; - } - } - - void PicoUnicorn::set_pixel(uint8_t x, uint8_t y, uint8_t v) { - set_pixel(x, y, v, v, v); - } - - bool PicoUnicorn::is_pressed(uint8_t button) { - return !gpio_get(button); - } - - void PicoUnicorn::dma_safe_abort(uint channel) { - // Tear down the DMA channel. - // This is copied from: https://github.com/raspberrypi/pico-sdk/pull/744/commits/5e0e8004dd790f0155426e6689a66e08a83cd9fc - uint32_t irq0_save = dma_hw->inte0 & (1u << channel); - hw_clear_bits(&dma_hw->inte0, irq0_save); - - dma_hw->abort = 1u << channel; - - // To fence off on in-flight transfers, the BUSY bit should be polled - // rather than the ABORT bit, because the ABORT bit can clear prematurely. - while (dma_hw->ch[channel].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents(); - - // Clear the interrupt (if any) and restore the interrupt masks. - dma_hw->ints0 = 1u << channel; - hw_set_bits(&dma_hw->inte0, irq0_save); - } - - void PicoUnicorn::update(PicoGraphics *graphics) { - if(unicorn == this) { - if(graphics->pen_type == PicoGraphics::PEN_RGB888) { - uint32_t *p = (uint32_t *)graphics->frame_buffer; - - for(int y = 0; y < HEIGHT; y++) { - for(int x = 0; x < WIDTH; x++) { - uint32_t col = *p; - uint8_t r = (col & 0xff0000) >> 16; - uint8_t g = (col & 0x00ff00) >> 8; - uint8_t b = (col & 0x0000ff) >> 0; - p++; - - set_pixel(x, y, r, g, b); - } - } - } - else if(graphics->pen_type == PicoGraphics::PEN_RGB565) { - uint16_t *p = (uint16_t *)graphics->frame_buffer; - for(int y = 0; y < HEIGHT; y++) { - for(int x = 0; x < WIDTH; x++) { - uint16_t col = __builtin_bswap16(*p); - uint8_t r = (col & 0b1111100000000000) >> 8; - uint8_t g = (col & 0b0000011111100000) >> 3; - uint8_t b = (col & 0b0000000000011111) << 3; - p++; - - set_pixel(x, y, r, g, b); - } - } - } - else if(graphics->pen_type == PicoGraphics::PEN_P8 || graphics->pen_type == PicoGraphics::PEN_P4) { - int offset = 0; - graphics->frame_convert(PicoGraphics::PEN_RGB888, [this, offset](void *data, size_t length) mutable { - uint32_t *p = (uint32_t *)data; - for(auto i = 0u; i < length / 4; i++) { - int x = offset % WIDTH; - int y = offset / WIDTH; - - uint32_t col = *p; - uint8_t r = (col & 0xff0000) >> 16; - uint8_t g = (col & 0x00ff00) >> 8; - uint8_t b = (col & 0x0000ff) >> 0; - - set_pixel(x, y, r, g, b); - offset++; - p++; - } - }); - } - } - } - -} +template class pimoroni::PicoUnicorn<14,1,0, uint16_t, pimoroni::GAMMA_14BIT>; +template class pimoroni::PicoUnicorn<12,1,4, uint16_t, pimoroni::GAMMA_12BIT>; +template class pimoroni::PicoUnicorn<12,6,4, uint16_t, pimoroni::GAMMA_12BIT>; diff --git a/libraries/pico_unicorn/pico_unicorn.hpp b/libraries/pico_unicorn/pico_unicorn.hpp index 176a075a..ed2e1927 100644 --- a/libraries/pico_unicorn/pico_unicorn.hpp +++ b/libraries/pico_unicorn/pico_unicorn.hpp @@ -1,10 +1,61 @@ #pragma once +#ifndef NO_QSTR +#include "pico_unicorn.pio.h" +#endif + +#include "hardware/dma.h" +#include "hardware/irq.h" #include "hardware/pio.h" +#include "common/pimoroni_common.hpp" + #include "pico_graphics.hpp" -namespace pimoroni { +// pixel data is stored as a stream of bits delivered in the +// order the PIO needs to manage the shift registers, row +// selects, delays, and latching/blanking +// +// the data consists of 7 rows each of which has 14 frames of +// bcd timing data +// +// each row looks like this: +// +// 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # pixel data +// 0b00000000, # dummy byte to 32-bit align the frame (could be used to extend row select in future) +// 0b01111111, # row 0 select (7-bit row address, 1-bit dummy data) +// 0b00001111, 0b11111111, # bcd tick count (0-65536) +// +// .. next BCD frame for this row (repeat for 14 frames) +// +// .. next row (repeat for 7 rows) +// +// pixels are encoded as 4 bits: r, g, b, dummy to conveniently +// pack them into nibbles +enum pin { + LED_DATA = 8, + LED_CLOCK = 9, + LED_LATCH = 10, + LED_BLANK = 11, + ROW_0 = 22, + ROW_1 = 21, + ROW_2 = 20, + ROW_3 = 19, + ROW_4 = 18, + ROW_5 = 17, + ROW_6 = 16, + A = 12, + B = 13, + X = 14, + Y = 15, +}; + +static uint32_t dma_channel; +static uint32_t dma_ctrl_channel; + +namespace pimoroni { + + template class PicoUnicorn { public: static const int WIDTH = 16; @@ -16,35 +67,343 @@ namespace pimoroni { static const uint32_t ROW_COUNT = 7; static const uint32_t ROW_BYTES = 12; - static const uint32_t BCD_FRAMES = 15; // includes fet discharge frame - static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * BCD_FRAMES); + // // static const uint32_t BCD_FRAMES = 14; // Original version + // static const uint32_t BCD_FRAMES = 12; + // // static const uint32_t DISCHARGE_FRAMES = 1; + // static const uint32_t DISCHARGE_FRAMES = 6; + static const uint32_t DISCHARGE_TICKS = 65535; // how long to run the discharge frame + // // static const uint16_t FRAME_DELAY = 0; // Original version + // static const uint16_t FRAME_DELAY = 4; + static const uint16_t TOTAL_FRAMES = BCD_FRAMES + DISCHARGE_FRAMES; + static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * TOTAL_FRAMES); private: static PIO bitstream_pio; static uint bitstream_sm; static uint bitstream_sm_offset; - + // must be aligned for 32bit dma transfer alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0}; const uint32_t bitstream_addr = (uint32_t)bitstream; static PicoUnicorn* unicorn; public: - PicoUnicorn(); - ~PicoUnicorn(); + PicoUnicorn(){ + if(unicorn != nullptr) { + partial_teardown(); + } - void init(); + // setup pins + gpio_init(pin::LED_DATA); gpio_set_dir(pin::LED_DATA, GPIO_OUT); + gpio_init(pin::LED_CLOCK); gpio_set_dir(pin::LED_CLOCK, GPIO_OUT); + gpio_init(pin::LED_LATCH); gpio_set_dir(pin::LED_LATCH, GPIO_OUT); + gpio_init(pin::LED_BLANK); gpio_set_dir(pin::LED_BLANK, GPIO_OUT); - void clear(); - void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b); - void set_pixel(uint8_t x, uint8_t y, uint8_t v); + gpio_init(pin::ROW_0); gpio_set_dir(pin::ROW_0, GPIO_OUT); + gpio_init(pin::ROW_1); gpio_set_dir(pin::ROW_1, GPIO_OUT); + gpio_init(pin::ROW_2); gpio_set_dir(pin::ROW_2, GPIO_OUT); + gpio_init(pin::ROW_3); gpio_set_dir(pin::ROW_3, GPIO_OUT); + gpio_init(pin::ROW_4); gpio_set_dir(pin::ROW_4, GPIO_OUT); + gpio_init(pin::ROW_5); gpio_set_dir(pin::ROW_5, GPIO_OUT); + gpio_init(pin::ROW_6); gpio_set_dir(pin::ROW_6, GPIO_OUT); - bool is_pressed(uint8_t button); + // initialise the bcd timing values and row selects in the bitstream + for(uint8_t row = 0; row < HEIGHT; row++) { + for(uint8_t frame = 0; frame < TOTAL_FRAMES; frame++) { + // determine offset in the buffer for this row/frame + uint16_t offset = (row * ROW_BYTES * TOTAL_FRAMES) + (ROW_BYTES * frame); + + uint16_t row_select_offset = offset + 9; + uint16_t bcd_offset = offset + 10; + + // the last bcd frame is used to allow the fets to discharge to avoid ghosting + if(frame >= BCD_FRAMES) { + bitstream[row_select_offset] = 0b11111111; + + uint16_t bcd_ticks = DISCHARGE_TICKS; + bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8; + bitstream[bcd_offset] = (bcd_ticks & 0xff); + + for(uint8_t col = 0; col < 6; col++) { + bitstream[offset + col] = 0xff; + } + } else { + uint8_t row_select_mask = ~(1 << (7 - row)); + bitstream[row_select_offset] = row_select_mask; + + // uint16_t frameperiod = std::max(uint16_t(1), FRAME_DELAY) << frame; + // uint16_t bcd_ticks = frameperiod - FRAME_DELAY; + uint16_t bcd_ticks = 1 << frame; + + bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8; + bitstream[bcd_offset] = (bcd_ticks & 0xff); + } + } + } + + // setup button inputs + gpio_set_function(pin::A, GPIO_FUNC_SIO); gpio_set_dir(pin::A, GPIO_IN); gpio_pull_up(pin::A); + gpio_set_function(pin::B, GPIO_FUNC_SIO); gpio_set_dir(pin::B, GPIO_IN); gpio_pull_up(pin::B); + gpio_set_function(pin::X, GPIO_FUNC_SIO); gpio_set_dir(pin::X, GPIO_IN); gpio_pull_up(pin::X); + gpio_set_function(pin::Y, GPIO_FUNC_SIO); gpio_set_dir(pin::Y, GPIO_IN); gpio_pull_up(pin::Y); + + // setup the pio + bitstream_pio = pio0; + if(unicorn == nullptr) { + bitstream_sm = pio_claim_unused_sm(bitstream_pio, true); + bitstream_sm_offset = pio_add_program(bitstream_pio, &unicorn_program); + } + + pio_gpio_init(bitstream_pio, pin::LED_DATA); + pio_gpio_init(bitstream_pio, pin::LED_CLOCK); + pio_gpio_init(bitstream_pio, pin::LED_LATCH); + pio_gpio_init(bitstream_pio, pin::LED_BLANK); + pio_gpio_init(bitstream_pio, pin::ROW_0); + pio_gpio_init(bitstream_pio, pin::ROW_1); + pio_gpio_init(bitstream_pio, pin::ROW_2); + pio_gpio_init(bitstream_pio, pin::ROW_3); + pio_gpio_init(bitstream_pio, pin::ROW_4); + pio_gpio_init(bitstream_pio, pin::ROW_5); + pio_gpio_init(bitstream_pio, pin::ROW_6); + + pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::LED_DATA, 4, true); + pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::ROW_6, 7, true); + + pio_sm_config c = unicorn_program_get_default_config(bitstream_sm_offset); + + // osr shifts right, autopull on, autopull threshold 8 + sm_config_set_out_shift(&c, true, false, 32); + + // configure out, set, and sideset pins + sm_config_set_out_pins(&c, pin::ROW_6, 7); + sm_config_set_sideset_pins(&c, pin::LED_CLOCK); + sm_config_set_set_pins(&c, pin::LED_DATA, 4); + + // join fifos as only tx needed (gives 8 deep fifo instead of 4) + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + // setup chained dma transfer for pixel data to the pio + dma_channel = dma_claim_unused_channel(true); + dma_ctrl_channel = dma_claim_unused_channel(true); + + dma_channel_config ctrl_config = dma_channel_get_default_config(dma_ctrl_channel); + channel_config_set_transfer_data_size(&ctrl_config, DMA_SIZE_32); + channel_config_set_read_increment(&ctrl_config, false); + channel_config_set_write_increment(&ctrl_config, false); + channel_config_set_chain_to(&ctrl_config, dma_channel); + + dma_channel_configure( + dma_ctrl_channel, + &ctrl_config, + &dma_hw->ch[dma_channel].read_addr, + &bitstream_addr, + 1, + false + ); + + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_transfer_data_size(&config, DMA_SIZE_32); + channel_config_set_bswap(&config, false); // byte swap to reverse little endian + channel_config_set_dreq(&config, pio_get_dreq(bitstream_pio, bitstream_sm, true)); + channel_config_set_chain_to(&config, dma_ctrl_channel); + + dma_channel_configure( + dma_channel, + &config, + &bitstream_pio->txf[bitstream_sm], + NULL, + BITSTREAM_LENGTH / 4, + false); + + pio_sm_init(bitstream_pio, bitstream_sm, bitstream_sm_offset, &c); + pio_sm_set_enabled(bitstream_pio, bitstream_sm, true); + + // start the control channel + dma_start_channel_mask(1u << dma_ctrl_channel); + + unicorn = this; + } + + ~PicoUnicorn(){ + if(unicorn == this) { + partial_teardown(); + + dma_channel_unclaim(dma_ctrl_channel); // This works now the teardown behaves correctly + dma_channel_unclaim(dma_channel); // This works now the teardown behaves correctly + pio_sm_unclaim(bitstream_pio, bitstream_sm); + pio_remove_program(bitstream_pio, &unicorn_program, bitstream_sm_offset); + + unicorn = nullptr; + } + } + + [[deprecated("Handled by constructor.")]] + void init() { + return; + } + + void clear(){ + for(uint8_t y = 0; y < HEIGHT; y++) { + for(uint8_t x = 0; x < WIDTH; x++) { + set_pixel(x, y, 0); + } + } + } + void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { + L gr = COLORLUT[r]; + L gg = COLORLUT[g]; + L gb = COLORLUT[b]; + + set_pixel_(x, y, gr, gg, gb); + } + + void set_pixel(uint8_t x, uint8_t y, float r, float g, float b) { + return set_pixel(x, y, + uint8_t(std::round(r)), + uint8_t(std::round(g)), + uint8_t(std::round(b)) + ); + } + void set_pixel_(uint8_t x, uint8_t y, L gr, L gg, L gb) { + if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return; + + // make those coordinates sane + x = (WIDTH - 1) - x; + + // work out the byte offset of this pixel + uint8_t byte_offset = x / 2; + + // check if it's the high or low nibble and create mask and shift value + uint8_t shift = x % 2 == 0 ? 0 : 4; + uint8_t nibble_mask = 0b00001111 << shift; + + // set the appropriate bits in the separate bcd frames + for(uint8_t frame = 0; frame < TOTAL_FRAMES; frame++) { + // determine offset in the buffer for this row/frame + uint16_t offset = (y * ROW_BYTES * TOTAL_FRAMES) + (ROW_BYTES * frame); + + uint8_t rgbd = ((gr & 0b1) << 1) | ((gg & 0b1) << 3) | ((gb & 0b1) << 2); + + // shift to correct nibble + rgbd <<= shift; + + // clear existing data + uint16_t othernibble = bitstream[offset + byte_offset] & ~nibble_mask; + + // set new data + bitstream[offset + byte_offset] = othernibble | rgbd; + + gr >>= 1; + gg >>= 1; + gb >>= 1; + } + } + // void set_pixel(uint8_t x, uint8_t y, int r, int g, int b); + void set_pixel(uint8_t x, uint8_t y, uint8_t v){ + set_pixel(x, y, v, v, v); + } + + bool is_pressed(uint8_t button){ + return !gpio_get(button); + } + + void update(PicoGraphics *graphics) { + if(unicorn == this) { + if(graphics->pen_type == PicoGraphics::PEN_RGB888) { + uint32_t *p = (uint32_t *)graphics->frame_buffer; + + for(int y = 0; y < HEIGHT; y++) { + for(int x = 0; x < WIDTH; x++) { + uint32_t col = *p; + uint8_t r = (col & 0xff0000) >> 16; + uint8_t g = (col & 0x00ff00) >> 8; + uint8_t b = (col & 0x0000ff) >> 0; + p++; + + set_pixel(x, y, r, g, b); + } + } + } + else if(graphics->pen_type == PicoGraphics::PEN_RGB565) { + uint16_t *p = (uint16_t *)graphics->frame_buffer; + for(int y = 0; y < HEIGHT; y++) { + for(int x = 0; x < WIDTH; x++) { + uint16_t col = __builtin_bswap16(*p); + uint8_t r = (col & 0b1111100000000000) >> 8; + uint8_t g = (col & 0b0000011111100000) >> 3; + uint8_t b = (col & 0b0000000000011111) << 3; + p++; + + set_pixel(x, y, r, g, b); + } + } + } + else if(graphics->pen_type == PicoGraphics::PEN_P8 || graphics->pen_type == PicoGraphics::PEN_P4) { + int offset = 0; + graphics->frame_convert(PicoGraphics::PEN_RGB888, [this, offset](void *data, size_t length) mutable { + uint32_t *p = (uint32_t *)data; + for(auto i = 0u; i < length / 4; i++) { + int x = offset % WIDTH; + int y = offset / WIDTH; + + uint32_t col = *p; + uint8_t r = (col & 0xff0000) >> 16; + uint8_t g = (col & 0x00ff00) >> 8; + uint8_t b = (col & 0x0000ff) >> 0; + + set_pixel(x, y, r, g, b); + offset++; + p++; + } + }); + } + } + } - void update(PicoGraphics *graphics); private: - void partial_teardown(); - void dma_safe_abort(uint channel); + void partial_teardown(){ + // Stop the bitstream SM + pio_sm_set_enabled(bitstream_pio, bitstream_sm, false); + + // Make sure the display is off and switch it to an invisible row, to be safe + const uint pins_to_set = 0b1111111 << ROW_6; + pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set); + + dma_hw->ch[dma_ctrl_channel].al1_ctrl = (dma_hw->ch[dma_ctrl_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_ctrl_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); + dma_hw->ch[dma_channel].al1_ctrl = (dma_hw->ch[dma_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); + // Abort any in-progress DMA transfer + dma_safe_abort(dma_ctrl_channel); + dma_safe_abort(dma_channel); + } + + void dma_safe_abort(uint channel) + { + // Tear down the DMA channel. + // This is copied from: https://github.com/raspberrypi/pico-sdk/pull/744/commits/5e0e8004dd790f0155426e6689a66e08a83cd9fc + uint32_t irq0_save = dma_hw->inte0 & (1u << channel); + hw_clear_bits(&dma_hw->inte0, irq0_save); + + dma_hw->abort = 1u << channel; + + // To fence off on in-flight transfers, the BUSY bit should be polled + // rather than the ABORT bit, because the ABORT bit can clear prematurely. + while (dma_hw->ch[channel].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents(); + + // Clear the interrupt (if any) and restore the interrupt masks. + dma_hw->ints0 = 1u << channel; + hw_set_bits(&dma_hw->inte0, irq0_save); + } + }; -} \ No newline at end of file + template + PicoUnicorn* PicoUnicorn::unicorn = nullptr; + + template + PIO PicoUnicorn::bitstream_pio = pio0; + template + uint PicoUnicorn::bitstream_sm = 0; + template + uint PicoUnicorn::bitstream_sm_offset = 0; + +}