kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Cosmic Unicorn: Driver and C++ examples.
rodzic
93ac854672
commit
c3672d7e3d
|
@ -0,0 +1,84 @@
|
|||
add_executable(
|
||||
cosmic_rainbow_text
|
||||
cosmic_rainbow_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_rainbow_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_rainbow_text)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_rainbow
|
||||
cosmic_rainbow.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_rainbow 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_rainbow)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_eighties_super_computer
|
||||
cosmic_eighties_super_computer.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_eighties_super_computer 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_eighties_super_computer)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_fire_effect
|
||||
cosmic_fire_effect.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_fire_effect 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_fire_effect)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_scroll_text
|
||||
cosmic_scroll_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_scroll_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_scroll_text)
|
||||
|
||||
|
||||
add_executable(
|
||||
cosmic_lava_lamp
|
||||
cosmic_lava_lamp.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
|
||||
pico_enable_stdio_usb(cosmic_lava_lamp 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(cosmic_lava_lamp)
|
||||
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,67 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
float lifetime[32][32];
|
||||
float age[32][32];
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
if(age[x][y] < lifetime[x][y] * 0.3f) {
|
||||
graphics.set_pen(230, 150, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(age[x][y] < lifetime[x][y] * 0.5f) {
|
||||
float decay = (lifetime[x][y] * 0.5f - age[x][y]) * 5.0f;
|
||||
graphics.set_pen(decay * 230, decay * 150, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
if(age[x][y] >= lifetime[x][y]) {
|
||||
age[x][y] = 0.0f;
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
}
|
||||
|
||||
age[x][y] += 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
int width = 32;
|
||||
int height = 33;
|
||||
|
||||
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
|
||||
float heat[2000] = {0.0f};
|
||||
|
||||
void set(int x, int y, float v) {
|
||||
heat[x + y * width] = v;
|
||||
}
|
||||
|
||||
float get(int x, int y) {
|
||||
/*if(x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return 0.0f;
|
||||
}*/
|
||||
x = x < 0 ? 0 : x;
|
||||
x = x >= width ? width - 1 : x;
|
||||
|
||||
return heat[x + y * width];
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
cosmic_unicorn.init();
|
||||
cosmic_unicorn.set_brightness(0.2);
|
||||
|
||||
bool landscape = true;
|
||||
/*
|
||||
while(true) {
|
||||
cosmic_unicorn.set_pixel(0, 0, 255, 0, 0);
|
||||
cosmic_unicorn.set_pixel(1, 1, 0, 255, 0);
|
||||
cosmic_unicorn.set_pixel(2, 2, 0, 0, 255);
|
||||
}*/
|
||||
|
||||
while(true) {
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
|
||||
for(int y = 0; y < height; y++) {
|
||||
for(int x = 0; x < width; x++) {
|
||||
float value = get(x, y);
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
if(value > 0.5f) {
|
||||
graphics.set_pen(255, 255, 180);
|
||||
}else if(value > 0.4f) {
|
||||
graphics.set_pen(220, 160, 0);
|
||||
}else if(value > 0.3f) {
|
||||
graphics.set_pen(180, 30, 0);
|
||||
}else if(value > 0.22f) {
|
||||
graphics.set_pen(20, 20, 20);
|
||||
}
|
||||
|
||||
if(landscape) {
|
||||
graphics.pixel(Point(x, y));
|
||||
}else{
|
||||
graphics.pixel(Point(y, x));
|
||||
}
|
||||
|
||||
// update this pixel by averaging the below pixels
|
||||
float average = (get(x, y) + get(x, y + 2) + get(x, y + 1) + get(x - 1, y + 1) + get(x + 1, y + 1)) / 5.0f;
|
||||
|
||||
// damping factor to ensure flame tapers out towards the top of the displays
|
||||
average *= 0.98f;
|
||||
|
||||
// update the heat map with our newly averaged value
|
||||
set(x, y, average);
|
||||
}
|
||||
}
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
// clear the bottom row and then add a new fire seed to it
|
||||
for(int x = 0; x < width; x++) {
|
||||
set(x, height - 1, 0.0f);
|
||||
}
|
||||
|
||||
// add a new random heat source
|
||||
int source_count = landscape ? 5 : 1;
|
||||
for(int c = 0; c < source_count; c++) {
|
||||
int px = (rand() % (width - 4)) + 2;
|
||||
set(px , height - 2, 1.0f);
|
||||
set(px + 1, height - 2, 1.0f);
|
||||
set(px - 1, height - 2, 1.0f);
|
||||
set(px , height - 1, 1.0f);
|
||||
set(px + 1, height - 1, 1.0f);
|
||||
set(px - 1, height - 1, 1.0f);
|
||||
}
|
||||
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_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;
|
||||
}
|
||||
}
|
||||
|
||||
struct blob_t {
|
||||
float x, y;
|
||||
float r;
|
||||
float dx, dy;
|
||||
};
|
||||
|
||||
constexpr int blob_count = 20;
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
cosmic_unicorn.init();
|
||||
cosmic_unicorn.set_brightness(0.5);
|
||||
|
||||
// randomise blob start positions, directions, and size
|
||||
std::array<blob_t, blob_count> blobs;
|
||||
for(auto &blob : blobs) {
|
||||
blob.x = rand() % 32;
|
||||
blob.y = rand() % 32;
|
||||
blob.r = ((rand() % 40) / 10.0f) + 5.0f;
|
||||
blob.dx = ((rand() % 2) / 10.0f) - 0.05f;
|
||||
blob.dy = ((rand() % 3) / 10.0f) - 0.1f;
|
||||
}
|
||||
|
||||
float hue = 0.0f;
|
||||
|
||||
while(true) {
|
||||
// allow user to adjust brightness
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
uint start_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
// calculate the influence of each blob on the liquid based
|
||||
// on their distance to each pixel. this causes blobs to
|
||||
// "merge" into each other when we use fixed thresholds to
|
||||
// determine which colour to draw with
|
||||
float liquid[32][32] = {0.0f};
|
||||
for(auto &blob : blobs) {
|
||||
float r_sq = blob.r * blob.r;
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
float d_sq = (x - blob.x) * (x - blob.x) + (y - blob.y) * (y - blob.y);
|
||||
if(d_sq <= r_sq) {
|
||||
// add this blobs influence to this pixel
|
||||
liquid[x][y] += 1.0f - (d_sq / r_sq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update the blob positions
|
||||
for(auto &blob : blobs) {
|
||||
blob.x += blob.dx;
|
||||
blob.y += blob.dy;
|
||||
|
||||
// if we hit the edge then bounce!
|
||||
if(blob.x < 0.0f || blob.x >= 11.0f) {
|
||||
blob.dx *= -1.0f;
|
||||
}
|
||||
if(blob.y < 0.0f || blob.y >= 53.0f) {
|
||||
blob.dy *= -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// rotate the hue
|
||||
hue += 0.001f;
|
||||
|
||||
// calculate dark, medium, and bright shades for rendering the
|
||||
// lava
|
||||
uint8_t dark_r, dark_g, dark_b;
|
||||
from_hsv(hue, 1.0f, 0.3f, dark_r, dark_g, dark_b);
|
||||
uint8_t mid_r, mid_g, mid_b;
|
||||
from_hsv(hue, 1.0f, 0.6f, mid_r, mid_g, mid_b);
|
||||
uint8_t bright_r, bright_g, bright_b;
|
||||
from_hsv(hue, 1.0f, 1.0f, bright_r, bright_g, bright_b);
|
||||
|
||||
// clear the canvas
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
// render the lava
|
||||
for(int y = 0; y < 32; y++) {
|
||||
for(int x = 0; x < 32; x++) {
|
||||
float v = liquid[x][y];
|
||||
|
||||
// select a colour for this pixel based on how much
|
||||
// "blobfluence" there is at this position in the liquid
|
||||
if(v >= 1.5f) {
|
||||
graphics.set_pen(bright_r, bright_g, bright_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}else if(v >= 1.25f) {
|
||||
graphics.set_pen(mid_r, mid_g, mid_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}else if(v >= 1.0f) {
|
||||
graphics.set_pen(dark_r, dark_g, dark_b);
|
||||
graphics.pixel(Point(y, x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint end_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
printf("rendering took %dms\n", end_ms - start_ms);
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_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() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
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);
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
|
||||
|
||||
float i = 0;
|
||||
|
||||
float hue_offset = 0.0f;
|
||||
|
||||
bool animate = true;
|
||||
|
||||
float stripe_width = 3.0f;
|
||||
float speed = 1.0f;
|
||||
float curve = 0.0f;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(animate) {
|
||||
i += speed;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_VOLUME_UP)) {
|
||||
curve += 0.05;
|
||||
if(hue_offset > 1.0f) hue_offset = 1.0f;
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_VOLUME_DOWN)) {
|
||||
curve -= 0.05;
|
||||
if(hue_offset < 0.0f) hue_offset = 0.0f;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_SLEEP)) {
|
||||
animate = false;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_A)) {
|
||||
speed += 0.05f;
|
||||
speed = speed >= 10.0f ? 10.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_B)) {
|
||||
speed -= 0.05f;
|
||||
speed = speed <= 0.0f ? 0.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_C)) {
|
||||
stripe_width += 0.05f;
|
||||
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
|
||||
}
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_D)) {
|
||||
stripe_width -= 0.05f;
|
||||
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
|
||||
}
|
||||
|
||||
for(int x = 0; x < 32; x++) {
|
||||
for(int y = 0; y < 32; y++) {
|
||||
int v = ((sin((x + y) / stripe_width + (sin((y * 3.1415927f * 2.0f) / 11.0f) * curve) + i / 15.0f) + 1.5f) / 2.5f) * 255.0f;
|
||||
|
||||
uint8_t r = (hue_map[x][0] * v) / 256;
|
||||
uint8_t g = (hue_map[x][1] * v) / 256;
|
||||
uint8_t b = (hue_map[x][2] * v) / 256;
|
||||
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
}
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
printf("%d\n", cosmic_unicorn.light());
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_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 += (32 / 2) - (w / 2);
|
||||
p.y += (32 / 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);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
uint8_t hue_map[32][3];
|
||||
for(int i = 0; i < 32; i++) {
|
||||
from_hsv(i / 32.0f, 1.0f, 0.5f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
graphics.set_font("sans");
|
||||
uint i = 0;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
|
||||
float x = (sin((i) / 50.0f) * 90.0f);
|
||||
float y = (cos((i) / 40.0f) * 5.0f);
|
||||
graphics.set_pen(255, 255, 255);
|
||||
text("Galactic Unicorn", Point(x, y), s, a);
|
||||
uint8_t *p = (uint8_t *)graphics.frame_buffer;
|
||||
for(size_t i = 0; i < 32 * 32; i++) {
|
||||
int x = i % 32;
|
||||
int y = i / 32;
|
||||
uint r = *p++;
|
||||
uint g = *p++;
|
||||
uint b = *p++;
|
||||
p++;
|
||||
|
||||
if(r > 0) {
|
||||
r = hue_map[x][0];
|
||||
g = hue_map[x][1];
|
||||
b = hue_map[x][2];
|
||||
}
|
||||
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
}
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "cosmic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
|
||||
CosmicUnicorn cosmic_unicorn;
|
||||
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
cosmic_unicorn.init();
|
||||
|
||||
float scroll = -32.0f;
|
||||
|
||||
while(true) {
|
||||
//uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
cosmic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
cosmic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.25f;
|
||||
|
||||
if(scroll > width) {
|
||||
scroll = -32.0f;
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
uint8_t r = 0, g = 0, b = 0;
|
||||
from_hsv(scroll / 100.0f, 1.0f, 0.5f, r, g, b);
|
||||
graphics.set_pen(r, g, b);
|
||||
graphics.text(message, Point(0 - scroll, 14), -1, 0.55);
|
||||
|
||||
cosmic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
include(cosmic_unicorn.cmake)
|
|
@ -0,0 +1,259 @@
|
|||
# Galactic Unicorn (C/C++)<!-- omit in toc -->
|
||||
|
||||
Galactic Unicorn offers 53x11 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
|
||||
|
||||
You can buy one here: https://shop.pimoroni.com/products/galactic-unicorn
|
||||
|
||||
## These are not your everyday RGB LEDs!
|
||||
|
||||
Internally Galactic Unicorn applies gamma correction to the supplied image data and updates the display with 14-bit precision resulting in extremely linear visual output - including at the low end.
|
||||
|
||||
The display is refreshed around 300 times per second (300fps!) allowing for rock solid stability even when being filmed, no smearing or flickering even when in motion.
|
||||
|
||||
No strobing or brightness stepping here folks - it's the perfect backdrop for your tricked out streaming setup!
|
||||
|
||||
## Getting started
|
||||
|
||||
The Galactic Unicorn library provides a collection of methods that allow you to easily access all of the features on the board.
|
||||
|
||||
Drawing is primarily handled via our [PicoGraphics](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_graphics) library which provides a comprehensive selection of drawing methods - once your drawing work is complete you pass the PicoGraphics object to Galactic Unicorn to have it displayed on the screen.
|
||||
|
||||
- [Example Program](#example-program)
|
||||
- [Interleaved Framebuffer](#interleaved-framebuffer)
|
||||
- [Function Reference](#function-reference)
|
||||
- [System State](#system-state)
|
||||
- [`void init()`](#void-init)
|
||||
- [`void set_brightness(float value)`](#void-set_brightnessfloat-value)
|
||||
- [`float get_brightness()`](#float-get_brightness)
|
||||
- [`void adjust_brightness(float delta)`](#void-adjust_brightnessfloat-delta)
|
||||
- [`void set_volume(float value)`](#void-set_volumefloat-value)
|
||||
- [`float get_volume()`](#float-get_volume)
|
||||
- [`void adjust_volume(float delta)`](#void-adjust_volumefloat-delta)
|
||||
- [`uint16_t light()`](#uint16_t-light)
|
||||
- [`bool is_pressed(uint8_t button)`](#bool-is_presseduint8_t-button)
|
||||
- [Drawing](#drawing)
|
||||
- [`void update(PicoGraphics *graphics)`](#void-updatepicographics-graphics)
|
||||
- [`void clear()`](#void-clear)
|
||||
- [`void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b)`](#void-set_pixelint-x-int-y-uint8_t-r-uint8_t-g-uint8_t-b)
|
||||
- [Audio](#audio)
|
||||
- [`void play_sample(uint8_t *data, uint32_t length)`](#void-play_sampleuint8_t-data-uint32_t-length)
|
||||
- [`AudioChannel& synth_channel(uint channel)`](#audiochannel-synth_channeluint-channel)
|
||||
- [`void play_synth()`](#void-play_synth)
|
||||
- [`void stop_playing()`](#void-stop_playing)
|
||||
- [Constants](#constants)
|
||||
- [`WIDTH` & `HEIGHT`](#width--height)
|
||||
|
||||
# Example Program
|
||||
|
||||
The following example shows how to scroll a simple message across the display.
|
||||
|
||||
```c++
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
// create a PicoGraphics framebuffer to draw into
|
||||
PicoGraphics_PenRGB888 graphics(GalacticUnicorn::WIDTH, GalacticUnicorn::HEIGHT, nullptr);
|
||||
|
||||
// create our GalacticUnicorn object
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// message to scroll
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
// initialise the GalacticUnicorn object
|
||||
galactic_unicorn.init();
|
||||
|
||||
// start position for scrolling (off the side of the display)
|
||||
float scroll = -(float)GalacticUnicorn::WIDTH;
|
||||
|
||||
while(true) {
|
||||
// determine the scroll position of the text
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.25f;
|
||||
if(scroll > width) {
|
||||
scroll = -(float)GalacticUnicorn::WIDTH;
|
||||
}
|
||||
|
||||
// clear the graphics object
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
// draw the text
|
||||
graphics.set_pen(255, 255, 0); // a nice yellow
|
||||
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
|
||||
|
||||
// update the display
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
# Interleaved Framebuffer
|
||||
|
||||
Galactic Unicorn takes advantage of the RP2040's PIOs to drive screen updates - this is what gives it the performance it needs to render with 14-bit precision at over 300 frames per second.
|
||||
|
||||
The PIO is a powerful, but limited, tool. It has no way to access memory at random and minimal support for decision making and branching. All it can really do is process a stream of data/instructions in order.
|
||||
|
||||
This means that we need to be clever about the way we pass data into the PIO program, the information needs to be delivered in the exact order that the PIO will need to process it. To achieve this we "interleave" our framebuffer - each frame of BCM data is passed one after another with values for the current row, pixel count, and timing inserted as needed:
|
||||
|
||||
row 0 data:
|
||||
for each bcd frame:
|
||||
bit : data
|
||||
0: 00110110 // row pixel count (minus one)
|
||||
1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
56: xxxxrrrr // row select bits
|
||||
57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
|
||||
row 1 data:
|
||||
...
|
||||
|
||||
If you're working with our library then you don't need to worry about any of these details, they are handled for you.
|
||||
|
||||
# Function Reference
|
||||
|
||||
## System State
|
||||
|
||||
### `void init()`
|
||||
|
||||
Initialise the Galactic Unicorn hardware, interleaved framebuffer, and PIO programs. This function must be called before attempting to do anything else with Galactic Unicorn.
|
||||
|
||||
### `void set_brightness(float value)`
|
||||
|
||||
Set the brightness - `value` is supplied as a floating point value between `0.0` and `1.0`.
|
||||
|
||||
### `float get_brightness()`
|
||||
|
||||
Returns the current brightness as a value between `0.0` to `1.0`.
|
||||
|
||||
### `void adjust_brightness(float delta)`
|
||||
|
||||
Adjust the brightness of the display - `delta` is supplied as a floating point value and will be added to the current brightness (and then clamped to the range `0.0` to `1.0`).
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
galactic.set_brightness(0.5f);
|
||||
galactic.adjust_brightness(0.1f); // brightness is now 0.6
|
||||
galactic.adjust_brightness(0.7f); // brightness is now 1.0
|
||||
galactic.adjust_brightness(-0.2f); // brightness is now 0.8
|
||||
```
|
||||
|
||||
### `void set_volume(float value)`
|
||||
|
||||
Set the volume - `value` is supplied as a floating point value between `0.0` and `1.0`.
|
||||
|
||||
### `float get_volume()`
|
||||
|
||||
Returns the current volume as a value between `0.0` and `1.0`.
|
||||
|
||||
### `void adjust_volume(float delta)`
|
||||
|
||||
Adjust the volume - `delta` is supplied as a floating point value and will be added to the current volume (and then clamped to the range `0.0` to `1.0`).
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
galactic.set_volume(0.5f);
|
||||
galactic.adjust_volume(0.1f); // volume is now 0.6
|
||||
galactic.adjust_volume(0.7f); // volume is now 1.0
|
||||
galactic.adjust_volume(-0.2f); // volume is now 0.8
|
||||
```
|
||||
|
||||
### `uint16_t light()`
|
||||
|
||||
Get the current value seen by the onboard light sensor as a value between `0` and `4095`.
|
||||
|
||||
### `bool is_pressed(uint8_t button)`
|
||||
|
||||
Returns true if the requested `button` is currently pressed.
|
||||
|
||||
There are a set of constants on the GalacticUnicorn class that represent each of the buttons. The brightness, sleep, and volume buttons are not tied to hardware functions (they are implemented entirely in software) so can also be used for user functions if preferred.
|
||||
|
||||
```c++
|
||||
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_SLEEP = 27;
|
||||
static const uint8_t SWITCH_VOLUME_UP = 7;
|
||||
static const uint8_t SWITCH_VOLUME_DOWN = 8;
|
||||
static const uint8_t SWITCH_BRIGHTNESS_UP = 21;
|
||||
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 26;
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
while(!galactic.is_pressed(GalacticUnicorn::SWITCH_A)) {
|
||||
// wait for switch A to be pressed
|
||||
}
|
||||
printf("We did it! We pressed switch A! Heck yeah!");
|
||||
```
|
||||
|
||||
## Drawing
|
||||
|
||||
### `void update(PicoGraphics *graphics)`
|
||||
|
||||
**This is our recommended way to update the image on Galactic Unicorn.** The PicoGraphics library provides a collection of powerful drawing methods to make things simple.
|
||||
|
||||
The image on the PicoGraphics object provided is copied to the interleaved framebuffer with gamma correction applied. This lets you have multiple PicoGraphics objects on the go at once and switch between them by changing which gets passed into this function.
|
||||
|
||||
If however you'd rather twiddle individual pixels (for example you're producing some sort of algorithmic output) then you can simply use the `clear()` and `set_pixel()` methods mentioned below.
|
||||
|
||||
### `void clear()`
|
||||
|
||||
Clear the contents of the interleaved framebuffer. This will make your Galactic Unicorn display turn off when the next frame is displayed.
|
||||
|
||||
If you're using PicoGraphics to build your image (recommended!) then you won't need to call this method as you'll overwrite the entire display when you call `update()` anyway.
|
||||
|
||||
### `void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b)`
|
||||
|
||||
Set a single pixel to the specified colour. The newly set colour will be shown at the next frame. Pixel coordinates go from `0` to `52` along the `x` axis and from `0` to `10` on the `y` axis. Colour values are specified as a `0` to `255` RGB triplet - the supplied colour will be gamma corrected automatically.
|
||||
|
||||
When drawing a full image it's recommended that you keep the time between each `set_pixel` call short to ensure your image gets displayed on the next frame. Otherwise you can get scanning-like visual artefacts (unless that is your intention of course!)
|
||||
|
||||
## Audio
|
||||
|
||||
Audio functionality is supported by our [PicoSynth library](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_synth) which allows you to create multiple voice channels with ADSR (attack decay sustain release) envelopes. It provides a similar set of functionality to the classic SID chip in the Commodore 64.
|
||||
|
||||
### `void play_sample(uint8_t *data, uint32_t length)`
|
||||
|
||||
Play the provided 16-bit audio sample. `data` must point to a buffer that contains 16-bit PCM data and `length` must be the number of samples.
|
||||
|
||||
### `AudioChannel& synth_channel(uint channel)`
|
||||
|
||||
Gets an `AudioChannel` object which can then be configured with voice, ADSR envelope, etc.
|
||||
|
||||
### `void play_synth()`
|
||||
|
||||
Start the synth playing.
|
||||
|
||||
### `void stop_playing()`
|
||||
|
||||
Stops any currently playing audio.
|
||||
|
||||
## Constants
|
||||
|
||||
### `WIDTH` & `HEIGHT`
|
||||
|
||||
The width and height of Galactic Unicorn are available in constants `WIDTH` and `HEIGHT`.
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
int num_pixels = GalacticUnicorn::WIDTH * GalacticUnicorn::HEIGHT;
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
;
|
||||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
|
||||
; Transmit a mono or stereo I2S audio stream as stereo
|
||||
; This is 16 bits per sample; can be altered by modifying the "set" params,
|
||||
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
|
||||
;
|
||||
; Autopull must be enabled, with threshold set to 32.
|
||||
; Since I2S is MSB-first, shift direction should be to left.
|
||||
; Hence the format of the FIFO word is:
|
||||
;
|
||||
; | 31 : 16 | 15 : 0 |
|
||||
; | sample ws=0 | sample ws=1 |
|
||||
;
|
||||
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
|
||||
; Fractional divider will probably be needed to get correct bit clock period,
|
||||
; but for common syslck freqs this should still give a constant word select period.
|
||||
;
|
||||
; One output pin is used for the data output.
|
||||
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
|
||||
|
||||
; Send 16 bit words to the PIO for mono, 32 bit words for stereo
|
||||
|
||||
.program audio_i2s
|
||||
.side_set 2
|
||||
|
||||
; /--- LRCLK
|
||||
; |/-- BCLK
|
||||
bitloop1: ; ||
|
||||
out pins, 1 side 0b10
|
||||
jmp x-- bitloop1 side 0b11
|
||||
out pins, 1 side 0b00
|
||||
set x, 14 side 0b01
|
||||
|
||||
bitloop0:
|
||||
out pins, 1 side 0b00
|
||||
jmp x-- bitloop0 side 0b01
|
||||
out pins, 1 side 0b10
|
||||
public entry_point:
|
||||
set x, 14 side 0b11
|
||||
|
||||
% c-sdk {
|
||||
|
||||
static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
|
||||
pio_sm_config sm_config = audio_i2s_program_get_default_config(offset);
|
||||
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, 32);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // clear pins
|
||||
|
||||
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point));
|
||||
}
|
||||
|
||||
%}
|
|
@ -0,0 +1,15 @@
|
|||
add_library(cosmic_unicorn INTERFACE)
|
||||
|
||||
pico_generate_pio_header(cosmic_unicorn ${CMAKE_CURRENT_LIST_DIR}/cosmic_unicorn.pio)
|
||||
pico_generate_pio_header(cosmic_unicorn ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pio)
|
||||
|
||||
|
||||
target_sources(cosmic_unicorn INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/cosmic_unicorn.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../pico_synth/pico_synth.cpp
|
||||
)
|
||||
|
||||
target_include_directories(cosmic_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(cosmic_unicorn INTERFACE pico_stdlib pico_graphics hardware_adc hardware_pio hardware_dma)
|
|
@ -0,0 +1,578 @@
|
|||
#include <math.h>
|
||||
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/adc.h"
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
|
||||
#include "cosmic_unicorn.pio.h"
|
||||
#include "audio_i2s.pio.h"
|
||||
|
||||
#include "cosmic_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 pins used are:
|
||||
//
|
||||
// - 13: column clock (sideset)
|
||||
// - 14: column data (out base)
|
||||
// - 15: column latch
|
||||
// - 16: column blank
|
||||
// - 17: row select bit 0
|
||||
// - 18: row select bit 1
|
||||
// - 19: row select bit 2
|
||||
// - 20: row select bit 3
|
||||
//
|
||||
// the framebuffer data is structured like this:
|
||||
//
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 00111111 // row pixel count (minus one)
|
||||
// 1 - 64: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
// 65 - 67: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
// 68: xxxxrrrr // row select bits
|
||||
// 69 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
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 uint32_t dma_ctrl_channel;
|
||||
static uint32_t audio_dma_channel;
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
CosmicUnicorn* CosmicUnicorn::unicorn = nullptr;
|
||||
PIO CosmicUnicorn::bitstream_pio = pio0;
|
||||
uint CosmicUnicorn::bitstream_sm = 0;
|
||||
uint CosmicUnicorn::bitstream_sm_offset = 0;
|
||||
PIO CosmicUnicorn::audio_pio = pio0;
|
||||
uint CosmicUnicorn::audio_sm = 0;
|
||||
uint CosmicUnicorn::audio_sm_offset = 0;
|
||||
|
||||
// once the dma transfer of the scanline is complete we move to the
|
||||
// next scanline (or quit if we're finished)
|
||||
void __isr CosmicUnicorn::dma_complete() {
|
||||
if(unicorn != nullptr && dma_channel_get_irq0_status(audio_dma_channel)) {
|
||||
unicorn->next_audio_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
CosmicUnicorn::~CosmicUnicorn() {
|
||||
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, &cosmic_unicorn_program, bitstream_sm_offset);
|
||||
|
||||
dma_channel_unclaim(audio_dma_channel); // This works now the teardown behaves correctly
|
||||
pio_sm_unclaim(audio_pio, audio_sm);
|
||||
pio_remove_program(audio_pio, &audio_i2s_program, audio_sm_offset);
|
||||
irq_remove_handler(DMA_IRQ_0, dma_complete);
|
||||
|
||||
unicorn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::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 = 1 << COLUMN_BLANK | 0b1111 << ROW_BIT_0;
|
||||
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_channel_abort(dma_ctrl_channel);
|
||||
//dma_channel_abort(dma_channel);
|
||||
dma_safe_abort(dma_channel);
|
||||
|
||||
|
||||
// Stop the audio SM
|
||||
pio_sm_set_enabled(audio_pio, audio_sm, false);
|
||||
|
||||
// Reset the I2S pins to avoid popping when audio is suddenly stopped
|
||||
const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK;
|
||||
pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear);
|
||||
|
||||
// Abort any in-progress DMA transfer
|
||||
dma_safe_abort(audio_dma_channel);
|
||||
}
|
||||
|
||||
uint16_t CosmicUnicorn::light() {
|
||||
adc_select_input(2);
|
||||
return adc_read();
|
||||
}
|
||||
|
||||
void CosmicUnicorn::init() {
|
||||
|
||||
if(unicorn != nullptr) {
|
||||
// Tear down the old GU instance's hardware resources
|
||||
partial_teardown();
|
||||
}
|
||||
|
||||
|
||||
// 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 = 1.8f;
|
||||
r_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, r_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
|
||||
float g_gamma = 1.8f;
|
||||
g_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, g_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
|
||||
float b_gamma = 1.8f;
|
||||
b_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, b_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
|
||||
}
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 00111111 // row pixel count (minus one)
|
||||
// 1 - 64: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
// 65 - 67: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
// 68: xxxrrrrr // row select bits
|
||||
// 69 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
|
||||
// initialise the bcd timing values and row selects in the bitstream
|
||||
for(uint8_t row = 0; row < 16; row++) {
|
||||
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
|
||||
// find the offset of this row and frame in the bitstream
|
||||
uint8_t *p = &bitstream[row * ROW_BYTES + (BCD_FRAME_BYTES * frame)];
|
||||
|
||||
p[ 0] = 64 - 1; // row pixel count
|
||||
p[68] = row; // row select
|
||||
|
||||
// set the number of bcd ticks for this frame
|
||||
uint32_t bcd_ticks = (1 << frame);
|
||||
p[69] = (bcd_ticks & 0xff) >> 0;
|
||||
p[70] = (bcd_ticks & 0xff00) >> 8;
|
||||
p[71] = (bcd_ticks & 0xff0000) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
// setup light sensor adc
|
||||
adc_init();
|
||||
adc_gpio_init(LIGHT_SENSOR);
|
||||
|
||||
gpio_init(COLUMN_CLOCK); gpio_set_dir(COLUMN_CLOCK, GPIO_OUT); gpio_put(COLUMN_CLOCK, false);
|
||||
gpio_init(COLUMN_DATA); gpio_set_dir(COLUMN_DATA, GPIO_OUT); gpio_put(COLUMN_DATA, false);
|
||||
gpio_init(COLUMN_LATCH); gpio_set_dir(COLUMN_LATCH, GPIO_OUT); gpio_put(COLUMN_LATCH, false);
|
||||
gpio_init(COLUMN_BLANK); gpio_set_dir(COLUMN_BLANK, GPIO_OUT); gpio_put(COLUMN_BLANK, true);
|
||||
|
||||
// initialise the row select, and set them to a non-visible row to avoid flashes during setup
|
||||
gpio_init(ROW_BIT_0); gpio_set_dir(ROW_BIT_0, GPIO_OUT); gpio_put(ROW_BIT_0, true);
|
||||
gpio_init(ROW_BIT_1); gpio_set_dir(ROW_BIT_1, GPIO_OUT); gpio_put(ROW_BIT_1, true);
|
||||
gpio_init(ROW_BIT_2); gpio_set_dir(ROW_BIT_2, GPIO_OUT); gpio_put(ROW_BIT_2, true);
|
||||
gpio_init(ROW_BIT_3); gpio_set_dir(ROW_BIT_3, GPIO_OUT); gpio_put(ROW_BIT_3, true);
|
||||
|
||||
sleep_ms(100);
|
||||
|
||||
// configure full output current in register 2
|
||||
|
||||
uint16_t reg1 = 0b1111111111001110;
|
||||
|
||||
// clock the register value to the first 11 driver chips
|
||||
for(int j = 0; j < 11; j++) {
|
||||
for(int i = 0; i < 16; i++) {
|
||||
if(reg1 & (1U << (15 - i))) {
|
||||
gpio_put(COLUMN_DATA, true);
|
||||
}else{
|
||||
gpio_put(COLUMN_DATA, false);
|
||||
}
|
||||
sleep_us(10);
|
||||
gpio_put(COLUMN_CLOCK, true);
|
||||
sleep_us(10);
|
||||
gpio_put(COLUMN_CLOCK, false);
|
||||
}
|
||||
}
|
||||
|
||||
// clock the last chip and latch the value
|
||||
for(int i = 0; i < 16; i++) {
|
||||
if(reg1 & (1U << (15 - i))) {
|
||||
gpio_put(COLUMN_DATA, true);
|
||||
}else{
|
||||
gpio_put(COLUMN_DATA, false);
|
||||
}
|
||||
|
||||
sleep_us(10);
|
||||
gpio_put(COLUMN_CLOCK, true);
|
||||
sleep_us(10);
|
||||
gpio_put(COLUMN_CLOCK, false);
|
||||
|
||||
if(i == 4) {
|
||||
gpio_put(COLUMN_LATCH, true);
|
||||
}
|
||||
}
|
||||
gpio_put(COLUMN_LATCH, false);
|
||||
|
||||
// reapply the blank as the above seems to cause a slight glow.
|
||||
// Note, this will produce a brief flash if a visible row is selected (which it shouldn't be)
|
||||
gpio_put(COLUMN_BLANK, false);
|
||||
sleep_us(10);
|
||||
gpio_put(COLUMN_BLANK, true);
|
||||
|
||||
gpio_init(MUTE); gpio_set_dir(MUTE, GPIO_OUT); gpio_put(MUTE, true);
|
||||
|
||||
// setup button inputs
|
||||
gpio_init(SWITCH_A); gpio_pull_up(SWITCH_A);
|
||||
gpio_init(SWITCH_B); gpio_pull_up(SWITCH_B);
|
||||
gpio_init(SWITCH_C); gpio_pull_up(SWITCH_C);
|
||||
gpio_init(SWITCH_D); gpio_pull_up(SWITCH_D);
|
||||
|
||||
gpio_init(SWITCH_SLEEP); gpio_pull_up(SWITCH_SLEEP);
|
||||
|
||||
gpio_init(SWITCH_BRIGHTNESS_UP); gpio_pull_up(SWITCH_BRIGHTNESS_UP);
|
||||
gpio_init(SWITCH_BRIGHTNESS_DOWN); gpio_pull_up(SWITCH_BRIGHTNESS_DOWN);
|
||||
|
||||
gpio_init(SWITCH_VOLUME_UP); gpio_pull_up(SWITCH_VOLUME_UP);
|
||||
gpio_init(SWITCH_VOLUME_DOWN); gpio_pull_up(SWITCH_VOLUME_DOWN);
|
||||
|
||||
// setup the pio if it has not previously been set up
|
||||
bitstream_pio = pio0;
|
||||
if(unicorn == nullptr) {
|
||||
bitstream_sm = pio_claim_unused_sm(bitstream_pio, true);
|
||||
bitstream_sm_offset = pio_add_program(bitstream_pio, &cosmic_unicorn_program);
|
||||
}
|
||||
|
||||
pio_gpio_init(bitstream_pio, COLUMN_CLOCK);
|
||||
pio_gpio_init(bitstream_pio, COLUMN_DATA);
|
||||
pio_gpio_init(bitstream_pio, COLUMN_LATCH);
|
||||
pio_gpio_init(bitstream_pio, COLUMN_BLANK);
|
||||
|
||||
pio_gpio_init(bitstream_pio, ROW_BIT_0);
|
||||
pio_gpio_init(bitstream_pio, ROW_BIT_1);
|
||||
pio_gpio_init(bitstream_pio, ROW_BIT_2);
|
||||
pio_gpio_init(bitstream_pio, ROW_BIT_3);
|
||||
|
||||
// set the blank and row pins to be high, then set all led driving pins as outputs.
|
||||
// This order is important to avoid a momentary flash
|
||||
const uint pins_to_set = 1 << COLUMN_BLANK | 0b1111 << ROW_BIT_0;
|
||||
pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set);
|
||||
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, COLUMN_CLOCK, 8, true);
|
||||
|
||||
pio_sm_config c = cosmic_unicorn_program_get_default_config(bitstream_sm_offset);
|
||||
|
||||
// osr shifts right, autopull on, autopull threshold 8
|
||||
sm_config_set_out_shift(&c, true, true, 32);
|
||||
|
||||
// configure out, set, and sideset pins
|
||||
sm_config_set_out_pins(&c, ROW_BIT_0, 4);
|
||||
sm_config_set_set_pins(&c, COLUMN_DATA, 3);
|
||||
sm_config_set_sideset_pins(&c, COLUMN_CLOCK);
|
||||
|
||||
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
// setup dma transfer for pixel data to the pio
|
||||
//if(unicorn == nullptr) {
|
||||
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);
|
||||
|
||||
|
||||
// setup audio pio program
|
||||
audio_pio = pio0;
|
||||
if(unicorn == nullptr) {
|
||||
audio_sm = pio_claim_unused_sm(audio_pio, true);
|
||||
audio_sm_offset = pio_add_program(audio_pio, &audio_i2s_program);
|
||||
}
|
||||
|
||||
pio_gpio_init(audio_pio, I2S_DATA);
|
||||
pio_gpio_init(audio_pio, I2S_BCLK);
|
||||
pio_gpio_init(audio_pio, I2S_LRCLK);
|
||||
|
||||
audio_i2s_program_init(audio_pio, audio_sm, audio_sm_offset, I2S_DATA, I2S_BCLK);
|
||||
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
|
||||
uint32_t divider = system_clock_frequency * 4 / SYSTEM_FREQ; // avoid arithmetic overflow
|
||||
pio_sm_set_clkdiv_int_frac(audio_pio, audio_sm, divider >> 8u, divider & 0xffu);
|
||||
|
||||
audio_dma_channel = dma_claim_unused_channel(true);
|
||||
dma_channel_config audio_config = dma_channel_get_default_config(audio_dma_channel);
|
||||
channel_config_set_transfer_data_size(&audio_config, DMA_SIZE_16);
|
||||
//channel_config_set_bswap(&audio_config, false); // byte swap to reverse little endian
|
||||
channel_config_set_dreq(&audio_config, pio_get_dreq(audio_pio, audio_sm, true));
|
||||
dma_channel_configure(audio_dma_channel, &audio_config, &audio_pio->txf[audio_sm], NULL, 0, false);
|
||||
|
||||
dma_channel_set_irq0_enabled(audio_dma_channel, true);
|
||||
|
||||
if(unicorn == nullptr) {
|
||||
irq_add_shared_handler(DMA_IRQ_0, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
||||
irq_set_enabled(DMA_IRQ_0, true);
|
||||
}
|
||||
|
||||
unicorn = this;
|
||||
}
|
||||
|
||||
void CosmicUnicorn::clear() {
|
||||
if(unicorn == this) {
|
||||
for(uint8_t y = 0; y < HEIGHT; y++) {
|
||||
for(uint8_t x = 0; x < WIDTH; x++) {
|
||||
set_pixel(x, y, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::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 CosmicUnicorn::play_sample(uint8_t *data, uint32_t length) {
|
||||
stop_playing();
|
||||
|
||||
if(unicorn == this) {
|
||||
// Restart the audio SM and start a new DMA transfer
|
||||
pio_sm_set_enabled(audio_pio, audio_sm, true);
|
||||
dma_channel_transfer_from_buffer_now(audio_dma_channel, data, length / 2);
|
||||
play_mode = PLAYING_BUFFER;
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::play_synth() {
|
||||
if(play_mode != PLAYING_SYNTH) {
|
||||
stop_playing();
|
||||
}
|
||||
|
||||
if(unicorn == this && play_mode == NOT_PLAYING) {
|
||||
// Nothing is playing, so we can set up the first buffer straight away
|
||||
current_buffer = 0;
|
||||
|
||||
populate_next_synth();
|
||||
|
||||
// Restart the audio SM and start a new DMA transfer
|
||||
pio_sm_set_enabled(audio_pio, audio_sm, true);
|
||||
|
||||
play_mode = PLAYING_SYNTH;
|
||||
|
||||
next_audio_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::next_audio_sequence() {
|
||||
// Clear any interrupt request caused by our channel
|
||||
//dma_channel_acknowledge_irq0(audio_dma_channel);
|
||||
// NOTE Temporary replacement of the above until this reaches pico-sdk main:
|
||||
// https://github.com/raspberrypi/pico-sdk/issues/974
|
||||
dma_hw->ints0 = 1u << audio_dma_channel;
|
||||
|
||||
if(play_mode == PLAYING_SYNTH) {
|
||||
|
||||
dma_channel_transfer_from_buffer_now(audio_dma_channel, tone_buffers[current_buffer], TONE_BUFFER_SIZE);
|
||||
current_buffer = (current_buffer + 1) % NUM_TONE_BUFFERS;
|
||||
|
||||
populate_next_synth();
|
||||
}
|
||||
else {
|
||||
play_mode = NOT_PLAYING;
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::populate_next_synth() {
|
||||
int16_t *samples = tone_buffers[current_buffer];
|
||||
for(uint i = 0; i < TONE_BUFFER_SIZE; i++) {
|
||||
samples[i] = synth.get_audio_frame();
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::stop_playing() {
|
||||
if(unicorn == this) {
|
||||
// Stop the audio SM
|
||||
pio_sm_set_enabled(audio_pio, audio_sm, false);
|
||||
|
||||
// Reset the I2S pins to avoid popping when audio is suddenly stopped
|
||||
const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK;
|
||||
pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear);
|
||||
|
||||
// Abort any in-progress DMA transfer
|
||||
dma_safe_abort(audio_dma_channel);
|
||||
|
||||
play_mode = NOT_PLAYING;
|
||||
}
|
||||
}
|
||||
|
||||
AudioChannel& CosmicUnicorn::synth_channel(uint channel) {
|
||||
assert(channel < PicoSynth::CHANNEL_COUNT);
|
||||
return synth.channels[channel];
|
||||
}
|
||||
|
||||
void CosmicUnicorn::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;
|
||||
|
||||
x = (WIDTH - 1) - x;
|
||||
y = (HEIGHT - 1) - y;
|
||||
|
||||
// map coordinates into display space
|
||||
if(y < 16) {
|
||||
// move to top half of display (which is actually the right half of the framebuffer)
|
||||
x += 32;
|
||||
}else{
|
||||
// remap y coordinate
|
||||
y -= 16;
|
||||
}
|
||||
|
||||
r = (r * this->brightness) >> 8;
|
||||
g = (g * this->brightness) >> 8;
|
||||
b = (b * this->brightness) >> 8;
|
||||
|
||||
uint16_t gamma_r = r_gamma_lut[r];
|
||||
uint16_t gamma_g = g_gamma_lut[g];
|
||||
uint16_t gamma_b = b_gamma_lut[b];
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 00111111 // row pixel count (minus one)
|
||||
// 1 - 64: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
// 65 - 67: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
// 68: xxxxrrrr // row select bits
|
||||
// 69 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
// set the appropriate bits in the separate bcd frames
|
||||
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
|
||||
uint8_t *p = &bitstream[y * ROW_BYTES + (BCD_FRAME_BYTES * frame) + 1 + x];
|
||||
|
||||
uint8_t red_bit = gamma_r & 0b1;
|
||||
uint8_t green_bit = gamma_g & 0b1;
|
||||
uint8_t blue_bit = gamma_b & 0b1;
|
||||
|
||||
*p = (blue_bit << 0) | (green_bit << 1) | (red_bit << 2);
|
||||
|
||||
gamma_r >>= 1;
|
||||
gamma_g >>= 1;
|
||||
gamma_b >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void CosmicUnicorn::set_brightness(float value) {
|
||||
value = value < 0.0f ? 0.0f : value;
|
||||
value = value > 1.0f ? 1.0f : value;
|
||||
this->brightness = floor(value * 256.0f);
|
||||
}
|
||||
|
||||
float CosmicUnicorn::get_brightness() {
|
||||
return this->brightness / 255.0f;
|
||||
}
|
||||
|
||||
void CosmicUnicorn::adjust_brightness(float delta) {
|
||||
this->set_brightness(this->get_brightness() + delta);
|
||||
}
|
||||
|
||||
void CosmicUnicorn::set_volume(float value) {
|
||||
value = value < 0.0f ? 0.0f : value;
|
||||
value = value > 1.0f ? 1.0f : value;
|
||||
this->volume = floor(value * 255.0f);
|
||||
this->synth.volume = this->volume * 255.0f;
|
||||
}
|
||||
|
||||
float CosmicUnicorn::get_volume() {
|
||||
return this->volume / 255.0f;
|
||||
}
|
||||
|
||||
void CosmicUnicorn::adjust_volume(float delta) {
|
||||
this->set_volume(this->get_volume() + delta);
|
||||
}
|
||||
|
||||
void CosmicUnicorn::update(PicoGraphics *graphics) {
|
||||
if(unicorn == this) {
|
||||
if(graphics->pen_type == PicoGraphics::PEN_RGB888) {
|
||||
uint32_t *p = (uint32_t *)graphics->frame_buffer;
|
||||
for(size_t j = 0; j < 32 * 32; j++) {
|
||||
int x = j % 32;
|
||||
int y = j / 32;
|
||||
|
||||
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(size_t j = 0; j < 32 * 32; j++) {
|
||||
int x = j % 32;
|
||||
int y = j / 32;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CosmicUnicorn::is_pressed(uint8_t button) {
|
||||
return !gpio_get(button);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
#pragma once
|
||||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico_graphics.hpp"
|
||||
#include "../pico_synth/pico_synth.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
class CosmicUnicorn {
|
||||
public:
|
||||
static const int WIDTH = 32;
|
||||
static const int HEIGHT = 32;
|
||||
|
||||
// pin assignments
|
||||
static const uint8_t COLUMN_CLOCK = 13;
|
||||
static const uint8_t COLUMN_DATA = 14;
|
||||
static const uint8_t COLUMN_LATCH = 15;
|
||||
static const uint8_t COLUMN_BLANK = 16;
|
||||
|
||||
static const uint8_t ROW_BIT_0 = 17;
|
||||
static const uint8_t ROW_BIT_1 = 18;
|
||||
static const uint8_t ROW_BIT_2 = 19;
|
||||
static const uint8_t ROW_BIT_3 = 20;
|
||||
|
||||
static const uint8_t LIGHT_SENSOR = 28;
|
||||
|
||||
static const uint8_t MUTE = 22;
|
||||
|
||||
static const uint8_t I2S_DATA = 9;
|
||||
static const uint8_t I2S_BCLK = 10;
|
||||
static const uint8_t I2S_LRCLK = 11;
|
||||
|
||||
static const uint8_t I2C_SDA = 4;
|
||||
static const uint8_t I2C_SCL = 5;
|
||||
|
||||
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_SLEEP = 27;
|
||||
|
||||
static const uint8_t SWITCH_VOLUME_UP = 7;
|
||||
static const uint8_t SWITCH_VOLUME_DOWN = 8;
|
||||
static const uint8_t SWITCH_BRIGHTNESS_UP = 21;
|
||||
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 26;
|
||||
|
||||
private:
|
||||
static const uint32_t ROW_COUNT = 16;
|
||||
static const uint32_t BCD_FRAME_COUNT = 14;
|
||||
static const uint32_t BCD_FRAME_BYTES = 72;
|
||||
static const uint32_t ROW_BYTES = BCD_FRAME_COUNT * BCD_FRAME_BYTES;
|
||||
static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES);
|
||||
static const uint SYSTEM_FREQ = 22050;
|
||||
|
||||
private:
|
||||
static PIO bitstream_pio;
|
||||
static uint bitstream_sm;
|
||||
static uint bitstream_sm_offset;
|
||||
|
||||
static PIO audio_pio;
|
||||
static uint audio_sm;
|
||||
static uint audio_sm_offset;
|
||||
|
||||
uint16_t brightness = 256;
|
||||
uint16_t volume = 127;
|
||||
|
||||
// must be aligned for 32bit dma transfer
|
||||
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
|
||||
const uint32_t bitstream_addr = (uint32_t)bitstream;
|
||||
static CosmicUnicorn* unicorn;
|
||||
static void dma_complete();
|
||||
|
||||
static const uint NUM_TONE_BUFFERS = 2;
|
||||
static const uint TONE_BUFFER_SIZE = 4;
|
||||
int16_t tone_buffers[NUM_TONE_BUFFERS][TONE_BUFFER_SIZE] = {0};
|
||||
uint current_buffer = 0;
|
||||
|
||||
PicoSynth synth;
|
||||
|
||||
enum PlayMode {
|
||||
PLAYING_BUFFER,
|
||||
//PLAYING_TONE,
|
||||
PLAYING_SYNTH,
|
||||
NOT_PLAYING
|
||||
};
|
||||
PlayMode play_mode = NOT_PLAYING;
|
||||
|
||||
public:
|
||||
~CosmicUnicorn();
|
||||
|
||||
void init();
|
||||
static inline void pio_program_init(PIO pio, uint sm, uint offset);
|
||||
|
||||
void clear();
|
||||
|
||||
void update(PicoGraphics *graphics);
|
||||
|
||||
void set_brightness(float value);
|
||||
float get_brightness();
|
||||
void adjust_brightness(float delta);
|
||||
|
||||
void set_volume(float value);
|
||||
float get_volume();
|
||||
void adjust_volume(float delta);
|
||||
|
||||
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
|
||||
|
||||
uint16_t light();
|
||||
|
||||
bool is_pressed(uint8_t button);
|
||||
|
||||
void play_sample(uint8_t *data, uint32_t length);
|
||||
void play_synth();
|
||||
void stop_playing();
|
||||
AudioChannel& synth_channel(uint channel);
|
||||
|
||||
private:
|
||||
void partial_teardown();
|
||||
void dma_safe_abort(uint channel);
|
||||
void next_audio_sequence();
|
||||
void populate_next_synth();
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
.program cosmic_unicorn
|
||||
.side_set 1 opt
|
||||
|
||||
; out pins:
|
||||
;
|
||||
; - 3: row select bit 0
|
||||
; - 4: row select bit 1
|
||||
; - 5: row select bit 2
|
||||
; - 6: row select bit 3
|
||||
|
||||
; set pins:
|
||||
;
|
||||
; - 0: column data (base)
|
||||
; - 1: column latch
|
||||
; - 2: column blank
|
||||
|
||||
; sideset pin:
|
||||
;
|
||||
; - 0: column clock
|
||||
|
||||
; for each row:
|
||||
; for each bcd frame:
|
||||
; 0: 00111111 // row pixel count (minus one)
|
||||
; 1 - 64: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
; 65 - 67: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
; 68: xxxxrrrr // row select bits
|
||||
; 69 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
;
|
||||
; .. and back to the start
|
||||
|
||||
|
||||
.wrap_target
|
||||
|
||||
; loop over row pixels
|
||||
out y, 8 ; get row pixel count (minus 1 because test is pre decrement)
|
||||
pixels:
|
||||
|
||||
; red bit
|
||||
out x, 1 side 0 [1] ; pull in blue bit from OSR into register x, clear clock
|
||||
set pins, 0b100 ; clear data bit, blank high
|
||||
jmp !x endb ; if bit was zero jump
|
||||
set pins, 0b101 ; set data bit, blank high
|
||||
endb:
|
||||
nop side 1 [2] ; clock in bit
|
||||
|
||||
; green bit
|
||||
out x, 1 side 0 [1] ; pull in green bit from OSR into register X, clear clock
|
||||
set pins, 0b100 ; clear data bit, blank high
|
||||
jmp !x endg ; if bit was zero jump
|
||||
set pins, 0b101 ; set data bit, blank high
|
||||
endg:
|
||||
nop side 1 [2] ; clock in bit
|
||||
|
||||
; blue bit
|
||||
out x, 1 side 0 [1] ; pull in red bit from OSR into register X, clear clock
|
||||
set pins, 0b100 ; clear data bit, blank high
|
||||
jmp !x endr ; if bit was zero jump
|
||||
set pins, 0b101 ; set data bit, blank high
|
||||
endr:
|
||||
out null, 5 side 1 [2] ; clock in bit
|
||||
|
||||
;out null, 5 side 0 ; discard the five dummy bits for this pixel
|
||||
|
||||
jmp y-- pixels
|
||||
|
||||
out null, 24 ; discard dummy bytes
|
||||
|
||||
out pins, 8 ; output row select
|
||||
|
||||
set pins, 0b110 [5] ; latch high, blank high
|
||||
set pins, 0b000 ; blank low (enable output)
|
||||
|
||||
; loop over bcd delay period
|
||||
out y, 24 ; get bcd delay counter value
|
||||
bcd_delay:
|
||||
jmp y-- bcd_delay
|
||||
|
||||
set pins 0b100 ; blank high (disable output)
|
||||
|
||||
.wrap
|
Ładowanie…
Reference in New Issue