diff --git a/.gitignore b/.gitignore index 91ae7e9..a46b35d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -*.pio.h +./*.pio.h protodemo diff --git a/Makefile b/Makefile index 409771f..8eb40ed 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -protodemo: protodemo.c piolib/*.c protomatter.pio.h matrixmap.h include/*.h - g++ -Og -ggdb -x c++ -Iinclude -Ipiolib/include -o $@ $(filter %.c, $^) -Wno-narrowing +protodemo: protodemo.c piolib/*.c include/piomatter/*.h include/piomatter/protomatter.pio.h + g++ -std=c++20 -Og -ggdb -x c++ -Iinclude -Ipiolib/include -o $@ $(filter %.c, $^) -Wno-narrowing matrixmap.h: -protomatter.pio.h: protomatter.pio assemble.py +include/piomatter/protomatter.pio.h: protomatter.pio assemble.py python assemble.py $< $@ diff --git a/include/piomatter/buffer_manager.h b/include/piomatter/buffer_manager.h new file mode 100644 index 0000000..886e2fa --- /dev/null +++ b/include/piomatter/buffer_manager.h @@ -0,0 +1,39 @@ +#pragma once +#include "thread_queue.h" + +namespace piomatter { + +struct buffer_manager { + enum { no_buffer = -1, exit_request = -2 }; + + buffer_manager() { + free_buffers.push(0); + free_buffers.push(1); + free_buffers.push(2); + } + + int get_free_buffer() { + return free_buffers.pop_blocking(); + } + void put_free_buffer(int i) { + free_buffers.push(i); + } + + int get_filled_buffer() { + auto r = filled_buffers.pop_nonblocking(); + return r ? r.value() : no_buffer; + } + + void put_filled_buffer(int i) { + filled_buffers.push(i); + } + + void request_exit() { + filled_buffers.push(exit_request); + } + +private: + thread_queue free_buffers, filled_buffers; +}; + +} diff --git a/matrixmap.h b/include/piomatter/matrixmap.h similarity index 73% rename from matrixmap.h rename to include/piomatter/matrixmap.h index f5b0a38..6c8b9cf 100644 --- a/matrixmap.h +++ b/include/piomatter/matrixmap.h @@ -3,7 +3,9 @@ #include #include -using MatrixMap = std::vector; +namespace piomatter { + +using matrix_map = std::vector; int orientation_normal(int width, int height, int x, int y) { return x + width * y; @@ -43,7 +45,7 @@ namespace { } template -MatrixMap make_matrixmap( +matrix_map make_matrixmap( int width, int height, int n_addr_lines, @@ -57,13 +59,12 @@ MatrixMap make_matrixmap( int half_panel_height = 1 << n_addr_lines; int v_panels = height / panel_height; - int across = width * v_panels; - MatrixMap result; + int pixels_across = width * v_panels; + matrix_map result; result.reserve(width*height); -printf("width=%d across=%d height=%d panel_height=%d v_panels=%d\n", width, across, height, panel_height, v_panels); for(int i=0; i + matrix_geometry(int pixels_across, int n_addr_lines, int n_planes, int width, int height, bool serpentine, const Cb &cb) : pixels_across(pixels_across), n_addr_lines(n_addr_lines), n_planes(n_planes), width(width), height(height), map{make_matrixmap(width, height, n_addr_lines, serpentine, cb)} { + int pixels_down = 2 << n_addr_lines; + if (map.size() != pixels_down * pixels_across) { + throw std::range_error("map size does not match calculated pixel count"); + } +} + int pixels_across, n_addr_lines, n_planes, width, height; + matrix_map map; +}; +} diff --git a/include/piomatter/pins.h b/include/piomatter/pins.h new file mode 100644 index 0000000..800fb2f --- /dev/null +++ b/include/piomatter/pins.h @@ -0,0 +1,25 @@ +#pragma once + +namespace piomatter { + +typedef uint8_t pin_t; + +struct adafruit_matrix_bonnet_pinout { + static constexpr pin_t PIN_RGB[] = {5, 13, 6, 12, 16, 23}; + static constexpr pin_t PIN_ADDR[] = {22, 26, 27, 20, 24}; + static constexpr pin_t PIN_OE = 4; // /OE: output enable when LOW + static constexpr pin_t PIN_CLK = 17; // SRCLK: clocks on RISING edge + static constexpr pin_t PIN_LAT = 21; // RCLK: latches on RISING edge + + static constexpr uint32_t clk_bit = 1u << PIN_CLK; + static constexpr uint32_t lat_bit = 1u << PIN_LAT; + static constexpr uint32_t oe_bit = 1u << PIN_OE; + static constexpr uint32_t oe_active = 0; + static constexpr uint32_t oe_inactive = oe_bit; + + static constexpr uint32_t post_oe_delay = 0; + static constexpr uint32_t post_latch_delay = 0; + static constexpr uint32_t post_addr_delay = 500; +}; + +} diff --git a/include/piomatter/piomatter.h b/include/piomatter/piomatter.h new file mode 100644 index 0000000..36f46ce --- /dev/null +++ b/include/piomatter/piomatter.h @@ -0,0 +1,144 @@ +#pragma once + +#include + +#include "hardware/pio.h" + +#include "piomatter/pins.h" +#include "piomatter/buffer_manager.h" +#include "piomatter/render.h" +#include "piomatter/matrixmap.h" +#include "piomatter/protomatter.pio.h" + +namespace piomatter { + +constexpr size_t MAX_XFER = 65548; + +void pio_sm_xfer_data_large(PIO pio, int sm, int direction, size_t size, uint32_t *databuf) { + while(size) { + size_t xfersize = std::min(size_t{MAX_XFER}, size); + int r = pio_sm_xfer_data(pio, sm, direction, xfersize, databuf); + if (r) { + perror("pio_sm_xfer_data (reboot may be required)"); + abort(); + } + size -= xfersize; + databuf += xfersize / sizeof(*databuf); + } +} + +struct piomatter_base { + virtual ~piomatter_base() {} + virtual void show() = 0; +}; + +template +struct piomatter : piomatter_base { + using buffer_type = std::vector; + piomatter(std::span framebuffer, const matrix_geometry &geometry) : framebuffer(framebuffer), geometry{geometry}, converter{}, blitter_thread{&piomatter::blit_thread, this} { + if (geometry.n_addr_lines > std::size(pinout::PIN_ADDR)) { + throw std::runtime_error("too many address lines requested"); + } + program_init(); + show(); + } + + void show() override { + int buffer_idx = manager.get_free_buffer(); + auto &buffer = buffers[buffer_idx]; + auto converted = converter.convert(framebuffer); + protomatter_render_rgb10(buffer, geometry, converted.data()); + } + + ~piomatter() { + if (pio != NULL && sm >= 0) { + pio_sm_unclaim(pio, sm); + } + + manager.request_exit(); + if (blitter_thread.joinable()) { + blitter_thread.join(); + } + } +private: + void program_init() { + pio = pio0; + sm = pio_claim_unused_sm(pio, true); + if (sm < 0) { + throw std::runtime_error("pio_claim_unused_sm"); + } + int r = pio_sm_config_xfer(pio, sm, PIO_DIR_TO_SM, MAX_XFER, 2); + if (!r) { + throw std::runtime_error("pio_sm_config_xfer"); + } + + static const struct pio_program protomatter_program = { + .instructions = protomatter, + .length = 32, + .origin = -1, + }; + + uint offset = pio_add_program(pio, &protomatter_program); + if (offset== PIO_ORIGIN_INVALID) { + throw std::runtime_error("pio_add_program"); + } + + pio_sm_clear_fifos(pio, sm); + pio_sm_set_clkdiv(pio, sm, 1.0); + + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + protomatter_wrap_target, offset + protomatter_wrap); + sm_config_set_out_shift(&c, /* shift_right= */ false, /* auto_pull = */ true, 32); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + sm_config_set_clkdiv(&c, 1.0); + sm_config_set_out_pins(&c, 0, 28); + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); + + pin_init_one(pinout::PIN_OE); + pin_init_one(pinout::PIN_CLK); + pin_init_one(pinout::PIN_LAT); + + for(const auto p : pinout::PIN_RGB) + pin_init_one(p); + + for(size_t i=0; i framebuffer; + buffer_type buffers[3]; + buffer_manager manager{}; + matrix_geometry geometry; + colorspace converter; + std::thread blitter_thread; +}; + +} diff --git a/include/piomatter/protomatter.pio.h b/include/piomatter/protomatter.pio.h new file mode 100644 index 0000000..d895754 --- /dev/null +++ b/include/piomatter/protomatter.pio.h @@ -0,0 +1,49 @@ +const int protomatter_wrap = 4; +const int protomatter_wrap_target = 0; +const int protomatter_sideset_pin_count = 0; +const bool protomatter_sideset_enable = 0; +const uint16_t protomatter[] = { + // ; data format (out-shift-right): + // ; MSB ... LSB + // ; 0 ddd......ddd: 31-bit delay + // ; 1 ccc......ccc: 31 bit data count + // .wrap_target + // top: + 0x6021, // out x, 1 + 0x605f, // out y, 31 + 0x0025, // jmp !x delay_loop + // data_loop: + 0x6000, // out pins, 32 + 0x0083, // jmp y--, data_loop + // .wrap + // delay_loop: + 0x0085, // jmp y--, delay_loop + 0x0000, // jmp top + // ;; fill program out to 32 instructions so nothing else can load + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop + 0xa042, // nop +}; + diff --git a/include/piomatter/render.h b/include/piomatter/render.h new file mode 100644 index 0000000..7f05c08 --- /dev/null +++ b/include/piomatter/render.h @@ -0,0 +1,239 @@ +#pragma once + +#include +#include +#include +#include "matrixmap.h" + +namespace piomatter { + + +constexpr unsigned DATA_OVERHEAD = 3; +constexpr unsigned CLOCKS_PER_DATA = 2; +constexpr unsigned DELAY_OVERHEAD = 5; +constexpr unsigned CLOCKS_PER_DELAY = 1; + +constexpr uint32_t command_data = 1u <<31; +constexpr uint32_t command_delay = 0; + +struct gamma_lut { + gamma_lut(double exponent=2.2) { + for(int i=0; i<256; i++) { + auto v = std::max(i, int(round(1023 * pow(i / 255, exponent)))); + lut[i] = v; + } + } + + unsigned convert(unsigned v) { + if(v >= std::size(lut)) return 1023; + return lut[v]; + } + + void convert_rgb888_packed_to_rgb10(std::vector &result, std::span source) { + result.resize(source.size() / 3); + for(size_t i=0; i &result, std::span source) { + result.resize(source.size()); + for(size_t i=0; i> 16) & 0xff; + uint32_t g = (data >> 8) & 0xff; + uint32_t b = data & 0xff; + result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b); + } + } + + + void convert_rgb565_to_rgb10(std::vector &result, std::span source) { + result.resize(source.size()); + for(size_t i=0; i> 11) & 0x1f; + unsigned r = (r5 << 3) | (r5 >> 2); + unsigned g6 = (data >> 5) & 0x3f; + unsigned g = (g6 << 2) | (g6 >> 4); + unsigned b5 = (data) & 0x1f; + unsigned b = (b5 << 3) | (b5 >> 2); + + result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b); + } + } + + uint16_t lut[256]; +}; + +struct colorspace_rgb565 { + using data_type = uint16_t; + + colorspace_rgb565(float gamma=2.2) : lut{gamma} {} + gamma_lut lut; + const std::span convert(std::span data_in) { + lut.convert_rgb565_to_rgb10(rgb10, data_in); + return rgb10; + } + std::vector rgb10; +}; + +struct colorspace_rgb888 { + using data_type = uint32_t; + + colorspace_rgb888(float gamma=2.2) : lut{gamma} {} + gamma_lut lut; + const std::span convert(std::span data_in) { + lut.convert_rgb888_to_rgb10(rgb10, data_in); + return rgb10; + } + std::vector rgb10; +}; + +struct colorspace_rgb888_packed { + using data_type = uint8_t; + + colorspace_rgb888_packed(float gamma=2.2) : lut{gamma} {} + gamma_lut lut; + const std::span convert(std::span data_in) { + lut.convert_rgb888_packed_to_rgb10(rgb10, data_in); + return rgb10; + } + std::vector rgb10; +}; + +struct colorspace_rgb10 { + using data_type = uint32_t; + + const std::span convert(std::span data_in) { + return data_in; + } +}; + + +// Render a buffer in linear RGB10 format into a piomatter stream +template +void protomatter_render_rgb10(std::vector &result, const matrix_geometry &matrixmap, const uint32_t *pixels) { + result.clear(); + + int data_count = 0; + + auto do_delay = [&](uint32_t delay) { + if (delay == 0) return; + assert(delay < 1000000); + assert(!data_count); + result.push_back(command_delay | (delay ? delay - 1 : 0)); + }; + + auto prep_data = [&data_count, &result](uint32_t n) { + assert(!data_count); + assert(n); + assert(n < 60000); + result.push_back(command_data | (n - 1)); + data_count = n; + }; + + auto do_data = [&](uint32_t d) { + assert(data_count); + data_count --; + result.push_back(d); + }; + + auto do_data_delay = [&](uint32_t d, uint32_t delay) { + prep_data(1); + do_data(d); + do_delay(delay); + }; + + auto calc_addr_bits = [](int addr) { + uint32_t data = 0; + if(addr & 1) data |= (1 << pinout::PIN_ADDR[0]); + if(addr & 2) data |= (1 << pinout::PIN_ADDR[1]); + if(addr & 4) data |= (1 << pinout::PIN_ADDR[2]); + if constexpr(std::size(pinout::PIN_ADDR) >= 4) { + if(addr & 8) data |= (1 << pinout::PIN_ADDR[3]); + } + if constexpr(std::size(pinout::PIN_ADDR) >= 5) { + if(addr & 16) data |= (1 << pinout::PIN_ADDR[4]); + } + return data; + }; + + auto add_pixels = [&do_data, &result](uint32_t addr_bits, bool r0, bool g0, bool b0, bool r1, bool g1, bool b1, bool active) { + uint32_t data = (active ? pinout::oe_active : pinout::oe_inactive) | addr_bits; + if(r0) data |= (1 << pinout::PIN_RGB[0]); + if(g0) data |= (1 << pinout::PIN_RGB[1]); + if(b0) data |= (1 << pinout::PIN_RGB[2]); + if(r1) data |= (1 << pinout::PIN_RGB[3]); + if(g1) data |= (1 << pinout::PIN_RGB[4]); + if(b1) data |= (1 << pinout::PIN_RGB[5]); + + do_data(data); + do_data(data | pinout::clk_bit); + }; + + + int last_bit = 0; + int prev_addr = 7; + // illuminate the right row for data in the shift register (the previous address) + uint32_t addr_bits = calc_addr_bits(prev_addr); + + const auto n_addr = 1u << matrixmap.n_addr_lines; + const auto n_planes = matrixmap.n_planes; + constexpr auto n_bits = 10u; + unsigned offset = n_bits - n_planes; + const auto pixels_across = matrixmap.pixels_across; + + for(int addr = 0; addr < n_addr; addr++) { + for(int bit = n_planes - 1; bit >= 0; bit--) { + uint32_t r = 1 << (20 + offset + bit); + uint32_t g = 1 << (10 + offset + bit); + uint32_t b = 1 << (0 + offset + bit); + + // the shortest /OE we can do is one DATA_OVERHEAD... + // TODO: should make sure desired duration of MSB is at least `pixels_across` + uint32_t desired_duration = 1 << last_bit; + last_bit = bit; + + + prep_data(2 * pixels_across); + auto mapiter = matrixmap.map.begin() + 2 * addr * pixels_across; + for(int x = 0; x < pixels_across; x++) { + assert(mapiter != matrixmap.map.end()); + auto pixel0 = pixels[*mapiter++]; + auto r0 = pixel0 & r; + auto g0 = pixel0 & g; + auto b0 = pixel0 & b; + assert(mapiter != matrixmap.map.end()); + auto pixel1 = pixels[*mapiter++]; + auto r1 = pixel1 & r; + auto g1 = pixel1 & g; + auto b1 = pixel1 & b; + + add_pixels(addr_bits, r0, g0, b0, r1, g1, b1, x < desired_duration); + } + + // hold /OE low until desired time has elapsed to illuminate the LAST line + int remain = desired_duration - pixels_across; + if (remain > 0) { + do_data_delay(addr_bits | pinout::oe_active, remain * CLOCKS_PER_DATA - DELAY_OVERHEAD); + } + + do_data_delay(addr_bits | pinout::oe_inactive, pinout::post_oe_delay); + do_data_delay(addr_bits | pinout::oe_inactive | pinout::lat_bit, pinout::post_latch_delay); + + // with oe inactive, set address bits to illuminate THIS line + if (addr != prev_addr) { + addr_bits = calc_addr_bits(addr); + do_data_delay(addr_bits | pinout::oe_inactive, pinout::post_addr_delay); + prev_addr = addr; + } + } + } +} + +} diff --git a/include/piomatter/thread_queue.h b/include/piomatter/thread_queue.h new file mode 100644 index 0000000..29ea19e --- /dev/null +++ b/include/piomatter/thread_queue.h @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include + +namespace piomatter { + +template +struct thread_queue { + thread_queue() : queue{}, mutex{}, cv{} {} + + void push(T t) { + std::lock_guard lock(mutex); + queue.push(t); + cv.notify_one(); + } + + std::optional pop_nonblocking() { + std::unique_lock lock(mutex); + if (queue.empty()) { return {}; } + T val = queue.front(); + queue.pop(); + return val; + } + + T pop_blocking() { + std::unique_lock lock(mutex); + while (queue.empty()) { cv.wait(lock); } + T val = queue.front(); + queue.pop(); + return val; + } +private: + std::queue queue; + std::mutex mutex; + std::condition_variable cv; +}; + +} diff --git a/protodemo.c b/protodemo.c index b1f9b41..dadca02 100644 --- a/protodemo.c +++ b/protodemo.c @@ -6,234 +6,12 @@ #include #include -#include "hardware/pio.h" -#include "protomatter.pio.h" +#include "piomatter/piomatter.h" -#include "matrixmap.h" - -#define countof(arr) (sizeof((arr)) / sizeof((arr)[0])) - -#define PIN_R0 (5) -#define PIN_G0 (13) -#define PIN_B0 (6) -#define PIN_R1 (12) -#define PIN_G1 (16) -#define PIN_B1 (23) -#define PIN_ADDR_A (22) -#define PIN_ADDR_B (26) -#define PIN_ADDR_C (27) -#define PIN_ADDR_D (20) -#define PIN_ADDR_E (24) -#define PIN_OE (4) // /OE: output enable when LOW -#define PIN_CLK (17) // SRCLK: clocks on RISING edge -#define PIN_LAT (21) // RCLK: latches on RISING edge - -typedef uint8_t pin_t; - -const pin_t all_pins[] = { - PIN_R0, - PIN_G0, - PIN_B0, - PIN_R1, - PIN_G1, - PIN_B1, - PIN_OE, - PIN_CLK, - PIN_LAT, - PIN_ADDR_A, - PIN_ADDR_B, - PIN_ADDR_C, - PIN_ADDR_D, - PIN_ADDR_E, -}; -#define DATA_OVERHEAD (3) -#define CLOCKS_PER_DATA (2) -#define DELAY_OVERHEAD (5) // including the y==0 loop iteration and "jmp top" -#define CLOCKS_PER_DELAY (1) - -constexpr uint32_t command_data = 1u <<31; -constexpr uint32_t command_delay = 0; - -constexpr uint32_t clk_bit = 1u << PIN_CLK; -constexpr uint32_t lat_bit = 1u << PIN_LAT; -constexpr uint32_t oe_bit = 1u << PIN_OE; -constexpr uint32_t oe_active = 0; -constexpr uint32_t oe_inactive = oe_bit; - -constexpr uint32_t post_oe_delay = 0; -constexpr uint32_t post_latch_delay = 0; -constexpr uint32_t post_addr_delay = 500; - -// so for example sending 8 data words will take DATA_OVERHEAD + CLOCKS_PER_DATA*8 PIO cycles -// and sending a delay of 5 will take DELAY_OVERHEAD + CLOCKS_PER_DELAY * 5 - -static const struct pio_program protomatter_program = { - .instructions = protomatter, - .length = 32, - .origin = -1, -}; - -static inline pio_sm_config protomatter_program_get_default_config(uint offset) { - pio_sm_config c = pio_get_default_sm_config(); - sm_config_set_wrap(&c, offset + protomatter_wrap_target, offset + protomatter_wrap); - return c; -} - -static inline void protomatter_pin_init(PIO pio, int sm, int num_addr_pins) { - for(int i=0; i &result, const MatrixMap &matrixmap, uint32_t *pixels, int across, int down, int n_planes) { - result.clear(); - - int data_count = 0; - - auto do_delay = [&](uint32_t delay) { - if (delay == 0) return; - assert(delay < 1000000); - assert(!data_count); - result.push_back(command_delay | (delay ? delay - 1 : 0)); - }; - - auto prep_data = [&data_count, &result](uint32_t n) { - assert(!data_count); - assert(n < 60000); - result.push_back(command_data | (n - 1)); - data_count = n; - }; - - auto do_data = [&](uint32_t d) { - assert(data_count); - data_count --; - result.push_back(d); - }; - - auto do_data_delay = [&](uint32_t d, uint32_t delay) { - prep_data(1); - do_data(d); - do_delay(delay); - }; - - auto calc_addr_bits = [](int addr) { - uint32_t data = 0; - if(addr & 1) data |= (1 << PIN_ADDR_A); - if(addr & 2) data |= (1 << PIN_ADDR_B); - if(addr & 4) data |= (1 << PIN_ADDR_C); - if(addr & 8) data |= (1 << PIN_ADDR_D); - if(addr & 16) data |= (1 << PIN_ADDR_E); - return data; - }; - - auto add_pixels = [&do_data, &result](uint32_t addr_bits, bool r0, bool g0, bool b0, bool r1, bool g1, bool b1, bool active) { - uint32_t data = (active ? oe_active : oe_inactive) | addr_bits; - if(r0) data |= (1 << PIN_R0); - if(g0) data |= (1 << PIN_G0); - if(b0) data |= (1 << PIN_B0); - if(r1) data |= (1 << PIN_R1); - if(g1) data |= (1 << PIN_G1); - if(b1) data |= (1 << PIN_B1); - - do_data(data); - do_data(data | clk_bit); - }; - - - int last_bit = 0; - int prev_addr = 7; - // illuminate the right row for data in the shift register (the previous address) - uint32_t addr_bits = calc_addr_bits(prev_addr); - assert(matrixmap.size() == down * across); - -#define N_BITS 10 -#define OFFSET (N_BITS - n_planes) - for(int addr = 0; addr < down / 2; addr++) { - for(int bit = n_planes - 1; bit >= 0; bit--) { - uint32_t r = 1 << (20 + OFFSET + bit); - uint32_t g = 1 << (10 + OFFSET + bit); - uint32_t b = 1 << (0 + OFFSET + bit); - - // the shortest /OE we can do is one DATA_OVERHEAD... - // TODO: should make sure desired duration of MSB is at least `across` - uint32_t desired_duration = 1 << last_bit; - last_bit = bit; - - - prep_data(2 * across); - auto mapiter = matrixmap.begin() + 2 * addr * across; - for(int x = 0; x < across; x++) { - assert(mapiter != matrixmap.end()); - auto pixel0 = pixels[*mapiter++]; - auto r0 = pixel0 & r; - auto g0 = pixel0 & g; - auto b0 = pixel0 & b; - assert(mapiter != matrixmap.end()); - auto pixel1 = pixels[*mapiter++]; - auto r1 = pixel1 & r; - auto g1 = pixel1 & g; - auto b1 = pixel1 & b; - - add_pixels(addr_bits, r0, g0, b0, r1, g1, b1, x < desired_duration); - } - - // hold /OE low until desired time has elapsed to illuminate the LAST line - int remain = desired_duration - across; - if (remain > 0) { - do_data_delay(addr_bits | oe_active, remain * CLOCKS_PER_DATA - DELAY_OVERHEAD); - } - - do_data_delay(addr_bits | oe_inactive, post_oe_delay); - do_data_delay(addr_bits | oe_inactive | lat_bit, post_latch_delay); - - // with oe inactive, set address bits to illuminate THIS line - if (addr != prev_addr) { - addr_bits = calc_addr_bits(addr); - do_data_delay(addr_bits | oe_inactive, post_addr_delay); - prev_addr = addr; - } - } - } -} - - -uint16_t gamma_lut[256]; -void make_gamma_lut(double exponent) { - for(int i=0; i<256; i++) { - auto v = std::max(i, int(round(1023 * pow(i / 255, exponent)))); - gamma_lut[i] = v; - } -} - -uint32_t rgb(unsigned r, unsigned g, unsigned b) { - assert(r < 256); - assert(g < 256); - assert(b < 256); - return (gamma_lut[r] << 20) | (gamma_lut[g] << 10) | gamma_lut[b]; -} - - -#define ACROSS (128) -#define DOWN (32) -#define N_PLANES (10) #define _ (0) -#define r (1023 << 20) -#define g (1023 << 10) -#define b (1023) +#define r (255 << 16) +#define g (255 << 8) +#define b (255) #define y (r|g) #define c (g|b) #define m (r|b) @@ -267,6 +45,8 @@ uint32_t pixels[height][width] = { #undef w #undef _ +#define rgb(r,g,b) ((r << 16) | (g << 8) | b) + uint32_t colorwheel(int i) { i = i & 0xff; if(i < 85) { @@ -280,9 +60,7 @@ uint32_t colorwheel(int i) { return rgb(i * 3, 255 - i * 3, 0); } -MatrixMap matrixmap; - -void test_pattern(std::vector &result, int offs) { +void test_pattern(int offs) { for(int i=0; i &result, int offs) { for(int i=0; i data; - test_pattern(data, 0); - printf("%zd data elements\n", data.size()); - bool first = true; - fprintf(f, "[\n"); - for(auto i : data) { - if (!first) { fprintf(f, ",\n"); } - first=false; - fprintf(f, "%u", i); } - fprintf(f, "]\n"); - fclose(f); -} - -constexpr size_t MAX_XFER = 32768; - -void pio_sm_xfer_data_large(PIO pio, int sm, int direction, size_t size, uint32_t *databuf) { - while(size) { - size_t xfersize = std::min(size_t{MAX_XFER}, size); - int r = pio_sm_xfer_data(pio, sm, direction, xfersize, databuf); - if (r) { - perror("pio_sm_xfer_data (reboot may be required)"); - abort(); - } - size -= xfersize; - databuf += xfersize / sizeof(*databuf); - } -} - -static_assert(!(DOWN & (DOWN-1))); // is a power of two - int main(int argc, char **argv) { int n = argc > 1 ? atoi(argv[1]) : 0; - // matrixmap = make_matrixmap(width, height, 3, false, orientation_r180); - matrixmap = make_matrixmap(width, height, 4, true, orientation_normal); + piomatter::matrix_geometry geometry(128, 4, 10, 64, 64, true, piomatter::orientation_normal); + piomatter::piomatter p(std::span(&pixels[0][0], 64*64), geometry); - if(n == 0) { - dump_matrixmap(); - dump_test_pattern(); - exit(0); - } - - PIO pio = pio0; - int sm = pio_claim_unused_sm(pio, true); - int r = pio_sm_config_xfer(pio, sm, PIO_DIR_TO_SM, MAX_XFER, 2); - if (r) { - perror("pio_sm_config_xfer"); - abort(); - } - - make_gamma_lut(2.2); - -printf("clock %fMHz\n", clock_get_hz(clk_sys)/1e6); - - uint offset = pio_add_program(pio, &protomatter_program); - printf("Loaded program at %d, using sm %d\n", offset, sm); - - pio_sm_clear_fifos(pio, sm); - pio_sm_set_clkdiv(pio, sm, 1.0); - - protomatter_program_init(pio, sm, offset); - protomatter_pin_init(pio, sm, __builtin_ffs(DOWN)-2); - - - std::vector data; uint64_t start = monotonicns64(); for(int i=0; i