Rebasing Galactic Unicorn branch to main

pull/537/head
jon 2022-06-29 08:54:30 +01:00 zatwierdzone przez Phil Howard
rodzic a27a539a1f
commit 54ad59c77a
10 zmienionych plików z 838 dodań i 0 usunięć

Wyświetl plik

@ -55,3 +55,4 @@ add_subdirectory(servo2040)
add_subdirectory(motor2040)
add_subdirectory(inventor2040w)
add_subdirectory(encoder)
add_subdirectory(galactic_unicorn)

Wyświetl plik

@ -0,0 +1,10 @@
add_executable(
galactic_unicorn_demo
demo.cpp
)
# Pull in pico libraries that we need
target_link_libraries(galactic_unicorn_demo pico_stdlib hardware_pio hardware_dma pico_graphics galactic_unicorn)
# create map/bin/hex file etc.
pico_add_extra_outputs(galactic_unicorn_demo)

Wyświetl plik

@ -0,0 +1,164 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB565 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (53 / 2) - (w / 2);
p.y += (11 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
struct star_t {
float dx, dy, x, y, a;
uint8_t brightness() {
int b = a / 5;
return b > 15 ? 15 : b;
}
};
void init_star(star_t &s) {
s.x = ((rand() % 100) / 5.0f) - 10.0f;
s.y = ((rand() % 100) / 10.0f) - 5.0f;
s.dx = s.x / 10.0f;
s.dy = s.y / 10.0f;
s.a = 0;
}
void step_star(star_t &s) {
s.x += s.dx;
s.y += s.dy;
s.a++;
if(s.a > 100) {
init_star(s);
}
}
int main() {
uint8_t hue_map[53][3];
for(int i = 0; i < 53; i++) {
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
star_t stars[100];
for(int i = 0; i < 100; i++) {
init_star(stars[i]);
stars[i].a = i;
}
gpio_set_function(28, GPIO_FUNC_SIO);
gpio_set_dir(28, GPIO_OUT);
for(int i = 0; i < 10; i++) {
gpio_put(28, !gpio_get(28));
sleep_ms(100);
}
sleep_ms(1000);
gpio_put(28,true);
galactic_unicorn.init();
/*
bool a_pressed = false;
bool b_pressed = false;
bool x_pressed = false;
bool y_pressed = false;
*/
graphics.set_font("sans");
uint i = 0;
int v = 255;
while(true) {
i++;
graphics.set_pen(0, 0, 0);
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {graphics.set_pen(255, 0, 0);}
graphics.clear();
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {v = v == 0 ? 0 : v - 1;}
for(int i = 0; i < 100; i++) {
star_t &star = stars[i];
step_star(star);
uint b = star.brightness();
graphics.set_pen(b, b, b);
graphics.pixel(Point(star.x + (53 / 2), star.y + (11 / 2)));
}
graphics.set_pen(255, 255, 255);
float s = 1.0f;//0.65f + (sin(i / 25.0f) * 0.15f);
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
float x = (sin(i / 25.0f) * 40.0f) * s;
float y = (cos(i / 15.0f) * 10.0f) * s;
text("Galactic", Point(x, y), s, a);
uint16_t *p = (uint16_t *)graphics.frame_buffer;
for(size_t i = 0; i < 53 * 11; i++) {
int x = i % 53;
int y = i / 53;
uint r = ((*p & 0b1111100000000000) >> 11) << 3;
uint g = ((*p & 0b0000011111100000) >> 5) << 2;
uint b = ((*p & 0b0000000000011111) >> 0) << 3;
p++;
if(r > 200 && g > 200 && b > 200) {
r = hue_map[x][0];
g = hue_map[x][1];
b = hue_map[x][2];
}
galactic_unicorn.set_pixel(x, y, r, g, b);
}
sleep_ms(10);
}
printf("done\n");
return 0;
}

Wyświetl plik

@ -35,3 +35,4 @@ add_subdirectory(inventor2040w)
add_subdirectory(adcfft)
add_subdirectory(jpegdec)
add_subdirectory(inky_frame)
add_subdirectory(galactic_unicorn)

Wyświetl plik

@ -0,0 +1,12 @@
add_library(galactic_unicorn INTERFACE)
pico_generate_pio_header(galactic_unicorn ${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.pio)
target_sources(galactic_unicorn INTERFACE
${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.cpp
)
target_include_directories(galactic_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(galactic_unicorn INTERFACE pico_stdlib hardware_pio hardware_dma)

Wyświetl plik

@ -0,0 +1,101 @@
# Galactic Unicorn <!-- omit in toc -->
Galactic Unicorn offers 53x11 7x17 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier and speaker.
Galactic Unicorn uses SM0 of PIO0.
TODO: Update documentation
We've included helper functions to handle every aspect of drawing to the display and interfacing with the buttons. See the [function reference](#function-reference) for details.
- [Example Program](#example-program)
- [Reference](#reference)
- [Constants](#constants)
- [Buttons](#buttons)
- [WIDTH / HEIGHT](#width--height)
- [Functions](#functions)
- [init](#init)
- [set_pixel](#set_pixel)
- [is_pressed](#is_pressed)
## Example Program
The following example sets up Pico Unicorn, displays some basic demo text and graphics and will illuminate the RGB LED green if the A button is presse
```c++
```
## Reference
### Constants
#### Buttons
The four buttons, A, B, X and Y have correponding constants set to their respective GPIO pins. For example:
```c++
bool a_is_pressed = pico_unicorn.is_pressed(pico_unicorn.A);
```
#### WIDTH / HEIGHT
The width and height of Pico Unicorn are available in constants `WIDTH` and `HEIGHT`.
For example:
```c++
int num_pixels = pico_unicorn.WIDTH * pico_unicorn.HEIGHT;
```
### Functions
#### init
Sets up Pico Unicorn. `init` must be called before any other functions since it configures the PIO and require GPIO inputs. Just call `init()` like so:
```c++
PicoUnicorn pico_unicorn;
pico_unicorn.init();
```
#### set_pixel
```c++
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);
```
Sets an RGB LED on Pico Unicorn with an RGB triplet:
```c++
pico_unicorn.set_pixel(x, y, r, g, b);
```
Uses hardware PWM to drive the LED. Values are automatically gamma-corrected to provide smooth brightness transitions and low values may map as "off."
Alternatively you can use:
```c++
pico_unicorn.set_pixel(x, y, v);
```
Which sets the R, G and B elements of the pixel to the same value- lighting it up white at your chosen intensity.
#### is_pressed
```c++
bool is_pressed(uint8_t button);
```
Reads the GPIO pin connected to one of Pico Unicorn's buttons, returning a `bool` - `true` if it's pressed and `false` if it is released.
```c++
pico_unicorn.is_pressed(button);
```
The button vaule should be a `uint8_t` denoting a pin, and constants `A`, `B`, `X` and `Y` are supplied to make it easier. e:
```c++
bool is_a_button_pressed = pico_unicorn.is_pressed(PicoUnicorn::A)
```

Wyświetl plik

@ -0,0 +1,12 @@
add_library(pico_unicorn INTERFACE)
pico_generate_pio_header(pico_unicorn ${CMAKE_CURRENT_LIST_DIR}/pico_unicorn.pio)
target_sources(pico_unicorn INTERFACE
${CMAKE_CURRENT_LIST_DIR}/pico_unicorn.cpp
)
target_include_directories(pico_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(pico_unicorn INTERFACE pico_stdlib hardware_pio hardware_dma)

Wyświetl plik

@ -0,0 +1,434 @@
#include <math.h>
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "galactic_unicorn.pio.h"
#include "galactic_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 11 rows each of which has 14 frames of
// bcd timing data
//
// bits are output in order:
//
// ROW_CLEAR, ROW_DATA1, ROW_DATA0, LED_BLANK, LED_LATCH, LED_CLOCK, LED_DATA0, LED_DATA1
//
// the data is structured like this:
//
// loop through the eleven rows of the display...
//
// 1rr00000 // set row select bit on rows 0 and 8 (side set the clock)
// 00000000 00000000 00000000 // dummy bytes to align to dwords
//
// within this row we loop through the 14 bcd frames for this row...
//
// 0 - 161: 100100rr, 100101rr, 100100gg, 100101gg, 100100bb, 100101bb, ... x 27 # left+right half rgb pixel data doubled for clock pulses, keep BLANK high
// 162: 10011000 // LATCH pixel data
// 163: 10000000 // turn off BLANK to output pixel data - now at 164 bytes (41 dwords)
// 164 - 165: 00001111, 11111111, # bcd tick count (0-65536)
// 166: 10010000 // turn BLANK back on
// 167: 00000000 // dummy byte to ensure dword aligned
//
// .. and back to the start
/*
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,
};*/
enum pin {
LED_DATA1 = 12,
LED_DATA0 = 13,
LED_CLOCK = 14,
LED_LATCH = 15,
LED_BLANK = 16,
ROW_DATA0 = 17,
ROW_DATA1 = 18,
ROW_CLEAR = 19,
ROW_CLOCK = 20,
SWITCH_A = 0,
SWITCH_B = 1,
SWITCH_C = 3,
SWITCH_D = 6,
SWITCH_E = 2,
SWITCH_VOLUME_UP = 21,
SWITCH_VOLUME_DOWN = 26,
SWITCH_BRIGHTNESS_UP = 7,
SWITCH_BRIGHTNESS_DOWN = 8
};
constexpr uint32_t ROW_COUNT = 11;
constexpr uint32_t ROW_BYTES = 4;
constexpr uint32_t ROW_FRAME_BYTES = 168;
constexpr uint32_t BCD_FRAMES = 15; // includes fet discharge frame
constexpr uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES + ROW_COUNT * ROW_FRAME_BYTES * BCD_FRAMES);
// must be aligned for 32bit dma transfer
alignas(4) static uint8_t bitstream[BITSTREAM_LENGTH] = {0};
static uint16_t r_gamma_lut[256] = {0};
static uint16_t g_gamma_lut[256] = {0};
static uint16_t b_gamma_lut[256] = {0};
static uint32_t dma_channel;
static inline void unicorn_jetpack_program_init(PIO pio, uint sm, uint offset) {
pio_gpio_init(pio, pin::LED_DATA1);
pio_gpio_init(pio, pin::LED_DATA0);
pio_gpio_init(pio, pin::LED_CLOCK);
pio_gpio_init(pio, pin::LED_LATCH);
pio_gpio_init(pio, pin::LED_BLANK);
pio_gpio_init(pio, pin::ROW_DATA0);
pio_gpio_init(pio, pin::ROW_DATA1);
pio_gpio_init(pio, pin::ROW_CLEAR);
pio_gpio_init(pio, pin::ROW_CLOCK);
pio_sm_set_consecutive_pindirs(pio, sm, pin::LED_DATA1, 5, true);
pio_sm_set_consecutive_pindirs(pio, sm, pin::ROW_DATA0, 4, true);
pio_sm_config c = galactic_unicorn_program_get_default_config(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::LED_DATA1, 8);
sm_config_set_sideset_pins(&c, pin::ROW_CLOCK);
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
///pio_sm_set_clkdiv(pio, sm, 4.0f);
}
namespace pimoroni {
// once the dma transfer of the scanline is complete we move to the
// next scanline (or quit if we're finished)
void __isr dma_complete() {
if (dma_hw->ints0 & (1u << dma_channel)) {
dma_hw->ints0 = (1u << dma_channel); // clear irq flag
dma_channel_set_trans_count(dma_channel, BITSTREAM_LENGTH / 4, false);
dma_channel_set_read_addr(dma_channel, bitstream, true);
}
}
GalacticUnicorn::~GalacticUnicorn() {
// stop and release the dma channel
irq_set_enabled(DMA_IRQ_0, false);
dma_channel_set_irq0_enabled(dma_channel, false);
irq_set_enabled(pio_get_dreq(bitstream_pio, bitstream_sm, true), false);
irq_remove_handler(DMA_IRQ_0, dma_complete);
dma_channel_wait_for_finish_blocking(dma_channel);
dma_channel_unclaim(dma_channel);
// release the pio and sm
pio_sm_unclaim(bitstream_pio, bitstream_sm);
pio_clear_instruction_memory(bitstream_pio);
pio_sm_restart(bitstream_pio, bitstream_sm);
}
void GalacticUnicorn::init() {
// todo: shouldn't need to do this if things were cleaned up properly but without
// this any attempt to run a micropython script twice will fail
static bool already_init = false;
// setup pins
gpio_init(pin::LED_DATA0); gpio_set_dir(pin::LED_DATA0, GPIO_OUT);
gpio_init(pin::LED_DATA1); gpio_set_dir(pin::LED_DATA1, 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_DATA0); gpio_set_dir(pin::ROW_DATA0, GPIO_OUT);
gpio_init(pin::ROW_DATA1); gpio_set_dir(pin::ROW_DATA1, GPIO_OUT);
gpio_init(pin::ROW_CLEAR); gpio_set_dir(pin::ROW_CLEAR, GPIO_OUT);
gpio_init(pin::ROW_CLOCK); gpio_set_dir(pin::ROW_CLOCK, GPIO_OUT);
// create 14-bit gamma luts
for(uint16_t v = 0; v < 256; v++) {
// gamma correct the provided 0-255 brightness value onto a
// 0-65535 range for the pwm counter
float r_gamma = 2.8f;
r_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, r_gamma) * 16383.0f + 0.5f);
float g_gamma = 2.8f;
g_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, g_gamma) * 16383.0f + 0.5f);
float b_gamma = 2.8f;
b_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, b_gamma) * 16383.0f + 0.5f);
}
// the data is structured like this:
//
// loop through the eleven rows of the display...
//
// 1rr00000 // set row select bit on rows 0 and 8 (side set the clock)
// 00000000 00000000 00000000 // dummy bytes to align to dwords
//
// within this row we loop through the 14 bcd frames for this row...
//
// 0 - 161: 100100rr, 100101rr, 100100gg, 100101gg, 100100bb, 100101bb, ... x 27 # left+right half rgb pixel data doubled for clock pulses, keep BLANK high
// 162: 10011000 // LATCH pixel data
// 163: 10000000 // turn off BLANK to output pixel data - now at 164 bytes (41 dwords)
// 164 - 165: 00001111, 11111111, # bcd tick count (0-65536)
// 166: 10010000 // turn BLANK back on
// 167: 00000000 // dummy byte to ensure dword aligned
//
// .. and back to the start
// initialise the bcd timing values and row selects in the bitstream
for(uint8_t row = 0; row < HEIGHT; row++) {
uint16_t row_offset = row * (ROW_BYTES + ROW_FRAME_BYTES * BCD_FRAMES);
// setup row select on rows 0 and 8
uint8_t row_select = row == 0 ? 0b10111000 : (row == 8 ? 0b11011000 : 0b10011000);
bitstream[row_offset + 0] = row_select;
for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) {
uint16_t frame_offset = row_offset + ROW_BYTES + (ROW_FRAME_BYTES * frame);
bitstream[frame_offset + 162] = 0b10011000; // LATCH pixel data
bitstream[frame_offset + 163] = 0b10001000; // BLANK low to enable column outputs
// set the number of bcd ticks for this frame
uint16_t bcd_ticks = frame == BCD_FRAMES - 1 ? 65535 : 1 << frame;
bitstream[frame_offset + 164] = (bcd_ticks & 0xff);
bitstream[frame_offset + 165] = (bcd_ticks & 0xff00) >> 8;
bitstream[frame_offset + 166] = 0b10010000; // BLANK high again to disable outputs
// setup empty pixels with BLANK high and a clock pulse
for(uint8_t col = 0; col < 162; col += 2) {
bitstream[frame_offset + col + 0] = 0b10010000;
bitstream[frame_offset + col + 1] = 0b10010100;
}
/*
uint16_t row_select_offset = offset + 164;
uint16_t bcd_offset = offset + 165;
// the last bcd frame is used to allow the fets to discharge to avoid ghosting
if(frame == BCD_FRAMES - 1) {
uint16_t bcd_ticks = 65535;
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
bitstream[bcd_offset] = (bcd_ticks & 0xff);
}else{
uint8_t row_select = row == 0 ? 0b01000000 : (row == 8 ? 0b00100000 : 0b00000000);
bitstream[row_select_offset] = row_select;
uint16_t bcd_ticks = 1 << frame;
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
bitstream[bcd_offset] = (bcd_ticks & 0xff);
}*/
}
/*
for(size_t i = 0; i < sizeof(bitstream); i++) {
bitstream[i] = 0b11100000;
}*/
}
// setup button inputs
gpio_set_function(pin::SWITCH_A, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_A, GPIO_IN); gpio_pull_up(pin::SWITCH_A);
gpio_set_function(pin::SWITCH_B, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_B, GPIO_IN); gpio_pull_up(pin::SWITCH_B);
gpio_set_function(pin::SWITCH_C, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_C, GPIO_IN); gpio_pull_up(pin::SWITCH_C);
gpio_set_function(pin::SWITCH_D, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_D, GPIO_IN); gpio_pull_up(pin::SWITCH_D);
gpio_set_function(pin::SWITCH_E, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_E, GPIO_IN); gpio_pull_up(pin::SWITCH_E);
gpio_set_function(pin::SWITCH_BRIGHTNESS_UP, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_BRIGHTNESS_UP, GPIO_IN); gpio_pull_up(pin::SWITCH_BRIGHTNESS_UP);
gpio_set_function(pin::SWITCH_BRIGHTNESS_DOWN, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_BRIGHTNESS_DOWN, GPIO_IN); gpio_pull_up(pin::SWITCH_BRIGHTNESS_DOWN);
gpio_set_function(pin::SWITCH_VOLUME_UP, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_VOLUME_UP, GPIO_IN); gpio_pull_up(pin::SWITCH_VOLUME_UP);
gpio_set_function(pin::SWITCH_VOLUME_DOWN, GPIO_FUNC_SIO); gpio_set_dir(pin::SWITCH_VOLUME_DOWN, GPIO_IN); gpio_pull_up(pin::SWITCH_VOLUME_DOWN);
if(already_init) {
// stop and release the dma channel
irq_set_enabled(DMA_IRQ_0, false);
dma_channel_abort(dma_channel);
dma_channel_wait_for_finish_blocking(dma_channel);
dma_channel_set_irq0_enabled(dma_channel, false);
irq_set_enabled(pio_get_dreq(bitstream_pio, bitstream_sm, true), false);
irq_remove_handler(DMA_IRQ_0, dma_complete);
dma_channel_unclaim(dma_channel);
// release the pio and sm
pio_sm_unclaim(bitstream_pio, bitstream_sm);
pio_clear_instruction_memory(bitstream_pio);
pio_sm_restart(bitstream_pio, bitstream_sm);
//return;
}
// setup the pio
bitstream_pio = pio0;
bitstream_sm = pio_claim_unused_sm(pio0, true);
sm_offset = pio_add_program(bitstream_pio, &galactic_unicorn_program);
unicorn_jetpack_program_init(bitstream_pio, bitstream_sm, sm_offset);
// setup dma transfer for pixel data to the pio
dma_channel = dma_claim_unused_channel(true);
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));
dma_channel_configure(dma_channel, &config, &bitstream_pio->txf[bitstream_sm], NULL, 0, false);
dma_channel_set_irq0_enabled(dma_channel, true);
irq_set_enabled(pio_get_dreq(bitstream_pio, bitstream_sm, true), true);
irq_set_exclusive_handler(DMA_IRQ_0, dma_complete);
irq_set_enabled(DMA_IRQ_0, true);
dma_channel_set_trans_count(dma_channel, BITSTREAM_LENGTH / 4, false);
dma_channel_set_read_addr(dma_channel, bitstream, true);
already_init = true;
}
void GalacticUnicorn::clear() {
for(uint8_t y = 0; y < HEIGHT; y++) {
for(uint8_t x = 0; x < WIDTH; x++) {
set_pixel(x, y, 0);
}
}
}
void GalacticUnicorn::set_pixel(int x, int 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;
y = (HEIGHT - 1) - y;
// determine offset in the buffer for this row
uint16_t row_offset = y * (ROW_BYTES + ROW_FRAME_BYTES * BCD_FRAMES);
uint16_t bits[3] = {r_gamma_lut[r], g_gamma_lut[g], b_gamma_lut[b]};
//uint16_t gr = r_gamma_lut[r];
//uint16_t gg = g_gamma_lut[g];
//uint16_t gb = b_gamma_lut[b];
// set the appropriate bits in the separate bcd frames
for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) {
uint16_t frame_offset = (ROW_FRAME_BYTES * frame) + 4;
uint16_t offset = row_offset + frame_offset;// + byte_offset;
// loop through the eleven rows of the display...
//
// 1rr00000 // set row select bit on rows 0 and 8 (side set the clock)
// 00000000 00000000 00000000 // dummy bytes to align to dwords
//
// within this row we loop through the 14 bcd frames for this row...
//
// 0 - 161: 100100rr, 100101rr, 100100gg, 100101gg, 100100bb, 100101bb, ... x 27 # left+right half rgb pixel data doubled for clock pulses, keep BLANK high
// 162: 10011000 // LATCH pixel data
// 163: 10000000 // turn off BLANK to output pixel data - now at 164 bytes (41 dwords)
// 164 - 165: 00001111, 11111111, # bcd tick count (0-65536)
// 166: 10010000 // turn BLANK back on
// 167: 00000000 // dummy byte to ensure dword aligned
//
// .. and back to the start
// work out the byte offset of this pixel
/*if(bit_offset >= 160) {
bit_offset -= 160;
}*/
for(int bit = 0; bit < 3; bit++) {
int16_t bit_offset = x * 6 + 4 + (bit * 2);
uint8_t bit_position = bit_offset >= 160 ? 1 : 0;
uint8_t mask = 0b1 << bit_position;
uint8_t value = (bits[bit] & 0b1) << bit_position;
bitstream[offset + (bit_offset % 160) + 0] &= ~mask;
bitstream[offset + (bit_offset % 160) + 0] |= value;
bitstream[offset + (bit_offset % 160) + 1] &= ~mask;
bitstream[offset + (bit_offset % 160) + 1] |= value;
//bit_offset += 2;
bits[bit] >>= 1;
}
/* // setup pixel data and matching mask
uint8_t bit_position = x >= 26 ? 1 : 0;
uint8_t mask = 0b1 << bit_position;
uint8_t red = (gr & 0b1) << bit_position;
uint8_t green = (gg & 0b1) << bit_position;
uint8_t blue = (gb & 0b1) << bit_position;
// clear existing data and set new data
bitstream[offset + 0] &= ~mask;
bitstream[offset + 0] |= red;
bitstream[offset + 1] &= ~mask;
bitstream[offset + 1] |= red;
bitstream[offset + 2] &= ~mask;
bitstream[offset + 2] |= green;
bitstream[offset + 3] &= ~mask;
bitstream[offset + 3] |= green;
bitstream[offset + 4] &= ~mask;
bitstream[offset + 4] |= blue;
bitstream[offset + 5] &= ~mask;
bitstream[offset + 5] |= blue;*/
/*
uint8_t mask = 0b11;
uint8_t red = ((gr & 0b1) << 1) | (gr & 0b1) ;
uint8_t green = ((gg & 0b1) << 1) | (gg & 0b1) ;
uint8_t blue = ((gb & 0b1) << 1) | (gb & 0b1) ;
bitstream[offset + 0] &= ~mask;
bitstream[offset + 0] |= red;
bitstream[offset + 1] &= ~mask;
bitstream[offset + 1] |= red;
bitstream[offset + 2] &= ~mask;
bitstream[offset + 2] |= green;
bitstream[offset + 3] &= ~mask;
bitstream[offset + 3] |= green;
bitstream[offset + 4] &= ~mask;
bitstream[offset + 4] |= blue;
bitstream[offset + 5] &= ~mask;
bitstream[offset + 5] |= blue;
*/
/* gr >>= 1;
gg >>= 1;
gb >>= 1;*/
}
}
void GalacticUnicorn::set_pixel(int x, int y, uint8_t v) {
set_pixel(x, y, v, v, v);
}
bool GalacticUnicorn::is_pressed(uint8_t button) {
return !gpio_get(button);
}
}

Wyświetl plik

@ -0,0 +1,37 @@
#pragma once
#include "hardware/pio.h"
namespace pimoroni {
class GalacticUnicorn {
public:
static const int WIDTH = 53;
static const int HEIGHT = 11;
static const uint8_t SWITCH_A = 0;
static const uint8_t SWITCH_B = 1;
static const uint8_t SWITCH_C = 3;
static const uint8_t SWITCH_D = 6;
static const uint8_t SWITCH_E = 2;
static const uint8_t SWITCH_VOLUME_UP = 21;
static const uint8_t SWITCH_VOLUME_DOWN = 26;
static const uint8_t SWITCH_BRIGHTNESS_UP = 7;
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 8;
private:
PIO bitstream_pio = pio0;
uint bitstream_sm = 0;
uint sm_offset = 0;
public:
~GalacticUnicorn();
void init();
void clear();
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
void set_pixel(int x, int y, uint8_t v);
bool is_pressed(uint8_t button);
};
}

Wyświetl plik

@ -0,0 +1,66 @@
.program galactic_unicorn
.side_set 1 opt
; out pins:
; 0: data1 (base)
; 1: data0
; 2: clock
; 3: latch
; 4: blank
; 5: row1
; 6: row2
; 7: row_clear
; sideset pin: row_clock
.wrap_target
; set row select pins
pull
out pins, 8 side 0 [4]
nop side 1 [4] ; pulse row select clock
out null, 24 ; discard dummy data
; loop for bcd frames
set x, 14
bcd_frame:
; clock out 53 pixels worth of data - two pixels at a time, so 27 (actually 26.5) bits of data
set y, 26 ; 26 because `jmp` test is pre decrement
pixels:
pull ifempty
out pins, 8 [1] ; two red bits..
out pins, 8 [1] ; ..with clock pulse
pull ifempty
out pins, 8 [1] ; two green bits..
out pins, 8 [1] ; ..with green pulse
pull ifempty
out pins, 8 [1] ; two blue bits..
out pins, 8 [1] ; ..with blue pulse
jmp y-- pixels
out pins, 8 ; LATCH pixel data
out pins, 8 ; turn off BLANK signal
pull
; pull bcd tick count into x register
out y, 16
bcd_count:
jmp y-- bcd_count ; loop until bcd delay complete
; disable led output (blank) and clear latch pin
out pins, 8 ; turn off BLANK signal and clear row output
out null, 8 ; discard dummy data
jmp x-- bcd_frame ; loop to next bcd frame
.wrap