kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Rebasing Galactic Unicorn branch to main
rodzic
a27a539a1f
commit
54ad59c77a
|
@ -55,3 +55,4 @@ add_subdirectory(servo2040)
|
|||
add_subdirectory(motor2040)
|
||||
add_subdirectory(inventor2040w)
|
||||
add_subdirectory(encoder)
|
||||
add_subdirectory(galactic_unicorn)
|
||||
|
|
|
@ -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)
|
|
@ -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;
|
||||
}
|
|
@ -35,3 +35,4 @@ add_subdirectory(inventor2040w)
|
|||
add_subdirectory(adcfft)
|
||||
add_subdirectory(jpegdec)
|
||||
add_subdirectory(inky_frame)
|
||||
add_subdirectory(galactic_unicorn)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
```
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
Ładowanie…
Reference in New Issue