|
@ -86,6 +86,8 @@ jobs:
|
|||
board: PICO_W
|
||||
- name: cosmic_unicorn
|
||||
board: PICO_W
|
||||
- name: stellar_unicorn
|
||||
board: PICO_W
|
||||
- name: inky_frame
|
||||
board: PICO_W_INKY
|
||||
patch: true
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace pimoroni {
|
|||
return to_ms_since_boot(get_absolute_time());
|
||||
}
|
||||
|
||||
constexpr uint8_t GAMMA_8BIT[256] = {
|
||||
inline constexpr uint8_t GAMMA_8BIT[256] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
|
||||
2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
|
||||
|
@ -98,7 +98,7 @@ namespace pimoroni {
|
|||
|
||||
/* Moved from pico_unicorn.cpp
|
||||
v = (uint16_t)(powf((float)(n) / 255.0f, 2.2) * 16383.0f + 0.5f) */
|
||||
constexpr uint16_t GAMMA_14BIT[256] = {
|
||||
inline constexpr uint16_t GAMMA_14BIT[256] = {
|
||||
0, 0, 0, 1, 2, 3, 4, 6, 8, 10, 13, 16, 20, 23, 28, 32,
|
||||
37, 42, 48, 54, 61, 67, 75, 82, 90, 99, 108, 117, 127, 137, 148, 159,
|
||||
170, 182, 195, 207, 221, 234, 249, 263, 278, 294, 310, 326, 343, 361, 379, 397,
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace pimoroni {
|
|||
public:
|
||||
Analog(uint pin, float amplifier_gain = 1.0f, float resistor = 0.0f, float offset = 0.0f) :
|
||||
pin(pin), amplifier_gain(amplifier_gain), resistor(resistor), offset(offset) {
|
||||
adc_init();
|
||||
if (!(adc_hw->cs & ADC_CS_EN_BITS)) adc_init();
|
||||
|
||||
//Make sure GPIO is high-impedance, no pullups etc
|
||||
adc_gpio_init(pin);
|
||||
|
|
|
@ -7,4 +7,4 @@ target_sources(rgbled INTERFACE
|
|||
target_include_directories(rgbled INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(rgbled INTERFACE pico_stdlib hardware_pwm)
|
||||
target_link_libraries(rgbled INTERFACE pico_stdlib hardware_pwm)
|
||||
|
|
|
@ -61,3 +61,4 @@ add_subdirectory(encoder)
|
|||
add_subdirectory(galactic_unicorn)
|
||||
add_subdirectory(gfx_pack)
|
||||
add_subdirectory(cosmic_unicorn)
|
||||
add_subdirectory(stellar_unicorn)
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
add_executable(
|
||||
stellar_rainbow_text
|
||||
stellar_rainbow_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics stellar_unicorn)
|
||||
pico_enable_stdio_usb(stellar_rainbow_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(stellar_rainbow_text)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
stellar_rainbow
|
||||
stellar_rainbow.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics stellar_unicorn)
|
||||
pico_enable_stdio_usb(stellar_rainbow 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(stellar_rainbow)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
stellar_eighties_super_computer
|
||||
stellar_eighties_super_computer.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics stellar_unicorn)
|
||||
pico_enable_stdio_usb(stellar_eighties_super_computer 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(stellar_eighties_super_computer)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
stellar_fire_effect
|
||||
stellar_fire_effect.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics stellar_unicorn)
|
||||
pico_enable_stdio_usb(stellar_fire_effect 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(stellar_fire_effect)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
stellar_scroll_text
|
||||
stellar_scroll_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics stellar_unicorn)
|
||||
pico_enable_stdio_usb(stellar_scroll_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(stellar_scroll_text)
|
||||
|
||||
|
||||
add_executable(
|
||||
stellar_lava_lamp
|
||||
stellar_lava_lamp.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics stellar_unicorn)
|
||||
pico_enable_stdio_usb(stellar_lava_lamp 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(stellar_lava_lamp)
|
||||
|
|
@ -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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(16, 16, nullptr);
|
||||
StellarUnicorn stellar_unicorn;
|
||||
|
||||
float lifetime[16][16];
|
||||
float age[16][16];
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
for(int y = 0; y < 16; y++) {
|
||||
for(int x = 0; x < 16; x++) {
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
stellar_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
stellar_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
stellar_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 16; y++) {
|
||||
for(int x = 0; x < 16; 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;
|
||||
}
|
||||
}
|
||||
|
||||
stellar_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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(16, 16, nullptr);
|
||||
StellarUnicorn stellar_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
int width = 16;
|
||||
int height = 17;
|
||||
|
||||
// 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();
|
||||
|
||||
stellar_unicorn.init();
|
||||
stellar_unicorn.set_brightness(0.2);
|
||||
|
||||
bool landscape = true;
|
||||
/*
|
||||
while(true) {
|
||||
stellar_unicorn.set_pixel(0, 0, 255, 0, 0);
|
||||
stellar_unicorn.set_pixel(1, 1, 0, 255, 0);
|
||||
stellar_unicorn.set_pixel(2, 2, 0, 0, 255);
|
||||
}*/
|
||||
|
||||
while(true) {
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
stellar_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
stellar_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.95f;
|
||||
|
||||
// update the heat map with our newly averaged value
|
||||
set(x, y, average);
|
||||
}
|
||||
}
|
||||
|
||||
stellar_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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(16, 16, nullptr);
|
||||
StellarUnicorn stellar_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();
|
||||
|
||||
stellar_unicorn.init();
|
||||
stellar_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() % 16;
|
||||
blob.y = rand() % 16;
|
||||
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(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
stellar_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
stellar_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[16][16] = {0.0f};
|
||||
for(auto &blob : blobs) {
|
||||
float r_sq = blob.r * blob.r;
|
||||
for(int y = 0; y < 16; y++) {
|
||||
for(int x = 0; x < 16; 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 < 16; y++) {
|
||||
for(int x = 0; x < 16; 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);
|
||||
|
||||
stellar_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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(16, 16, nullptr);
|
||||
StellarUnicorn stellar_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[16][3];
|
||||
for(int i = 0; i < 16; i++) {
|
||||
from_hsv(i / 16.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);
|
||||
|
||||
stellar_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(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_VOLUME_UP)) {
|
||||
curve += 0.05;
|
||||
if(hue_offset > 1.0f) hue_offset = 1.0f;
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_VOLUME_DOWN)) {
|
||||
curve -= 0.05;
|
||||
if(hue_offset < 0.0f) hue_offset = 0.0f;
|
||||
}
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
stellar_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
stellar_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_SLEEP)) {
|
||||
animate = false;
|
||||
}
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_A)) {
|
||||
speed += 0.05f;
|
||||
speed = speed >= 10.0f ? 10.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_B)) {
|
||||
speed -= 0.05f;
|
||||
speed = speed <= 0.0f ? 0.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_C)) {
|
||||
stripe_width += 0.05f;
|
||||
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
|
||||
}
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_D)) {
|
||||
stripe_width -= 0.05f;
|
||||
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
|
||||
}
|
||||
|
||||
for(int x = 0; x < 16; x++) {
|
||||
for(int y = 0; y < 16; 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));
|
||||
}
|
||||
}
|
||||
stellar_unicorn.update(&graphics);
|
||||
|
||||
printf("%d\n", stellar_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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(16, 16, nullptr);
|
||||
StellarUnicorn stellar_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 += (16 / 2) - (w / 2);
|
||||
p.y += (16 / 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[16][3];
|
||||
for(int i = 0; i < 16; i++) {
|
||||
from_hsv(i / 16.0f, 1.0f, 0.5f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
stellar_unicorn.init();
|
||||
|
||||
graphics.set_font("sans");
|
||||
uint i = 0;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
stellar_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
stellar_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
float s = 0.4f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
|
||||
float x = (sin((i) / 50.0f) * 45.0f);
|
||||
float y = (cos((i) / 40.0f) * 2.5f);
|
||||
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 < 16 * 16; i++) {
|
||||
int x = i % 16;
|
||||
int y = i / 16;
|
||||
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));
|
||||
}
|
||||
|
||||
stellar_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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(16, 16, nullptr);
|
||||
StellarUnicorn stellar_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();
|
||||
|
||||
stellar_unicorn.init();
|
||||
|
||||
float scroll = -16.0f;
|
||||
|
||||
while(true) {
|
||||
//uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
stellar_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(stellar_unicorn.is_pressed(stellar_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
stellar_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.125f;
|
||||
|
||||
if(scroll > width) {
|
||||
scroll = -16.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, 5), -1, 0.55);
|
||||
|
||||
stellar_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -42,3 +42,4 @@ add_subdirectory(galactic_unicorn)
|
|||
add_subdirectory(gfx_pack)
|
||||
add_subdirectory(interstate75)
|
||||
add_subdirectory(cosmic_unicorn)
|
||||
add_subdirectory(stellar_unicorn)
|
||||
|
|
|
@ -47,7 +47,7 @@ void ADCFFT::init() {
|
|||
|
||||
// Initialize the ADC harware
|
||||
// (resets it, enables the clock, spins until the hardware is ready)
|
||||
adc_init();
|
||||
if (!(adc_hw->cs & ADC_CS_EN_BITS)) adc_init();
|
||||
|
||||
// Select analog mux input (0...3 are GPIO 26, 27, 28, 29; 4 is temp sensor)
|
||||
adc_select_input(adc_channel);
|
||||
|
|
|
@ -38,10 +38,6 @@
|
|||
//
|
||||
// .. 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;
|
||||
|
@ -122,19 +118,6 @@ namespace pimoroni {
|
|||
// 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:
|
||||
|
@ -166,7 +149,7 @@ namespace pimoroni {
|
|||
}
|
||||
|
||||
// setup light sensor adc
|
||||
adc_init();
|
||||
if (!(adc_hw->cs & ADC_CS_EN_BITS)) adc_init();
|
||||
adc_gpio_init(LIGHT_SENSOR);
|
||||
|
||||
gpio_init(COLUMN_CLOCK); gpio_set_dir(COLUMN_CLOCK, GPIO_OUT); gpio_put(COLUMN_CLOCK, false);
|
||||
|
@ -476,9 +459,9 @@ namespace pimoroni {
|
|||
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];
|
||||
uint16_t gamma_r = GAMMA_14BIT[r];
|
||||
uint16_t gamma_g = GAMMA_14BIT[g];
|
||||
uint16_t gamma_b = GAMMA_14BIT[b];
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico_graphics.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "../pico_synth/pico_synth.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
|
|
@ -38,10 +38,6 @@
|
|||
//
|
||||
// .. 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;
|
||||
|
@ -122,19 +118,6 @@ namespace pimoroni {
|
|||
// 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:
|
||||
|
@ -166,7 +149,7 @@ namespace pimoroni {
|
|||
}
|
||||
|
||||
// setup light sensor adc
|
||||
adc_init();
|
||||
if (!(adc_hw->cs & ADC_CS_EN_BITS)) adc_init();
|
||||
adc_gpio_init(LIGHT_SENSOR);
|
||||
|
||||
gpio_init(COLUMN_CLOCK); gpio_set_dir(COLUMN_CLOCK, GPIO_OUT); gpio_put(COLUMN_CLOCK, false);
|
||||
|
@ -470,9 +453,9 @@ namespace pimoroni {
|
|||
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];
|
||||
uint16_t gamma_r = GAMMA_14BIT[r];
|
||||
uint16_t gamma_g = GAMMA_14BIT[g];
|
||||
uint16_t gamma_b = GAMMA_14BIT[b];
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico_graphics.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "../pico_synth/pico_synth.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
include(stellar_unicorn.cmake)
|
|
@ -0,0 +1,259 @@
|
|||
# Stellar Unicorn (C/C++)<!-- omit in toc -->
|
||||
|
||||
Stellar Unicorn offers 16x16 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/stellar-unicorn
|
||||
|
||||
## These are not your everyday RGB LEDs!
|
||||
|
||||
Internally Stellar 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 Stellar 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 Stellar 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 "stellar_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
// create a PicoGraphics framebuffer to draw into
|
||||
PicoGraphics_PenRGB888 graphics(StellarUnicorn::WIDTH, StellarUnicorn::HEIGHT, nullptr);
|
||||
|
||||
// create our StellarUnicorn object
|
||||
StellarUnicorn stellar_unicorn;
|
||||
|
||||
// message to scroll
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
// initialise the StellarUnicorn object
|
||||
stellar_unicorn.init();
|
||||
|
||||
// start position for scrolling (off the side of the display)
|
||||
float scroll = -(float)StellarUnicorn::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)StellarUnicorn::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
|
||||
stellar_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
# Interleaved Framebuffer
|
||||
|
||||
Stellar 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 Stellar Unicorn hardware, interleaved framebuffer, and PIO programs. This function must be called before attempting to do anything else with Stellar 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++
|
||||
stellar.set_brightness(0.5f);
|
||||
stellar.adjust_brightness(0.1f); // brightness is now 0.6
|
||||
stellar.adjust_brightness(0.7f); // brightness is now 1.0
|
||||
stellar.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++
|
||||
stellar.set_volume(0.5f);
|
||||
stellar.adjust_volume(0.1f); // volume is now 0.6
|
||||
stellar.adjust_volume(0.7f); // volume is now 1.0
|
||||
stellar.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 StellarUnicorn 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(!stellar.is_pressed(StellarUnicorn::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 Stellar 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 Stellar 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 Stellar Unicorn are available in constants `WIDTH` and `HEIGHT`.
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
int num_pixels = StellarUnicorn::WIDTH * StellarUnicorn::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(stellar_unicorn INTERFACE)
|
||||
|
||||
pico_generate_pio_header(stellar_unicorn ${CMAKE_CURRENT_LIST_DIR}/stellar_unicorn.pio)
|
||||
pico_generate_pio_header(stellar_unicorn ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pio)
|
||||
|
||||
|
||||
target_sources(stellar_unicorn INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/stellar_unicorn.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../pico_synth/pico_synth.cpp
|
||||
)
|
||||
|
||||
target_include_directories(stellar_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(stellar_unicorn INTERFACE pico_stdlib pico_graphics hardware_adc hardware_pio hardware_dma)
|
|
@ -0,0 +1,569 @@
|
|||
#include <math.h>
|
||||
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/adc.h"
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
|
||||
#include "stellar_unicorn.pio.h"
|
||||
#include "audio_i2s.pio.h"
|
||||
|
||||
#include "stellar_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: xxxxrrrr // row select bits
|
||||
// 2 - 65: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
// 66 - 67: xxxxxxxx, xxxxxxxx, // dummy bytes to dword align
|
||||
// 68 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
static uint32_t dma_channel;
|
||||
static uint32_t dma_ctrl_channel;
|
||||
static uint32_t audio_dma_channel;
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
StellarUnicorn* StellarUnicorn::unicorn = nullptr;
|
||||
PIO StellarUnicorn::bitstream_pio = pio0;
|
||||
uint StellarUnicorn::bitstream_sm = 0;
|
||||
uint StellarUnicorn::bitstream_sm_offset = 0;
|
||||
PIO StellarUnicorn::audio_pio = pio0;
|
||||
uint StellarUnicorn::audio_sm = 0;
|
||||
uint StellarUnicorn::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 StellarUnicorn::dma_complete() {
|
||||
if(unicorn != nullptr && dma_channel_get_irq0_status(audio_dma_channel)) {
|
||||
unicorn->next_audio_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
StellarUnicorn::~StellarUnicorn() {
|
||||
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, &stellar_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 StellarUnicorn::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 StellarUnicorn::light() {
|
||||
adc_select_input(2);
|
||||
return adc_read();
|
||||
}
|
||||
|
||||
void StellarUnicorn::init() {
|
||||
|
||||
if(unicorn != nullptr) {
|
||||
// Tear down the old GU instance's hardware resources
|
||||
partial_teardown();
|
||||
}
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 00011111 // row pixel count (minus one)
|
||||
// 1 - 16: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
// 17 - 19: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
// 20: xxxrrrrr // row select bits
|
||||
// 21 - 23: 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] = 16 - 1; // row pixel count
|
||||
p[ 1] = row; // row select
|
||||
|
||||
// set the number of bcd ticks for this frame
|
||||
uint32_t bcd_ticks = (1 << frame);
|
||||
p[20] = (bcd_ticks & 0xff) >> 0;
|
||||
p[21] = (bcd_ticks & 0xff00) >> 8;
|
||||
p[22] = (bcd_ticks & 0xff0000) >> 16;
|
||||
p[23] = (bcd_ticks & 0xff000000) >> 24;
|
||||
}
|
||||
}
|
||||
|
||||
// setup light sensor adc
|
||||
if (!(adc_hw->cs & ADC_CS_EN_BITS)) 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, &stellar_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 = stellar_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 StellarUnicorn::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 StellarUnicorn::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 StellarUnicorn::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 StellarUnicorn::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 StellarUnicorn::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 StellarUnicorn::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 StellarUnicorn::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& StellarUnicorn::synth_channel(uint channel) {
|
||||
assert(channel < PicoSynth::CHANNEL_COUNT);
|
||||
return synth.channels[channel];
|
||||
}
|
||||
|
||||
void StellarUnicorn::set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||||
x = (WIDTH - 1) - x;
|
||||
y = (HEIGHT - 1) - y;
|
||||
|
||||
r = (r * this->brightness) >> 8;
|
||||
g = (g * this->brightness) >> 8;
|
||||
b = (b * this->brightness) >> 8;
|
||||
|
||||
uint16_t gamma_r = GAMMA_14BIT[r];
|
||||
uint16_t gamma_g = GAMMA_14BIT[g];
|
||||
uint16_t gamma_b = GAMMA_14BIT[b];
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 00011111 // row pixel count (minus one)
|
||||
// 1 - 32: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
// 33 - 35: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
// 36: xxxxrrrr // row select bits
|
||||
// 37 - 39: 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) + 2 + 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 StellarUnicorn::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 StellarUnicorn::get_brightness() {
|
||||
return this->brightness / 255.0f;
|
||||
}
|
||||
|
||||
void StellarUnicorn::adjust_brightness(float delta) {
|
||||
this->set_brightness(this->get_brightness() + delta);
|
||||
}
|
||||
|
||||
void StellarUnicorn::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 StellarUnicorn::get_volume() {
|
||||
return this->volume / 255.0f;
|
||||
}
|
||||
|
||||
void StellarUnicorn::adjust_volume(float delta) {
|
||||
this->set_volume(this->get_volume() + delta);
|
||||
}
|
||||
|
||||
void StellarUnicorn::update(PicoGraphics *graphics) {
|
||||
if(unicorn == this) {
|
||||
if(graphics->pen_type == PicoGraphics::PEN_RGB888) {
|
||||
uint32_t *p = (uint32_t *)graphics->frame_buffer;
|
||||
|
||||
for(int y = 0; y < 16; y++) {
|
||||
for(int x = 0; x < 16; x++) {
|
||||
uint32_t col = *p;
|
||||
uint8_t r = (col & 0xff0000) >> 16;
|
||||
uint8_t g = (col & 0x00ff00) >> 8;
|
||||
uint8_t b = (col & 0x0000ff) >> 0;
|
||||
p++;
|
||||
|
||||
set_pixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(graphics->pen_type == PicoGraphics::PEN_RGB565) {
|
||||
uint16_t *p = (uint16_t *)graphics->frame_buffer;
|
||||
for(int y = 0; y < 16; y++) {
|
||||
for(int x = 0; x < 16; x++) {
|
||||
uint16_t col = __builtin_bswap16(*p);
|
||||
uint8_t r = (col & 0b1111100000000000) >> 8;
|
||||
uint8_t g = (col & 0b0000011111100000) >> 3;
|
||||
uint8_t b = (col & 0b0000000000011111) << 3;
|
||||
p++;
|
||||
|
||||
set_pixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(graphics->pen_type == PicoGraphics::PEN_P8 || graphics->pen_type == PicoGraphics::PEN_P4) {
|
||||
int offset = 0;
|
||||
graphics->frame_convert(PicoGraphics::PEN_RGB888, [this, offset](void *data, size_t length) mutable {
|
||||
uint32_t *p = (uint32_t *)data;
|
||||
for(auto i = 0u; i < length / 4; i++) {
|
||||
int x = offset % 16;
|
||||
int y = offset / 16;
|
||||
|
||||
uint32_t col = *p;
|
||||
uint8_t r = (col & 0xff0000) >> 16;
|
||||
uint8_t g = (col & 0x00ff00) >> 8;
|
||||
uint8_t b = (col & 0x0000ff) >> 0;
|
||||
|
||||
set_pixel(x, y, r, g, b);
|
||||
offset++;
|
||||
p++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool StellarUnicorn::is_pressed(uint8_t button) {
|
||||
return !gpio_get(button);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
#pragma once
|
||||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico_graphics.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "../pico_synth/pico_synth.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
class StellarUnicorn {
|
||||
public:
|
||||
static const int WIDTH = 16;
|
||||
static const int HEIGHT = 16;
|
||||
|
||||
// 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 = 24;
|
||||
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 StellarUnicorn* 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:
|
||||
~StellarUnicorn();
|
||||
|
||||
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,79 @@
|
|||
.program stellar_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: 00011111 // row pixel count (minus one)
|
||||
; 1: xxxxrrrr // row select bits
|
||||
; 2 - 65: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
; 66 - 67: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
; 68 - 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)
|
||||
out pins, 8 ; output row select
|
||||
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, 16 ; discard dummy bytes
|
||||
|
||||
set pins, 0b110 [5] ; latch high, blank high
|
||||
set pins, 0b000 ; blank low (enable output)
|
||||
|
||||
; loop over bcd delay period
|
||||
out y, 32 ; get bcd delay counter value
|
||||
bcd_delay:
|
||||
jmp y-- bcd_delay
|
||||
|
||||
set pins 0b100 ; blank high (disable output)
|
||||
|
||||
.wrap
|
|
@ -152,8 +152,6 @@ tone_b = 0
|
|||
# The current synth beat
|
||||
beat = 0
|
||||
|
||||
text = ""
|
||||
|
||||
|
||||
def next_beat():
|
||||
global beat
|
||||
|
@ -312,6 +310,8 @@ while True:
|
|||
# print("white gradient")
|
||||
gradient(255, 255, 255)
|
||||
|
||||
text = ""
|
||||
|
||||
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
|
||||
text = "PlaySyn"
|
||||
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
# Stellar Unicorn MicroPython Examples <!-- omit in toc -->
|
||||
|
||||
- [About Stellar Unicorn](#about-stellar-unicorn)
|
||||
- [Stellar Unicorn and PicoGraphics](#stellar-unicorn-and-picographics)
|
||||
- [Examples](#examples)
|
||||
- [Clock](#clock)
|
||||
- [Eighties Super Computer](#eighties-super-computer)
|
||||
- [Feature Test](#feature-test)
|
||||
- [Feature Test With Audio](#feature-test-with-audio)
|
||||
- [Fire Effect](#fire-effect)
|
||||
- [Lava Lamp](#lava-lamp)
|
||||
- [Nostalgia Prompt](#nostalgia-prompt)
|
||||
- [Rainbow](#rainbow)
|
||||
- [Scrolling Text](#scrolling-text)
|
||||
- [Today](#today)
|
||||
- [Wireless Examples](#wireless-examples)
|
||||
- [Cheerlights History](#cheerlights-history)
|
||||
- [Stellar Paint](#stellar-paint)
|
||||
- [Exchange Ticker](#exchange-ticker)
|
||||
- [HTTP Text](#http-text)
|
||||
- [Weather](#weather)
|
||||
- [NumPy examples](#numpy-examples)
|
||||
- [Other Examples](#other-examples)
|
||||
- [Launch (Demo Reel)](#launch-demo-reel)
|
||||
- [Other Resources](#other-resources)
|
||||
|
||||
## About Stellar Unicorn
|
||||
|
||||
Stellar Unicorn offers 16x16 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!
|
||||
|
||||
- :link: [Stellar Unicorn store page](https://shop.pimoroni.com/products/stellar-unicorn)
|
||||
|
||||
Stellar Unicorn ships with MicroPython firmware pre-loaded, but you can download the most recent version at the link below (you'll want the `stellar-unicorn` image).
|
||||
|
||||
- [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases)
|
||||
- [Installing MicroPython](../../../setting-up-micropython.md)
|
||||
|
||||
## Stellar Unicorn and PicoGraphics
|
||||
|
||||
The easiest way to start displaying cool stuff on Stellar Unicorn is using our Stellar Unicorn module (which contains a bunch of helpful functions for interacting with the buttons, adjusting brightness and suchlike) and our PicoGraphics library, which is chock full of useful functions for drawing on the LED matrix.
|
||||
|
||||
- [Stellar Unicorn function reference](../../modules/stellar_unicorn/README.md)
|
||||
- [PicoGraphics function reference](../../modules/picographics/README.md)
|
||||
|
||||
## Examples
|
||||
|
||||
### Clock
|
||||
|
||||
[clock.py](clock.py)
|
||||
|
||||
Clock example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the time by pressing A.
|
||||
|
||||
### Eighties Super Computer
|
||||
|
||||
[eighties_super_computer.py](eighties_super_computer.py)
|
||||
|
||||
Random LEDs blink on and off mimicking the look of a movie super computer doing its work in the eighties. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Feature Test
|
||||
|
||||
[feature_test.py](feature_test.py)
|
||||
|
||||
Displays some text, gradients and colours and demonstrates button use. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Feature Test With Audio
|
||||
|
||||
[feature_test_with_audio.py](feature_test_with_audio.py)
|
||||
|
||||
Displays some text, gradients and colours and demonstrates button use. Also demonstrates some of the audio / synth features.
|
||||
- Button A plays a synth tune
|
||||
- Button B plays a solo channel of the synth tune
|
||||
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
|
||||
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
|
||||
- Sleep button stops the sounds
|
||||
|
||||
### Fire Effect
|
||||
|
||||
[fire_effect.py](fire_effect.py)
|
||||
|
||||
A pretty, procedural fire effect. Switch between landscape fire and vertical fire using the A and B buttons! You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Lava Lamp
|
||||
|
||||
[lava_lamp.py](lava_lamp.py)
|
||||
|
||||
A 70s-tastic, procedural rainbow lava lamp. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Nostalgia Prompt
|
||||
|
||||
[nostalgia_prompt.py](nostalgia_prompt.py)
|
||||
|
||||
A collection of copies of classic terminal styles including C64, MS-DOS, Spectrum, and more. Images and text are drawn pixel by pixel from a pattern of Os and Xs. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Rainbow
|
||||
|
||||
[rainbow.py](rainbow.py)
|
||||
|
||||
Some good old fashioned rainbows! You can adjust the cycling speed with A and B, stripe width with C and D, hue with VOL + and -, and the brightness with LUX + and -. The sleep button stops the animation (can be started again with A or B).
|
||||
|
||||
### Scrolling Text
|
||||
|
||||
[scrolling_text.py](scrolling_text.py)
|
||||
|
||||
Display scrolling wisdom, quotes or greetz. You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Today
|
||||
|
||||
[today.py](today.py)
|
||||
|
||||
Calendar example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the date by pressing C.
|
||||
|
||||
## Wireless Examples
|
||||
|
||||
These examples need `WIFI_CONFIG.py` and `network_manager.py` (from the `common` directory) to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
|
||||
|
||||
- [micropython/examples/common](../../examples/common)
|
||||
|
||||
### Cheerlights History
|
||||
|
||||
[cheerlights_history.py](cheerlights_history.py)
|
||||
|
||||
Updates one pixel every two minutes to display the most recent #Cheerlights colour. Discover the most popular colours over time, or use it as an avant garde (but colourful) 16 hour clock! Find out more about the Cheerlights API at https://cheerlights.com/
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
|
||||
### Stellar Paint
|
||||
|
||||
[stellar_paint](stellar_paint)
|
||||
|
||||
Draw on your Stellar Unicorn from another device in real time, over wifi!
|
||||
|
||||
This example needs the `micropython-phew` and `microdot` libraries (you can install these using Thonny's 'Tools > Manage Packages').
|
||||
|
||||
### Exchange Ticker
|
||||
|
||||
[exchange_ticker.py](exchange_ticker.py)
|
||||
|
||||
This example uses the Coinbase open API to collect the current exchange rates of various cryptocurrencies.
|
||||
|
||||
Press A to change to a different base exchange currency.
|
||||
|
||||
### HTTP Text
|
||||
|
||||
[http_text](http_text)
|
||||
|
||||
Display scrolling wisdom, quotes or greetz... from another computer or device!
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
|
||||
Requires `logging.mpy` and `tinyweb` from [micropython/examples/common](../../examples/common) - copy these into the `lib` folder on your Pico W. You'll also need `index.html` to be saved alongside `html_text.py`.
|
||||
|
||||
### Weather
|
||||
|
||||
[weather](weather)
|
||||
|
||||
Display current weather data from the [Open-Meteo](https://open-meteo.com/) weather API.
|
||||
|
||||
## NumPy examples
|
||||
|
||||
[numpy](numpy)
|
||||
|
||||
The examples in the folder use `numpy`-like array functions contained in the `ulab` library for super fast graphical effects.
|
||||
|
||||
## Other Examples
|
||||
|
||||
### Launch (Demo Reel)
|
||||
|
||||
[launch](launch)
|
||||
|
||||
If you want to get the demo reel that Stellar Unicorn ships with back, copy the contents of this `launch` folder to your Pico W.
|
||||
|
||||
## Other Resources
|
||||
|
||||
Here are some cool Stellar Unicorn community projects and resources that you might find useful / inspirational! Note that code at the links below has not been tested by us and we're not able to offer support with it.
|
||||
|
||||
- :link: [Green Energy Display with Stellar Unicorn](https://www.hackster.io/andreas-motzek/clock-and-green-energy-display-with-stellar-unicorn-641dcb)
|
||||
- :link: [stellar-emoji-react - paint emojis from a computer, phone or tablet](https://github.com/chriscareycode/stellar-unicorn/tree/main/stellar-emoji-react)
|
||||
- :link: [stellar-paste - paste images from the clipboard to Stellar Unicorn](https://github.com/chriscareycode/stellar-unicorn/tree/main/stellar-paste)
|
|
@ -0,0 +1,151 @@
|
|||
# This Stellar Unicorn example updates a pixel every two(ish) minutes
|
||||
# to display the most recent #cheerlights colour. Discover the most popular
|
||||
# colours over time, or use it as an avant garde (but colourful) 16 hour clock!
|
||||
# Find out more about the Cheerlights API at https://cheerlights.com/
|
||||
#
|
||||
# To run this example you'll need WIFI_CONFIG.py and network_manager.py from
|
||||
# the pimoroni-pico micropython/examples/common folder
|
||||
|
||||
import WIFI_CONFIG
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio
|
||||
import urequests
|
||||
import time
|
||||
import random
|
||||
from machine import Timer, Pin
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY, PEN_P8 as PEN
|
||||
|
||||
URL = 'http://api.thingspeak.com/channels/1417/field/1/last.txt'
|
||||
|
||||
UPDATE_INTERVAL = 60 * 60 / 16 # refresh interval in secs. Be nice to free APIs!
|
||||
# Calculated as 60 minutes * 60 seconds divided by number of pixels per row
|
||||
# so that a row of LEDs equates (approximately) to an hour
|
||||
|
||||
CHEERLIGHTS_COLOR_VALUES = [
|
||||
(0x00, 0x00, 0x00), # Black/Unlit
|
||||
(0xFF, 0x00, 0x00),
|
||||
(0x00, 0x80, 0x00),
|
||||
(0x00, 0x00, 0xFF),
|
||||
(0x00, 0xFF, 0xFF),
|
||||
(0xFF, 0xFF, 0xFF),
|
||||
(0xFD, 0xF5, 0xE6),
|
||||
(0x80, 0x00, 0x80),
|
||||
(0xFF, 0x00, 0xFF),
|
||||
(0xFF, 0xFF, 0x00),
|
||||
(0xFF, 0xA5, 0x00),
|
||||
(0xFF, 0xC0, 0xCB),
|
||||
]
|
||||
|
||||
CHEERLIGHTS_COLOR_NAMES = [
|
||||
"black", # Black/Unlit, not part of cheerlights colours
|
||||
"red",
|
||||
"green",
|
||||
"blue",
|
||||
"cyan",
|
||||
"white",
|
||||
"oldlace",
|
||||
"purple",
|
||||
"magenta",
|
||||
"yellow",
|
||||
"orange",
|
||||
"pink"
|
||||
]
|
||||
|
||||
|
||||
def status_handler(mode, status, ip):
|
||||
# reports wifi connection status
|
||||
print(mode, status, ip)
|
||||
print('Connecting to wifi...')
|
||||
if status is not None:
|
||||
if status:
|
||||
print('Wifi connection successful!')
|
||||
else:
|
||||
print('Wifi connection failed!')
|
||||
|
||||
|
||||
def get_data():
|
||||
global index
|
||||
# open the json file
|
||||
if UPDATE_INTERVAL >= 60:
|
||||
print(f'Requesting URL: {URL}')
|
||||
r = urequests.get(URL)
|
||||
name = r.content.decode("utf-8").strip()
|
||||
r.close()
|
||||
print('Data obtained!')
|
||||
|
||||
else:
|
||||
print("Random test colour!")
|
||||
# For sped-up testing we don't want to hit the API at all
|
||||
name = random.choice(CHEERLIGHTS_COLOR_NAMES[1:])
|
||||
|
||||
# flash the onboard LED after getting data
|
||||
pico_led.value(True)
|
||||
time.sleep(0.2)
|
||||
pico_led.value(False)
|
||||
|
||||
# add the new hex colour to the end of the array
|
||||
if index == (width * height):
|
||||
index = 0
|
||||
graphics.clear()
|
||||
|
||||
colour_array[index] = CHEERLIGHTS_COLOR_NAMES.index(name)
|
||||
index += 1
|
||||
print(f'Colour added to array: {name}')
|
||||
|
||||
su.update(graphics)
|
||||
print("LEDs updated!")
|
||||
|
||||
|
||||
su = StellarUnicorn()
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
# set up a buffer to store the colours
|
||||
colour_array = bytearray(width * height)
|
||||
|
||||
# We'll use palette mode, so just make the colour list the display buffer
|
||||
graphics = PicoGraphics(DISPLAY, pen_type=PEN, buffer=colour_array)
|
||||
|
||||
# Set up the palette with cheerlights colour values
|
||||
graphics.set_palette(CHEERLIGHTS_COLOR_VALUES)
|
||||
graphics.set_pen(0)
|
||||
graphics.clear()
|
||||
|
||||
# Keep track of the pixel we're lighting
|
||||
index = 0
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
# set up the Pico W's onboard LED
|
||||
pico_led = Pin('LED', Pin.OUT)
|
||||
|
||||
# set up wifi
|
||||
try:
|
||||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
|
||||
except Exception as e:
|
||||
print(f'Wifi connection failed! {e}')
|
||||
|
||||
# get the first lot of data
|
||||
get_data()
|
||||
|
||||
# start timer (the timer will call the function to update our data every UPDATE_INTERVAL)
|
||||
timer = Timer(-1)
|
||||
timer.init(period=int(UPDATE_INTERVAL * 1000), mode=Timer.PERIODIC, callback=lambda t: get_data())
|
||||
|
||||
while True:
|
||||
# adjust brightness with LUX + and -
|
||||
# LEDs take a couple of secs to update, so adjust in big (10%) steps
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.1)
|
||||
su.update(graphics)
|
||||
print(f"Brightness set to {su.get_brightness()}")
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.1)
|
||||
su.update(graphics)
|
||||
print(f"Brightness set to {su.get_brightness()}")
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,229 @@
|
|||
# Clock example with NTP synchronization
|
||||
#
|
||||
# Create a secrets.py with your Wifi details to be able to get the time
|
||||
# when the Stellar Unicorn isn't connected to Thonny.
|
||||
#
|
||||
# secrets.py should contain:
|
||||
# WIFI_SSID = "Your WiFi SSID"
|
||||
# WIFI_PASSWORD = "Your WiFi password"
|
||||
#
|
||||
# Clock synchronizes time on start, and resynchronizes if you press the A button
|
||||
|
||||
import time
|
||||
import math
|
||||
import machine
|
||||
import network
|
||||
import ntptime
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
try:
|
||||
from secrets import WIFI_SSID, WIFI_PASSWORD
|
||||
wifi_available = True
|
||||
except ImportError:
|
||||
print("Create secrets.py with your WiFi credentials to get time from NTP")
|
||||
wifi_available = False
|
||||
|
||||
|
||||
# constants for controlling the background colour throughout the day
|
||||
MIDDAY_HUE = 1.1
|
||||
MIDNIGHT_HUE = 0.8
|
||||
HUE_OFFSET = -0.1
|
||||
|
||||
MIDDAY_SATURATION = 1.0
|
||||
MIDNIGHT_SATURATION = 1.0
|
||||
|
||||
MIDDAY_VALUE = 0.8
|
||||
MIDNIGHT_VALUE = 0.3
|
||||
|
||||
|
||||
# create stellar object and graphics surface for drawing
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
# create the rtc object
|
||||
rtc = machine.RTC()
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
# set up some pens to use later
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
BLACK = graphics.create_pen(0, 0, 0)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def from_hsv(h, s, v):
|
||||
i = math.floor(h * 6.0)
|
||||
f = h * 6.0 - i
|
||||
v *= 255.0
|
||||
p = v * (1.0 - s)
|
||||
q = v * (1.0 - f * s)
|
||||
t = v * (1.0 - (1.0 - f) * s)
|
||||
|
||||
i = int(i) % 6
|
||||
if i == 0:
|
||||
return int(v), int(t), int(p)
|
||||
if i == 1:
|
||||
return int(q), int(v), int(p)
|
||||
if i == 2:
|
||||
return int(p), int(v), int(t)
|
||||
if i == 3:
|
||||
return int(p), int(q), int(v)
|
||||
if i == 4:
|
||||
return int(t), int(p), int(v)
|
||||
if i == 5:
|
||||
return int(v), int(p), int(q)
|
||||
|
||||
|
||||
# function for drawing a gradient background
|
||||
def gradient_background(start_hue, start_sat, start_val, end_hue, end_sat, end_val):
|
||||
half_width = width // 2
|
||||
for x in range(0, half_width):
|
||||
hue = ((end_hue - start_hue) * (x / half_width)) + start_hue
|
||||
sat = ((end_sat - start_sat) * (x / half_width)) + start_sat
|
||||
val = ((end_val - start_val) * (x / half_width)) + start_val
|
||||
colour = from_hsv(hue, sat, val)
|
||||
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
|
||||
for y in range(0, height):
|
||||
graphics.pixel(x, y)
|
||||
graphics.pixel(width - x - 1, y)
|
||||
|
||||
colour = from_hsv(end_hue, end_sat, end_val)
|
||||
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
|
||||
for y in range(0, height):
|
||||
graphics.pixel(half_width, y)
|
||||
|
||||
|
||||
# function for drawing outlined text
|
||||
def outline_text(text, x, y):
|
||||
graphics.set_pen(BLACK)
|
||||
graphics.text(text, x - 1, y - 1, -1, 1)
|
||||
graphics.text(text, x, y - 1, -1, 1)
|
||||
graphics.text(text, x + 1, y - 1, -1, 1)
|
||||
graphics.text(text, x - 1, y, -1, 1)
|
||||
graphics.text(text, x + 1, y, -1, 1)
|
||||
graphics.text(text, x - 1, y + 1, -1, 1)
|
||||
graphics.text(text, x, y + 1, -1, 1)
|
||||
graphics.text(text, x + 1, y + 1, -1, 1)
|
||||
|
||||
graphics.set_pen(WHITE)
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
# Connect to wifi and synchronize the RTC time from NTP
|
||||
def sync_time():
|
||||
if not wifi_available:
|
||||
return
|
||||
|
||||
# Start connection
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
|
||||
|
||||
# Wait for connect success or failure
|
||||
max_wait = 100
|
||||
while max_wait > 0:
|
||||
if wlan.status() < 0 or wlan.status() >= 3:
|
||||
break
|
||||
max_wait -= 1
|
||||
print('waiting for connection...')
|
||||
time.sleep(0.2)
|
||||
|
||||
redraw_display_if_reqd()
|
||||
su.update(graphics)
|
||||
|
||||
if max_wait > 0:
|
||||
print("Connected")
|
||||
|
||||
try:
|
||||
ntptime.settime()
|
||||
print("Time set")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
wlan.disconnect()
|
||||
wlan.active(False)
|
||||
|
||||
|
||||
# NTP synchronizes the time to UTC, this allows you to adjust the displayed time
|
||||
# by one hour increments from UTC by pressing the volume up/down buttons
|
||||
#
|
||||
# We use the IRQ method to detect the button presses to avoid incrementing/decrementing
|
||||
# multiple times when the button is held.
|
||||
utc_offset = 0
|
||||
|
||||
up_button = machine.Pin(StellarUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
down_button = machine.Pin(StellarUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
|
||||
|
||||
def adjust_utc_offset(pin):
|
||||
global utc_offset
|
||||
if pin == up_button:
|
||||
utc_offset += 1
|
||||
if pin == down_button:
|
||||
utc_offset -= 1
|
||||
|
||||
|
||||
up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
|
||||
down_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
|
||||
|
||||
|
||||
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
|
||||
|
||||
last_second = second
|
||||
|
||||
|
||||
# Check whether the RTC time has changed and if so redraw the display
|
||||
def redraw_display_if_reqd():
|
||||
global year, month, day, wd, hour, minute, second, last_second
|
||||
|
||||
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
|
||||
if second != last_second:
|
||||
hour = (hour + utc_offset) % 24
|
||||
time_through_day = (((hour * 60) + minute) * 60) + second
|
||||
percent_through_day = time_through_day / 86400
|
||||
percent_to_midday = 1.0 - ((math.cos(percent_through_day * math.pi * 2) + 1) / 2)
|
||||
print(percent_to_midday)
|
||||
|
||||
hue = ((MIDDAY_HUE - MIDNIGHT_HUE) * percent_to_midday) + MIDNIGHT_HUE
|
||||
sat = ((MIDDAY_SATURATION - MIDNIGHT_SATURATION) * percent_to_midday) + MIDNIGHT_SATURATION
|
||||
val = ((MIDDAY_VALUE - MIDNIGHT_VALUE) * percent_to_midday) + MIDNIGHT_VALUE
|
||||
|
||||
gradient_background(hue, sat, val,
|
||||
hue + HUE_OFFSET, sat, val)
|
||||
|
||||
clock = "{:02}:{:02}:{:02}".format(hour, minute, second)
|
||||
|
||||
# calculate text position so that it is centred
|
||||
w = graphics.measure_text(clock, 1)
|
||||
x = int(width / 2 - w / 2 + 1)
|
||||
y = 5
|
||||
|
||||
outline_text(clock, x, y)
|
||||
|
||||
last_second = second
|
||||
|
||||
|
||||
# set the font
|
||||
graphics.set_font("bitmap6")
|
||||
su.set_brightness(0.5)
|
||||
|
||||
sync_time()
|
||||
|
||||
while True:
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
sync_time()
|
||||
|
||||
redraw_display_if_reqd()
|
||||
|
||||
# update the display
|
||||
su.update(graphics)
|
||||
|
||||
time.sleep(0.01)
|
|
@ -0,0 +1,79 @@
|
|||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Random LEDs blink on and off mimicking the look of a movie
|
||||
super computer doing its work in the eighties.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
colour = (230, 150, 0)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def setup():
|
||||
global width, height, lifetime, age
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
lifetime = [[0.0 for y in range(height)] for x in range(width)]
|
||||
age = [[0.0 for y in range(height)] for x in range(width)]
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
|
||||
age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y]
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if age[x][y] < lifetime[x][y] * 0.3:
|
||||
graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2]))
|
||||
elif age[x][y] < lifetime[x][y] * 0.5:
|
||||
decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0
|
||||
graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2])))
|
||||
else:
|
||||
graphics.set_pen(0)
|
||||
graphics.pixel(x, y)
|
||||
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def update():
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if age[x][y] >= lifetime[x][y]:
|
||||
age[x][y] = 0.0
|
||||
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
|
||||
|
||||
age[x][y] += 0.025
|
||||
|
||||
|
||||
setup()
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
start = time.ticks_ms()
|
||||
|
||||
draw()
|
||||
update()
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,148 @@
|
|||
"""
|
||||
This example uses the Coinbase open API to collect the current exchange rates.
|
||||
Use Switch A to change to a different base exchange currency.
|
||||
"""
|
||||
|
||||
import WIFI_CONFIG
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio
|
||||
import urequests
|
||||
import time
|
||||
import math
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
import gc
|
||||
|
||||
URL = 'https://api.coinbase.com/v2/exchange-rates?currency={0}'
|
||||
|
||||
currencies = {"Bitcoin": "BTC", "Ethereun": "ETH", "Pound": "GBP", "Dollar": "USD", "Dogecoin": "DOGE"}
|
||||
currency_keys = list(currencies.keys())
|
||||
|
||||
ref_currency_name = ""
|
||||
currency_name = ""
|
||||
currency_symbol = ""
|
||||
currency_rate = ""
|
||||
rate_keys = []
|
||||
|
||||
# display options
|
||||
line_1_line = -2
|
||||
line_2_line = 9
|
||||
line_3_line = 20
|
||||
|
||||
ref_currency_index = 0
|
||||
|
||||
cycles_per_sequence = 120
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
|
||||
# for Handling the wifi connection
|
||||
def status_handler(mode, status, ip):
|
||||
# reports wifi connection status
|
||||
print(mode, status, ip)
|
||||
print('Connecting to wifi...')
|
||||
if status is not None:
|
||||
if status:
|
||||
print('Wifi connection successful!')
|
||||
else:
|
||||
print('Wifi connection failed!')
|
||||
|
||||
|
||||
try:
|
||||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
|
||||
except Exception as e:
|
||||
print(f'Wifi connection failed! {e}')
|
||||
|
||||
|
||||
def get_data(currency_selected):
|
||||
|
||||
graphics.set_pen(graphics.create_pen(20, 20, 20))
|
||||
graphics.clear()
|
||||
graphics.set_pen(graphics.create_pen(100, 100, 100))
|
||||
graphics.text("Get", 0, 10, scale=1, spacing=1)
|
||||
graphics.text("data", 8, 16, scale=1, spacing=1)
|
||||
su.update(graphics)
|
||||
gc.collect()
|
||||
# open the json file
|
||||
print('Requesting URL:')
|
||||
print(URL.format(currencies[currency_selected]))
|
||||
r = urequests.get(URL.format(currencies[currency_selected]))
|
||||
gc.collect()
|
||||
# open the json data
|
||||
data_obj = r.json()
|
||||
print('Data obtained!')
|
||||
r.close()
|
||||
return data_obj
|
||||
|
||||
|
||||
def calculate_xpos(length, cycle):
|
||||
cycle_phase = math.cos(math.pi * cycle / (cycles_per_sequence / 2))
|
||||
pos_x = int((-(length / 2) * 10) - (length / 2) * 10 * cycle_phase)
|
||||
return pos_x
|
||||
|
||||
|
||||
def update_display(cycle):
|
||||
|
||||
graphics.set_pen(graphics.create_pen(20, 20, 20))
|
||||
graphics.clear()
|
||||
graphics.set_pen(graphics.create_pen(100, 0, 0))
|
||||
graphics.text(ref_currency_name, calculate_xpos((len(ref_currency_name)), cycle), line_1_line, scale=2, spacing=1)
|
||||
graphics.set_pen(graphics.create_pen(100, 100, 0))
|
||||
if len(currency_symbol) > 3:
|
||||
graphics.text(currency_symbol, calculate_xpos((len(currency_symbol)), cycle), line_2_line, scale=2, spacing=1)
|
||||
else:
|
||||
graphics.text(currency_symbol, 0, line_2_line, scale=2, spacing=1)
|
||||
graphics.set_pen(graphics.create_pen(0, 100, 100))
|
||||
graphics.text(currency_rate, calculate_xpos((len(currency_rate)), cycle), line_3_line, scale=2, spacing=1)
|
||||
|
||||
|
||||
def update_base_currency(index):
|
||||
fetched_data = 0
|
||||
global rates, rate_keys, currency_symbol, currency_rate, ref_currency_name
|
||||
fetched_data = get_data(currency_keys[index])
|
||||
rates = fetched_data['data']['rates']
|
||||
rate_keys = list(rates.keys())
|
||||
currency_symbol = rate_keys[index]
|
||||
currency_rate = str(rates[rate_keys[index]])
|
||||
ref_currency_name = "{0}-{1}".format(currency_keys[index], currencies[currency_keys[index]])
|
||||
gc.collect()
|
||||
|
||||
|
||||
update_base_currency(ref_currency_index)
|
||||
update_display(0)
|
||||
su.update(graphics)
|
||||
cycle_count = 0
|
||||
symbol_index = 0
|
||||
print("Display {0} {1}".format(currency_symbol, currency_rate))
|
||||
|
||||
while 1:
|
||||
if cycle_count > 4 * cycles_per_sequence:
|
||||
cycle_count = 0
|
||||
symbol_index += 1
|
||||
if symbol_index > len(currency_keys):
|
||||
symbol_index = 0
|
||||
print("Display {0} {1}".format(currency_symbol, currency_rate))
|
||||
currency_symbol = rate_keys[symbol_index]
|
||||
currency_rate = rates[rate_keys[symbol_index]]
|
||||
|
||||
if (su.is_pressed(StellarUnicorn.SWITCH_A)):
|
||||
ref_currency_index += 1
|
||||
if (ref_currency_index > len(currency_keys)):
|
||||
ref_currency_index = 0
|
||||
update_base_currency(ref_currency_index)
|
||||
|
||||
if (su.is_pressed(StellarUnicorn.SWITCH_B)):
|
||||
cycle_count = 0
|
||||
symbol_index += 1
|
||||
|
||||
if symbol_index > len(rate_keys):
|
||||
symbol_index = 0
|
||||
currency_symbol = rate_keys[symbol_index]
|
||||
currency_rate = rates[rate_keys[symbol_index]]
|
||||
|
||||
update_display(cycle_count)
|
||||
su.update(graphics)
|
||||
cycle_count += 1
|
||||
time.sleep(0.1)
|
|
@ -0,0 +1,123 @@
|
|||
import time
|
||||
import math
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Displays some text, gradients and colours and demonstrates button use.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
|
||||
def gradient(r, g, b):
|
||||
for y in range(0, height):
|
||||
for x in range(0, width):
|
||||
graphics.set_pen(graphics.create_pen(int((r * x) / 16), int((g * x) / 16), int((b * x) / 16)))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def grid(r, g, b):
|
||||
for y in range(0, height):
|
||||
for x in range(0, width):
|
||||
if (x + y) % 2 == 0:
|
||||
graphics.set_pen(graphics.create_pen(r, g, b))
|
||||
else:
|
||||
graphics.set_pen(0)
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def outline_text(text):
|
||||
ms = time.ticks_ms()
|
||||
|
||||
graphics.set_font("bitmap8")
|
||||
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
|
||||
w = graphics.measure_text(text, 1)
|
||||
|
||||
x = int(16 / 2 - w / 2 + 1)
|
||||
y = 12
|
||||
|
||||
graphics.set_pen(0)
|
||||
graphics.text(text, x - 1, y - 1, -1, 1)
|
||||
graphics.text(text, x, y - 1, -1, 1)
|
||||
graphics.text(text, x + 1, y - 1, -1, 1)
|
||||
graphics.text(text, x - 1, y, -1, 1)
|
||||
graphics.text(text, x + 1, y, -1, 1)
|
||||
graphics.text(text, x - 1, y + 1, -1, 1)
|
||||
graphics.text(text, x, y + 1, -1, 1)
|
||||
graphics.text(text, x + 1, y + 1, -1, 1)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(v, v, v))
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
time_ms = time.ticks_ms()
|
||||
test = (time_ms // 1000) % 5
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
|
||||
if test == 0:
|
||||
print("grid pattern")
|
||||
grid(255, 255, 255)
|
||||
elif test == 1:
|
||||
print("red gradient")
|
||||
gradient(255, 0, 0)
|
||||
elif test == 2:
|
||||
print("green gradient")
|
||||
gradient(0, 255, 0)
|
||||
elif test == 3:
|
||||
print("blue gradient")
|
||||
gradient(0, 0, 255)
|
||||
elif test == 4:
|
||||
print("white gradient")
|
||||
gradient(255, 255, 255)
|
||||
|
||||
text = ""
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
text = "Button A"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
text = "Button B"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
text = "Button C"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_D):
|
||||
text = "Button D"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_UP):
|
||||
text = "Louder!"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_DOWN):
|
||||
text = "Quieter"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
text = "Brighter!"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
text = "Darker"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_SLEEP):
|
||||
text = "Zzz... zzz..."
|
||||
|
||||
outline_text(text)
|
||||
|
||||
su.update(graphics)
|
|
@ -0,0 +1,347 @@
|
|||
import gc
|
||||
import time
|
||||
import math
|
||||
from machine import Timer
|
||||
from stellar import StellarUnicorn, Channel
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Displays some text, gradients and colours and demonstrates button use.
|
||||
Also demonstrates some of the audio / synth features.
|
||||
|
||||
- Button A plays a synth tune
|
||||
- Button B plays a solo channel of the synth tune
|
||||
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
|
||||
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
|
||||
- Sleep button stops the sounds
|
||||
'''
|
||||
|
||||
gc.collect()
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
SONG_LENGTH = 384
|
||||
HAT = 20000
|
||||
BASS = 500
|
||||
SNARE = 6000
|
||||
SUB = 50
|
||||
|
||||
melody_notes = (
|
||||
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
|
||||
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
|
||||
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 262, 0, 294, 0, 392, 0, 440, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
rhythm_notes = (
|
||||
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
|
||||
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
|
||||
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0)
|
||||
|
||||
drum_beats = (
|
||||
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
|
||||
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
|
||||
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0)
|
||||
|
||||
hi_hat = (
|
||||
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
|
||||
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
|
||||
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1)
|
||||
|
||||
bass_notes = (
|
||||
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
|
||||
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
|
||||
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
notes = [melody_notes, rhythm_notes, drum_beats, hi_hat, bass_notes]
|
||||
channels = [su.synth_channel(i) for i in range(len(notes) + 1)] # Extra channel for tones
|
||||
|
||||
# Configure the synth to play our notes
|
||||
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
|
||||
attack=0.016,
|
||||
decay=0.168,
|
||||
sustain=0xafff / 65535,
|
||||
release=0.168,
|
||||
volume=10000 / 65535)
|
||||
|
||||
channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE,
|
||||
attack=0.038,
|
||||
decay=0.300,
|
||||
sustain=0,
|
||||
release=0,
|
||||
volume=12000 / 65535)
|
||||
|
||||
channels[2].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.010,
|
||||
sustain=16000 / 65535,
|
||||
release=0.100,
|
||||
volume=18000 / 65535)
|
||||
|
||||
channels[3].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.005,
|
||||
sustain=8000 / 65535,
|
||||
release=0.040,
|
||||
volume=8000 / 65535)
|
||||
|
||||
channels[4].configure(waveforms=Channel.SQUARE,
|
||||
attack=0.010,
|
||||
decay=0.100,
|
||||
sustain=0,
|
||||
release=0.500,
|
||||
volume=12000 / 65535)
|
||||
|
||||
|
||||
def gradient(r, g, b):
|
||||
for y in range(0, height):
|
||||
for x in range(0, width):
|
||||
graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52)))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def grid(r, g, b):
|
||||
for y in range(0, height):
|
||||
for x in range(0, width):
|
||||
if (x + y) % 2 == 0:
|
||||
graphics.set_pen(graphics.create_pen(r, g, b))
|
||||
else:
|
||||
graphics.set_pen(0)
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def outline_text(text):
|
||||
ms = time.ticks_ms()
|
||||
|
||||
graphics.set_font("bitmap6")
|
||||
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
|
||||
w = graphics.measure_text(text, 1)
|
||||
|
||||
x = int(16 / 2 - w / 2 + 1)
|
||||
y = 5
|
||||
|
||||
graphics.set_pen(0)
|
||||
graphics.text(text, x - 1, y - 1, -1, 1)
|
||||
graphics.text(text, x, y - 1, -1, 1)
|
||||
graphics.text(text, x + 1, y - 1, -1, 1)
|
||||
graphics.text(text, x - 1, y, -1, 1)
|
||||
graphics.text(text, x + 1, y, -1, 1)
|
||||
graphics.text(text, x - 1, y + 1, -1, 1)
|
||||
graphics.text(text, x, y + 1, -1, 1)
|
||||
graphics.text(text, x + 1, y + 1, -1, 1)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(v, v, v))
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
# Vars for storing button state
|
||||
was_a_pressed = False
|
||||
was_b_pressed = False
|
||||
was_c_pressed = False
|
||||
was_d_pressed = False
|
||||
was_z_pressed = False
|
||||
|
||||
# The two frequencies to play
|
||||
tone_a = 0
|
||||
tone_b = 0
|
||||
|
||||
# The current synth beat
|
||||
beat = 0
|
||||
|
||||
|
||||
def next_beat():
|
||||
global beat
|
||||
for i in range(5):
|
||||
if notes[i][beat] > 0:
|
||||
channels[i].frequency(notes[i][beat])
|
||||
channels[i].trigger_attack()
|
||||
elif notes[i][beat] == -1:
|
||||
channels[i].trigger_release()
|
||||
|
||||
beat = (beat + 1) % SONG_LENGTH
|
||||
|
||||
|
||||
def tick(timer):
|
||||
next_beat()
|
||||
|
||||
|
||||
timer = Timer(-1)
|
||||
|
||||
synthing = False
|
||||
|
||||
|
||||
while True:
|
||||
|
||||
time_ms = time.ticks_ms()
|
||||
test = (time_ms // 1000) % 5
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
if not was_a_pressed:
|
||||
channels[0].volume(10000 / 65535)
|
||||
channels[1].volume(12000 / 65535)
|
||||
channels[2].volume(18000 / 65535)
|
||||
channels[3].volume(8000 / 65535)
|
||||
channels[4].volume(12000 / 65535)
|
||||
channels[5].volume(0)
|
||||
|
||||
# If the synth is not already playing, init the first beat
|
||||
if not synthing:
|
||||
beat = 0
|
||||
next_beat()
|
||||
|
||||
su.play_synth()
|
||||
synthing = True
|
||||
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
|
||||
|
||||
was_a_pressed = True
|
||||
else:
|
||||
was_a_pressed = False
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
if not was_b_pressed:
|
||||
channels[0].volume(0)
|
||||
channels[1].volume(12000 / 65535)
|
||||
channels[2].volume(0)
|
||||
channels[3].volume(0)
|
||||
channels[4].volume(0)
|
||||
channels[5].volume(0)
|
||||
|
||||
# If the synth is not already playing, init the first beat
|
||||
if not synthing:
|
||||
beat = 0
|
||||
next_beat()
|
||||
|
||||
su.play_synth()
|
||||
synthing = True
|
||||
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
|
||||
|
||||
was_b_pressed = True
|
||||
else:
|
||||
was_b_pressed = False
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
if not was_c_pressed:
|
||||
# Stop synth (if running) and play Tone A
|
||||
timer.deinit()
|
||||
tone_a = 400
|
||||
channels[5].play_tone(tone_a, 0.06)
|
||||
channels[5].volume(12000 / 65535)
|
||||
|
||||
su.play_synth()
|
||||
synthing = False
|
||||
|
||||
was_c_pressed = True
|
||||
else:
|
||||
was_c_pressed = False
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_D):
|
||||
if not was_d_pressed:
|
||||
# Stop synth (if running) and play Tone B
|
||||
timer.deinit()
|
||||
tone_b = 600
|
||||
|
||||
channels[5].play_tone(tone_b, 0.06, attack=0.5)
|
||||
channels[5].volume(12000 / 65535)
|
||||
|
||||
su.play_synth()
|
||||
synthing = False
|
||||
|
||||
was_d_pressed = True
|
||||
else:
|
||||
was_d_pressed = False
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
if tone_b > 0: # Zero means tone not playing
|
||||
# Increase Tone B
|
||||
tone_b = min(tone_b + 10, 20000)
|
||||
channels[5].frequency(tone_b)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
if tone_b > 0: # Zero means tone not playing
|
||||
# Decrease Tone B
|
||||
tone_b = max(tone_b - 10, 10)
|
||||
channels[5].frequency(max(tone_b, 10))
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_UP):
|
||||
if tone_a > 0: # Zero means tone not playing
|
||||
# Increase Tone A
|
||||
tone_a = min(tone_a + 10, 20000)
|
||||
channels[5].frequency(tone_a)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_DOWN):
|
||||
if tone_a > 0: # Zero means tone not playing
|
||||
# Decrease Tone A
|
||||
tone_a = max(tone_a - 10, 10)
|
||||
channels[5].frequency(tone_a)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_SLEEP):
|
||||
if not was_z_pressed:
|
||||
# Stop synth and both tones
|
||||
tone_a = 0
|
||||
tone_b = 0
|
||||
su.stop_playing()
|
||||
timer.deinit()
|
||||
synthing = False
|
||||
|
||||
was_z_pressed = True
|
||||
else:
|
||||
was_z_pressed = False
|
||||
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
|
||||
if test == 0:
|
||||
# print("grid pattern")
|
||||
grid(255, 255, 255)
|
||||
elif test == 1:
|
||||
# print("red gradient")
|
||||
gradient(255, 0, 0)
|
||||
elif test == 2:
|
||||
# print("green gradient")
|
||||
gradient(0, 255, 0)
|
||||
elif test == 3:
|
||||
# print("blue gradient")
|
||||
gradient(0, 0, 255)
|
||||
elif test == 4:
|
||||
# print("white gradient")
|
||||
gradient(255, 255, 255)
|
||||
|
||||
text = ""
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
text = "PlaySyn"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
text = "SoloSyn"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
text = "Tone A"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_D):
|
||||
text = "Tone B"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_UP):
|
||||
text = "RaiseA"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_DOWN):
|
||||
text = "LowerA"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
text = "RaiseB"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
text = "LowerB"
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_SLEEP):
|
||||
text = "Stop"
|
||||
|
||||
outline_text(text)
|
||||
|
||||
su.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,102 @@
|
|||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
A pretty, procedural fire effect.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
fire_colours = [graphics.create_pen(0, 0, 0),
|
||||
graphics.create_pen(20, 20, 20),
|
||||
graphics.create_pen(180, 30, 0),
|
||||
graphics.create_pen(220, 160, 0),
|
||||
graphics.create_pen(255, 255, 180)]
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def update():
|
||||
# take local references as it's quicker than accessing the global
|
||||
# and we access it a lot in this method
|
||||
_heat = heat
|
||||
|
||||
# clear the bottom row and then add a new fire seed to it
|
||||
for x in range(width):
|
||||
_heat[x][height - 1] = 0.0
|
||||
_heat[x][height - 2] = 0.0
|
||||
|
||||
for c in range(fire_spawns):
|
||||
x = random.randint(0, width - 4) + 2
|
||||
_heat[x + 0][height - 1] = 1.0
|
||||
_heat[x + 1][height - 1] = 1.0
|
||||
_heat[x - 1][height - 1] = 1.0
|
||||
_heat[x + 0][height - 2] = 1.0
|
||||
_heat[x + 1][height - 2] = 1.0
|
||||
_heat[x - 1][height - 2] = 1.0
|
||||
|
||||
factor = damping_factor / 5.0
|
||||
for y in range(0, height - 2):
|
||||
for x in range(1, width - 1):
|
||||
_heat[x][y] += _heat[x][y + 1] + _heat[x][y + 2] + _heat[x - 1][y + 1] + _heat[x + 1][y + 1]
|
||||
_heat[x][y] *= factor
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
# take local references as it's quicker than accessing the global
|
||||
# and we access it a lot in this method
|
||||
_graphics = graphics
|
||||
_heat = heat
|
||||
_set_pen = graphics.set_pen
|
||||
_pixel = graphics.pixel
|
||||
_fire_colours = fire_colours
|
||||
|
||||
for y in range(StellarUnicorn.HEIGHT):
|
||||
for x in range(StellarUnicorn.WIDTH):
|
||||
value = _heat[x + 1][y]
|
||||
if value < 0.15:
|
||||
_set_pen(_fire_colours[0])
|
||||
elif value < 0.25:
|
||||
_set_pen(_fire_colours[1])
|
||||
elif value < 0.35:
|
||||
_set_pen(_fire_colours[2])
|
||||
elif value < 0.45:
|
||||
_set_pen(_fire_colours[3])
|
||||
else:
|
||||
_set_pen(_fire_colours[4])
|
||||
_pixel(x, y)
|
||||
|
||||
su.update(_graphics)
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH + 2
|
||||
height = StellarUnicorn.HEIGHT + 4
|
||||
heat = [[0.0 for y in range(height)] for x in range(width)]
|
||||
fire_spawns = 5
|
||||
damping_factor = 0.97
|
||||
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
start = time.ticks_ms()
|
||||
|
||||
update()
|
||||
draw()
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,190 @@
|
|||
import time
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
import WIFI_CONFIG
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio as asyncio
|
||||
import uasyncio.core
|
||||
from tinyweb.server import webserver
|
||||
|
||||
'''
|
||||
Display scrolling wisdom, quotes or greetz... from the internetz!
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
|
||||
Requires network_manager.py , WIFI_CONFIG.py, logging.mpy and tinyweb from micropython/examples/common
|
||||
You'll also need index.html to be saved alongside this file.
|
||||
'''
|
||||
|
||||
# Server Settings
|
||||
host = "0.0.0.0"
|
||||
port = 80
|
||||
|
||||
|
||||
def convert_colour(colour_str):
|
||||
colour_str = colour_str.split(',')
|
||||
print(colour_str)
|
||||
return colour_str[0], colour_str[1], colour_str[2]
|
||||
|
||||
|
||||
class text:
|
||||
|
||||
def get(self, data):
|
||||
global MESSAGE, MESSAGE_COLOUR, BACKGROUND_COLOUR
|
||||
print(data)
|
||||
if 'text' in data.keys():
|
||||
MESSAGE = data['text']
|
||||
if 'colourfg' in data.keys():
|
||||
MESSAGE_COLOUR = convert_colour(data['colourfg'])
|
||||
if 'colourbg' in data.keys():
|
||||
BACKGROUND_COLOUR = convert_colour(data['colourbg'])
|
||||
return {'message': 'text updated'}, 201
|
||||
|
||||
def post(self, data):
|
||||
|
||||
return {'message': 'text updated'}, 201
|
||||
|
||||
|
||||
def status_handler(mode, status, ip):
|
||||
global MESSAGE
|
||||
print("Network: {}".format(WIFI_CONFIG.SSID))
|
||||
status_text = "Connecting..."
|
||||
if status is not None:
|
||||
if status:
|
||||
status_text = "Connection successful!"
|
||||
else:
|
||||
status_text = "Connection failed!"
|
||||
|
||||
print(status_text)
|
||||
print("IP: {}".format(ip))
|
||||
MESSAGE = "{}".format(ip)
|
||||
|
||||
|
||||
# Create web server application
|
||||
app = webserver()
|
||||
|
||||
|
||||
# Index page
|
||||
@app.route('/')
|
||||
async def index(request, response):
|
||||
# Send actual HTML page
|
||||
await response.send_file('index.html', content_type='text/html')
|
||||
|
||||
|
||||
# HTTP redirection
|
||||
@app.route('/redirect')
|
||||
async def redirect(request, response):
|
||||
# Start HTTP response with content-type text/html
|
||||
await response.redirect('/')
|
||||
|
||||
# constants for controlling scrolling text
|
||||
PADDING = 5
|
||||
MESSAGE_COLOUR = (255, 255, 255)
|
||||
OUTLINE_COLOUR = (0, 0, 0)
|
||||
BACKGROUND_COLOUR = (10, 0, 96)
|
||||
MESSAGE = "Connecting"
|
||||
HOLD_TIME = 2.0
|
||||
STEP_TIME = 0.075
|
||||
|
||||
# create galactic object and graphics surface for drawing
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
|
||||
# function for drawing outlined text
|
||||
def outline_text(text, x, y):
|
||||
graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2])))
|
||||
graphics.text(text, x - 1, y - 1, -1, 1)
|
||||
graphics.text(text, x, y - 1, -1, 1)
|
||||
graphics.text(text, x + 1, y - 1, -1, 1)
|
||||
graphics.text(text, x - 1, y, -1, 1)
|
||||
graphics.text(text, x + 1, y, -1, 1)
|
||||
graphics.text(text, x - 1, y + 1, -1, 1)
|
||||
graphics.text(text, x, y + 1, -1, 1)
|
||||
graphics.text(text, x + 1, y + 1, -1, 1)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2])))
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
def run():
|
||||
# Setup wifi
|
||||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
|
||||
app.add_resource(text, '/update')
|
||||
|
||||
# Connect to Wifi network
|
||||
asyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
|
||||
while (not network_manager.isconnected()):
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
# Start wifi connection
|
||||
run()
|
||||
|
||||
|
||||
async def message_update():
|
||||
global MESSAGE
|
||||
last_time = time.ticks_ms()
|
||||
STATE_PRE_SCROLL = 0
|
||||
STATE_SCROLLING = 1
|
||||
STATE_POST_SCROLL = 2
|
||||
|
||||
shift = 0
|
||||
state = STATE_PRE_SCROLL
|
||||
|
||||
# set the font
|
||||
graphics.set_font("bitmap8")
|
||||
|
||||
# calculate the message width so scrolling can happen
|
||||
msg_width = graphics.measure_text(MESSAGE, 1)
|
||||
while 1:
|
||||
|
||||
msg_width = graphics.measure_text(MESSAGE, 1)
|
||||
time_ms = time.ticks_ms()
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
|
||||
if msg_width + PADDING * 2 >= width:
|
||||
state = STATE_SCROLLING
|
||||
last_time = time_ms
|
||||
|
||||
if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000:
|
||||
shift += 1
|
||||
if shift >= (msg_width + PADDING * 2) - width - 1:
|
||||
state = STATE_POST_SCROLL
|
||||
last_time = time_ms
|
||||
|
||||
if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
|
||||
state = STATE_PRE_SCROLL
|
||||
shift = 0
|
||||
last_time = time_ms
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2])))
|
||||
graphics.clear()
|
||||
|
||||
outline_text(MESSAGE, x=PADDING - shift, y=11)
|
||||
|
||||
# update the display
|
||||
su.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
await asyncio.sleep(0.001)
|
||||
|
||||
|
||||
# The following is required to run both the web server and the scrolling text coherently
|
||||
app._server_coro = app._tcp_server(host, port, app.backlog)
|
||||
loop = asyncio.get_event_loop()
|
||||
t1 = loop.create_task(message_update())
|
||||
t2 = loop.create_task(app._server_coro)
|
||||
loop.run_forever()
|
|
@ -0,0 +1,102 @@
|
|||
<http>
|
||||
<head>
|
||||
<style>
|
||||
h1 {
|
||||
align-content: center;
|
||||
color: rgb(192, 192, 214);
|
||||
margin-left: 20px;
|
||||
background-color: darkmagenta;
|
||||
font-size: xxx-large;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var getUrl = window.location;
|
||||
var baseUrl = getUrl.protocol + "//" + getUrl.host;
|
||||
var displayText;
|
||||
var colourBF ="#000000";
|
||||
var colourFG ="#000000";
|
||||
|
||||
function convertHexToRgb(hex) {
|
||||
|
||||
// Convert the first 2 characters to hexadecimal
|
||||
var r = parseInt(hex.substring(1, 3), 16),
|
||||
|
||||
// Convert the middle 2 characters to hexadecimal
|
||||
g = parseInt(hex.substring(3, 5), 16),
|
||||
|
||||
// Convert the last 2 characters to hexadecimal
|
||||
b = parseInt(hex.substring(5, 7), 16);
|
||||
|
||||
// append them all
|
||||
return r + ", " + g + ", "
|
||||
+ b ;
|
||||
}
|
||||
|
||||
function updateCU(comm,val){
|
||||
console.log(typeof(comm))
|
||||
console.log(typeof(val))
|
||||
console.log(comm, val)
|
||||
fetch(baseUrl+"/update" + "?"+comm+"="+String(val))
|
||||
.then(response => {
|
||||
// indicates whether the response is successful (status code 200-299) or not
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed with status ${reponse.status}`)
|
||||
}
|
||||
return response.json()
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
|
||||
})
|
||||
.catch(error => console.log(error))
|
||||
}
|
||||
function updateText(){
|
||||
displayText = document.getElementById("inputtxt").value;
|
||||
colourFG = convertHexToRgb(document.getElementById("inputfgcolour").value);
|
||||
colourBG = convertHexToRgb(document.getElementById("inputbgcolour").value);
|
||||
updateCU("colourfg", colourFG);
|
||||
updateCU("text", displayText);
|
||||
|
||||
updateCU("colourbg", colourBG);
|
||||
}
|
||||
function updateColour(){
|
||||
colourFG = document.getElementById('inputfgcolour').value;
|
||||
colourBG = document.getElementById('inputbgcolour').value;
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="title">Stellar Unicorn Web Text</h1>
|
||||
<center>
|
||||
<label for="inputtxt"> Enter text to display:</label>
|
||||
<br>
|
||||
|
||||
<input type="text" id="inputtxt" name="inputtxt" required
|
||||
minlength="4" size="16">
|
||||
<br>
|
||||
<label for="inputfgcolour"> Text Colour:</label>
|
||||
<br>
|
||||
|
||||
<input type="color" id="inputfgcolour" name="inputfgcolour" required
|
||||
minlength="4" maxlength="30" size="16" onchange="updateColour()" value='#000000'>
|
||||
<br>
|
||||
<label for="inputbgcolour"> Background Colour:</label>
|
||||
<br>
|
||||
|
||||
<input type="color" id="inputbgcolour" name="inputbgcolour" required
|
||||
minlength="4" maxlength="30" size="16" onchange="updateColour()" value='#000000'>
|
||||
<br>
|
||||
<button class="favorite styled"
|
||||
onclick="updateText()"
|
||||
type="button">
|
||||
Update
|
||||
</button>
|
||||
<p>
|
||||
Please type in what you wish to be displayed on the Stellar Unicorn and whe you are ready hit update to update the display
|
||||
</p>
|
||||
|
||||
</center>
|
||||
</body>
|
||||
</http>
|
|
@ -0,0 +1,80 @@
|
|||
import random
|
||||
from stellar import StellarUnicorn
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
# setup heat value buffer and fire parameters
|
||||
width = StellarUnicorn.WIDTH + 2
|
||||
height = StellarUnicorn.HEIGHT + 4
|
||||
heat = [[0.0 for y in range(height)] for x in range(width)]
|
||||
fire_spawns = 5
|
||||
damping_factor = 0.97
|
||||
|
||||
|
||||
def init():
|
||||
# a palette of five fiery colours (white, yellow, orange, red, smoke)
|
||||
global palette
|
||||
palette = [
|
||||
graphics.create_pen(0, 0, 0),
|
||||
graphics.create_pen(20, 20, 20),
|
||||
graphics.create_pen(180, 30, 0),
|
||||
graphics.create_pen(220, 160, 0),
|
||||
graphics.create_pen(255, 255, 180)
|
||||
]
|
||||
|
||||
|
||||
# returns the palette entry for a given heat value
|
||||
@micropython.native # noqa: F821
|
||||
def pen_from_value(value):
|
||||
if value < 0.15:
|
||||
return palette[0]
|
||||
elif value < 0.25:
|
||||
return palette[1]
|
||||
elif value < 0.35:
|
||||
return palette[2]
|
||||
elif value < 0.45:
|
||||
return palette[3]
|
||||
return palette[4]
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
# clear the the rows off the bottom of the display
|
||||
for x in range(width):
|
||||
heat[x][height - 1] = 0.0
|
||||
heat[x][height - 2] = 0.0
|
||||
|
||||
# add new fire spawns
|
||||
for c in range(fire_spawns):
|
||||
x = random.randint(0, width - 4) + 2
|
||||
heat[x + 0][height - 1] = 1.0
|
||||
heat[x + 1][height - 1] = 1.0
|
||||
heat[x - 1][height - 1] = 1.0
|
||||
heat[x + 0][height - 2] = 1.0
|
||||
heat[x + 1][height - 2] = 1.0
|
||||
heat[x - 1][height - 2] = 1.0
|
||||
|
||||
# average and damp out each value to create rising flame effect
|
||||
for y in range(0, height - 2):
|
||||
for x in range(1, width - 1):
|
||||
# update this pixel by averaging the below pixels
|
||||
average = (
|
||||
heat[x][y] + heat[x][y + 1] + heat[x][y + 2] + heat[x - 1][y + 1] + heat[x + 1][y + 1]
|
||||
) / 5.0
|
||||
|
||||
# damping factor to ensure flame tapers out towards the top of the displays
|
||||
average *= damping_factor
|
||||
|
||||
# update the heat map with our newly averaged value
|
||||
heat[x][y] = average
|
||||
|
||||
# render the heat values to the graphics buffer
|
||||
for y in range(StellarUnicorn.HEIGHT):
|
||||
for x in range(StellarUnicorn.WIDTH):
|
||||
graphics.set_pen(pen_from_value(heat[x + 1][y]))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def test():
|
||||
print("A")
|
|
@ -0,0 +1,123 @@
|
|||
import time
|
||||
import math
|
||||
import machine
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
# overclock to 200Mhz
|
||||
machine.freq(200000000)
|
||||
|
||||
# create stellar object and graphics surface for drawing
|
||||
stellar = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
brightness = 0.5
|
||||
|
||||
|
||||
# returns the id of the button that is currently pressed or
|
||||
# None if none are
|
||||
def pressed():
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
return StellarUnicorn.SWITCH_A
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
return StellarUnicorn.SWITCH_B
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
return StellarUnicorn.SWITCH_C
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_D):
|
||||
return StellarUnicorn.SWITCH_D
|
||||
return None
|
||||
|
||||
|
||||
# wait for a button to be pressed and load that effect
|
||||
while True:
|
||||
b = int((math.sin(time.ticks_ms() / 200) + 1) / 2.0 * 255)
|
||||
b = max(60, b)
|
||||
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
|
||||
graphics.set_pen(graphics.create_pen(b, 0, 0))
|
||||
graphics.pixel(0, 3)
|
||||
graphics.set_pen(graphics.create_pen(0, b, 0))
|
||||
graphics.pixel(0, 5)
|
||||
graphics.set_pen(graphics.create_pen(0, 0, b))
|
||||
graphics.pixel(0, 7)
|
||||
graphics.set_pen(graphics.create_pen(b, 0, b))
|
||||
graphics.pixel(0, 9)
|
||||
|
||||
# brightness up/down
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
brightness += 0.01
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
brightness -= 0.01
|
||||
brightness = max(min(brightness, 1.0), 0.0)
|
||||
|
||||
stellar.set_brightness(brightness)
|
||||
stellar.update(graphics)
|
||||
|
||||
if pressed() == StellarUnicorn.SWITCH_A:
|
||||
import fire as effect
|
||||
break
|
||||
if pressed() == StellarUnicorn.SWITCH_B:
|
||||
import supercomputer as effect # noqa: F811
|
||||
break
|
||||
if pressed() == StellarUnicorn.SWITCH_C:
|
||||
import rainbow as effect # noqa: F811
|
||||
break
|
||||
if pressed() == StellarUnicorn.SWITCH_D:
|
||||
import today as effect # noqa: F811
|
||||
break
|
||||
|
||||
# pause for a moment
|
||||
time.sleep(0.01)
|
||||
|
||||
# wait until all buttons are released
|
||||
while pressed() is not None:
|
||||
time.sleep(0.1)
|
||||
|
||||
effect.graphics = graphics
|
||||
effect.init()
|
||||
|
||||
sleep = False
|
||||
was_sleep_pressed = False
|
||||
|
||||
|
||||
# wait
|
||||
while True:
|
||||
# if A, B, C, or D are pressed then reset
|
||||
if pressed() is not None:
|
||||
machine.reset()
|
||||
|
||||
sleep_pressed = stellar.is_pressed(StellarUnicorn.SWITCH_SLEEP)
|
||||
if sleep_pressed and not was_sleep_pressed:
|
||||
sleep = not sleep
|
||||
|
||||
was_sleep_pressed = sleep_pressed
|
||||
|
||||
if sleep:
|
||||
# fade out if screen not off
|
||||
stellar.set_brightness(stellar.get_brightness() - 0.01)
|
||||
|
||||
if stellar.get_brightness() > 0.0:
|
||||
effect.draw()
|
||||
|
||||
# update the display
|
||||
stellar.update(graphics)
|
||||
else:
|
||||
effect.draw()
|
||||
|
||||
# update the display
|
||||
stellar.update(graphics)
|
||||
|
||||
# brightness up/down
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
brightness += 0.01
|
||||
if stellar.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
brightness -= 0.01
|
||||
brightness = max(min(brightness, 1.0), 0.0)
|
||||
|
||||
stellar.set_brightness(brightness)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,59 @@
|
|||
import math
|
||||
from stellar import StellarUnicorn
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def from_hsv(h, s, v):
|
||||
i = math.floor(h * 6.0)
|
||||
f = h * 6.0 - i
|
||||
v *= 255.0
|
||||
p = v * (1.0 - s)
|
||||
q = v * (1.0 - f * s)
|
||||
t = v * (1.0 - (1.0 - f) * s)
|
||||
|
||||
i = int(i) % 6
|
||||
if i == 0:
|
||||
return int(v), int(t), int(p)
|
||||
if i == 1:
|
||||
return int(q), int(v), int(p)
|
||||
if i == 2:
|
||||
return int(p), int(v), int(t)
|
||||
if i == 3:
|
||||
return int(p), int(q), int(v)
|
||||
if i == 4:
|
||||
return int(t), int(p), int(v)
|
||||
if i == 5:
|
||||
return int(v), int(p), int(q)
|
||||
|
||||
|
||||
phase = 0
|
||||
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
|
||||
hue_offset = 0.0
|
||||
stripe_width = 3.0
|
||||
speed = 5.0
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
global hue_offset, phase
|
||||
|
||||
phase += speed
|
||||
|
||||
phase_percent = phase / 15
|
||||
for x in range(width):
|
||||
colour = hue_map[int((x + (hue_offset * width)) % width)]
|
||||
for y in range(height):
|
||||
v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v)))
|
||||
graphics.pixel(x, y)
|
|
@ -0,0 +1,40 @@
|
|||
import random
|
||||
from stellar import StellarUnicorn
|
||||
|
||||
graphics = None
|
||||
|
||||
colour = (230, 150, 0)
|
||||
|
||||
|
||||
def init():
|
||||
global width, height, lifetime, age
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
lifetime = [[0.0 for y in range(height)] for x in range(width)]
|
||||
age = [[0.0 for y in range(height)] for x in range(width)]
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
|
||||
age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y]
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if age[x][y] >= lifetime[x][y]:
|
||||
age[x][y] = 0.0
|
||||
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
|
||||
|
||||
age[x][y] += 0.025
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if age[x][y] < lifetime[x][y] * 0.3:
|
||||
graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2]))
|
||||
elif age[x][y] < lifetime[x][y] * 0.5:
|
||||
decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0
|
||||
graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2])))
|
||||
else:
|
||||
graphics.set_pen(0)
|
||||
graphics.pixel(x, y)
|
|
@ -0,0 +1,99 @@
|
|||
import time
|
||||
import network
|
||||
import ntptime
|
||||
import machine
|
||||
|
||||
# You will need to create or update the file secrets.py with your network credentials using Thonny
|
||||
# in order for the example to update using the NTP.
|
||||
|
||||
# WIFI_CONFIG.py should contain:
|
||||
# SSID = ""
|
||||
# PSK = ""
|
||||
# COUNTRY = ""
|
||||
|
||||
try:
|
||||
from WIFI_CONFIG import SSID, PSK
|
||||
except ImportError:
|
||||
print("Create WIFI_CONFIG.py with your WiFi credentials")
|
||||
|
||||
graphics = None
|
||||
|
||||
WIDTH = 16 # StellarUnicorn.WIDTH
|
||||
HEIGHT = 16 # StellarUnicorn.HEIGHT
|
||||
|
||||
rtc = machine.RTC()
|
||||
|
||||
DAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
|
||||
# Enable the Wireless
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
|
||||
|
||||
def network_connect(ssid, psk):
|
||||
|
||||
# Number of attempts to make before timeout
|
||||
max_wait = 5
|
||||
|
||||
# Sets the Wireless LED pulsing and attempts to connect to your local network.
|
||||
print("connecting...")
|
||||
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
|
||||
wlan.connect(ssid, psk)
|
||||
|
||||
while max_wait > 0:
|
||||
if wlan.status() < 0 or wlan.status() >= 3:
|
||||
break
|
||||
max_wait -= 1
|
||||
print('waiting for connection...')
|
||||
time.sleep(1)
|
||||
|
||||
# Handle connection error. Switches the Warn LED on.
|
||||
if wlan.status() != 3:
|
||||
print("Unable to connect. Attempting connection again")
|
||||
|
||||
|
||||
# Function to sync the Pico RTC using NTP
|
||||
def sync_time():
|
||||
|
||||
try:
|
||||
network_connect(SSID, PSK)
|
||||
except NameError:
|
||||
print("Create WIFI_CONFIG.py with your WiFi credentials")
|
||||
|
||||
if wlan.status() < 0 or wlan.status() >= 3:
|
||||
try:
|
||||
ntptime.settime()
|
||||
except OSError:
|
||||
print("Unable to sync with NTP server. Check network and try again.")
|
||||
|
||||
|
||||
def init():
|
||||
sync_time()
|
||||
|
||||
|
||||
def draw():
|
||||
# Pens
|
||||
RED = graphics.create_pen(120, 0, 0)
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
|
||||
current_t = rtc.datetime()
|
||||
|
||||
# Set the pen to Red and clear the screen.
|
||||
graphics.set_pen(WHITE)
|
||||
graphics.clear()
|
||||
|
||||
# Measures the length of the text to help us with centring later.
|
||||
day_length = graphics.measure_text(DAYS[current_t[3]], 1)
|
||||
date_length = graphics.measure_text(str(current_t[2]), 1)
|
||||
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.set_pen(RED)
|
||||
graphics.rectangle(0, 0, WIDTH, 7)
|
||||
graphics.set_pen(WHITE)
|
||||
graphics.text(DAYS[current_t[3]], (WIDTH // 2) - (day_length // 2), 0, 16, 1)
|
||||
|
||||
graphics.set_pen(RED)
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.text(str(current_t[2]), (WIDTH // 2) - (date_length // 2) + 1, 8, 16, 1)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
|
@ -0,0 +1,151 @@
|
|||
import time
|
||||
import random
|
||||
import math
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
A 70s-tastic, procedural rainbow lava lamp.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
blob_count = 10
|
||||
|
||||
|
||||
class Blob():
|
||||
def __init__(self):
|
||||
self.x = float(random.randint(0, width - 1))
|
||||
self.y = float(random.randint(0, height - 1))
|
||||
self.r = (float(random.randint(0, 40)) / 10.0) + 5.0
|
||||
self.dx = (float(random.randint(0, 2)) / 10.0) - 0.1
|
||||
self.dy = (float(random.randint(0, 2)) / 10.0) - 0.05 # positive bias
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def setup_portrait():
|
||||
global width, height, liquid, blobs
|
||||
width = StellarUnicorn.HEIGHT
|
||||
height = StellarUnicorn.WIDTH
|
||||
liquid = [[0.0 for y in range(height)] for x in range(width)]
|
||||
blobs = [Blob() for i in range(blob_count)]
|
||||
|
||||
|
||||
hue = 0.0
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def from_hsv(h, s, v):
|
||||
i = math.floor(h * 6.0)
|
||||
f = h * 6.0 - i
|
||||
v *= 255.0
|
||||
p = v * (1.0 - s)
|
||||
q = v * (1.0 - f * s)
|
||||
t = v * (1.0 - (1.0 - f) * s)
|
||||
|
||||
i = int(i) % 6
|
||||
if i == 0:
|
||||
return graphics.create_pen(int(v), int(t), int(p))
|
||||
if i == 1:
|
||||
return graphics.create_pen(int(q), int(v), int(p))
|
||||
if i == 2:
|
||||
return graphics.create_pen(int(p), int(v), int(t))
|
||||
if i == 3:
|
||||
return graphics.create_pen(int(p), int(q), int(v))
|
||||
if i == 4:
|
||||
return graphics.create_pen(int(t), int(p), int(v))
|
||||
if i == 5:
|
||||
return graphics.create_pen(int(v), int(p), int(q))
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def update_liquid():
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
liquid[x][y] = 0.0
|
||||
|
||||
for blob in blobs:
|
||||
r_sq = blob.r * blob.r
|
||||
blob_y_range = range(max(math.floor(blob.y - blob.r), 0),
|
||||
min(math.ceil(blob.y + blob.r), height))
|
||||
blob_x_range = range(max(math.floor(blob.x - blob.r), 0),
|
||||
min(math.ceil(blob.x + blob.r), width))
|
||||
|
||||
for y in blob_y_range:
|
||||
for x in blob_x_range:
|
||||
x_diff = x - blob.x
|
||||
y_diff = y - blob.y
|
||||
d_sq = x_diff * x_diff + y_diff * y_diff
|
||||
if d_sq <= r_sq:
|
||||
liquid[x][y] += 1.0 - (d_sq / r_sq)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def move_blobs():
|
||||
for blob in blobs:
|
||||
blob.x += blob.dx
|
||||
blob.y += blob.dy
|
||||
|
||||
if blob.x < 0.0 or blob.x >= float(width):
|
||||
blob.dx = 0.0 - blob.dx
|
||||
|
||||
if blob.y < 0.0 or blob.y >= float(height):
|
||||
blob.dy = 0.0 - blob.dy
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw_portrait():
|
||||
global hue
|
||||
hue += 0.001
|
||||
|
||||
dark = from_hsv(hue, 1.0, 0.3)
|
||||
mid = from_hsv(hue, 1.0, 0.6)
|
||||
bright = from_hsv(hue, 1.0, 1.0)
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
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.5:
|
||||
graphics.set_pen(bright)
|
||||
elif v >= 1.25:
|
||||
graphics.set_pen(mid)
|
||||
elif v >= 1.0:
|
||||
graphics.set_pen(dark)
|
||||
else:
|
||||
graphics.set_pen(0)
|
||||
graphics.pixel(y, x)
|
||||
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
setup_portrait()
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
setup_portrait()
|
||||
|
||||
start = time.ticks_ms()
|
||||
|
||||
update_liquid()
|
||||
move_blobs()
|
||||
draw_portrait()
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,131 @@
|
|||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn, Channel
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
|
||||
"""
|
||||
A gloriously terrible melody maker.
|
||||
|
||||
Use Vol + and Vol - to move up/down (note pitch)
|
||||
|
||||
Use Lux - and D to move left/right (note position)
|
||||
|
||||
Press A to set a note.
|
||||
|
||||
Press B to delete a note.
|
||||
|
||||
Use Lux + to play/pause.
|
||||
"""
|
||||
|
||||
NOTE_DURATION = 125
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
boopety_beepety = su.synth_channel(0)
|
||||
boopety_beepety.configure(
|
||||
waveforms=Channel.SQUARE | Channel.SINE,
|
||||
attack=0.1,
|
||||
decay=0.5,
|
||||
sustain=0.0,
|
||||
release=1.0,
|
||||
volume=1.0
|
||||
)
|
||||
|
||||
su.play_synth()
|
||||
|
||||
black = graphics.create_pen(0, 0, 0)
|
||||
note = graphics.create_pen(255, 255, 255)
|
||||
cursor_bg = graphics.create_pen(64, 0, 0)
|
||||
cursor = graphics.create_pen(255, 0, 0)
|
||||
playhead = graphics.create_pen(0, 128, 0)
|
||||
|
||||
cursor_position = [0, 0]
|
||||
|
||||
playhead_position = 0
|
||||
|
||||
width, height = graphics.get_bounds()
|
||||
|
||||
notes = [random.randint(0, height) for _ in range(width)]
|
||||
|
||||
last_note_advance = time.ticks_ms()
|
||||
|
||||
last_action = time.ticks_ms()
|
||||
|
||||
playing = True
|
||||
|
||||
|
||||
def debounce(button, duration=100):
|
||||
global last_action
|
||||
if su.is_pressed(button) and time.ticks_ms() - last_action > duration:
|
||||
last_action = time.ticks_ms()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def note_to_frequency(note_number):
|
||||
return int((2 ** ((note_number - 69.0) / 12)) * 440)
|
||||
|
||||
|
||||
while True:
|
||||
if debounce(StellarUnicorn.SWITCH_D):
|
||||
cursor_position[0] -= 1
|
||||
cursor_position[0] %= width
|
||||
|
||||
if debounce(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
cursor_position[0] += 1
|
||||
cursor_position[0] %= width
|
||||
|
||||
if debounce(StellarUnicorn.SWITCH_VOLUME_DOWN):
|
||||
cursor_position[1] += 1
|
||||
cursor_position[1] %= height
|
||||
|
||||
if debounce(StellarUnicorn.SWITCH_VOLUME_UP):
|
||||
cursor_position[1] -= 1
|
||||
cursor_position[1] %= height
|
||||
|
||||
if debounce(StellarUnicorn.SWITCH_BRIGHTNESS_UP, 500):
|
||||
playing = not playing
|
||||
if not playing:
|
||||
boopety_beepety.trigger_release()
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
notes[cursor_position[0]] = cursor_position[1]
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
notes[cursor_position[0]] = None
|
||||
|
||||
if time.ticks_ms() - last_note_advance > NOTE_DURATION:
|
||||
current_note = None
|
||||
if playing:
|
||||
playhead_position += 1
|
||||
playhead_position %= width
|
||||
current_note = notes[playhead_position]
|
||||
if current_note is not None:
|
||||
current_note = height - current_note # Bottom = Low, Top = High
|
||||
current_note += 36 # Shift up the scale a couple of octaves
|
||||
current_freq = note_to_frequency(current_note)
|
||||
boopety_beepety.frequency(current_freq)
|
||||
boopety_beepety.trigger_attack()
|
||||
last_note_advance = time.ticks_ms()
|
||||
|
||||
graphics.set_pen(black)
|
||||
graphics.clear()
|
||||
|
||||
graphics.set_pen(playhead)
|
||||
graphics.line(playhead_position, 0, playhead_position, height)
|
||||
|
||||
graphics.set_pen(cursor_bg)
|
||||
graphics.line(cursor_position[0], 0, cursor_position[0], height)
|
||||
|
||||
graphics.set_pen(note)
|
||||
for x in range(width):
|
||||
y = notes[x]
|
||||
if y is not None:
|
||||
graphics.pixel(x, y)
|
||||
|
||||
graphics.set_pen(cursor)
|
||||
graphics.pixel(*cursor_position)
|
||||
|
||||
su.update(graphics)
|
|
@ -0,0 +1,158 @@
|
|||
import time
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
A collection of copies of classic terminal styles including
|
||||
C64, MS-DOS, Spectrum, and more. Images and text are drawn
|
||||
pixel by pixel from a pattern of Os and Xs.
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
prompt_x = 0
|
||||
prompt_y = 4
|
||||
|
||||
c64 = [
|
||||
" ",
|
||||
" ",
|
||||
" OOOOO OOOOOO OO OOOO ",
|
||||
" OO OO OO OOOO OO OO ",
|
||||
" OO OO OO OO OO OO OO ",
|
||||
" OOOOO OOOO OOOOOO OO OO ",
|
||||
" OOOO OO OO OO OO OO ",
|
||||
" OO OO OO OO OO OO OO ",
|
||||
" OO OO OOOOOO OO OO OOOO ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" OO OO XXXXXXX ",
|
||||
" OO OO XXXXXXX ",
|
||||
" OO OO XXXXXXX ",
|
||||
" OOOO XXXXXXX ",
|
||||
" OO XXXXXXX ",
|
||||
" OO OO XXXXXXX ",
|
||||
" OO OO XXXXXXX ",
|
||||
" XXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_C64 = (230, 210, 250)
|
||||
BACKGROUND_C64 = (20, 20, 120)
|
||||
|
||||
spectrum = [
|
||||
" ",
|
||||
" ",
|
||||
" O OOOO OOOO OOOOO ",
|
||||
" O O O O O O O ",
|
||||
" O O O O O O O ",
|
||||
" O O O OOOOOO O O ",
|
||||
" O O O O O O O ",
|
||||
" OOOOOO OOOO O O OOOOO ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" O O O O XXXXXXXX ",
|
||||
" O O O O X XXXXXX ",
|
||||
" X XXXXXX ",
|
||||
" X XXXXXX ",
|
||||
" X XXXXXX ",
|
||||
" X XXXXXX ",
|
||||
" X X ",
|
||||
" XXXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_SPECTRUM = (0, 0, 0)
|
||||
BACKGROUND_SPECTRUM = (180, 150, 150)
|
||||
|
||||
bbc_micro = [
|
||||
" ",
|
||||
" ",
|
||||
" OOOOO OO OOOO OOO ",
|
||||
" O O O O O O O ",
|
||||
" O O O O O O ",
|
||||
" OOOOO O O OOOO O ",
|
||||
" O O OOOOOO O O ",
|
||||
" O O O O O O O ",
|
||||
" OOOOO O O OOOO OOO ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" ",
|
||||
" OOOO O ",
|
||||
" O O O ",
|
||||
" O O ",
|
||||
" O O ",
|
||||
" O O ",
|
||||
" O O O ",
|
||||
" OOOO O ",
|
||||
" XXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_BBC_MICRO = (255, 255, 255)
|
||||
BACKGROUND_BBC_MICRO = (0, 0, 0)
|
||||
|
||||
PROMPT_C64 = 0
|
||||
PROMPT_SPECTRUM = 1
|
||||
PROMPT_BBC_MICRO = 2
|
||||
prompt = 0
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw(image, fg, bg, time_ms):
|
||||
fg_pen = graphics.create_pen(fg[0], fg[1], fg[2])
|
||||
bg_pen = graphics.create_pen(bg[0], bg[1], bg[2])
|
||||
graphics.set_pen(bg_pen)
|
||||
graphics.clear()
|
||||
for y in range(len(image)):
|
||||
row = image[y]
|
||||
for x in range(len(row)):
|
||||
pixel = row[x]
|
||||
# draw the prompt text
|
||||
if pixel == 'O':
|
||||
graphics.set_pen(fg_pen)
|
||||
|
||||
# draw the caret blinking
|
||||
elif pixel == 'X' and (time_ms // 300) % 2:
|
||||
graphics.set_pen(fg_pen)
|
||||
|
||||
else:
|
||||
graphics.set_pen(bg_pen)
|
||||
|
||||
graphics.pixel(x + prompt_x, y + prompt_y)
|
||||
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
time_ms = time.ticks_ms()
|
||||
prompt = (time_ms // 3000) % 3
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
start = time.ticks_ms()
|
||||
|
||||
if prompt == PROMPT_C64:
|
||||
draw(c64, FOREGROUND_C64, BACKGROUND_C64, time_ms)
|
||||
|
||||
elif prompt == PROMPT_SPECTRUM:
|
||||
draw(spectrum, FOREGROUND_SPECTRUM, BACKGROUND_SPECTRUM, time_ms)
|
||||
|
||||
elif prompt == PROMPT_BBC_MICRO:
|
||||
draw(bbc_micro, FOREGROUND_BBC_MICRO, BACKGROUND_BBC_MICRO, time_ms)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,115 @@
|
|||
import gc
|
||||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn, Channel
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
A random, computer effect.
|
||||
Experiment with the damping, number of spawns and intensity to change the effect.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
DAMPING_FACTOR = 0.95
|
||||
NUMBER_OF_LIGHTS = 4
|
||||
INTENSITY = 20
|
||||
|
||||
volume = 0.5
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
boopety_beepety = su.synth_channel(0)
|
||||
boopety_beepety.configure(
|
||||
waveforms=Channel.SQUARE | Channel.SINE,
|
||||
attack=0.1,
|
||||
decay=0.1,
|
||||
sustain=0.0,
|
||||
release=0.5,
|
||||
volume=volume
|
||||
)
|
||||
|
||||
su.play_synth()
|
||||
|
||||
# Fill palette with a yellow
|
||||
r, g, b = (230, 150, 0)
|
||||
PALETTE_ENTRIES = 255
|
||||
for x in range(PALETTE_ENTRIES):
|
||||
_ = graphics.create_pen(r * x // PALETTE_ENTRIES, g * x // PALETTE_ENTRIES, b)
|
||||
|
||||
|
||||
def update():
|
||||
computer[:] *= DAMPING_FACTOR
|
||||
|
||||
# Spawn random drops
|
||||
for _ in range(NUMBER_OF_LIGHTS):
|
||||
x = random.randint(0, width - 1)
|
||||
y = random.randint(0, height - 1)
|
||||
computer[y][x] = random.randint(0, INTENSITY)
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the effect to the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(computer, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes()
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
computer = numpy.zeros((height, width))
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
|
||||
while True:
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_DOWN):
|
||||
volume -= 0.1
|
||||
volume = max(0.0, volume)
|
||||
boopety_beepety.volume(volume)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_UP):
|
||||
volume += 0.1
|
||||
volume = min(1.0, volume)
|
||||
boopety_beepety.volume(volume)
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
# Play random notes between 100 and 880Hz for a computery effect
|
||||
boopety_beepety.frequency(random.randint(100, 880))
|
||||
boopety_beepety.trigger_attack()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,126 @@
|
|||
import time
|
||||
import gc
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
Classic fire effect.
|
||||
Play with the number of spawns, heat, damping factor and colour palette to tweak it.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
# Number of random fire spawns
|
||||
FIRE_SPAWNS = 3
|
||||
|
||||
# Fire damping
|
||||
DAMPING_FACTOR = 0.98
|
||||
|
||||
# TURN UP THE HEEEAAT
|
||||
HEAT = 3.0
|
||||
|
||||
# Create the fire palette
|
||||
"""
|
||||
# Raging Gas Inferno
|
||||
graphics.create_pen(0, 0, 0)
|
||||
graphics.create_pen(0, 0, 0)
|
||||
graphics.create_pen(20, 20, 20)
|
||||
graphics.create_pen(50, 10, 0)
|
||||
graphics.create_pen(180, 30, 0)
|
||||
graphics.create_pen(220, 160, 0)
|
||||
graphics.create_pen(255, 255, 180)
|
||||
graphics.create_pen(255, 255, 220)
|
||||
graphics.create_pen(90, 90, 255)
|
||||
graphics.create_pen(255, 0, 255)
|
||||
"""
|
||||
# Original Colours
|
||||
graphics.create_pen(0, 0, 0)
|
||||
graphics.create_pen(20, 20, 20)
|
||||
graphics.create_pen(180, 30, 0)
|
||||
graphics.create_pen(220, 160, 0)
|
||||
graphics.create_pen(255, 255, 180)
|
||||
|
||||
PALETTE_SIZE = 5 # Should match the number of colours defined above
|
||||
|
||||
|
||||
def update():
|
||||
# Clear the bottom two rows (off screen)
|
||||
heat[height - 1][:] = 0.0
|
||||
heat[height - 2][:] = 0.0
|
||||
|
||||
# Add random fire spawns
|
||||
for c in range(FIRE_SPAWNS):
|
||||
x = random.randint(0, width - 4) + 2
|
||||
heat[height - 1][x - 1:x + 1] = HEAT / 2.0
|
||||
heat[height - 2][x - 1:x + 1] = HEAT
|
||||
|
||||
# Propagate the fire upwards
|
||||
a = numpy.roll(heat, -1, axis=0) # y + 1, x
|
||||
b = numpy.roll(heat, -2, axis=0) # y + 2, x
|
||||
c = numpy.roll(heat, -1, axis=0) # y + 1
|
||||
d = numpy.roll(c, 1, axis=1) # y + 1, x + 1
|
||||
e = numpy.roll(c, -1, axis=1) # y + 1, x - 1
|
||||
|
||||
# Average over 5 adjacent pixels and apply damping
|
||||
heat[:] += a + b + d + e
|
||||
heat[:] *= DAMPING_FACTOR / 5.0
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the fire effect to the framebuffer
|
||||
# Clips the fire to 0.0 to 1.0
|
||||
# Multiplies it by the number of palette entries (-1) to turn it into a palette index
|
||||
# Converts to uint8_t datatype to match the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:16, 0:16], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes()
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT + 4
|
||||
heat = numpy.zeros((height, width))
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
while True:
|
||||
gc.collect()
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,120 @@
|
|||
import gc
|
||||
import time
|
||||
import math
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
A lava lamp effect, created by blurred, moving particles.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
NUM_BLOBS = 3
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
su.set_brightness(0.5)
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
lava = numpy.zeros((height, width))
|
||||
|
||||
|
||||
class Blob():
|
||||
def __init__(self):
|
||||
self.x = float(random.randint(0, width - 1))
|
||||
self.y = float(random.randint(0, height - 1))
|
||||
self.r = (float(random.randint(0, 40)) / 10.0) + 5.0
|
||||
self.dx = (float(random.randint(0, 2)) / 20.0) - 0.05
|
||||
self.dy = (float(random.randint(0, 2)) / 20.0) - 0.025 # positive bias
|
||||
|
||||
def move(self):
|
||||
self.x += self.dx
|
||||
self.y += self.dy
|
||||
|
||||
if self.x < 0.0 or self.x >= float(width):
|
||||
self.x = max(0.0, self.x)
|
||||
self.x = min(float(width - 1), self.x)
|
||||
self.dx = -self.dx
|
||||
|
||||
if self.y < 0.0 or self.y >= float(height):
|
||||
self.y = max(0.0, self.y)
|
||||
self.y = min(float(height - 1), self.y)
|
||||
self.dy = -self.dy
|
||||
|
||||
|
||||
blobs = [Blob() for _ in range(NUM_BLOBS)]
|
||||
|
||||
|
||||
# Fill palette with a steep falloff from bright red to dark blue
|
||||
PAL_COLS = 9
|
||||
for x in range(PAL_COLS):
|
||||
graphics.create_pen_hsv(0.5 + math.log(x + 1, PAL_COLS + 1) / 2.0, 1.0, math.log(x + 1, PAL_COLS + 1))
|
||||
|
||||
|
||||
def update():
|
||||
# Update the blobs and draw them into the effect
|
||||
for blob in blobs:
|
||||
blob.move()
|
||||
lava[int(blob.y)][int(blob.x)] = blob.r
|
||||
|
||||
# Propagate the blobs outwards
|
||||
a = numpy.roll(lava, 1, axis=0)
|
||||
b = numpy.roll(lava, -1, axis=0)
|
||||
d = numpy.roll(lava, 1, axis=1)
|
||||
e = numpy.roll(lava, -1, axis=1)
|
||||
|
||||
# Average over 5 adjacent pixels and apply damping
|
||||
lava[:] += a + b + d + e
|
||||
lava[:] *= 0.97 / 5.0
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the lava effect to the framebuffer
|
||||
# Clips to 0.0 - 1.0
|
||||
# Multiplies by palette entries (-1) to turn it into a palette index
|
||||
# Converts to uint8_t datatype to match the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(lava, 0.0, 1.0) * (PAL_COLS - 1), dtype=numpy.uint8).tobytes()
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
while True:
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,152 @@
|
|||
import gc
|
||||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
A randomly-seeded game-of-life cellular automata effect.
|
||||
Experiment with the values below to change the effect.
|
||||
|
||||
Press "A" to manually re-seed.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
INITIAL_LIFE = 128 # Number of live cells to seed
|
||||
GENERATION_TIME_MS = 100 # MS between generations
|
||||
SMOOTHED = True # Enable for a more organic if somewhat unsettling feel
|
||||
STALEMATE_DEPTH = 5 # How many generations of changes must match before reset
|
||||
|
||||
DECAY = 0.90 # Rate at which smoothing effect decays, higher number = more persistent, 1.0 = no decay
|
||||
TENACITY = 32 # Rate at which smoothing effect increases
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
changed_cells = []
|
||||
|
||||
|
||||
for c in range(256):
|
||||
graphics.create_pen(c // 2, 0, c)
|
||||
|
||||
|
||||
def update():
|
||||
global last_gen, changed_cells
|
||||
|
||||
if SMOOTHED:
|
||||
duration[:] += life * TENACITY
|
||||
duration[:] *= DECAY
|
||||
|
||||
if time.ticks_ms() - last_gen < GENERATION_TIME_MS:
|
||||
return
|
||||
|
||||
last_gen = time.ticks_ms()
|
||||
|
||||
# Rollin' rollin' rollin.
|
||||
_N = numpy.roll(life, -1, axis=0)
|
||||
_NW = numpy.roll(_N, -1, axis=1)
|
||||
_NE = numpy.roll(_N, 1, axis=1)
|
||||
_S = numpy.roll(life, 1, axis=0)
|
||||
_SW = numpy.roll(_S, -1, axis=1)
|
||||
_SE = numpy.roll(_S, 1, axis=1)
|
||||
_W = numpy.roll(life, -1, axis=1)
|
||||
_E = numpy.roll(life, 1, axis=1)
|
||||
|
||||
# Compute the total neighbours for each cell
|
||||
neighbours[:] = _N + _NW + _NE + _S + _SW + _SE + _W + _E
|
||||
|
||||
next_generation[:] = life[:]
|
||||
|
||||
# Any cells with exactly three neighbours should always stay alive
|
||||
next_generation[:] += neighbours[:] == 3
|
||||
|
||||
# Any alive cells with less than two neighbours should die
|
||||
next_generation[:] -= (neighbours[:] < 2) * life
|
||||
|
||||
# Any alive cells with more than three neighbours should die
|
||||
next_generation[:] -= (neighbours[:] > 3) * life
|
||||
|
||||
next_generation[:] = numpy.clip(next_generation, 0, 1)
|
||||
|
||||
changed_cells.append(numpy.sum(life != next_generation))
|
||||
changed_cells = changed_cells[-STALEMATE_DEPTH:]
|
||||
|
||||
life[:] = next_generation
|
||||
|
||||
if changed_cells.count(changed_cells[0]) == STALEMATE_DEPTH:
|
||||
seed_life(INITIAL_LIFE // 2)
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the effect to the framebuffer
|
||||
if SMOOTHED:
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(duration, 0, 255), dtype=numpy.uint8).tobytes()
|
||||
else:
|
||||
memoryview(graphics)[:] = numpy.ndarray(life * 255, dtype=numpy.uint8).tobytes()
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
def seed_life(amount=INITIAL_LIFE):
|
||||
for _ in range(amount):
|
||||
x = random.randint(0, width - 1)
|
||||
y = random.randint(0, height - 1)
|
||||
life[y][x] = int(True) # Avoid: TypeError: 'bool' object isn't iterable
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
life = numpy.zeros((height, width), dtype=numpy.bool)
|
||||
next_generation = numpy.zeros((height, width), dtype=numpy.bool)
|
||||
neighbours = numpy.zeros((height, width), dtype=numpy.uint8)
|
||||
duration = numpy.zeros((height, width))
|
||||
last_gen = time.ticks_ms()
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
seed_life()
|
||||
|
||||
|
||||
while True:
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
life[:] = int(False)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
SMOOTHED = not SMOOTHED
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,95 @@
|
|||
import gc
|
||||
import time
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_RGB888
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
This example demonstrates how to work with full RGB888 colour in ulab/numpy.
|
||||
|
||||
Each colour channel is given its own array, and these are combined before
|
||||
copying them into the PicoGraphics buffer.
|
||||
|
||||
At great cost to performance (about half the speed) this example works in
|
||||
floating point 0.0 to 1.0 and converts the result to 8bits per channel.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_RGB888)
|
||||
|
||||
|
||||
def update():
|
||||
# Do something basic with the colour channels
|
||||
# to prove this actually works.
|
||||
red[:] = numpy.roll(red, 1, axis=1)
|
||||
green[:] *= 0.999 # Slowly desaturate green
|
||||
blue[:] *= 1.001 # Slowly saturate blue
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the red, green, blue channels into
|
||||
# their respective places in the RGB_ array
|
||||
rgb[2::4] = red.flatten()
|
||||
rgb[1::4] = green.flatten()
|
||||
rgb[0::4] = blue.flatten()
|
||||
|
||||
# Convert the results to 8bit RGB and copy to the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(rgb, 0, 1) * 255, dtype=numpy.uint8).tobytes()
|
||||
|
||||
# Copy the framebuffer to Stellar
|
||||
su.update(graphics)
|
||||
# Whew!
|
||||
|
||||
|
||||
width, height = graphics.get_bounds()
|
||||
|
||||
# Individual channels
|
||||
red = numpy.zeros((height, width))
|
||||
green = numpy.zeros((height, width))
|
||||
blue = numpy.zeros((height, width))
|
||||
|
||||
# Reserved for combined channels
|
||||
rgb = numpy.zeros((width * height * 4),)
|
||||
|
||||
# Stick some gradients in the channels so we have something to look at
|
||||
red[::] = numpy.linspace(0, 1, width)
|
||||
|
||||
# There has to be a better way!?
|
||||
for x in range(width):
|
||||
green[::, x] = numpy.linspace(0, 1, width)
|
||||
blue[::, x] = numpy.linspace(1, 0, width,)
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
while True:
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,91 @@
|
|||
import gc
|
||||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
HELLO NEO.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
NUM_SPARKLES = 1
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(1.0)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
|
||||
# Fill half the palette with GREEEN
|
||||
for g in range(128):
|
||||
_ = graphics.create_pen(0, g, 0)
|
||||
|
||||
# And half with bright green for white sparkles
|
||||
for g in range(128):
|
||||
_ = graphics.create_pen(128, 128 + g, 128)
|
||||
|
||||
|
||||
def update():
|
||||
trippy[:] *= 0.65
|
||||
|
||||
for _ in range(NUM_SPARKLES):
|
||||
x = random.randint(0, width - 1)
|
||||
y = random.randint(0, height // 2)
|
||||
trippy[y][x] = random.randint(128, 255) / 255.0
|
||||
|
||||
# Propagate downwards
|
||||
old = numpy.ndarray(trippy) * 0.5
|
||||
trippy[:] = numpy.roll(trippy, 1, axis=0)
|
||||
trippy[:] += old
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the effect to the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * 254, dtype=numpy.uint8).tobytes()
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
trippy = numpy.zeros((height, width))
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
|
||||
while True:
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,131 @@
|
|||
import time
|
||||
import gc
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
THI IS FIN!
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
# Number of random fire spawns
|
||||
FIRE_SPAWNS = 4
|
||||
|
||||
# Fire damping
|
||||
DAMPING_FACTOR = 0.98
|
||||
|
||||
# TURN UP THE HEEEAAT
|
||||
HEAT = 3.0
|
||||
|
||||
# Create the fire palette
|
||||
"""
|
||||
# Raging Gas Inferno
|
||||
graphics.create_pen(0, 0, 0)
|
||||
graphics.create_pen(0, 0, 0)
|
||||
graphics.create_pen(20, 20, 20)
|
||||
graphics.create_pen(50, 10, 0)
|
||||
graphics.create_pen(180, 30, 0)
|
||||
graphics.create_pen(220, 160, 0)
|
||||
graphics.create_pen(255, 255, 180)
|
||||
graphics.create_pen(255, 255, 220)
|
||||
graphics.create_pen(90, 90, 255)
|
||||
graphics.create_pen(255, 0, 255)
|
||||
"""
|
||||
# Original Colours
|
||||
graphics.create_pen(0, 0, 0)
|
||||
graphics.create_pen(20, 20, 20)
|
||||
graphics.create_pen(180, 30, 0)
|
||||
graphics.create_pen(220, 160, 0)
|
||||
graphics.create_pen(255, 255, 180)
|
||||
|
||||
PALETTE_SIZE = 5 # Should match the number of colours defined above
|
||||
|
||||
|
||||
def update():
|
||||
# Clear the bottom two rows (off screen)
|
||||
heat[height - 1][:] = 0.0
|
||||
heat[height - 2][:] = 0.0
|
||||
|
||||
# Add random fire spawns
|
||||
for c in range(FIRE_SPAWNS):
|
||||
x = random.randint(0, width - 4) + 2
|
||||
heat[height - 1][x - 1:x + 1] = HEAT / 2.0
|
||||
heat[height - 2][x - 1:x + 1] = HEAT
|
||||
|
||||
# Propagate the fire upwards
|
||||
a = numpy.roll(heat, -1, axis=0) # y + 1, x
|
||||
b = numpy.roll(heat, -2, axis=0) # y + 2, x
|
||||
c = numpy.roll(heat, -1, axis=0) # y + 1
|
||||
d = numpy.roll(c, 1, axis=1) # y + 1, x + 1
|
||||
e = numpy.roll(c, -1, axis=1) # y + 1, x - 1
|
||||
|
||||
# Average over 5 adjacent pixels and apply damping
|
||||
heat[:] += a + b + d + e
|
||||
heat[:] *= DAMPING_FACTOR / 5.0
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the fire effect to the framebuffer
|
||||
# Clips the fire to 0.0 to 1.0
|
||||
# Multiplies it by the number of palette entries (-1) to turn it into a palette index
|
||||
# Converts to uint8_t datatype to match the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:16, 0:16], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes()
|
||||
|
||||
# Draw text over the top
|
||||
graphics.set_pen(0)
|
||||
graphics.text("This", 1, 0, 1, 1)
|
||||
graphics.text("is", 1, 3, 1, 1)
|
||||
graphics.text("fine", 1, 9, 1, 1)
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT + 4
|
||||
heat = numpy.zeros((height, width))
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
while True:
|
||||
gc.collect()
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,97 @@
|
|||
import gc
|
||||
import time
|
||||
import random
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN, PEN_P8
|
||||
from ulab import numpy
|
||||
|
||||
"""
|
||||
A random, trippy effect.
|
||||
Experiment with the damping, number of spawns, intensity and offset to change the effect.
|
||||
"""
|
||||
|
||||
# MAXIMUM OVERKILL
|
||||
# machine.freq(250_000_000)
|
||||
|
||||
su = StellarUnicorn()
|
||||
su.set_brightness(0.5)
|
||||
graphics = PicoGraphics(DISPLAY_STELLAR_UNICORN, pen_type=PEN_P8)
|
||||
|
||||
|
||||
DAMPING_FACTOR = 0.8
|
||||
NUMBER_OF_DROPS = 2
|
||||
INTENSITY = 10
|
||||
OFFSET = 0.0 # Try 0.5
|
||||
|
||||
# Fill palette with a rainbow sweep
|
||||
PALETTE_ENTRIES = 255
|
||||
for x in range(PALETTE_ENTRIES):
|
||||
_ = graphics.create_pen_hsv(float(x) / PALETTE_ENTRIES + OFFSET, 1.0, 1.0)
|
||||
|
||||
|
||||
def update():
|
||||
trippy[:] *= DAMPING_FACTOR
|
||||
|
||||
# Spawn random drops
|
||||
for _ in range(NUMBER_OF_DROPS):
|
||||
x = random.randint(0, width - 1)
|
||||
y = random.randint(0, height - 1)
|
||||
trippy[y][x] = random.randint(0, INTENSITY)
|
||||
|
||||
a = numpy.roll(trippy, 1, axis=0)
|
||||
b = numpy.roll(trippy, -1, axis=0)
|
||||
d = numpy.roll(trippy, 1, axis=1)
|
||||
e = numpy.roll(trippy, -1, axis=1)
|
||||
|
||||
# Average over 5 adjacent pixels and apply damping
|
||||
trippy[:] += a + b + d + e
|
||||
trippy[:] /= 5.0
|
||||
|
||||
|
||||
def draw():
|
||||
# Copy the effect to the framebuffer
|
||||
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes()
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
trippy = numpy.zeros((height, width))
|
||||
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
|
||||
while True:
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
tstart = time.ticks_ms()
|
||||
gc.collect()
|
||||
update()
|
||||
draw()
|
||||
tfinish = time.ticks_ms()
|
||||
|
||||
total = tfinish - tstart
|
||||
t_total += total
|
||||
t_count += 1
|
||||
|
||||
if t_count == 60:
|
||||
per_frame_avg = t_total / t_count
|
||||
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
|
||||
t_count = 0
|
||||
t_total = 0
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
# try to pace at 60fps or 30fps
|
||||
if total > 1000 / 30:
|
||||
time.sleep(0.0001)
|
||||
elif total > 1000 / 60:
|
||||
t = 1000 / 30 - total
|
||||
time.sleep(t / 1000)
|
||||
else:
|
||||
t = 1000 / 60 - total
|
||||
time.sleep(t / 1000)
|
|
@ -0,0 +1,115 @@
|
|||
import time
|
||||
import math
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Some good old fashioned rainbows!
|
||||
|
||||
You can adjust the cycling speed with A and B,
|
||||
stripe width with C and D, hue with VOL + and -,
|
||||
and the brightness with LUX + and -.
|
||||
The sleep button stops the animation (can be started again with A or B).
|
||||
'''
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def from_hsv(h, s, v):
|
||||
i = math.floor(h * 6.0)
|
||||
f = h * 6.0 - i
|
||||
v *= 255.0
|
||||
p = v * (1.0 - s)
|
||||
q = v * (1.0 - f * s)
|
||||
t = v * (1.0 - (1.0 - f) * s)
|
||||
|
||||
i = int(i) % 6
|
||||
if i == 0:
|
||||
return int(v), int(t), int(p)
|
||||
if i == 1:
|
||||
return int(q), int(v), int(p)
|
||||
if i == 2:
|
||||
return int(p), int(v), int(t)
|
||||
if i == 3:
|
||||
return int(p), int(q), int(v)
|
||||
if i == 4:
|
||||
return int(t), int(p), int(v)
|
||||
if i == 5:
|
||||
return int(v), int(p), int(q)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
global hue_offset, phase
|
||||
phase_percent = phase / 15
|
||||
for x in range(width):
|
||||
colour = hue_map[int((x + (hue_offset * width)) % width)]
|
||||
for y in range(height):
|
||||
v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v)))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
|
||||
hue_offset = 0.0
|
||||
|
||||
animate = True
|
||||
stripe_width = 3.0
|
||||
speed = 1.0
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
phase = 0
|
||||
while True:
|
||||
|
||||
if animate:
|
||||
phase += speed
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_UP):
|
||||
hue_offset += 0.01
|
||||
hue_offset = 1.0 if hue_offset > 1.0 else hue_offset
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_VOLUME_DOWN):
|
||||
hue_offset -= 0.01
|
||||
hue_offset = 0.0 if hue_offset < 0.0 else hue_offset
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_SLEEP):
|
||||
animate = False
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
speed += 0.05
|
||||
speed = 10.0 if speed > 10.0 else speed
|
||||
animate = True
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
speed -= 0.05
|
||||
speed = 0.0 if speed < 0.0 else speed
|
||||
animate = True
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
stripe_width += 0.05
|
||||
stripe_width = 10.0 if stripe_width > 10.0 else stripe_width
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_D):
|
||||
stripe_width -= 0.05
|
||||
stripe_width = 1.0 if stripe_width < 1.0 else stripe_width
|
||||
|
||||
start = time.ticks_ms()
|
||||
|
||||
draw()
|
||||
|
||||
print("total took: {} ms".format(time.ticks_ms() - start))
|
|
@ -0,0 +1,96 @@
|
|||
import time
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
'''
|
||||
Display scrolling wisdom, quotes or greetz.
|
||||
|
||||
You can adjust the brightness with LUX + and -.
|
||||
'''
|
||||
|
||||
# constants for controlling scrolling text
|
||||
PADDING = 5
|
||||
MESSAGE_COLOUR = (255, 255, 255)
|
||||
OUTLINE_COLOUR = (0, 0, 0)
|
||||
BACKGROUND_COLOUR = (10, 0, 96)
|
||||
MESSAGE = "\"Space is big. Really big. You just won't believe how vastly hugely mind-bogglingly big it is. I mean, you may think it's a long way down the road to the chemist, but that's just peanuts to space.\" - Douglas Adams"
|
||||
HOLD_TIME = 2.0
|
||||
STEP_TIME = 0.075
|
||||
|
||||
# create stellar object and graphics surface for drawing
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = StellarUnicorn.WIDTH
|
||||
height = StellarUnicorn.HEIGHT
|
||||
|
||||
|
||||
# function for drawing outlined text
|
||||
def outline_text(text, x, y):
|
||||
graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2])))
|
||||
graphics.text(text, x - 1, y - 1, -1, 1)
|
||||
graphics.text(text, x, y - 1, -1, 1)
|
||||
graphics.text(text, x + 1, y - 1, -1, 1)
|
||||
graphics.text(text, x - 1, y, -1, 1)
|
||||
graphics.text(text, x + 1, y, -1, 1)
|
||||
graphics.text(text, x - 1, y + 1, -1, 1)
|
||||
graphics.text(text, x, y + 1, -1, 1)
|
||||
graphics.text(text, x + 1, y + 1, -1, 1)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2])))
|
||||
graphics.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
su.set_brightness(0.5)
|
||||
|
||||
# state constants
|
||||
STATE_PRE_SCROLL = 0
|
||||
STATE_SCROLLING = 1
|
||||
STATE_POST_SCROLL = 2
|
||||
|
||||
shift = 0
|
||||
state = STATE_PRE_SCROLL
|
||||
|
||||
# set the font
|
||||
graphics.set_font("bitmap8")
|
||||
|
||||
# calculate the message width so scrolling can happen
|
||||
msg_width = graphics.measure_text(MESSAGE, 1)
|
||||
|
||||
last_time = time.ticks_ms()
|
||||
|
||||
while True:
|
||||
time_ms = time.ticks_ms()
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
if state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
|
||||
if msg_width + PADDING * 2 >= width:
|
||||
state = STATE_SCROLLING
|
||||
last_time = time_ms
|
||||
|
||||
if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000:
|
||||
shift += 1
|
||||
if shift >= (msg_width + PADDING * 2) - width - 1:
|
||||
state = STATE_POST_SCROLL
|
||||
last_time = time_ms
|
||||
|
||||
if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
|
||||
state = STATE_PRE_SCROLL
|
||||
shift = 0
|
||||
last_time = time_ms
|
||||
|
||||
graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2])))
|
||||
graphics.clear()
|
||||
|
||||
outline_text(MESSAGE, x=PADDING - shift, y=4)
|
||||
|
||||
# update the display
|
||||
su.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,11 @@
|
|||
# Stellar Paint
|
||||
|
||||
Stellar Paint lets you paint pixels onto your Stellar Unicorn over WiFi, in realtime!
|
||||
|
||||
## Setting Up
|
||||
|
||||
You'll need `WIFI_CONFIG.py` from the `common` directory to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
|
||||
|
||||
You will also have to install `micropython-phew` and `microdot` through Thonny's Tools -> Manage Packages.
|
||||
|
||||
Run the example through Thonny and it should get connected and give you a URL to visit. Open that URL in your browser and start painting!
|
|
@ -0,0 +1,113 @@
|
|||
import os
|
||||
from microdot_asyncio import Microdot, send_file
|
||||
from microdot_asyncio_websocket import with_websocket
|
||||
from phew import connect_to_wifi
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
from WIFI_CONFIG import SSID, PSK
|
||||
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
mv_graphics = memoryview(graphics)
|
||||
su.set_brightness(0.5)
|
||||
|
||||
WIDTH, HEIGHT = graphics.get_bounds()
|
||||
|
||||
ip = connect_to_wifi(SSID, PSK)
|
||||
|
||||
print(f"Start painting at: http://{ip}")
|
||||
|
||||
|
||||
server = Microdot()
|
||||
|
||||
|
||||
@server.route("/", methods=["GET"])
|
||||
def route_index(request):
|
||||
return send_file("stellar_paint/index.html")
|
||||
|
||||
|
||||
@server.route("/static/<path:path>", methods=["GET"])
|
||||
def route_static(request, path):
|
||||
return send_file(f"stellar_paint/static/{path}")
|
||||
|
||||
|
||||
def get_pixel(x, y):
|
||||
if x < WIDTH and y < HEIGHT and x >= 0 and y >= 0:
|
||||
o = (y * WIDTH + x) * 4
|
||||
return tuple(mv_graphics[o:o + 3])
|
||||
return None
|
||||
|
||||
|
||||
def flood_fill(x, y, r, g, b):
|
||||
todo = []
|
||||
|
||||
def fill(x, y, c):
|
||||
if get_pixel(x, y) != c:
|
||||
return
|
||||
|
||||
graphics.pixel(x, y)
|
||||
|
||||
up = get_pixel(x, y - 1)
|
||||
dn = get_pixel(x, y + 1)
|
||||
lf = get_pixel(x - 1, y)
|
||||
ri = get_pixel(x + 1, y)
|
||||
|
||||
if up == c:
|
||||
todo.append((x, y - 1))
|
||||
if dn == c:
|
||||
todo.append((x, y + 1))
|
||||
if lf == c:
|
||||
todo.append((x - 1, y))
|
||||
if ri == c:
|
||||
todo.append((x + 1, y))
|
||||
|
||||
c = get_pixel(x, y)
|
||||
|
||||
if c is None:
|
||||
return
|
||||
|
||||
fill(x, y, c)
|
||||
|
||||
while len(todo):
|
||||
x, y = todo.pop(0)
|
||||
fill(x, y, c)
|
||||
|
||||
|
||||
@server.route('/paint')
|
||||
@with_websocket
|
||||
async def echo(request, ws):
|
||||
while True:
|
||||
data = await ws.receive()
|
||||
try:
|
||||
x, y, r, g, b = [int(n) for n in data[0:5]]
|
||||
graphics.set_pen(graphics.create_pen(r, g, b))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
except ValueError:
|
||||
if data == "show":
|
||||
su.update(graphics)
|
||||
|
||||
if data == "fill":
|
||||
data = await ws.receive()
|
||||
x, y, r, g, b = [int(n) for n in data[0:5]]
|
||||
graphics.set_pen(graphics.create_pen(r, g, b))
|
||||
flood_fill(x, y, r, g, b)
|
||||
|
||||
if data == "clear":
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
|
||||
if data == "save":
|
||||
filename = await ws.receive()
|
||||
print(f"Saving to {filename}.bin")
|
||||
try:
|
||||
os.mkdir("saves")
|
||||
except OSError:
|
||||
pass
|
||||
with open(f"saves/{filename}.bin", "wb") as f:
|
||||
f.write(graphics)
|
||||
await ws.send(f"alert: Saved to saves/{filename}.bin")
|
||||
|
||||
|
||||
server.run(host="0.0.0.0", port=80)
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Stellar Paint</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="/static/paint.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="window">
|
||||
<h1>Stellar Paint</h1>
|
||||
<table cellspacing="0" cellpadding="0" border-collapse="collapse">
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div id="palette">
|
||||
<ul>
|
||||
<li class="selected" style="background:rgb(0,0,0);"></li>
|
||||
<li style="background:rgb(132,0,0);"></li>
|
||||
<li style="background:rgb(0,132,0);"></li>
|
||||
<li style="background:rgb(132,132,0);"></li>
|
||||
<li style="background:rgb(0,0,132);"></li>
|
||||
<li style="background:rgb(132,0,132);"></li>
|
||||
<li style="background:rgb(0,132,132);"></li>
|
||||
<li style="background:rgb(132,132,132);"></li>
|
||||
<li style="background:rgb(198,198,198);"></li>
|
||||
<li style="background:rgb(255,0,0);"></li>
|
||||
<li style="background:rgb(0,255,0);"></li>
|
||||
<li style="background:rgb(255,255,0);"></li>
|
||||
<li style="background:rgb(0,0,255);"></li>
|
||||
<li style="background:rgb(255,0,255);"></li>
|
||||
<li style="background:rgb(0,255,255);"></li>
|
||||
<li style="background:rgb(255,255,255);"></li>
|
||||
</ul>
|
||||
|
||||
<input type="color" id="custom" name="custom" value="#ff0000">
|
||||
</div>
|
||||
<ul class="tools">
|
||||
<li data-tool="paint" class="paint selected"><span class="fa fa-pencil"></span></li>
|
||||
<li data-tool="fill" class="fill"><span class="fa fa-bitbucket"></span></li>
|
||||
<li data-tool="erase" class="erase"><span class="fa fa-eraser"></span></li>
|
||||
<li data-tool="pick" class="pick"><span class="fa fa-eyedropper"></span></li>
|
||||
<li data-tool="lighten" class="lighten"><span class="fa fa-sun-o"></span></li>
|
||||
<li data-tool="darken" class="darken"><span class="fa fa-adjust"></span></li>
|
||||
<li data-tool="trash" class="trash"><span class="fa fa-trash"></span></li>
|
||||
<li data-tool="save" class="save"><span class="fa fa-save"></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/static/tinycolor.js"></script>
|
||||
<script type="text/javascript" src="/static/paint.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
body {
|
||||
background:#333;
|
||||
padding:20px;
|
||||
font-family:Arial, Verdana, Sans-Serif;
|
||||
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAaUlEQVQYV33Q0Q3AIAgEUBjBFVyBFRzbWVjBEajXBIOVypcJj1NhETG61BiDVJX4Bh211v5hRDiniV+Elx0wQwd0hEatlUop65srMSah23vf8Auz65AWMc8rDHvCCjAQK2KeDcuQDzh+AHEJX8mbbU1BAAAAAElFTkSuQmCC) repeat;
|
||||
}
|
||||
|
||||
.icons {
|
||||
position:absolute;
|
||||
margin:0;
|
||||
padding:20px;
|
||||
list-style:none;
|
||||
}
|
||||
|
||||
.icons li {
|
||||
margin:20px;
|
||||
padding:0;
|
||||
list-style:none;
|
||||
padding-top:80px;
|
||||
width:100px;
|
||||
}
|
||||
|
||||
.icons li span {
|
||||
background:#FFF;
|
||||
color:#000;
|
||||
border:1px solid #000;
|
||||
line-height:20px;
|
||||
padding:5px 10px;
|
||||
text-align:center;
|
||||
font-size:10px;
|
||||
line-height:10px;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
#palette ul, #palette li {
|
||||
margin:0;padding:0;list-style:none;
|
||||
}
|
||||
|
||||
#palette {
|
||||
list-style:none;
|
||||
position:relative;
|
||||
height: 122px;
|
||||
padding:0 8px;
|
||||
}
|
||||
|
||||
#palette ul {
|
||||
display:block;
|
||||
width:456px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#palette li, #palette input {
|
||||
border: 2px outset;
|
||||
width:49px;
|
||||
height:49px;
|
||||
float:left;
|
||||
display:block;
|
||||
margin:2px;
|
||||
}
|
||||
|
||||
#palette input {
|
||||
width:110px;
|
||||
height:110px;
|
||||
}
|
||||
|
||||
.window {
|
||||
width: 640px;
|
||||
position: relative;
|
||||
background: #0E071A;
|
||||
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.tools {
|
||||
margin:0;padding:0;list-style:none;
|
||||
clear:both;
|
||||
display:block;
|
||||
position:absolute;
|
||||
top: 50px;
|
||||
right: 8px;
|
||||
width: 98px;
|
||||
background:#999999;
|
||||
font-size:0;
|
||||
}
|
||||
.tools span {
|
||||
line-height:30px;
|
||||
}
|
||||
|
||||
.tools li {
|
||||
font-size:16px;
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
text-align:center;
|
||||
margin:0;
|
||||
padding:0;
|
||||
display:inline-block;
|
||||
line-height:40px;
|
||||
border:2px outset #EEEEEE;
|
||||
background:#F5F5F5;
|
||||
cursor:pointer;
|
||||
color:#000;
|
||||
}
|
||||
|
||||
.tools li.selected {
|
||||
background:#000;
|
||||
color:#FFF;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #FFF;
|
||||
background: #6D38BB;
|
||||
height:40px;
|
||||
margin:0;
|
||||
padding:0 8px;
|
||||
line-height:40px;
|
||||
font-weight:normal;
|
||||
font-size:24px;
|
||||
}
|
||||
|
||||
table {
|
||||
clear:both;
|
||||
cursor:pointer;
|
||||
margin:10px;
|
||||
border:1px solid #333;
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
table td {
|
||||
width:14px;
|
||||
height:14px;
|
||||
border:1px solid #333;
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
'use strict';
|
||||
|
||||
var WIDTH = 16;
|
||||
var HEIGHT = 16;
|
||||
var md = false;
|
||||
var color = tinycolor('#840000');
|
||||
var update;
|
||||
|
||||
$(document).ready(function(){
|
||||
|
||||
var picker = $('#custom');
|
||||
var palette = $('#palette');
|
||||
|
||||
picker.val(color.toHexString());
|
||||
|
||||
$(document)
|
||||
.on('mousedown',function(e){md=true;})
|
||||
.on('mouseup',function(e){md=false;});
|
||||
|
||||
$('table').on('dragstart', function(e){
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
for (var y = 0; y < HEIGHT; y++) {
|
||||
var row = $('<tr></tr>');
|
||||
for (var x = 0; x < WIDTH; x++) {
|
||||
row.append('<td></td>');
|
||||
}
|
||||
$('tbody').append(row);
|
||||
}
|
||||
|
||||
$('.tools li').on('click', function(){
|
||||
switch($(this).index()){
|
||||
case 6:
|
||||
clear();
|
||||
break;
|
||||
case 7:
|
||||
save();
|
||||
break;
|
||||
default:
|
||||
$('.tools li').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
picker.on('change', function(){
|
||||
color = tinycolor($(this).val());
|
||||
})
|
||||
|
||||
palette.find('li').on('click', function(){
|
||||
pick(this);
|
||||
});
|
||||
|
||||
function handle_tool(obj, is_click){
|
||||
switch($('.tools li.selected').index()){
|
||||
case 0: //'paint':
|
||||
paint(obj);
|
||||
break;
|
||||
case 1: // Fill
|
||||
if( is_click ) fill(obj);
|
||||
break;
|
||||
case 2: // Erase
|
||||
update_pixel(obj, tinycolor('#000000'));
|
||||
break;
|
||||
case 3: //'pick':
|
||||
pick(obj);
|
||||
break;
|
||||
case 4: //'lighten':
|
||||
lighten(obj);
|
||||
break;
|
||||
case 5: //'darken':
|
||||
darken(obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var fill_target = null;
|
||||
var fill_stack = [];
|
||||
function fill(obj){
|
||||
fill_target = tinycolor($(obj).css('background-color')).toRgbString();
|
||||
|
||||
if( fill_target == color.toRgbString() ){
|
||||
return false;
|
||||
}
|
||||
|
||||
var x = $(obj).index();
|
||||
var y = $(obj).parent().index();
|
||||
|
||||
socket.send("fill");
|
||||
socket.send(new Uint8Array([x, y, color.toRgb().r, color.toRgb().g, color.toRgb().b]));
|
||||
socket.send('show');
|
||||
|
||||
do_fill(obj);
|
||||
|
||||
while(fill_stack.length > 0){
|
||||
var pixel = fill_stack.pop();
|
||||
do_fill(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
function is_target_color(obj){
|
||||
return ( tinycolor($(obj).css('background-color')).toRgbString() == fill_target);
|
||||
}
|
||||
|
||||
function do_fill(obj){
|
||||
var obj = $(obj);
|
||||
|
||||
if( is_target_color(obj) ){
|
||||
|
||||
$(obj).css('background-color', color.toRgbString());
|
||||
|
||||
var r = obj.next('td'); // Right
|
||||
var l = obj.prev('td'); // Left
|
||||
var u = obj.parent().prev('tr').find('td:eq(' + obj.index() + ')'); // Above
|
||||
var d = obj.parent().next('tr').find('td:eq(' + obj.index() + ')'); // Below
|
||||
|
||||
if( r.length && is_target_color(r[0]) ) fill_stack.push(r[0]);
|
||||
if( l.length && is_target_color(l[0]) ) fill_stack.push(l[0]);
|
||||
if( u.length && is_target_color(u[0]) ) fill_stack.push(u[0]);
|
||||
if( d.length && is_target_color(d[0]) ) fill_stack.push(d[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function save(){
|
||||
var filename = prompt('Please enter a filename', 'mypaint');
|
||||
filename = filename.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||
socket.send('save');
|
||||
socket.send(filename);
|
||||
}
|
||||
|
||||
function clear(){
|
||||
$('td').css('background-color','rgb(0,0,0)').data('changed',false);
|
||||
socket.send('clear');
|
||||
socket.send('show');
|
||||
}
|
||||
|
||||
function lighten(obj){
|
||||
var c = tinycolor($(obj).css('background-color'));
|
||||
c.lighten(5);
|
||||
update_pixel(obj, c);
|
||||
}
|
||||
|
||||
function darken(obj){
|
||||
var c = tinycolor($(obj).css('background-color'));
|
||||
c.darken(5);
|
||||
update_pixel(obj, c);
|
||||
}
|
||||
|
||||
function pick(obj){
|
||||
color = tinycolor($(obj).css('background-color'));
|
||||
picker.val(color.toHexString());
|
||||
}
|
||||
|
||||
function update_pixel(obj, col){
|
||||
var bgcol = tinycolor($(obj).css('background-color'));
|
||||
|
||||
if(col != bgcol){
|
||||
$(obj)
|
||||
.data('changed', true)
|
||||
.css('background-color', col.toRgbString());
|
||||
}
|
||||
}
|
||||
|
||||
function update_pixels(){
|
||||
var changed = false;
|
||||
|
||||
$('td').each(function( index, obj ){
|
||||
if($(obj).data('changed')){
|
||||
$(obj).data('changed',false);
|
||||
changed = true;
|
||||
|
||||
var x = $(this).index();
|
||||
var y = $(this).parent().index();
|
||||
var col = tinycolor($(obj).css('background-color')).toRgb();
|
||||
|
||||
if(socket) {
|
||||
socket.send(new Uint8Array([x, y, col.r, col.g, col.b]));
|
||||
}
|
||||
}
|
||||
});
|
||||
if(changed){
|
||||
socket.send('show');
|
||||
}
|
||||
}
|
||||
|
||||
function paint(obj){
|
||||
update_pixel(obj, color);
|
||||
}
|
||||
|
||||
$('table td').on('click', function(){
|
||||
handle_tool(this, true);
|
||||
});
|
||||
$('table td').on('mousemove', function(){
|
||||
if(!md) return false;
|
||||
handle_tool(this, false);
|
||||
})
|
||||
|
||||
const socket = new WebSocket('ws://' + window.location.host + '/paint');
|
||||
socket.addEventListener('message', ev => {
|
||||
console.log('<<< ' + ev.data);
|
||||
|
||||
if(ev.data.substring(0, 6) == "alert:") {
|
||||
alert(ev.data.substring(6));
|
||||
}
|
||||
});
|
||||
socket.addEventListener('close', ev => {
|
||||
console.log('<<< closed');
|
||||
});
|
||||
|
||||
socket.addEventListener('open', ev => {
|
||||
clear();
|
||||
update = setInterval(update_pixels, 50);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,128 @@
|
|||
import time
|
||||
import network
|
||||
import ntptime
|
||||
import machine
|
||||
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
# Default Brightness
|
||||
su.set_brightness(0.4)
|
||||
|
||||
# You will need to create or update the file secrets.py with your network credentials using Thonny
|
||||
# in order for the example to update using the NTP.
|
||||
|
||||
# secrets.py should contain:
|
||||
# WIFI_SSID = ""
|
||||
# WIFI_PASSWORD = ""
|
||||
|
||||
try:
|
||||
from secrets import WIFI_SSID, WIFI_PASSWORD
|
||||
except ImportError:
|
||||
print("Create secrets.py with your WiFi credentials")
|
||||
|
||||
|
||||
WIDTH = StellarUnicorn.WIDTH
|
||||
HEIGHT = StellarUnicorn.HEIGHT
|
||||
|
||||
rtc = machine.RTC()
|
||||
|
||||
DAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
|
||||
# Enable the Wireless
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
|
||||
|
||||
def network_connect(SSID, PSK):
|
||||
|
||||
# Number of attempts to make before timeout
|
||||
max_wait = 5
|
||||
|
||||
# Sets the Wireless LED pulsing and attempts to connect to your local network.
|
||||
print("connecting...")
|
||||
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
|
||||
wlan.connect(SSID, PSK)
|
||||
|
||||
while max_wait > 0:
|
||||
if wlan.status() < 0 or wlan.status() >= 3:
|
||||
break
|
||||
max_wait -= 1
|
||||
print('waiting for connection...')
|
||||
time.sleep(1)
|
||||
|
||||
# Handle connection error. Switches the Warn LED on.
|
||||
if wlan.status() != 3:
|
||||
print("Unable to connect. Attempting connection again")
|
||||
|
||||
|
||||
# Function to sync the Pico RTC using NTP
|
||||
def sync_time():
|
||||
|
||||
try:
|
||||
network_connect(WIFI_SSID, WIFI_PASSWORD)
|
||||
except NameError:
|
||||
print("Create secrets.py with your WiFi credentials")
|
||||
|
||||
if wlan.status() < 0 or wlan.status() >= 3:
|
||||
try:
|
||||
ntptime.settime()
|
||||
except OSError:
|
||||
print("Unable to sync with NTP server. Check network and try again.")
|
||||
|
||||
|
||||
def init():
|
||||
|
||||
sync_time()
|
||||
|
||||
|
||||
def draw():
|
||||
|
||||
# Pens
|
||||
RED = graphics.create_pen(120, 0, 0)
|
||||
WHITE = graphics.create_pen(255, 255, 255)
|
||||
|
||||
current_t = rtc.datetime()
|
||||
|
||||
# Set the pen to Red and clear the screen.
|
||||
graphics.set_pen(WHITE)
|
||||
graphics.clear()
|
||||
|
||||
# Measures the length of the text to help us with centring later.
|
||||
day_length = graphics.measure_text(DAYS[current_t[3]], 1)
|
||||
date_length = graphics.measure_text(str(current_t[2]), 1)
|
||||
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.set_pen(RED)
|
||||
graphics.rectangle(0, 0, WIDTH, 7)
|
||||
graphics.set_pen(WHITE)
|
||||
graphics.text(DAYS[current_t[3]], (WIDTH // 2) - (day_length // 2), 0, 16, 1)
|
||||
|
||||
graphics.set_pen(RED)
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.text(str(current_t[2]), (WIDTH // 2) - (date_length // 2) + 1, 8, 16, 1)
|
||||
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
|
||||
su.update(graphics)
|
||||
|
||||
|
||||
init()
|
||||
|
||||
while 1:
|
||||
|
||||
# Adjust Brightness +/-
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
su.adjust_brightness(+0.01)
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
su.adjust_brightness(-0.01)
|
||||
|
||||
# Connect to the network and sync with the NTP server
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
sync_time()
|
||||
|
||||
draw()
|
Po Szerokość: | Wysokość: | Rozmiar: 1.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.5 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.6 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.6 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.0 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.3 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.7 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.8 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 1.7 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 2.1 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 81 KiB |
|
@ -0,0 +1,153 @@
|
|||
import time
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN as DISPLAY
|
||||
import WIFI_CONFIG
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio as asyncio
|
||||
import urequests
|
||||
import jpegdec
|
||||
|
||||
# Set your latitude/longitude here (find yours by right clicking in Google Maps!)
|
||||
LAT = 53.38609085276884
|
||||
LNG = -1.4239983439328177
|
||||
TIMEZONE = "auto" # determines time zone from lat/long
|
||||
|
||||
URL = "http://api.open-meteo.com/v1/forecast?latitude=" + str(LAT) + "&longitude=" + str(LNG) + "¤t_weather=true&timezone=" + TIMEZONE
|
||||
WEATHER_TEXT = ''
|
||||
user_icon = None
|
||||
|
||||
|
||||
def get_data():
|
||||
global WEATHER_TEXT, temperature, weathercode
|
||||
print(f"Requesting URL: {URL}")
|
||||
r = urequests.get(URL)
|
||||
# open the json data
|
||||
j = r.json()
|
||||
print("Data obtained!")
|
||||
print(j)
|
||||
|
||||
# parse relevant data from JSON
|
||||
current = j["current_weather"]
|
||||
temperature = current["temperature"]
|
||||
windspeed = current["windspeed"]
|
||||
winddirection = calculate_bearing(current["winddirection"])
|
||||
weathercode = current["weathercode"]
|
||||
date, now = current["time"].split("T")
|
||||
WEATHER_TEXT = f"Temp: {temperature}°C Wind Speed: {windspeed}kmph Wind Direction: {winddirection} As of: {date}, {now}"
|
||||
print(WEATHER_TEXT)
|
||||
r.close()
|
||||
|
||||
|
||||
def calculate_bearing(d):
|
||||
# calculates a compass direction from the wind direction in degrees
|
||||
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
|
||||
ix = round(d / (360. / len(dirs)))
|
||||
return dirs[ix % len(dirs)]
|
||||
|
||||
|
||||
def status_handler(mode, status, ip):
|
||||
global MESSAGE
|
||||
print("Network: {}".format(WIFI_CONFIG.SSID))
|
||||
|
||||
|
||||
# create galactic object and graphics surface for drawing
|
||||
su = StellarUnicorn()
|
||||
display = PicoGraphics(DISPLAY)
|
||||
|
||||
WIDTH = StellarUnicorn.WIDTH
|
||||
HEIGHT = StellarUnicorn.HEIGHT
|
||||
|
||||
jpeg = jpegdec.JPEG(display)
|
||||
TEXT_COLOUR = display.create_pen(200, 0, 200)
|
||||
BLACK = display.create_pen(0, 0, 0)
|
||||
WHITE = display.create_pen(255, 255, 255)
|
||||
|
||||
|
||||
def run():
|
||||
# Setup wifi
|
||||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
|
||||
# Connect to Wifi network
|
||||
asyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
|
||||
while (not network_manager.isconnected()):
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
su.set_brightness(1)
|
||||
run() # Sets up Wifi connection
|
||||
|
||||
|
||||
def outline_text(text, x, y):
|
||||
display.set_pen(BLACK)
|
||||
display.text(text, x - 1, y - 1, -1, 1)
|
||||
display.text(text, x, y - 1, -1, 1)
|
||||
display.text(text, x + 1, y - 1, -1, 1)
|
||||
display.text(text, x - 1, y, -1, 1)
|
||||
display.text(text, x + 1, y, -1, 1)
|
||||
display.text(text, x - 1, y + 1, -1, 1)
|
||||
display.text(text, x, y + 1, -1, 1)
|
||||
display.text(text, x + 1, y + 1, -1, 1)
|
||||
|
||||
display.set_pen(WHITE)
|
||||
display.text(text, x, y, -1, 1)
|
||||
|
||||
|
||||
def draw_page(cycle):
|
||||
global user_icon
|
||||
text_cycle = cycle % 1000
|
||||
cycle = cycle % 4
|
||||
# Clear the display
|
||||
display.set_pen(15)
|
||||
display.clear()
|
||||
|
||||
# Draw the page header
|
||||
display.set_font("bitmap6")
|
||||
|
||||
if temperature is not None:
|
||||
# Choose an appropriate icon based on the weather code
|
||||
# Weather codes from https://open-meteo.com/en/docs
|
||||
if user_icon is not None:
|
||||
icons = ["icons/snow{0}.jpg".format(cycle + 1), "icons/rain{0}.jpg".format(cycle + 1), "icons/cloud{0}.jpg".format(cycle + 1), "icons/sun{0}.jpg".format(cycle + 1), "icons/storm{0}.jpg".format(cycle + 1)]
|
||||
jpeg.open_file(icons[user_icon])
|
||||
else:
|
||||
if weathercode in [71, 73, 75, 77, 85, 86]: # codes for snow
|
||||
jpeg.open_file("icons/snow{0}.jpg".format(cycle + 1))
|
||||
elif weathercode in [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82]: # codes for rain
|
||||
jpeg.open_file("icons/rain{0}.jpg".format(cycle + 1))
|
||||
elif weathercode in [1, 2, 3, 45, 48]: # codes for cloud
|
||||
jpeg.open_file("icons/cloud{0}.jpg".format(cycle + 1))
|
||||
elif weathercode in [0]: # codes for sun
|
||||
jpeg.open_file("icons/sun{0}.jpg".format(cycle + 1))
|
||||
elif weathercode in [95, 96, 99]: # codes for storm
|
||||
jpeg.open_file("icons/storm{0}.jpg".format(cycle + 1))
|
||||
jpeg.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
|
||||
display.set_pen(TEXT_COLOUR)
|
||||
outline_text(WEATHER_TEXT, 16 - text_cycle, 0)
|
||||
|
||||
else:
|
||||
display.set_pen(0)
|
||||
display.set_pen(15)
|
||||
display.text("Unable to display weather! Check your network settings in WIFI_CONFIG.py", 5, 65, WIDTH, 1)
|
||||
|
||||
su.update(display)
|
||||
|
||||
|
||||
while 1:
|
||||
|
||||
get_data()
|
||||
for count in range(500):
|
||||
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
user_icon = 0
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_B):
|
||||
user_icon = 1
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_C):
|
||||
user_icon = 2
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_D):
|
||||
user_icon = 3
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
user_icon = 4
|
||||
if su.is_pressed(StellarUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
user_icon = None
|
||||
draw_page(count)
|
||||
time.sleep(0.2)
|
|
@ -82,7 +82,7 @@ while True:
|
|||
|
||||
# draw the text
|
||||
graphics.set_pen(YELLOW)
|
||||
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55);
|
||||
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55)
|
||||
|
||||
# update the display
|
||||
cu.update(graphics)
|
||||
|
|
|
@ -131,20 +131,20 @@ const mp_obj_type_t CosmicUnicorn_type = {
|
|||
#endif
|
||||
|
||||
/***** Globals Table *****/
|
||||
STATIC const mp_map_elem_t Cosmic_globals_table[] = {
|
||||
STATIC const mp_map_elem_t cosmic_globals_table[] = {
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_cosmic) },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_Channel), (mp_obj_t)&Channel_type },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_CosmicUnicorn), (mp_obj_t)&CosmicUnicorn_type },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_Cosmic_globals, Cosmic_globals_table);
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_cosmic_globals, cosmic_globals_table);
|
||||
|
||||
/***** Module Definition *****/
|
||||
const mp_obj_module_t Cosmic_user_cmodule = {
|
||||
const mp_obj_module_t cosmic_user_cmodule = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t*)&mp_module_Cosmic_globals,
|
||||
.globals = (mp_obj_dict_t*)&mp_module_cosmic_globals,
|
||||
};
|
||||
#if MICROPY_VERSION <= 70144
|
||||
MP_REGISTER_MODULE(MP_QSTR_cosmic, Cosmic_user_cmodule, MODULE_Cosmic_ENABLED);
|
||||
MP_REGISTER_MODULE(MP_QSTR_cosmic, cosmic_user_cmodule, MODULE_COSMIC_ENABLED);
|
||||
#else
|
||||
MP_REGISTER_MODULE(MP_QSTR_cosmic, Cosmic_user_cmodule);
|
||||
MP_REGISTER_MODULE(MP_QSTR_cosmic, cosmic_user_cmodule);
|
||||
#endif
|
||||
|
|
|
@ -341,7 +341,7 @@ mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw
|
|||
/***** Variables Struct *****/
|
||||
typedef struct _CosmicUnicorn_obj_t {
|
||||
mp_obj_base_t base;
|
||||
CosmicUnicorn* Cosmic;
|
||||
CosmicUnicorn* cosmic;
|
||||
} _CosmicUnicorn_obj_t;
|
||||
|
||||
typedef struct _ModPicoGraphics_obj_t {
|
||||
|
@ -388,12 +388,12 @@ mp_obj_t CosmicUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t
|
|||
}
|
||||
|
||||
|
||||
CosmicUnicorn *Cosmic = m_new_class(CosmicUnicorn);
|
||||
Cosmic->init();
|
||||
CosmicUnicorn *cosmic = m_new_class(CosmicUnicorn);
|
||||
cosmic->init();
|
||||
|
||||
self = m_new_obj_with_finaliser(_CosmicUnicorn_obj_t);
|
||||
self->base.type = &CosmicUnicorn_type;
|
||||
self->Cosmic = Cosmic;
|
||||
self->cosmic = cosmic;
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ mp_obj_t CosmicUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t
|
|||
/***** Destructor ******/
|
||||
mp_obj_t CosmicUnicorn___del__(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
m_del_class(CosmicUnicorn, self->Cosmic);
|
||||
m_del_class(CosmicUnicorn, self->cosmic);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
@ -410,7 +410,7 @@ mp_obj_t CosmicUnicorn___del__(mp_obj_t self_in) {
|
|||
/***** Methods *****/
|
||||
extern mp_obj_t CosmicUnicorn_clear(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->clear();
|
||||
self->cosmic->clear();
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
@ -418,54 +418,54 @@ extern mp_obj_t CosmicUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in) {
|
|||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
ModPicoGraphics_obj_t *picographics = MP_OBJ_TO_PTR2(graphics_in, ModPicoGraphics_obj_t);
|
||||
|
||||
self->Cosmic->update(picographics->graphics);
|
||||
self->cosmic->update(picographics->graphics);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->set_brightness(mp_obj_get_float(value));
|
||||
self->cosmic->set_brightness(mp_obj_get_float(value));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_get_brightness(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->Cosmic->get_brightness());
|
||||
return mp_obj_new_float(self->cosmic->get_brightness());
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->adjust_brightness(mp_obj_get_float(delta));
|
||||
self->cosmic->adjust_brightness(mp_obj_get_float(delta));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->set_volume(mp_obj_get_float(value));
|
||||
self->cosmic->set_volume(mp_obj_get_float(value));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_get_volume(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->Cosmic->get_volume());
|
||||
return mp_obj_new_float(self->cosmic->get_volume());
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->adjust_volume(mp_obj_get_float(delta));
|
||||
self->cosmic->adjust_volume(mp_obj_get_float(delta));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_light(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->Cosmic->light());
|
||||
return mp_obj_new_float(self->cosmic->light());
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
return mp_obj_new_bool(self->Cosmic->is_pressed((uint8_t)mp_obj_get_int(button)));
|
||||
return mp_obj_new_bool(self->cosmic->is_pressed((uint8_t)mp_obj_get_int(button)));
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
|
||||
|
@ -477,21 +477,21 @@ extern mp_obj_t CosmicUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
|
|||
mp_raise_ValueError("Supplied buffer is too small!");
|
||||
}
|
||||
|
||||
self->Cosmic->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
|
||||
self->cosmic->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_play_synth(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->play_synth();
|
||||
self->cosmic->play_synth();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t CosmicUnicorn_stop_playing(mp_obj_t self_in) {
|
||||
_CosmicUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _CosmicUnicorn_obj_t);
|
||||
self->Cosmic->stop_playing();
|
||||
self->cosmic->stop_playing();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
@ -509,7 +509,7 @@ extern mp_obj_t CosmicUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_i
|
|||
// Could very easily mess up in weird ways once object deletion is considered?
|
||||
_Channel_obj_t *channel_obj = m_new_obj_with_finaliser(_Channel_obj_t);
|
||||
channel_obj->base.type = &Channel_type;
|
||||
channel_obj->channel = &self->Cosmic->synth_channel(channel);
|
||||
channel_obj->channel = &self->cosmic->synth_channel(channel);
|
||||
|
||||
return MP_OBJ_FROM_PTR(channel_obj);
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ while True:
|
|||
|
||||
# draw the text
|
||||
graphics.set_pen(YELLOW)
|
||||
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55);
|
||||
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55)
|
||||
|
||||
# update the display
|
||||
gu.update(graphics)
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
include_directories(${CMAKE_CURRENT_LIST_DIR}/../../)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
# Essential
|
||||
include(pimoroni_i2c/micropython)
|
||||
include(pimoroni_bus/micropython)
|
||||
|
||||
# Pico Graphics Essential
|
||||
include(hershey_fonts/micropython)
|
||||
include(bitmap_fonts/micropython)
|
||||
include(picographics/micropython)
|
||||
|
||||
# Pico Graphics Extra
|
||||
include(jpegdec/micropython)
|
||||
include(qrcode/micropython/micropython)
|
||||
|
||||
# Sensors & Breakouts
|
||||
include(micropython-common-breakouts)
|
||||
include(pcf85063a/micropython)
|
||||
|
||||
# Utility
|
||||
include(adcfft/micropython)
|
||||
|
||||
# LEDs & Matrices
|
||||
include(stellar_unicorn/micropython)
|
||||
|
||||
# ULAB
|
||||
include(micropython-common-ulab)
|
||||
enable_ulab()
|
||||
|
||||
include(modules_py/modules_py)
|
||||
|
||||
# C++ Magic Memory
|
||||
include(cppmem/micropython)
|
|
@ -72,6 +72,7 @@ Bear in mind that MicroPython has only 192K of RAM available- a 320x240 pixel di
|
|||
* Galactic Unicorn - 53x11 LED Matrix - `DISPLAY_GALACTIC_UNICORN`
|
||||
* Interstate75 and 75W - HUB75 Matrix driver - `DISPLAY_INTERSTATE75_SIZEOFMATRIX` please read below!
|
||||
* Cosmic Unicorn - 32x32 LED Matrix - `DISPLAY_COSMIC_UNICORN`
|
||||
* Stellar Unicorn - 16x16 LED Matrix - `DISPLAY_STELLAR_UNICORN`
|
||||
|
||||
#### Interstate75 and Interstate75W Display modes
|
||||
|
||||
|
|
|
@ -152,6 +152,7 @@ STATIC const mp_map_elem_t picographics_globals_table[] = {
|
|||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_256X64), MP_ROM_INT(DISPLAY_INTERSTATE75_256X64) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME_7), MP_ROM_INT(DISPLAY_INKY_FRAME_7) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_COSMIC_UNICORN), MP_ROM_INT(DISPLAY_COSMIC_UNICORN) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_STELLAR_UNICORN), MP_ROM_INT(DISPLAY_STELLAR_UNICORN) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_UNICORN_PACK), MP_ROM_INT(DISPLAY_UNICORN_PACK) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_SCROLL_PACK), MP_ROM_INT(DISPLAY_SCROLL_PACK) },
|
||||
|
||||
|
|
|
@ -210,6 +210,14 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height,
|
|||
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
|
||||
if(pen_type == -1) pen_type = PEN_RGB888;
|
||||
break;
|
||||
case DISPLAY_STELLAR_UNICORN:
|
||||
width = 16;
|
||||
height = 16;
|
||||
bus_type = BUS_PIO;
|
||||
// Portrait to match labelling
|
||||
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
|
||||
if(pen_type == -1) pen_type = PEN_RGB888;
|
||||
break;
|
||||
case DISPLAY_UNICORN_PACK:
|
||||
width = 16;
|
||||
height = 7;
|
||||
|
@ -354,6 +362,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
|
|||
|| display == DISPLAY_INTERSTATE75_64X32
|
||||
|| display == DISPLAY_GALACTIC_UNICORN
|
||||
|| display == DISPLAY_COSMIC_UNICORN
|
||||
|| display == DISPLAY_STELLAR_UNICORN
|
||||
|| display == DISPLAY_UNICORN_PACK
|
||||
|| display == DISPLAY_SCROLL_PACK) {
|
||||
// Create a dummy display driver
|
||||
|
|
|
@ -26,6 +26,7 @@ enum PicoGraphicsDisplay {
|
|||
DISPLAY_INTERSTATE75_256X64,
|
||||
DISPLAY_INKY_FRAME_7,
|
||||
DISPLAY_COSMIC_UNICORN,
|
||||
DISPLAY_STELLAR_UNICORN,
|
||||
DISPLAY_UNICORN_PACK,
|
||||
DISPLAY_SCROLL_PACK
|
||||
};
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
# Stellar Unicorn (MicroPython) <!-- omit in toc -->
|
||||
|
||||
Stellar Unicorn offers 16x16 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/stellar-unicorn
|
||||
|
||||
## These are not your everyday RGB LEDs!
|
||||
|
||||
Internally Stellar 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 Stellar 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/micropython/modules/picographics) library which provides a comprehensive selection of drawing methods - once your drawing work is complete you pass the PicoGraphics object to Stellar Unicorn to have it displayed on the screen.
|
||||
|
||||
- [Example Program](#example-program)
|
||||
- [Interleaved Framebuffer](#interleaved-framebuffer)
|
||||
- [Function Reference](#function-reference)
|
||||
- [Imports and Objects](#imports-and-objects)
|
||||
- [System State](#system-state)
|
||||
- [`set_brightness(value)`](#set_brightnessvalue)
|
||||
- [`get_brightness()`](#get_brightness)
|
||||
- [`adjust_brightness(delta)`](#adjust_brightnessdelta)
|
||||
- [`set_volume(value)`](#set_volumevalue)
|
||||
- [`get_volume()`](#get_volume)
|
||||
- [`adjust_volume(delta)`](#adjust_volumedelta)
|
||||
- [`light()`](#light)
|
||||
- [`is_pressed(button)`](#is_pressedbutton)
|
||||
- [Drawing](#drawing)
|
||||
- [`update(PicoGraphics)`](#updatepicographics)
|
||||
- [`clear()`](#clear)
|
||||
- [Audio](#audio)
|
||||
- [`play_sample(data)`](#play_sampledata)
|
||||
- [`synth_channel(channel)`](#synth_channelchannel)
|
||||
- [`play_synth()`](#play_synth)
|
||||
- [`stop_playing()`](#stop_playing)
|
||||
- [Channel Reference](#channel-reference)
|
||||
- [Constants](#constants)
|
||||
- [`WIDTH` \& `HEIGHT`](#width--height)
|
||||
- [Using Breakouts](#using-breakouts)
|
||||
|
||||
# Example Program
|
||||
|
||||
The following example shows how to scroll a simple message across the display.
|
||||
|
||||
```python
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN
|
||||
import time
|
||||
|
||||
# create a PicoGraphics framebuffer to draw into
|
||||
graphics = PicoGraphics(display=DISPLAY_STELLAR_UNICORN)
|
||||
|
||||
# create our StellarUnicorn object
|
||||
su = StellarUnicorn()
|
||||
|
||||
# start position for scrolling (off the side of the display)
|
||||
scroll = float(-StellarUnicorn.WIDTH)
|
||||
|
||||
# message to scroll
|
||||
MESSAGE = "Pirate. Monkey. Robot. Ninja."
|
||||
|
||||
# pen colours to draw with
|
||||
BLACK = graphics.create_pen(0, 0, 0)
|
||||
YELLOW = graphics.create_pen(255, 255, 0)
|
||||
|
||||
while True:
|
||||
# determine the scroll position of the text
|
||||
width = graphics.measure_text(MESSAGE, 1)
|
||||
scroll += 0.25
|
||||
if scroll > width:
|
||||
scroll = float(-StellarUnicorn.WIDTH)
|
||||
|
||||
# clear the graphics object
|
||||
graphics.set_pen(BLACK)
|
||||
graphics.clear()
|
||||
|
||||
# draw the text
|
||||
graphics.set_pen(YELLOW)
|
||||
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55)
|
||||
|
||||
# update the display
|
||||
su.update(graphics)
|
||||
|
||||
time.sleep(0.02)
|
||||
```
|
||||
|
||||
# Interleaved Framebuffer
|
||||
|
||||
Stellar 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
|
||||
|
||||
## Imports and Objects
|
||||
|
||||
To access these functions, you'll need to first `import` the relevant libraries and then set up a Stellar Unicorn object:
|
||||
|
||||
```python
|
||||
from stellar import StellarUnicorn
|
||||
|
||||
su = StellarUnicorn()
|
||||
```
|
||||
|
||||
or (with PicoGraphics):
|
||||
|
||||
```python
|
||||
from stellar import StellarUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN
|
||||
|
||||
su = StellarUnicorn()
|
||||
graphics = PicoGraphics(display=DISPLAY_STELLAR_UNICORN)
|
||||
```
|
||||
|
||||
## System State
|
||||
|
||||
### `set_brightness(value)`
|
||||
|
||||
Set the brightness - `value` is supplied as a floating point value between `0.0` and `1.0`.
|
||||
|
||||
### `get_brightness()`
|
||||
|
||||
Returns the current brightness as a value between `0.0` and `1.0`.
|
||||
|
||||
### `adjust_brightness(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:
|
||||
|
||||
```python
|
||||
su.set_brightness(0.5)
|
||||
su.adjust_brightness(0.1) # brightness is now 0.6
|
||||
su.adjust_brightness(0.7) # brightness is now 1.0
|
||||
su.adjust_brightness(-0.2) # brightness is now 0.8
|
||||
```
|
||||
|
||||
### `set_volume(value)`
|
||||
|
||||
Set the volume - `value` is supplied as a floating point value between `0.0` and `1.0`.
|
||||
|
||||
### `get_volume()`
|
||||
|
||||
Returns the current volume as a value between `0.0` and `1.0`.
|
||||
|
||||
### `adjust_volume(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:
|
||||
|
||||
```python
|
||||
su.set_volume(0.5)
|
||||
su.set_volume(0.1) # volume is now 0.6
|
||||
su.adjust_volume(0.7) # volume is now 1.0
|
||||
su.adjust_volume(-0.2) # volume is now 0.8
|
||||
```
|
||||
|
||||
### `light()`
|
||||
|
||||
Get the current value seen by the onboard light sensor as a value between `0` and `4095`.
|
||||
|
||||
### `is_pressed(button)`
|
||||
|
||||
Returns true if the requested `button` is currently pressed.
|
||||
|
||||
There are a set of constants in the StellarUnicorn 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. Here's a list of the constants and their associated pin numbers:
|
||||
|
||||
```python
|
||||
SWITCH_A = 0
|
||||
SWITCH_B = 1
|
||||
SWITCH_C = 3
|
||||
SWITCH_D = 6
|
||||
SWITCH_SLEEP = 27
|
||||
SWITCH_VOLUME_UP = 7
|
||||
SWITCH_VOLUME_DOWN = 8
|
||||
SWITCH_BRIGHTNESS_UP = 21
|
||||
SWITCH_BRIGHTNESS_DOWN = 26
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
while not su.is_pressed(StellarUnicorn.SWITCH_A):
|
||||
# wait for switch A to be pressed
|
||||
pass
|
||||
|
||||
print("We did it! We pressed switch A! Heck yeah!")
|
||||
```
|
||||
|
||||
## Drawing
|
||||
|
||||
### `update(PicoGraphics)`
|
||||
|
||||
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.
|
||||
|
||||
For example (assuming you've set up your Stellar Unicorn and PicoGraphics objects up [as we did above](#imports-and-objects)):
|
||||
|
||||
```python
|
||||
su.update(graphics)
|
||||
```
|
||||
|
||||
⚠️ If you've used PicoGraphics on our other boards note that this `update` function works a little differently. Here it's a Stellar Unicorn function to which you need to pass a PicoGraphics object to.
|
||||
|
||||
### `clear()`
|
||||
|
||||
Clear the contents of the interleaved framebuffer. This will make your Stellar Unicorn display turn off. To show an image again, call the `update()` function as described above.
|
||||
|
||||
## 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.
|
||||
|
||||
### `play_sample(data)`
|
||||
|
||||
Play the provided 16-bit audio sample. `data` must point to a `bytearray` that contains 16-bit PCM data. The number of samples is retrieved from the array's length.
|
||||
|
||||
### `synth_channel(channel)`
|
||||
|
||||
Gets a `Channel` object which can then be configured with voice, ADSR envelope, etc.
|
||||
|
||||
### `play_synth()`
|
||||
|
||||
Start the synth playing.
|
||||
|
||||
### `stop_playing()`
|
||||
|
||||
Stops any currently playing audio.
|
||||
|
||||
### Channel Reference
|
||||
|
||||
```python
|
||||
configure(waveforms=None, frequency=None, volume=None,
|
||||
attack=None, decay=None, sustain=None,
|
||||
release=None, pulse_width=None)
|
||||
restore()
|
||||
waveforms()
|
||||
waveforms(waveforms)
|
||||
frequency()
|
||||
frequency(frequency)
|
||||
volume()
|
||||
volume(volume)
|
||||
attack_duration()
|
||||
attack_duration(duration)
|
||||
decay_duration()
|
||||
decay_duration(duration)
|
||||
sustain_level()
|
||||
sustain_level(level)
|
||||
release_duration()
|
||||
release_duration(duration)
|
||||
pulse_width()
|
||||
pulse_width(width)
|
||||
trigger_attack() # start the channel playing
|
||||
trigger_release() # stop the channel playing
|
||||
play_tone(frequency, volume=None, attack=None, release=None)
|
||||
```
|
||||
|
||||
## Constants
|
||||
|
||||
### `WIDTH` & `HEIGHT`
|
||||
|
||||
The width and height of Stellar Unicorn are available in constants `WIDTH` and `HEIGHT`.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
num_pixels = StellarUnicorn.WIDTH * StellarUnicorn.HEIGHT
|
||||
print(num_pixels)
|
||||
```
|
||||
|
||||
## Using Breakouts
|
||||
|
||||
Stellar Unicorn has two Qw/ST (Qwiic/STEMMA QT) connectors. Breakouts with Qw/ST connectors, can be plugged straight in with a [JST-SH to JST-SH cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609813587). You can connect I2C Breakout Garden breakouts without Qw/ST connectors using a [JST-SH to JST-SH cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609813587) and a [Qw/ST to Breakout Garden adaptor](https://shop.pimoroni.com/products/stemma-qt-qwiic-to-breakout-garden-adapter).
|
||||
|
||||
- [List of breakouts currently supported in our C++/MicroPython build](https://github.com/pimoroni/pimoroni-pico#breakouts)
|
||||
|
||||
Stellar Unicorn uses GP4 and GP5 for its I2C interface. You can use the constants in the shared `pimoroni` module to set up the I2C interface:
|
||||
|
||||
```python
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
from pimoroni import BREAKOUT_GARDEN_I2C_PINS
|
||||
|
||||
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
|
||||
```
|
||||
|
||||
Alternatively, you can specify the pin numbers directly:
|
||||
|
||||
```python
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
|
||||
i2c = PimoroniI2C(sda=4, scl=5)
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
set(MOD_NAME stellar_unicorn)
|
||||
string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER)
|
||||
add_library(usermod_${MOD_NAME} INTERFACE)
|
||||
|
||||
target_sources(usermod_${MOD_NAME} INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/stellar_unicorn/stellar_unicorn.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_synth/pico_synth.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_rgb888.cpp
|
||||
)
|
||||
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/stellar_unicorn/stellar_unicorn.pio)
|
||||
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/stellar_unicorn/audio_i2s.pio)
|
||||
|
||||
target_include_directories(usermod_${MOD_NAME} INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/
|
||||
)
|
||||
|
||||
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
|
||||
MODULE_STELLAR_ENABLED=1
|
||||
)
|
||||
|
||||
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})
|
|
@ -0,0 +1,150 @@
|
|||
#include "stellar_unicorn.h"
|
||||
|
||||
|
||||
/***** Methods *****/
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(Channel___del___obj, Channel___del__);
|
||||
MP_DEFINE_CONST_FUN_OBJ_KW(Channel_configure_obj, 1, Channel_configure);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(Channel_restore_obj, Channel_restore);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_waveforms_obj, 1, 2, Channel_waveforms);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_frequency_obj, 1, 2, Channel_frequency);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_volume_obj, 1, 2, Channel_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_attack_duration_obj, 1, 2, Channel_attack_duration);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_decay_duration_obj, 1, 2, Channel_decay_duration);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_sustain_level_obj, 1, 2, Channel_sustain_level);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_release_duration_obj, 1, 2, Channel_release_duration);
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_pulse_width_obj, 1, 2, Channel_pulse_width);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(Channel_trigger_attack_obj, Channel_trigger_attack);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(Channel_trigger_release_obj, Channel_trigger_release);
|
||||
MP_DEFINE_CONST_FUN_OBJ_KW(Channel_play_tone_obj, 2, Channel_play_tone);
|
||||
//MP_DEFINE_CONST_FUN_OBJ_1(Channel_stop_playing_obj, Channel_stop_playing);
|
||||
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn___del___obj, StellarUnicorn___del__);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_clear_obj, StellarUnicorn_clear);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_update_obj, StellarUnicorn_update);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_set_brightness_obj, StellarUnicorn_set_brightness);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_get_brightness_obj, StellarUnicorn_get_brightness);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_adjust_brightness_obj, StellarUnicorn_adjust_brightness);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_set_volume_obj, StellarUnicorn_set_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_get_volume_obj, StellarUnicorn_get_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_adjust_volume_obj, StellarUnicorn_adjust_volume);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_light_obj, StellarUnicorn_light);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_is_pressed_obj, StellarUnicorn_is_pressed);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_play_sample_obj, StellarUnicorn_play_sample);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_play_synth_obj, StellarUnicorn_play_synth);
|
||||
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_stop_playing_obj, StellarUnicorn_stop_playing);
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_synth_channel_obj, StellarUnicorn_synth_channel);
|
||||
|
||||
/***** Binding of Methods *****/
|
||||
STATIC const mp_rom_map_elem_t Channel_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Channel___del___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_configure), MP_ROM_PTR(&Channel_configure_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_restore), MP_ROM_PTR(&Channel_restore_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_waveforms), MP_ROM_PTR(&Channel_waveforms_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&Channel_frequency_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_volume), MP_ROM_PTR(&Channel_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_attack_duration), MP_ROM_PTR(&Channel_attack_duration_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_decay_duration), MP_ROM_PTR(&Channel_decay_duration_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_sustain_level), MP_ROM_PTR(&Channel_sustain_level_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_release_duration), MP_ROM_PTR(&Channel_release_duration_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_pulse_width), MP_ROM_PTR(&Channel_pulse_width_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_trigger_attack), MP_ROM_PTR(&Channel_trigger_attack_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_trigger_release), MP_ROM_PTR(&Channel_trigger_release_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_play_tone), MP_ROM_PTR(&Channel_play_tone_obj) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_NOISE), MP_ROM_INT(128) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SQUARE), MP_ROM_INT(64) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SAW), MP_ROM_INT(32) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_TRIANGLE), MP_ROM_INT(16) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SINE), MP_ROM_INT(8) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_WAVE), MP_ROM_INT(1) },
|
||||
};
|
||||
|
||||
STATIC const mp_rom_map_elem_t StellarUnicorn_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&StellarUnicorn___del___obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&StellarUnicorn_clear_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&StellarUnicorn_update_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&StellarUnicorn_set_brightness_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_get_brightness), MP_ROM_PTR(&StellarUnicorn_get_brightness_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_adjust_brightness), MP_ROM_PTR(&StellarUnicorn_adjust_brightness_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_set_volume), MP_ROM_PTR(&StellarUnicorn_set_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_get_volume), MP_ROM_PTR(&StellarUnicorn_get_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_adjust_volume), MP_ROM_PTR(&StellarUnicorn_adjust_volume_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_light), MP_ROM_PTR(&StellarUnicorn_light_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&StellarUnicorn_is_pressed_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_play_sample), MP_ROM_PTR(&StellarUnicorn_play_sample_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_play_synth), MP_ROM_PTR(&StellarUnicorn_play_synth_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_stop_playing), MP_ROM_PTR(&StellarUnicorn_stop_playing_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_synth_channel), MP_ROM_PTR(&StellarUnicorn_synth_channel_obj) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(16) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(16) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_A), MP_ROM_INT(0) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_B), MP_ROM_INT(1) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_C), MP_ROM_INT(3) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_D), MP_ROM_INT(6) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_SLEEP), MP_ROM_INT(27) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_VOLUME_UP), MP_ROM_INT(7) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_VOLUME_DOWN), MP_ROM_INT(8) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_BRIGHTNESS_UP), MP_ROM_INT(21) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_SWITCH_BRIGHTNESS_DOWN), MP_ROM_INT(26) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(Channel_locals_dict, Channel_locals_dict_table);
|
||||
STATIC MP_DEFINE_CONST_DICT(StellarUnicorn_locals_dict, StellarUnicorn_locals_dict_table);
|
||||
|
||||
/***** Class Definition *****/
|
||||
#ifdef MP_DEFINE_CONST_OBJ_TYPE
|
||||
MP_DEFINE_CONST_OBJ_TYPE(
|
||||
Channel_type,
|
||||
MP_QSTR_Channel,
|
||||
MP_TYPE_FLAG_NONE,
|
||||
make_new, Channel_make_new,
|
||||
print, Channel_print,
|
||||
locals_dict, (mp_obj_dict_t*)&Channel_locals_dict
|
||||
);
|
||||
|
||||
MP_DEFINE_CONST_OBJ_TYPE(
|
||||
StellarUnicorn_type,
|
||||
MP_QSTR_StellarUnicorn,
|
||||
MP_TYPE_FLAG_NONE,
|
||||
make_new, StellarUnicorn_make_new,
|
||||
print, StellarUnicorn_print,
|
||||
locals_dict, (mp_obj_dict_t*)&StellarUnicorn_locals_dict
|
||||
);
|
||||
#else
|
||||
const mp_obj_type_t Channel_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_Channel,
|
||||
.print = Channel_print,
|
||||
.make_new = Channel_make_new,
|
||||
.locals_dict = (mp_obj_dict_t*)&Channel_locals_dict,
|
||||
};
|
||||
|
||||
const mp_obj_type_t StellarUnicorn_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_StellarUnicorn,
|
||||
.print = StellarUnicorn_print,
|
||||
.make_new = StellarUnicorn_make_new,
|
||||
.locals_dict = (mp_obj_dict_t*)&StellarUnicorn_locals_dict,
|
||||
};
|
||||
#endif
|
||||
|
||||
/***** Globals Table *****/
|
||||
STATIC const mp_map_elem_t stellar_globals_table[] = {
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_stellar) },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_Channel), (mp_obj_t)&Channel_type },
|
||||
{ MP_OBJ_NEW_QSTR(MP_QSTR_StellarUnicorn), (mp_obj_t)&StellarUnicorn_type },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_stellar_globals, stellar_globals_table);
|
||||
|
||||
/***** Module Definition *****/
|
||||
const mp_obj_module_t stellar_user_cmodule = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t*)&mp_module_stellar_globals,
|
||||
};
|
||||
#if MICROPY_VERSION <= 70144
|
||||
MP_REGISTER_MODULE(MP_QSTR_stellar, stellar_user_cmodule, MODULE_STELLAR_ENABLED);
|
||||
#else
|
||||
MP_REGISTER_MODULE(MP_QSTR_stellar, stellar_user_cmodule);
|
||||
#endif
|
|
@ -0,0 +1,516 @@
|
|||
#include "libraries/stellar_unicorn/stellar_unicorn.hpp"
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "micropython/modules/util.hpp"
|
||||
#include <cstdio>
|
||||
#include <cfloat>
|
||||
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
extern "C" {
|
||||
#include "stellar_unicorn.h"
|
||||
#include "micropython/modules/pimoroni_i2c/pimoroni_i2c.h"
|
||||
#include "py/builtin.h"
|
||||
|
||||
|
||||
/********** Channel **********/
|
||||
|
||||
/***** Variables Struct *****/
|
||||
typedef struct _Channel_obj_t {
|
||||
mp_obj_base_t base;
|
||||
AudioChannel* channel;
|
||||
} _Channel_obj_t;
|
||||
|
||||
|
||||
/***** Print *****/
|
||||
void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||
(void)kind; //Unused input parameter
|
||||
//_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
|
||||
//AudioChannel* channel = self->channel;
|
||||
mp_print_str(print, "Channel(");
|
||||
mp_print_str(print, ")");
|
||||
}
|
||||
|
||||
|
||||
/***** Constructor *****/
|
||||
mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, "Cannot create Channel objects. They can only be accessed from StellarUnicorn.synth_channel()");
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
/***** Destructor ******/
|
||||
mp_obj_t Channel___del__(mp_obj_t self_in) {
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
/***** Helper Functions *****/
|
||||
void set_channel_waveforms(AudioChannel& channel, mp_obj_t in) {
|
||||
int waveforms = mp_obj_get_int(in);
|
||||
const int mask = (NOISE | SQUARE | SAW | TRIANGLE | SINE | WAVE);
|
||||
if(waveforms < 0 || (waveforms & mask) == 0) {
|
||||
mp_raise_ValueError("waveforms invalid. Expected a combination of NOISE, SQUARE, SAW, TRIANGLE, SINE, or WAVE");
|
||||
}
|
||||
channel.waveforms = (uint8_t)waveforms;
|
||||
}
|
||||
|
||||
void set_channel_frequency(AudioChannel& channel, mp_obj_t in) {
|
||||
int freq = mp_obj_get_int(in);
|
||||
if(freq <= 0 || freq > UINT16_MAX) {
|
||||
mp_raise_ValueError("frequency out of range. Expected greater than 0Hz to 65535Hz");
|
||||
}
|
||||
channel.frequency = (uint16_t)freq;
|
||||
}
|
||||
|
||||
void set_channel_volume(AudioChannel& channel, mp_obj_t in) {
|
||||
float volume = mp_obj_get_float(in);
|
||||
if(volume < 0.0f || volume > 1.0f) {
|
||||
mp_raise_ValueError("volume out of range. Expected 0.0 to 1.0");
|
||||
}
|
||||
channel.volume = (uint16_t)(volume * UINT16_MAX);
|
||||
}
|
||||
|
||||
void set_channel_attack(AudioChannel& channel, mp_obj_t in) {
|
||||
int attack_ms = (int)(mp_obj_get_float(in) * 1000.0f);
|
||||
if(attack_ms < 0 || attack_ms > UINT16_MAX) {
|
||||
mp_raise_ValueError("attack out of range. Expected 0.0s to 65.5s");
|
||||
}
|
||||
channel.attack_ms = MAX(attack_ms, 1);
|
||||
}
|
||||
|
||||
void set_channel_decay(AudioChannel& channel, mp_obj_t in) {
|
||||
int decay_ms = (int)(mp_obj_get_float(in) * 1000.0f);
|
||||
if(decay_ms < 0 || decay_ms > UINT16_MAX) {
|
||||
mp_raise_ValueError("decay out of range. Expected 0.0s to 65.5s");
|
||||
}
|
||||
channel.decay_ms = MAX(decay_ms, 1);
|
||||
}
|
||||
|
||||
void set_channel_sustain(AudioChannel& channel, mp_obj_t in) {
|
||||
float sustain = mp_obj_get_float(in);
|
||||
if(sustain < 0.0f || sustain > 1.0f) {
|
||||
mp_raise_ValueError("sustain out of range. Expected 0.0 to 1.0");
|
||||
}
|
||||
channel.sustain = (uint16_t)(sustain * UINT16_MAX);
|
||||
}
|
||||
|
||||
void set_channel_release(AudioChannel& channel, mp_obj_t in) {
|
||||
int release_ms = (int)(mp_obj_get_float(in) * 1000.0f);
|
||||
if(release_ms < 0 || release_ms > UINT16_MAX) {
|
||||
mp_raise_ValueError("release out of range. Expected 0.0s to 65.5s");
|
||||
}
|
||||
channel.release_ms = MAX(release_ms, 1);
|
||||
}
|
||||
|
||||
void set_channel_pulse_width(AudioChannel& channel, mp_obj_t in) {
|
||||
float pulse_width = mp_obj_get_float(in);
|
||||
if(pulse_width < 0.0f || pulse_width > 1.0f) {
|
||||
mp_raise_ValueError("pulse_width out of range. Expected 0.0 to 1.0");
|
||||
}
|
||||
channel.pulse_width = (uint16_t)(pulse_width * UINT16_MAX);
|
||||
}
|
||||
|
||||
|
||||
/***** Methods *****/
|
||||
mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_self, ARG_waveforms, ARG_frequency, ARG_volume, ARG_attack, ARG_decay, ARG_sustain, ARG_release, ARG_pulse_width };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_waveforms, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_frequency, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_decay, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_sustain, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_pulse_width, MP_ARG_OBJ, {.u_obj = mp_const_none} }
|
||||
};
|
||||
|
||||
// Parse args.
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
|
||||
|
||||
mp_obj_t waveforms = args[ARG_waveforms].u_obj;
|
||||
if(waveforms != mp_const_none) {
|
||||
set_channel_waveforms(*self->channel, waveforms);
|
||||
}
|
||||
|
||||
mp_obj_t frequency = args[ARG_frequency].u_obj;
|
||||
if(frequency != mp_const_none) {
|
||||
set_channel_frequency(*self->channel, frequency);
|
||||
}
|
||||
|
||||
mp_obj_t volume = args[ARG_volume].u_obj;
|
||||
if(volume != mp_const_none) {
|
||||
set_channel_volume(*self->channel, volume);
|
||||
}
|
||||
|
||||
mp_obj_t attack = args[ARG_attack].u_obj;
|
||||
if(attack != mp_const_none) {
|
||||
set_channel_attack(*self->channel, attack);
|
||||
}
|
||||
|
||||
mp_obj_t decay = args[ARG_decay].u_obj;
|
||||
if(decay != mp_const_none) {
|
||||
set_channel_decay(*self->channel, decay);
|
||||
}
|
||||
|
||||
mp_obj_t sustain = args[ARG_sustain].u_obj;
|
||||
if(sustain != mp_const_none) {
|
||||
set_channel_sustain(*self->channel, sustain);
|
||||
}
|
||||
|
||||
mp_obj_t release = args[ARG_release].u_obj;
|
||||
if(release != mp_const_none) {
|
||||
set_channel_release(*self->channel, release);
|
||||
}
|
||||
|
||||
mp_obj_t pulse_width = args[ARG_pulse_width].u_obj;
|
||||
if(pulse_width != mp_const_none) {
|
||||
set_channel_pulse_width(*self->channel, pulse_width);
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_restore(mp_obj_t self_in) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
|
||||
self->channel->restore();
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_int(self->channel->waveforms);
|
||||
}
|
||||
|
||||
set_channel_waveforms(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_int(self->channel->frequency);
|
||||
}
|
||||
|
||||
set_channel_frequency(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_float((float)self->channel->volume / UINT16_MAX);
|
||||
}
|
||||
|
||||
set_channel_volume(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_float((float)self->channel->attack_ms / 1000.0f);
|
||||
}
|
||||
|
||||
set_channel_attack(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_float((float)self->channel->decay_ms / 1000.0f);
|
||||
}
|
||||
|
||||
set_channel_decay(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_float((float)self->channel->sustain / UINT16_MAX);
|
||||
}
|
||||
|
||||
set_channel_sustain(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_float((float)self->channel->release_ms / 1000.0f);
|
||||
}
|
||||
|
||||
set_channel_release(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
|
||||
|
||||
if(n_args == 1) {
|
||||
return mp_obj_new_float((float)self->channel->pulse_width / 0xffff);
|
||||
}
|
||||
|
||||
set_channel_pulse_width(*self->channel, args[1]);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_trigger_attack(mp_obj_t self_in) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
|
||||
self->channel->trigger_attack();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_trigger_release(mp_obj_t self_in) {
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
|
||||
self->channel->trigger_release();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_self, ARG_freq, ARG_volume, ARG_fade_in, ARG_fade_out };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_frequency, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
|
||||
};
|
||||
|
||||
// Parse args.
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
|
||||
|
||||
set_channel_frequency(*self->channel, args[ARG_freq].u_obj);
|
||||
|
||||
mp_obj_t volume = args[ARG_volume].u_obj;
|
||||
if(volume != mp_const_none) {
|
||||
set_channel_volume(*self->channel, volume);
|
||||
}
|
||||
else {
|
||||
self->channel->volume = UINT16_MAX;
|
||||
}
|
||||
|
||||
mp_obj_t attack_ms = args[ARG_fade_in].u_obj;
|
||||
if(attack_ms != mp_const_none) {
|
||||
set_channel_attack(*self->channel, attack_ms);
|
||||
}
|
||||
else {
|
||||
self->channel->attack_ms = 1;
|
||||
}
|
||||
|
||||
mp_obj_t release_ms = args[ARG_fade_out].u_obj;
|
||||
if(release_ms != mp_const_none) {
|
||||
set_channel_release(*self->channel, release_ms);
|
||||
}
|
||||
else {
|
||||
self->channel->release_ms = 1;
|
||||
}
|
||||
|
||||
self->channel->waveforms = Waveform::SINE;
|
||||
self->channel->decay_ms = 1;
|
||||
self->channel->sustain = UINT16_MAX;
|
||||
|
||||
self->channel->trigger_attack();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
/********** StellarUnicorn **********/
|
||||
|
||||
/***** Variables Struct *****/
|
||||
typedef struct _StellarUnicorn_obj_t {
|
||||
mp_obj_base_t base;
|
||||
StellarUnicorn* stellar;
|
||||
} _StellarUnicorn_obj_t;
|
||||
|
||||
typedef struct _ModPicoGraphics_obj_t {
|
||||
mp_obj_base_t base;
|
||||
PicoGraphics *graphics;
|
||||
DisplayDriver *display;
|
||||
void *spritedata;
|
||||
void *buffer;
|
||||
_PimoroniI2C_obj_t *i2c;
|
||||
//mp_obj_t scanline_callback; // Not really feasible in MicroPython
|
||||
} ModPicoGraphics_obj_t;
|
||||
|
||||
/***** Print *****/
|
||||
void StellarUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||
(void)kind; //Unused input parameter
|
||||
//_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
mp_print_str(print, "StellarUnicorn()");
|
||||
}
|
||||
|
||||
|
||||
/***** Constructor *****/
|
||||
mp_obj_t StellarUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||
_StellarUnicorn_obj_t *self = nullptr;
|
||||
|
||||
enum { ARG_pio, ARG_sm, ARG_pins, ARG_common_pin, ARG_direction, ARG_counts_per_rev, ARG_count_microsteps, ARG_freq_divider };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_pio, MP_ARG_INT },
|
||||
{ MP_QSTR_sm, MP_ARG_INT }
|
||||
};
|
||||
|
||||
// Parse args.
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
int pio_int = args[ARG_pio].u_int;
|
||||
if(pio_int < 0 || pio_int > (int)NUM_PIOS) {
|
||||
mp_raise_ValueError("pio out of range. Expected 0 to 1");
|
||||
}
|
||||
//PIO pio = pio_int == 0 ? pio0 : pio1;
|
||||
|
||||
int sm = args[ARG_sm].u_int;
|
||||
if(sm < 0 || sm > (int)NUM_PIO_STATE_MACHINES) {
|
||||
mp_raise_ValueError("sm out of range. Expected 0 to 3");
|
||||
}
|
||||
|
||||
|
||||
StellarUnicorn *stellar = m_new_class(StellarUnicorn);
|
||||
stellar->init();
|
||||
|
||||
self = m_new_obj_with_finaliser(_StellarUnicorn_obj_t);
|
||||
self->base.type = &StellarUnicorn_type;
|
||||
self->stellar = stellar;
|
||||
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
||||
|
||||
/***** Destructor ******/
|
||||
mp_obj_t StellarUnicorn___del__(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
m_del_class(StellarUnicorn, self->stellar);
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
/***** Methods *****/
|
||||
extern mp_obj_t StellarUnicorn_clear(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->clear();
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
ModPicoGraphics_obj_t *picographics = MP_OBJ_TO_PTR2(graphics_in, ModPicoGraphics_obj_t);
|
||||
|
||||
self->stellar->update(picographics->graphics);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->set_brightness(mp_obj_get_float(value));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_get_brightness(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->stellar->get_brightness());
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->adjust_brightness(mp_obj_get_float(delta));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->set_volume(mp_obj_get_float(value));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_get_volume(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->stellar->get_volume());
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->adjust_volume(mp_obj_get_float(delta));
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
|
||||
extern mp_obj_t StellarUnicorn_light(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
return mp_obj_new_float(self->stellar->light());
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
return mp_obj_new_bool(self->stellar->is_pressed((uint8_t)mp_obj_get_int(button)));
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
|
||||
mp_buffer_info_t bufinfo;
|
||||
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_RW);
|
||||
if(bufinfo.len < 1) {
|
||||
mp_raise_ValueError("Supplied buffer is too small!");
|
||||
}
|
||||
|
||||
self->stellar->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_play_synth(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->play_synth();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_stop_playing(mp_obj_t self_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
self->stellar->stop_playing();
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
extern mp_obj_t StellarUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in) {
|
||||
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
|
||||
|
||||
// Check that the channel is valid
|
||||
int channel = mp_obj_get_int(channel_in);
|
||||
if(channel < 0 || channel >= (int)PicoSynth::CHANNEL_COUNT) {
|
||||
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("channel out of range. Expected 0 to %d"), PicoSynth::CHANNEL_COUNT - 1);
|
||||
}
|
||||
|
||||
// NOTE This seems to work, in that it give MP access to the calibration object
|
||||
// Could very easily mess up in weird ways once object deletion is considered?
|
||||
_Channel_obj_t *channel_obj = m_new_obj_with_finaliser(_Channel_obj_t);
|
||||
channel_obj->base.type = &Channel_type;
|
||||
channel_obj->channel = &self->stellar->synth_channel(channel);
|
||||
|
||||
return MP_OBJ_FROM_PTR(channel_obj);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Include MicroPython API.
|
||||
#include "py/runtime.h"
|
||||
|
||||
/***** Extern of Class Definition *****/
|
||||
extern const mp_obj_type_t Channel_type;
|
||||
extern const mp_obj_type_t StellarUnicorn_type;
|
||||
|
||||
/***** Extern of Class Methods *****/
|
||||
extern void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
|
||||
extern mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
|
||||
extern mp_obj_t Channel___del__(mp_obj_t self_in);
|
||||
extern mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
|
||||
extern mp_obj_t Channel_restore(mp_obj_t self_in);
|
||||
extern mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args);
|
||||
extern mp_obj_t Channel_trigger_attack(mp_obj_t self_in);
|
||||
extern mp_obj_t Channel_trigger_release(mp_obj_t self_in);
|
||||
extern mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
|
||||
extern mp_obj_t Channel_stop_playing(mp_obj_t self_in);
|
||||
|
||||
extern void StellarUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
|
||||
extern mp_obj_t StellarUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
|
||||
extern mp_obj_t StellarUnicorn___del__(mp_obj_t self_in);
|
||||
extern mp_obj_t StellarUnicorn_clear(mp_obj_t self_in);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value);
|
||||
extern mp_obj_t StellarUnicorn_get_brightness(mp_obj_t self_in);
|
||||
extern mp_obj_t StellarUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value);
|
||||
extern mp_obj_t StellarUnicorn_get_volume(mp_obj_t self_in);
|
||||
extern mp_obj_t StellarUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_light(mp_obj_t self_in);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data);
|
||||
extern mp_obj_t StellarUnicorn_play_synth(mp_obj_t self_in);
|
||||
extern mp_obj_t StellarUnicorn_stop_playing(mp_obj_t self_in);
|
||||
|
||||
extern mp_obj_t StellarUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in);
|
|
@ -1 +1 @@
|
|||
Subproject commit ac2e9954edc185ec212e437e73e4b5e4290de35b
|
||||
Subproject commit e68bb707b20ee326d84ab75fc9fb35f2e85b87e3
|
|
@ -30,6 +30,7 @@ On the releases page you'll find a bunch of different .uf2 files for use on diff
|
|||
| Badger 2040 W | **pimoroni-badger2040w-vx.x.x-micropython.uf2** or **pimoroni-badger2040w-v0.0.1-micropython-with-badger-os.uf2** | :warning: Badger OS will overwrite your files!
|
||||
| Badger 2040 | **pimoroni-badger2040-vx.x.x-micropython.uf2** or **pimoroni-badger2040-v0.0.1-micropython-with-badger-os.uf2** | :warning: Badger OS will overwrite your files!
|
||||
| Cosmic Unicorn | **pimoroni-picow_cosmic_unicorn-vx.x.x-micropython.uf2** | |
|
||||
| Stellar Unicorn | **pimoroni-picow_stellar_unicorn-vx.x.x-micropython.uf2** | |
|
||||
|
||||
## Entering DFU/bootloader mode
|
||||
|
||||
|
|