milestone: it builds (didn't test it)

This commit is contained in:
Jeff Epler 2025-01-13 13:56:20 -06:00
parent 5792d45f73
commit b5a3da095f
10 changed files with 572 additions and 324 deletions

2
.gitignore vendored
View file

@ -1,2 +1,2 @@
*.pio.h
./*.pio.h
protodemo

View file

@ -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 $< $@

View file

@ -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<int> free_buffers, filled_buffers;
};
}

View file

@ -3,7 +3,9 @@
#include <vector>
#include <stdexcept>
using MatrixMap = std::vector<int>;
namespace piomatter {
using matrix_map = std::vector<int>;
int orientation_normal(int width, int height, int x, int y) {
return x + width * y;
@ -43,7 +45,7 @@ namespace {
}
template<typename Cb>
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<half_panel_height; i++) {
for(int j=0; j<across; j++) {
for(int j=0; j<pixels_across; j++) {
int panel_no = j / width;
int panel_idx = j % width;
int x, y0, y1;
@ -84,3 +85,16 @@ printf("width=%d across=%d height=%d panel_height=%d v_panels=%d\n", width, acro
return result;
}
struct matrix_geometry {
template<typename Cb>
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;
};
}

25
include/piomatter/pins.h Normal file
View file

@ -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;
};
}

View file

@ -0,0 +1,144 @@
#pragma once
#include <thread>
#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<class pinout=adafruit_matrix_bonnet_pinout, class colorspace=colorspace_rgb888>
struct piomatter : piomatter_base {
using buffer_type = std::vector<uint32_t>;
piomatter(std::span<typename colorspace::data_type const> 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<pinout>(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<geometry.n_addr_lines; i++) {
pin_init_one(pinout::PIN_ADDR[i]);
}
}
void pin_init_one(int pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
}
void blit_thread() {
const uint32_t *databuf = nullptr;
size_t datasize;
int buffer_idx;
while((buffer_idx = manager.get_filled_buffer()) != buffer_manager::exit_request) {
if(buffer_idx != buffer_manager::no_buffer) {
const auto &buffer = buffers[buffer_idx];
databuf = &buffer[0];
datasize = buffer.size() * sizeof(*databuf);
}
if (datasize) {
pio_sm_xfer_data_large(pio, sm, PIO_DIR_TO_SM, datasize, (uint32_t*)databuf);
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
PIO pio = NULL;
int sm = -1;
std::span<typename colorspace::data_type const> framebuffer;
buffer_type buffers[3];
buffer_manager manager{};
matrix_geometry geometry;
colorspace converter;
std::thread blitter_thread;
};
}

View file

@ -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
};

239
include/piomatter/render.h Normal file
View file

@ -0,0 +1,239 @@
#pragma once
#include <span>
#include <vector>
#include <cassert>
#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<uint32_t> &result, std::span<const uint8_t> source) {
result.resize(source.size() / 3);
for(size_t i=0; i<source.size(); i+=3) {
uint32_t r = source[i+0] & 0xff;
uint32_t g = source[i+1] & 0xff;
uint32_t b = source[i+2] & 0xff;
result[i] = (convert(r) << 20) | (convert(g) << 10) | convert(b);
}
}
void convert_rgb888_to_rgb10(std::vector<uint32_t> &result, std::span<const uint32_t> source) {
result.resize(source.size());
for(size_t i=0; i<source.size(); i++) {
uint32_t data = source[i];
uint32_t r = (data >> 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<uint32_t> &result, std::span<const uint16_t> source) {
result.resize(source.size());
for(size_t i=0; i<source.size(); i++) {
uint32_t data = source[i];
unsigned r5 = (data >> 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<const uint32_t> convert(std::span<const data_type> data_in) {
lut.convert_rgb565_to_rgb10(rgb10, data_in);
return rgb10;
}
std::vector<uint32_t> rgb10;
};
struct colorspace_rgb888 {
using data_type = uint32_t;
colorspace_rgb888(float gamma=2.2) : lut{gamma} {}
gamma_lut lut;
const std::span<const uint32_t> convert(std::span<const data_type> data_in) {
lut.convert_rgb888_to_rgb10(rgb10, data_in);
return rgb10;
}
std::vector<uint32_t> 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<const uint32_t> convert(std::span<const data_type> data_in) {
lut.convert_rgb888_packed_to_rgb10(rgb10, data_in);
return rgb10;
}
std::vector<uint32_t> rgb10;
};
struct colorspace_rgb10 {
using data_type = uint32_t;
const std::span<const uint32_t> convert(std::span<const data_type> data_in) {
return data_in;
}
};
// Render a buffer in linear RGB10 format into a piomatter stream
template<typename pinout>
void protomatter_render_rgb10(std::vector<uint32_t> &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;
}
}
}
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <optional>
#include <mutex>
#include <condition_variable>
#include <queue>
namespace piomatter {
template<class T>
struct thread_queue {
thread_queue() : queue{}, mutex{}, cv{} {}
void push(T t) {
std::lock_guard<std::mutex> lock(mutex);
queue.push(t);
cv.notify_one();
}
std::optional<T> pop_nonblocking() {
std::unique_lock<std::mutex> lock(mutex);
if (queue.empty()) { return {}; }
T val = queue.front();
queue.pop();
return val;
}
T pop_blocking() {
std::unique_lock<std::mutex> lock(mutex);
while (queue.empty()) { cv.wait(lock); }
T val = queue.front();
queue.pop();
return val;
}
private:
std::queue<T> queue;
std::mutex mutex;
std::condition_variable cv;
};
}

View file

@ -6,234 +6,12 @@
#include <vector>
#include <ctime>
#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<countof(all_pins) - 5 + num_addr_pins; i++) {
int pin = all_pins[i];
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
}
}
static inline void protomatter_program_init(PIO pio, int sm, uint offset) {
pio_sm_config c = protomatter_program_get_default_config(offset);
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);
}
void protomatter_convert(std::vector<uint32_t> &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<uint32_t> &result, int offs) {
void test_pattern(int offs) {
for(int i=0; i<width; i++) {
pixels[height-5][i] = rgb(1+i*4, 1+i*4, 1+i*4);
pixels[height-4][i] = colorwheel(2*i + offs / 3);
@ -293,9 +71,8 @@ void test_pattern(std::vector<uint32_t> &result, int offs) {
for(int i=0; i<height; i++) {
pixels[i][i] = rgb(0xff,0xff,0xff);
}
protomatter_convert(result, matrixmap, &pixels[0][0], ACROSS, DOWN, N_PLANES);
}
static uint64_t monotonicns64() {
struct timespec tp;
@ -304,95 +81,16 @@ static uint64_t monotonicns64() {
}
static void dump_matrixmap() {
FILE *f = fopen("matrixmap.txt", "w");
bool first = true;
fprintf(f, "[\n");
for(auto i : matrixmap) {
if (!first) { fprintf(f, ",\n"); }
first=false;
fprintf(f, "%u", i);
}
fprintf(f, "]\n");
fclose(f);
}
static void dump_test_pattern() {
FILE *f = fopen("pattern.txt", "w");
std::vector<uint32_t> 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<uint32_t> data;
uint64_t start = monotonicns64();
for(int i=0; i<n; i++) {
test_pattern(data, i);
uint32_t *databuf = &data[0];
size_t datasize = data.size() * sizeof(uint32_t);
pio_sm_xfer_data_large(pio, sm, PIO_DIR_TO_SM, datasize, databuf);
test_pattern(i);
p.show();
}
uint64_t end = monotonicns64();