Refactoring code as header library

This commit is contained in:
Nicolau Leal Werneck 2023-12-15 07:11:05 +01:00
parent 7bf03d9242
commit 4dd6a378a2
5 changed files with 393 additions and 387 deletions

View File

@ -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] = {
@ -116,9 +134,6 @@ namespace pimoroni {
12318, 12440, 12562, 12684, 12807, 12931, 13056, 13181, 13307, 13433, 13561, 13688, 13817, 13946, 14076, 14206,
14337, 14469, 14602, 14735, 14868, 15003, 15138, 15273, 15410, 15547, 15685, 15823, 15962, 16102, 16242, 16383};
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};
struct pin_pair {
union {
uint8_t first;

View File

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

View File

@ -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();

View File

@ -1,365 +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 < 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;
}
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) {
uint16_t gr = pimoroni::GAMMA_12BIT[r];
uint16_t gg = pimoroni::GAMMA_12BIT[g];
uint16_t gb = pimoroni::GAMMA_12BIT[b];
set_pixel_(x, y, gr, gg, gb);
}
// // Original version
// void PicoUnicorn::set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) {
// uint16_t gr = pimoroni::GAMMA_14BIT[r];
// uint16_t gg = pimoroni::GAMMA_14BIT[g];
// uint16_t gb = pimoroni::GAMMA_14BIT[b];
// set_pixel_(x, y, gr, gg, gb);
// }
void PicoUnicorn::set_pixel_(uint8_t x, uint8_t y, uint16_t gr, uint16_t gg, uint16_t 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 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>;

View File

@ -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<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
class PicoUnicorn {
public:
static const int WIDTH = 16;
@ -16,12 +67,13 @@ namespace pimoroni {
static const uint32_t ROW_COUNT = 7;
static const uint32_t ROW_BYTES = 12;
// 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 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 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);
@ -36,23 +88,322 @@ namespace pimoroni {
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, uint16_t r, uint16_t g, uint16_t b);
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);
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);
}
};
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>* PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::unicorn = nullptr;
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
PIO PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::bitstream_pio = pio0;
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
uint PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::bitstream_sm = 0;
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
uint PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::bitstream_sm_offset = 0;
}