kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Merge branch 'main' into driver/st7567
commit
952be145ec
|
@ -61,7 +61,7 @@ jobs:
|
|||
|
||||
build:
|
||||
needs: deps
|
||||
name: Build ${{matrix.board}}
|
||||
name: Build ${{matrix.name}} (${{matrix.board}})
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -70,6 +70,8 @@ jobs:
|
|||
board: PICO_W
|
||||
- name: picow_enviro
|
||||
board: PICO_W_ENVIRO
|
||||
- name: picow_galactic_unicorn
|
||||
board: PICO_W
|
||||
|
||||
env:
|
||||
# MicroPython version will be contained in github.event.release.tag_name for releases
|
||||
|
|
|
@ -16,6 +16,11 @@ This repository contains the C/C++ and MicroPython libraries for our range of RP
|
|||
- [C++ Examples](#c-examples)
|
||||
- [Boilerplate for C++ Projects](#boilerplate-for-c-projects)
|
||||
- [Supported Products](#supported-products)
|
||||
- [Packs and Bases](#packs-and-bases)
|
||||
- [SHIMs](#shims)
|
||||
- [RP2040 Boards](#rp2040-boards)
|
||||
- [Pico W Aboard](#pico-w-aboard)
|
||||
- [Breakouts](#breakouts)
|
||||
- [Tutorials & Guides](#tutorials--guides)
|
||||
|
||||
# MicroPython
|
||||
|
@ -27,7 +32,8 @@ The easiest way to get started. If you're new to Pico, we recommend you read our
|
|||
New releases are issued regularly with new libraries, bug fixes to our existing libraries and new features inherited from MicroPython upstream. Be sure to check back!
|
||||
|
||||
* :link: [Tutorial: Getting started with Pico](https://learn.pimoroni.com/article/getting-started-with-pico)
|
||||
* [Readme: Instructions for setting up MicroPython](setting-up-micropython.md)
|
||||
* [Readme: Instructions for installing MicroPython](setting-up-micropython.md)
|
||||
* [Readme: Frequently Asked Questions](faqs-micropython.md)
|
||||
* [Pimoroni Pico MicroPython + Drivers Releases](https://github.com/pimoroni/pimoroni-pico/releases)
|
||||
* [Readme: PicoGraphics](micropython/modules/picographics)
|
||||
|
||||
|
|
|
@ -50,9 +50,11 @@ namespace pimoroni {
|
|||
enum BOARD {
|
||||
BREAKOUT_GARDEN,
|
||||
PICO_EXPLORER,
|
||||
PLASMA_STICK,
|
||||
PLASMA_2040,
|
||||
INTERSTATE_75,
|
||||
SERVO_2040
|
||||
SERVO_2040,
|
||||
MOTOR_2040
|
||||
};
|
||||
|
||||
enum Rotation {
|
||||
|
|
|
@ -29,9 +29,15 @@ namespace pimoroni {
|
|||
scl = I2C_DEFAULT_SCL;
|
||||
interrupt = I2C_DEFAULT_INT;
|
||||
break;
|
||||
case PLASMA_STICK:
|
||||
sda = I2C_BG_SDA;
|
||||
scl = I2C_BG_SCL;
|
||||
interrupt = PIN_UNUSED;
|
||||
break;
|
||||
case PLASMA_2040:
|
||||
case INTERSTATE_75:
|
||||
case SERVO_2040:
|
||||
case MOTOR_2040:
|
||||
sda = I2C_HEADER_SDA;
|
||||
scl = I2C_HEADER_SCL;
|
||||
interrupt = I2C_HEADER_INT;
|
||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 43 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 55 KiB |
|
@ -88,16 +88,35 @@ namespace pimoroni {
|
|||
reset();
|
||||
busy_wait();
|
||||
|
||||
command(0x00, {0xE3, 0x08});
|
||||
command(0x01, {0x37, 0x00, 0x23, 0x23});
|
||||
command(0x03, {0x00});
|
||||
command(0x06, {0xC7, 0xC7, 0x1D});
|
||||
command(0x30, {0x3C});
|
||||
command(0x40, {0x00});
|
||||
command(0x50, {0x37});
|
||||
command(0x60, {0x22});
|
||||
command(0x61, {0x02, 0x58, 0x01, 0xC0});
|
||||
command(0xE3, {0xAA});
|
||||
uint8_t dimensions[4] = {
|
||||
uint8_t(width >> 8),
|
||||
uint8_t(width),
|
||||
uint8_t(height >> 8),
|
||||
uint8_t(height)
|
||||
};
|
||||
|
||||
if (width == 600) {
|
||||
if (rotation == ROTATE_0) {
|
||||
command(PSR, {0xE3, 0x08});
|
||||
} else {
|
||||
command(PSR, {0xEF, 0x08});
|
||||
}
|
||||
} else {
|
||||
if (rotation == ROTATE_0) {
|
||||
command(PSR, {0xA3, 0x08});
|
||||
} else {
|
||||
command(PSR, {0xAF, 0x08});
|
||||
}
|
||||
}
|
||||
command(PWR, {0x37, 0x00, 0x23, 0x23});
|
||||
command(PFS, {0x00});
|
||||
command(BTST, {0xC7, 0xC7, 0x1D});
|
||||
command(PLL, {0x3C});
|
||||
command(TSC, {0x00});
|
||||
command(CDI, {0x37});
|
||||
command(TCON, {0x22});
|
||||
command(TRES, 4, dimensions);
|
||||
command(PWS, {0xAA});
|
||||
|
||||
sleep_ms(100);
|
||||
|
||||
|
@ -154,6 +173,15 @@ namespace pimoroni {
|
|||
spi_write_blocking(spi, ®, 1);
|
||||
|
||||
gpio_put(DC, 1); // data mode
|
||||
|
||||
// HACK: Output 48 rows of data since our buffer is 400px tall
|
||||
// but the display has no offset configuration and H/V scan
|
||||
// are reversed.
|
||||
// Any garbage data will do.
|
||||
// 2px per byte, so we need width * 24 bytes
|
||||
if(height == 400 && rotation == ROTATE_0) {
|
||||
spi_write_blocking(spi, (uint8_t *)graphics->frame_buffer, width * 24);
|
||||
}
|
||||
graphics->frame_convert(PicoGraphics::PEN_P4, [this](void *buf, size_t length) {
|
||||
if (length > 0) {
|
||||
spi_write_blocking(spi, (const uint8_t*)buf, length);
|
||||
|
|
|
@ -42,10 +42,12 @@ namespace pimoroni {
|
|||
CLEAN = 7
|
||||
};
|
||||
|
||||
UC8159(uint16_t width, uint16_t height) : UC8159(width, height, {PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, 28, PIN_UNUSED}) {};
|
||||
UC8159(uint16_t width, uint16_t height) : UC8159(width, height, ROTATE_0, {PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, 28, PIN_UNUSED}) {};
|
||||
|
||||
UC8159(uint16_t width, uint16_t height, SPIPins pins, uint busy=PIN_UNUSED, uint reset=27) :
|
||||
DisplayDriver(width, height, ROTATE_0),
|
||||
UC8159(uint16_t width, uint16_t height, SPIPins pins, uint busy=PIN_UNUSED, uint reset=27) : UC8159(width, height, ROTATE_0, pins, busy, reset) {};
|
||||
|
||||
UC8159(uint16_t width, uint16_t height, Rotation rotation, SPIPins pins, uint busy=PIN_UNUSED, uint reset=27) :
|
||||
DisplayDriver(width, height, rotation),
|
||||
spi(pins.spi),
|
||||
CS(pins.cs), DC(pins.dc), SCK(pins.sck), MOSI(pins.mosi), BUSY(busy), RESET(reset) {
|
||||
init();
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
add_executable(
|
||||
rainbow_text
|
||||
rainbow_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(rainbow_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(rainbow_text)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
rainbow
|
||||
rainbow.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(rainbow 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(rainbow)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
nostalgia_prompt
|
||||
nostalgia_prompt.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(nostalgia_prompt pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(nostalgia_prompt 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(nostalgia_prompt)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
eighties_super_computer
|
||||
eighties_super_computer.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(eighties_super_computer 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(eighties_super_computer)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
fire_effect
|
||||
fire_effect.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(fire_effect 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(fire_effect)
|
||||
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
scroll_text
|
||||
scroll_text.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(scroll_text 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(scroll_text)
|
||||
|
||||
|
||||
add_executable(
|
||||
balls
|
||||
balls.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(balls pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(balls 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(balls)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
lava_lamp
|
||||
lava_lamp.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(lava_lamp 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(lava_lamp)
|
||||
|
||||
|
||||
|
||||
add_executable(
|
||||
feature_test
|
||||
feature_test.cpp
|
||||
audio_samples.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(feature_test pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
|
||||
pico_enable_stdio_usb(feature_test 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(feature_test)
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,87 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
float heat[53][13] = {0.0f};
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 12; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
if(heat[x][y] > 0.5f) {
|
||||
graphics.set_pen(255, 255, 180);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(heat[x][y] > 0.4f) {
|
||||
graphics.set_pen(220, 160, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(heat[x][y] > 0.3f) {
|
||||
graphics.set_pen(180, 50, 0);
|
||||
graphics.pixel(Point(x, y));
|
||||
}else if(heat[x][y] > 0.2f) {
|
||||
graphics.set_pen(40, 40, 40);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
// update this pixel by averaging the below pixels
|
||||
if(x == 0) {
|
||||
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x + 1][y + 1]) / 4.0f;
|
||||
} else if(x == 52) {
|
||||
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1]) / 4.0f;
|
||||
} else {
|
||||
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1] + heat[x + 1][y + 1]) / 5.0f;
|
||||
}
|
||||
|
||||
heat[x][y] -= 0.01f;
|
||||
heat[x][y] = heat[x][y] < 0.0f ? 0.0f: heat[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
// clear the bottom row and then add a new fire seed to it
|
||||
for(int x = 0; x < 53; x++) {
|
||||
heat[x][11] = 0.0f;
|
||||
}
|
||||
|
||||
// add a new random heat source
|
||||
for(int c = 0; c < 5; c++) {
|
||||
int px = (rand() % 51) + 1;
|
||||
heat[px][11] = 1.0f;
|
||||
heat[px + 1][11] = 1.0f;
|
||||
heat[px - 1][11] = 1.0f;
|
||||
heat[px][12] = 1.0f;
|
||||
heat[px + 1][12] = 1.0f;
|
||||
heat[px - 1][12] = 1.0f;
|
||||
}
|
||||
|
||||
sleep_ms(50);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
float lifetime[53][11];
|
||||
float age[53][11];
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
for(int y = 0; y < 11; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
|
||||
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
for(int y = 0; y < 11; y++) {
|
||||
for(int x = 0; x < 53; 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;
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// left_channel_bin and right_channel_bin are in audio_samples.cpp
|
||||
extern uint8_t left_channel_bin[];
|
||||
extern uint32_t left_channel_bin_len;
|
||||
extern uint8_t right_channel_bin[];
|
||||
extern uint32_t right_channel_bin_len;
|
||||
|
||||
void gradient(uint8_t r, uint8_t g, uint8_t b) {
|
||||
for(int y = 0; y < 12; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
// graphics.set_pen((r * x) / 52, (g * x) / 52, (b * x) / 52);
|
||||
graphics.set_pen(x, x, x);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef void (*shader_func_t)(int ms, int x, int y);
|
||||
|
||||
void grid(int ms, int x, int y) {
|
||||
int v = (x + y + (ms / 1000)) % 2 == 0 ? 255 : 0;
|
||||
graphics.set_pen(v, v, v);
|
||||
graphics.pixel(Point(x, y));
|
||||
}
|
||||
|
||||
void shader_fill(int ms, shader_func_t f) {
|
||||
for(int y = 0; y < 12; y++) {
|
||||
for(int x = 0; x < 53; x++) {
|
||||
f(ms, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void outline_text(std::string text) {
|
||||
uint ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
graphics.set_font("bitmap8");
|
||||
uint8_t v = (sin(ms / 100.0f) + 1.0f) * 127.0f;
|
||||
uint w = graphics.measure_text(text, 1);
|
||||
|
||||
int x = 53 / 2 - w / 2 + 1, y = 2;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.text(text, Point(x - 1, y - 1), -1, 1);
|
||||
graphics.text(text, Point(x , y - 1), -1, 1);
|
||||
graphics.text(text, Point(x + 1, y - 1), -1, 1);
|
||||
graphics.text(text, Point(x - 1, y ), -1, 1);
|
||||
graphics.text(text, Point(x + 1, y ), -1, 1);
|
||||
graphics.text(text, Point(x - 1, y + 1), -1, 1);
|
||||
graphics.text(text, Point(x , y + 1), -1, 1);
|
||||
graphics.text(text, Point(x + 1, y + 1), -1, 1);
|
||||
|
||||
graphics.set_pen(v, v, v);
|
||||
graphics.text(text, Point(x, y), -1, 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
//galactic_unicorn.set_brightness(0.5f);
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01f);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01f);
|
||||
}
|
||||
|
||||
uint ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
uint8_t test = (ms / 1000) % 4;
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
switch(test) {
|
||||
case 0: {
|
||||
printf("grid pattern");
|
||||
shader_fill(ms, grid);
|
||||
}break;
|
||||
|
||||
case 1: {
|
||||
printf("green gradient");
|
||||
gradient(0, 255, 0);
|
||||
}break;
|
||||
|
||||
case 2: {
|
||||
printf("blue gradient");
|
||||
gradient(0, 0, 255);
|
||||
}break;
|
||||
|
||||
case 3: {
|
||||
printf("white gradient");
|
||||
gradient(255, 255, 255);
|
||||
}break;
|
||||
}
|
||||
|
||||
printf("%d\n", galactic_unicorn.light());
|
||||
|
||||
std::string text = "";
|
||||
static bool was_a_pressed = false;
|
||||
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
|
||||
if(!was_a_pressed) {
|
||||
galactic_unicorn.play_sample(left_channel_bin, left_channel_bin_len);
|
||||
}
|
||||
was_a_pressed = true;
|
||||
}else{
|
||||
was_a_pressed = false;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
|
||||
text = "Button A";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_B)) {
|
||||
text = "Button B";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_C)) {
|
||||
text = "Button C";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_D)) {
|
||||
text = "Button D";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_UP)) {
|
||||
text = "Louder!";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_DOWN)) {
|
||||
text = "quieter";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_UP)) {
|
||||
text = "Brighter!";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_DOWN)) {
|
||||
text = "Darker";
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_SLEEP)) {
|
||||
text = "Zzz... zzz...";
|
||||
}
|
||||
|
||||
outline_text(text);
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(50);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// extra row of pixels for sourcing flames and averaging
|
||||
int width = 53;
|
||||
int height = 15;
|
||||
|
||||
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
|
||||
float heat[1000] = {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();
|
||||
|
||||
galactic_unicorn.init();
|
||||
galactic_unicorn.set_brightness(0.5);
|
||||
|
||||
bool landscape = true;
|
||||
|
||||
while(true) {
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
|
||||
landscape = true;
|
||||
width = 53;
|
||||
height = 15;
|
||||
memset(heat, 0, sizeof(heat));
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
|
||||
landscape = false;
|
||||
width = 11;
|
||||
height = 55;
|
||||
memset(heat, 0, sizeof(heat));
|
||||
}
|
||||
|
||||
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 *= landscape ? 0.95f : 0.99f;
|
||||
|
||||
// update the heat map with our newly averaged value
|
||||
set(x, y, average);
|
||||
}
|
||||
}
|
||||
|
||||
galactic_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,149 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
struct blob_t {
|
||||
float x, y;
|
||||
float r;
|
||||
float dx, dy;
|
||||
};
|
||||
|
||||
constexpr int blob_count = 20;
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
galactic_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() % 11;
|
||||
blob.y = rand() % 53;
|
||||
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(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_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[11][53] = {0.0f};
|
||||
for(auto &blob : blobs) {
|
||||
float r_sq = blob.r * blob.r;
|
||||
for(int y = 0; y < 53; y++) {
|
||||
for(int x = 0; x < 11; 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 < 53; y++) {
|
||||
for(int x = 0; x < 11; 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);
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
std::array<std::string, 11> c64 = {
|
||||
"",
|
||||
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX",
|
||||
" OO OO OO OOOO OO OO OO OO XXXXXXX",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX",
|
||||
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX",
|
||||
" OOOO OO OO OO OO OO OO XXXXXXX",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX",
|
||||
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX",
|
||||
" XXXXXXX"
|
||||
};
|
||||
|
||||
std::array<std::string, 11> spectrum = {
|
||||
"",
|
||||
" O OOOO OOOO OOOOO O O O O XXXXXXXX",
|
||||
" O O O O O O O O O O O X XXXXXX",
|
||||
" O O O O O O O X XXXXXX",
|
||||
" O O O OOOOOO O O X XXXXXX",
|
||||
" O O O O O O O X XXXXXX",
|
||||
" OOOOOO OOOO O O OOOOO X XXXXXX",
|
||||
" X X",
|
||||
" XXXXXXXX"
|
||||
};
|
||||
|
||||
std::array<std::string, 11> bbc_micro = {
|
||||
"",
|
||||
" OOOOO OO OOOO OOO OOOO O ",
|
||||
" O O O O O O O O O O ",
|
||||
" O O O O O O O O ",
|
||||
" OOOOO O O OOOO O O O ",
|
||||
" O O OOOOOO O O O O ",
|
||||
" O O O O O O O O O O ",
|
||||
" OOOOO O O OOOO OOO OOOO O ",
|
||||
" XXXXXXX"
|
||||
};
|
||||
|
||||
|
||||
constexpr uint PROMPT_C64 = 0;
|
||||
constexpr uint PROMPT_SPECTRUM = 1;
|
||||
constexpr uint PROMPT_BBC_MICRO = 2;
|
||||
uint prompt = 0;
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
while(true) {
|
||||
uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
prompt = (time_ms / 3000) % 3;
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
std::array<std::string, 11> image = c64;
|
||||
if(prompt == PROMPT_C64) {
|
||||
image = c64;
|
||||
}else if(prompt == PROMPT_SPECTRUM) {
|
||||
image = spectrum;
|
||||
}else if(prompt == PROMPT_BBC_MICRO) {
|
||||
image = bbc_micro;
|
||||
}
|
||||
|
||||
if(prompt == PROMPT_C64) {
|
||||
graphics.set_pen(20, 20, 120);
|
||||
}else if(prompt == PROMPT_SPECTRUM){
|
||||
graphics.set_pen(180, 150, 150);
|
||||
}else if(prompt == PROMPT_BBC_MICRO){
|
||||
graphics.set_pen(0, 0, 0);
|
||||
}
|
||||
|
||||
graphics.clear();
|
||||
|
||||
if(prompt == PROMPT_C64) {
|
||||
graphics.set_pen(230, 210, 250);
|
||||
}else if(prompt == PROMPT_SPECTRUM){
|
||||
graphics.set_pen(0, 0, 0);
|
||||
}else if(prompt == PROMPT_BBC_MICRO){
|
||||
graphics.set_pen(255, 255, 255);
|
||||
}
|
||||
|
||||
for(size_t y = 0; y < image.size(); y++) {
|
||||
for(size_t x = 0; x < image[y].length(); x++) {
|
||||
// draw the prompt text
|
||||
if(image[y][x] == 'O') {
|
||||
graphics.pixel(Point(x, y + 1));
|
||||
}
|
||||
|
||||
// draw the caret blinking
|
||||
if(image[y][x] == 'X' && (time_ms / 300) % 2) {
|
||||
graphics.pixel(Point(x, y + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,684 @@
|
|||
#pragma once
|
||||
// Copyright(c) 2021 Björn Ottosson
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this softwareand associated documentation files(the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and /or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions :
|
||||
// The above copyright noticeand this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include <cmath>
|
||||
#include <cfloat>
|
||||
|
||||
namespace ok_color
|
||||
{
|
||||
|
||||
struct Lab { float L; float a; float b; };
|
||||
struct RGB { float r; float g; float b; };
|
||||
struct HSV { float h; float s; float v; };
|
||||
struct HSL { float h; float s; float l; };
|
||||
struct LC { float L; float C; };
|
||||
|
||||
// Alternative representation of (L_cusp, C_cusp)
|
||||
// Encoded so S = C_cusp/L_cusp and T = C_cusp/(1-L_cusp)
|
||||
// The maximum value for C in the triangle is then found as fmin(S*L, T*(1-L)), for a given L
|
||||
struct ST { float S; float T; };
|
||||
|
||||
constexpr float pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062f;
|
||||
|
||||
float clamp(float x, float min, float max)
|
||||
{
|
||||
if (x < min)
|
||||
return min;
|
||||
if (x > max)
|
||||
return max;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
float sgn(float x)
|
||||
{
|
||||
return (float)(0.f < x) - (float)(x < 0.f);
|
||||
}
|
||||
|
||||
float srgb_transfer_function(float a)
|
||||
{
|
||||
return .0031308f >= a ? 12.92f * a : 1.055f * powf(a, .4166666666666667f) - .055f;
|
||||
}
|
||||
|
||||
float srgb_transfer_function_inv(float a)
|
||||
{
|
||||
return .04045f < a ? powf((a + .055f) / 1.055f, 2.4f) : a / 12.92f;
|
||||
}
|
||||
|
||||
Lab linear_srgb_to_oklab(RGB c)
|
||||
{
|
||||
float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
|
||||
float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
|
||||
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
|
||||
|
||||
float l_ = cbrtf(l);
|
||||
float m_ = cbrtf(m);
|
||||
float s_ = cbrtf(s);
|
||||
|
||||
return {
|
||||
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
|
||||
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
|
||||
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
|
||||
};
|
||||
}
|
||||
|
||||
RGB oklab_to_linear_srgb(Lab c)
|
||||
{
|
||||
float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
|
||||
float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
|
||||
float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
|
||||
|
||||
float l = l_ * l_ * l_;
|
||||
float m = m_ * m_ * m_;
|
||||
float s = s_ * s_ * s_;
|
||||
|
||||
return {
|
||||
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
|
||||
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
|
||||
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
|
||||
};
|
||||
}
|
||||
|
||||
// Finds the maximum saturation possible for a given hue that fits in sRGB
|
||||
// Saturation here is defined as S = C/L
|
||||
// a and b must be normalized so a^2 + b^2 == 1
|
||||
float compute_max_saturation(float a, float b)
|
||||
{
|
||||
// Max saturation will be when one of r, g or b goes below zero.
|
||||
|
||||
// Select different coefficients depending on which component goes below zero first
|
||||
float k0, k1, k2, k3, k4, wl, wm, ws;
|
||||
|
||||
if (-1.88170328f * a - 0.80936493f * b > 1)
|
||||
{
|
||||
// Red component
|
||||
k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
|
||||
wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
|
||||
}
|
||||
else if (1.81444104f * a - 1.19445276f * b > 1)
|
||||
{
|
||||
// Green component
|
||||
k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
|
||||
wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blue component
|
||||
k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
|
||||
wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
|
||||
}
|
||||
|
||||
// Approximate max saturation using a polynomial:
|
||||
float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
|
||||
|
||||
// Do one step Halley's method to get closer
|
||||
// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
|
||||
// this should be sufficient for most applications, otherwise do two/three steps
|
||||
|
||||
float k_l = +0.3963377774f * a + 0.2158037573f * b;
|
||||
float k_m = -0.1055613458f * a - 0.0638541728f * b;
|
||||
float k_s = -0.0894841775f * a - 1.2914855480f * b;
|
||||
|
||||
{
|
||||
float l_ = 1.f + S * k_l;
|
||||
float m_ = 1.f + S * k_m;
|
||||
float s_ = 1.f + S * k_s;
|
||||
|
||||
float l = l_ * l_ * l_;
|
||||
float m = m_ * m_ * m_;
|
||||
float s = s_ * s_ * s_;
|
||||
|
||||
float l_dS = 3.f * k_l * l_ * l_;
|
||||
float m_dS = 3.f * k_m * m_ * m_;
|
||||
float s_dS = 3.f * k_s * s_ * s_;
|
||||
|
||||
float l_dS2 = 6.f * k_l * k_l * l_;
|
||||
float m_dS2 = 6.f * k_m * k_m * m_;
|
||||
float s_dS2 = 6.f * k_s * k_s * s_;
|
||||
|
||||
float f = wl * l + wm * m + ws * s;
|
||||
float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
|
||||
float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
|
||||
|
||||
S = S - f * f1 / (f1 * f1 - 0.5f * f * f2);
|
||||
}
|
||||
|
||||
return S;
|
||||
}
|
||||
|
||||
// finds L_cusp and C_cusp for a given hue
|
||||
// a and b must be normalized so a^2 + b^2 == 1
|
||||
LC find_cusp(float a, float b)
|
||||
{
|
||||
// First, find the maximum saturation (saturation S = C/L)
|
||||
float S_cusp = compute_max_saturation(a, b);
|
||||
|
||||
// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
|
||||
RGB rgb_at_max = oklab_to_linear_srgb({ 1, S_cusp * a, S_cusp * b });
|
||||
float L_cusp = cbrtf(1.f / fmax(fmax(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
|
||||
float C_cusp = L_cusp * S_cusp;
|
||||
|
||||
return { L_cusp , C_cusp };
|
||||
}
|
||||
|
||||
// Finds intersection of the line defined by
|
||||
// L = L0 * (1 - t) + t * L1;
|
||||
// C = t * C1;
|
||||
// a and b must be normalized so a^2 + b^2 == 1
|
||||
float find_gamut_intersection(float a, float b, float L1, float C1, float L0, LC cusp)
|
||||
{
|
||||
// Find the intersection for upper and lower half seprately
|
||||
float t;
|
||||
if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.f)
|
||||
{
|
||||
// Lower half
|
||||
|
||||
t = cusp.C * L0 / (C1 * cusp.L + cusp.C * (L0 - L1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Upper half
|
||||
|
||||
// First intersect with triangle
|
||||
t = cusp.C * (L0 - 1.f) / (C1 * (cusp.L - 1.f) + cusp.C * (L0 - L1));
|
||||
|
||||
// Then one step Halley's method
|
||||
{
|
||||
float dL = L1 - L0;
|
||||
float dC = C1;
|
||||
|
||||
float k_l = +0.3963377774f * a + 0.2158037573f * b;
|
||||
float k_m = -0.1055613458f * a - 0.0638541728f * b;
|
||||
float k_s = -0.0894841775f * a - 1.2914855480f * b;
|
||||
|
||||
float l_dt = dL + dC * k_l;
|
||||
float m_dt = dL + dC * k_m;
|
||||
float s_dt = dL + dC * k_s;
|
||||
|
||||
|
||||
// If higher accuracy is required, 2 or 3 iterations of the following block can be used:
|
||||
{
|
||||
float L = L0 * (1.f - t) + t * L1;
|
||||
float C = t * C1;
|
||||
|
||||
float l_ = L + C * k_l;
|
||||
float m_ = L + C * k_m;
|
||||
float s_ = L + C * k_s;
|
||||
|
||||
float l = l_ * l_ * l_;
|
||||
float m = m_ * m_ * m_;
|
||||
float s = s_ * s_ * s_;
|
||||
|
||||
float ldt = 3 * l_dt * l_ * l_;
|
||||
float mdt = 3 * m_dt * m_ * m_;
|
||||
float sdt = 3 * s_dt * s_ * s_;
|
||||
|
||||
float ldt2 = 6 * l_dt * l_dt * l_;
|
||||
float mdt2 = 6 * m_dt * m_dt * m_;
|
||||
float sdt2 = 6 * s_dt * s_dt * s_;
|
||||
|
||||
float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1;
|
||||
float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
|
||||
float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
|
||||
|
||||
float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
|
||||
float t_r = -r * u_r;
|
||||
|
||||
float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1;
|
||||
float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
|
||||
float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
|
||||
|
||||
float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
|
||||
float t_g = -g * u_g;
|
||||
|
||||
float b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1;
|
||||
float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
|
||||
float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
|
||||
|
||||
float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
|
||||
float t_b = -b * u_b;
|
||||
|
||||
t_r = u_r >= 0.f ? t_r : FLT_MAX;
|
||||
t_g = u_g >= 0.f ? t_g : FLT_MAX;
|
||||
t_b = u_b >= 0.f ? t_b : FLT_MAX;
|
||||
|
||||
t += fmin(t_r, fmin(t_g, t_b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
|
||||
{
|
||||
// Find the cusp of the gamut triangle
|
||||
LC cusp = find_cusp(a, b);
|
||||
|
||||
return find_gamut_intersection(a, b, L1, C1, L0, cusp);
|
||||
}
|
||||
|
||||
RGB gamut_clip_preserve_chroma(RGB rgb)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L0 = clamp(L, 0, 1);
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1 - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_project_to_0_5(RGB rgb)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L0 = 0.5;
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1 - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_project_to_L_cusp(RGB rgb)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
|
||||
float L0 = cusp.L;
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
|
||||
float L_clipped = L0 * (1 - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha = 0.05f)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float Ld = L - 0.5f;
|
||||
float e1 = 0.5f + fabs(Ld) + alpha * C;
|
||||
float L0 = 0.5f * (1.f + sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * fabs(Ld))));
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1.f - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha = 0.05f)
|
||||
{
|
||||
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
|
||||
return rgb;
|
||||
|
||||
Lab lab = linear_srgb_to_oklab(rgb);
|
||||
|
||||
float L = lab.L;
|
||||
float eps = 0.00001f;
|
||||
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
|
||||
float Ld = L - cusp.L;
|
||||
float k = 2.f * (Ld > 0 ? 1.f - cusp.L : cusp.L);
|
||||
|
||||
float e1 = 0.5f * k + fabs(Ld) + alpha * C / k;
|
||||
float L0 = cusp.L + 0.5f * (sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * k * fabs(Ld))));
|
||||
|
||||
float t = find_gamut_intersection(a_, b_, L, C, L0);
|
||||
float L_clipped = L0 * (1.f - t) + t * L;
|
||||
float C_clipped = t * C;
|
||||
|
||||
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
|
||||
}
|
||||
|
||||
float toe(float x)
|
||||
{
|
||||
constexpr float k_1 = 0.206f;
|
||||
constexpr float k_2 = 0.03f;
|
||||
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
|
||||
return 0.5f * (k_3 * x - k_1 + sqrtf((k_3 * x - k_1) * (k_3 * x - k_1) + 4 * k_2 * k_3 * x));
|
||||
}
|
||||
|
||||
float toe_inv(float x)
|
||||
{
|
||||
constexpr float k_1 = 0.206f;
|
||||
constexpr float k_2 = 0.03f;
|
||||
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
|
||||
return (x * x + k_1 * x) / (k_3 * (x + k_2));
|
||||
}
|
||||
|
||||
ST to_ST(LC cusp)
|
||||
{
|
||||
float L = cusp.L;
|
||||
float C = cusp.C;
|
||||
return { C / L, C / (1 - L) };
|
||||
}
|
||||
|
||||
// Returns a smooth approximation of the location of the cusp
|
||||
// This polynomial was created by an optimization process
|
||||
// It has been designed so that S_mid < S_max and T_mid < T_max
|
||||
ST get_ST_mid(float a_, float b_)
|
||||
{
|
||||
float S = 0.11516993f + 1.f / (
|
||||
+7.44778970f + 4.15901240f * b_
|
||||
+ a_ * (-2.19557347f + 1.75198401f * b_
|
||||
+ a_ * (-2.13704948f - 10.02301043f * b_
|
||||
+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_
|
||||
)))
|
||||
);
|
||||
|
||||
float T = 0.11239642f + 1.f / (
|
||||
+1.61320320f - 0.68124379f * b_
|
||||
+ a_ * (+0.40370612f + 0.90148123f * b_
|
||||
+ a_ * (-0.27087943f + 0.61223990f * b_
|
||||
+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_
|
||||
)))
|
||||
);
|
||||
|
||||
return { S, T };
|
||||
}
|
||||
|
||||
struct Cs { float C_0; float C_mid; float C_max; };
|
||||
Cs get_Cs(float L, float a_, float b_)
|
||||
{
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
|
||||
float C_max = find_gamut_intersection(a_, b_, L, 1, L, cusp);
|
||||
ST ST_max = to_ST(cusp);
|
||||
|
||||
// Scale factor to compensate for the curved part of gamut shape:
|
||||
float k = C_max / fmin((L * ST_max.S), (1 - L) * ST_max.T);
|
||||
|
||||
float C_mid;
|
||||
{
|
||||
ST ST_mid = get_ST_mid(a_, b_);
|
||||
|
||||
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
|
||||
float C_a = L * ST_mid.S;
|
||||
float C_b = (1.f - L) * ST_mid.T;
|
||||
C_mid = 0.9f * k * sqrtf(sqrtf(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));
|
||||
}
|
||||
|
||||
float C_0;
|
||||
{
|
||||
// for C_0, the shape is independent of hue, so ST are constant. Values picked to roughly be the average values of ST.
|
||||
float C_a = L * 0.4f;
|
||||
float C_b = (1.f - L) * 0.8f;
|
||||
|
||||
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
|
||||
C_0 = sqrtf(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));
|
||||
}
|
||||
|
||||
return { C_0, C_mid, C_max };
|
||||
}
|
||||
|
||||
RGB okhsl_to_srgb(HSL hsl)
|
||||
{
|
||||
float h = hsl.h;
|
||||
float s = hsl.s;
|
||||
float l = hsl.l;
|
||||
|
||||
if (l == 1.0f)
|
||||
{
|
||||
return { 1.f, 1.f, 1.f };
|
||||
}
|
||||
|
||||
else if (l == 0.f)
|
||||
{
|
||||
return { 0.f, 0.f, 0.f };
|
||||
}
|
||||
|
||||
float a_ = cosf(2.f * pi * h);
|
||||
float b_ = sinf(2.f * pi * h);
|
||||
float L = toe_inv(l);
|
||||
|
||||
Cs cs = get_Cs(L, a_, b_);
|
||||
float C_0 = cs.C_0;
|
||||
float C_mid = cs.C_mid;
|
||||
float C_max = cs.C_max;
|
||||
|
||||
float mid = 0.8f;
|
||||
float mid_inv = 1.25f;
|
||||
|
||||
float C, t, k_0, k_1, k_2;
|
||||
|
||||
if (s < mid)
|
||||
{
|
||||
t = mid_inv * s;
|
||||
|
||||
k_1 = mid * C_0;
|
||||
k_2 = (1.f - k_1 / C_mid);
|
||||
|
||||
C = t * k_1 / (1.f - k_2 * t);
|
||||
}
|
||||
else
|
||||
{
|
||||
t = (s - mid)/ (1 - mid);
|
||||
|
||||
k_0 = C_mid;
|
||||
k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
|
||||
k_2 = (1.f - (k_1) / (C_max - C_mid));
|
||||
|
||||
C = k_0 + t * k_1 / (1.f - k_2 * t);
|
||||
}
|
||||
|
||||
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
|
||||
return {
|
||||
srgb_transfer_function(rgb.r),
|
||||
srgb_transfer_function(rgb.g),
|
||||
srgb_transfer_function(rgb.b),
|
||||
};
|
||||
}
|
||||
|
||||
HSL srgb_to_okhsl(RGB rgb)
|
||||
{
|
||||
Lab lab = linear_srgb_to_oklab({
|
||||
srgb_transfer_function_inv(rgb.r),
|
||||
srgb_transfer_function_inv(rgb.g),
|
||||
srgb_transfer_function_inv(rgb.b)
|
||||
});
|
||||
|
||||
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L = lab.L;
|
||||
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
|
||||
|
||||
Cs cs = get_Cs(L, a_, b_);
|
||||
float C_0 = cs.C_0;
|
||||
float C_mid = cs.C_mid;
|
||||
float C_max = cs.C_max;
|
||||
|
||||
// Inverse of the interpolation in okhsl_to_srgb:
|
||||
|
||||
float mid = 0.8f;
|
||||
float mid_inv = 1.25f;
|
||||
|
||||
float s;
|
||||
if (C < C_mid)
|
||||
{
|
||||
float k_1 = mid * C_0;
|
||||
float k_2 = (1.f - k_1 / C_mid);
|
||||
|
||||
float t = C / (k_1 + k_2 * C);
|
||||
s = t * mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
float k_0 = C_mid;
|
||||
float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
|
||||
float k_2 = (1.f - (k_1) / (C_max - C_mid));
|
||||
|
||||
float t = (C - k_0) / (k_1 + k_2 * (C - k_0));
|
||||
s = mid + (1.f - mid) * t;
|
||||
}
|
||||
|
||||
float l = toe(L);
|
||||
return { h, s, l };
|
||||
}
|
||||
|
||||
|
||||
RGB okhsv_to_srgb(HSV hsv)
|
||||
{
|
||||
float h = hsv.h;
|
||||
float s = hsv.s;
|
||||
float v = hsv.v;
|
||||
|
||||
float a_ = cosf(2.f * pi * h);
|
||||
float b_ = sinf(2.f * pi * h);
|
||||
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
ST ST_max = to_ST(cusp);
|
||||
float S_max = ST_max.S;
|
||||
float T_max = ST_max.T;
|
||||
float S_0 = 0.5f;
|
||||
float k = 1 - S_0 / S_max;
|
||||
|
||||
// first we compute L and V as if the gamut is a perfect triangle:
|
||||
|
||||
// L, C when v==1:
|
||||
float L_v = 1 - s * S_0 / (S_0 + T_max - T_max * k * s);
|
||||
float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);
|
||||
|
||||
float L = v * L_v;
|
||||
float C = v * C_v;
|
||||
|
||||
// then we compensate for both toe and the curved top part of the triangle:
|
||||
float L_vt = toe_inv(L_v);
|
||||
float C_vt = C_v * L_vt / L_v;
|
||||
|
||||
float L_new = toe_inv(L);
|
||||
C = C * L_new / L;
|
||||
L = L_new;
|
||||
|
||||
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
|
||||
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
|
||||
|
||||
L = L * scale_L;
|
||||
C = C * scale_L;
|
||||
|
||||
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
|
||||
return {
|
||||
srgb_transfer_function(rgb.r),
|
||||
srgb_transfer_function(rgb.g),
|
||||
srgb_transfer_function(rgb.b),
|
||||
};
|
||||
}
|
||||
|
||||
HSV srgb_to_okhsv(RGB rgb)
|
||||
{
|
||||
Lab lab = linear_srgb_to_oklab({
|
||||
srgb_transfer_function_inv(rgb.r),
|
||||
srgb_transfer_function_inv(rgb.g),
|
||||
srgb_transfer_function_inv(rgb.b)
|
||||
});
|
||||
|
||||
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
|
||||
float a_ = lab.a / C;
|
||||
float b_ = lab.b / C;
|
||||
|
||||
float L = lab.L;
|
||||
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
|
||||
|
||||
LC cusp = find_cusp(a_, b_);
|
||||
ST ST_max = to_ST(cusp);
|
||||
float S_max = ST_max.S;
|
||||
float T_max = ST_max.T;
|
||||
float S_0 = 0.5f;
|
||||
float k = 1 - S_0 / S_max;
|
||||
|
||||
// first we find L_v, C_v, L_vt and C_vt
|
||||
|
||||
float t = T_max / (C + L * T_max);
|
||||
float L_v = t * L;
|
||||
float C_v = t * C;
|
||||
|
||||
float L_vt = toe_inv(L_v);
|
||||
float C_vt = C_v * L_vt / L_v;
|
||||
|
||||
// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
|
||||
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
|
||||
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
|
||||
|
||||
L = L / scale_L;
|
||||
C = C / scale_L;
|
||||
|
||||
C = C * toe(L) / L;
|
||||
L = toe(L);
|
||||
|
||||
// we can now compute v and s:
|
||||
|
||||
float v = L / L_v;
|
||||
float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
|
||||
|
||||
return { h, s, v };
|
||||
}
|
||||
|
||||
} // namespace ok_color
|
|
@ -0,0 +1,200 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
|
||||
int w = graphics.measure_text(t, s);
|
||||
p.x += (53 / 2) - (w / 2);
|
||||
p.y += (11 / 2);
|
||||
graphics.text(t, Point(p.x, p.y), -1, s, a);
|
||||
//graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
|
||||
//graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
|
||||
//graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
|
||||
}
|
||||
|
||||
struct star_t {
|
||||
float dx, dy, x, y, a;
|
||||
|
||||
uint8_t brightness() {
|
||||
int b = a / 5;
|
||||
return b > 15 ? 15 : b;
|
||||
}
|
||||
};
|
||||
|
||||
void init_star(star_t &s) {
|
||||
s.x = ((rand() % 100) / 5.0f) - 10.0f;
|
||||
s.y = ((rand() % 100) / 10.0f) - 5.0f;
|
||||
|
||||
s.dx = s.x / 10.0f;
|
||||
s.dy = s.y / 10.0f;
|
||||
s.a = 0;
|
||||
}
|
||||
|
||||
void step_star(star_t &s) {
|
||||
s.x += s.dx;
|
||||
s.y += s.dy;
|
||||
s.a++;
|
||||
|
||||
if(s.a > 100) {
|
||||
init_star(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
uint8_t hue_map[53][3];
|
||||
for(int i = 0; i < 53; i++) {
|
||||
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
star_t stars[100];
|
||||
for(int i = 0; i < 100; i++) {
|
||||
init_star(stars[i]);
|
||||
stars[i].a = i;
|
||||
}
|
||||
|
||||
gpio_set_function(28, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(28, GPIO_OUT);
|
||||
|
||||
for(int i = 0; i < 10; i++) {
|
||||
gpio_put(28, !gpio_get(28));
|
||||
sleep_ms(100);
|
||||
}
|
||||
sleep_ms(1000);
|
||||
|
||||
gpio_put(28,true);
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
/*
|
||||
bool a_pressed = false;
|
||||
bool b_pressed = false;
|
||||
bool x_pressed = false;
|
||||
bool y_pressed = false;
|
||||
*/
|
||||
graphics.set_font("bitmap8");
|
||||
|
||||
|
||||
|
||||
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(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_UP)) {
|
||||
curve += 0.05;
|
||||
if(hue_offset > 1.0f) hue_offset = 1.0f;
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_DOWN)) {
|
||||
curve -= 0.05;
|
||||
if(hue_offset < 0.0f) hue_offset = 0.0f;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_SLEEP)) {
|
||||
animate = false;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
|
||||
speed += 0.05f;
|
||||
speed = speed >= 10.0f ? 10.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
|
||||
speed -= 0.05f;
|
||||
speed = speed <= 0.0f ? 0.0f : speed;
|
||||
animate = true;
|
||||
}
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_C)) {
|
||||
stripe_width += 0.05f;
|
||||
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_D)) {
|
||||
stripe_width -= 0.05f;
|
||||
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
|
||||
}
|
||||
|
||||
/*
|
||||
graphics.set_pen(255, 255, 255);
|
||||
float s = 0.65f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
float x = (sin(i / 74.0f) * 80.0f) * s;
|
||||
float y = (cos(i / 43.0f) * 6.0f) * s;
|
||||
text("Chester smells!", Point(x, y - 3), s, a);
|
||||
*/
|
||||
|
||||
for(int x = 0; x < 53; x++) {
|
||||
for(int y = 0; y < 11; 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));
|
||||
}
|
||||
}
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
printf("%d\n", galactic_unicorn.light());
|
||||
sleep_ms(20);
|
||||
}
|
||||
|
||||
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
|
||||
// Outputs are rgb in the range 0-255 for each channel
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
|
||||
int w = graphics.measure_text(t, s);
|
||||
p.x += (53 / 2) - (w / 2);
|
||||
p.y += (11 / 2);
|
||||
graphics.text(t, Point(p.x, p.y), -1, s, a);
|
||||
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
|
||||
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
|
||||
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
uint8_t hue_map[53][3];
|
||||
for(int i = 0; i < 53; i++) {
|
||||
from_hsv(i / 53.0f, 1.0f, 0.1f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
|
||||
}
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
graphics.set_font("sans");
|
||||
uint i = 0;
|
||||
|
||||
while(true) {
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
|
||||
i++;
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
|
||||
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
|
||||
|
||||
float x = (sin((i) / 50.0f) * 90.0f);
|
||||
float y = (cos((i) / 40.0f) * 5.0f);
|
||||
graphics.set_pen(255, 255, 255);
|
||||
text("Galactic Unicorn", Point(x, y), s, a);
|
||||
|
||||
uint8_t *p = (uint8_t *)graphics.frame_buffer;
|
||||
for(size_t i = 0; i < 53 * 11; i++) {
|
||||
int x = i % 53;
|
||||
int y = i / 53;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
printf("done\n");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
#include "okcolor.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
galactic_unicorn.init();
|
||||
|
||||
float scroll = -53.0f;
|
||||
|
||||
while(true) {
|
||||
//uint time_ms = to_ms_since_boot(get_absolute_time());
|
||||
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
|
||||
galactic_unicorn.adjust_brightness(+0.01);
|
||||
}
|
||||
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
|
||||
galactic_unicorn.adjust_brightness(-0.01);
|
||||
}
|
||||
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.25f;
|
||||
|
||||
if(scroll > width) {
|
||||
scroll = -53.0f;
|
||||
}
|
||||
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
ok_color::HSL hsl{scroll / 100.0f, 1.0f, 0.5f};
|
||||
ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl);
|
||||
graphics.set_pen(rgb.r * 255, rgb.g * 255, rgb.b * 255);
|
||||
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
|
||||
/*graphics.text(message, Point(0 - scroll + 1, 5), -1, 0.5);
|
||||
graphics.text(message, Point(0 - scroll, 5 + 1), -1, 0.5);
|
||||
graphics.text(message, Point(0 - scroll + 1, 5 + 1), -1, 0.5);*/
|
||||
/*
|
||||
for(int x = 0; x < 53; x++) {
|
||||
for(int y = 0; y < 2; y++) {
|
||||
float b = sin((x - y * 3 + int(scroll) + 100) / 3.0f);
|
||||
graphics.set_pen(180 * b, 150 * b, 0);
|
||||
graphics.pixel(Point(52 - x, y));
|
||||
graphics.pixel(Point(52 - x, y + 9));
|
||||
}
|
||||
}*/
|
||||
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
using namespace pimoroni;
|
||||
|
||||
InkyFrame inky;
|
||||
InkyFrame inky(640, 400);
|
||||
datetime_t now;
|
||||
datetime_t today;
|
||||
|
||||
|
@ -79,7 +79,7 @@ int days_in_month(datetime_t &dt)
|
|||
|
||||
void center_text(std::string message, int y, float scale = 1.0f) {
|
||||
int32_t tw = inky.measure_text(message, scale);
|
||||
inky.text(message, {(600 / 2) - (tw / 2), y}, scale);
|
||||
inky.text(message, {(inky.width / 2) - (tw / 2), y}, scale);
|
||||
}
|
||||
|
||||
void center_text(std::string message, int x, int y, int w, float scale = 1.0f) {
|
||||
|
@ -94,7 +94,7 @@ void render_calendar_view() {
|
|||
//inky.text_aspect(1.1f);
|
||||
|
||||
inky.set_pen(InkyFrame::RED);
|
||||
inky.rectangle({0, 0, width, 448});
|
||||
inky.rectangle({0, 0, width, inky.height});
|
||||
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
|
||||
|
@ -343,7 +343,7 @@ void render_calendar_entries() {
|
|||
int spacing = 5;
|
||||
int xoff = 240 + spacing;
|
||||
int yoff = spacing;
|
||||
int width = 600 - xoff - spacing;
|
||||
int width = inky.width - xoff - spacing;
|
||||
int row_height = 50;
|
||||
|
||||
//inky.text_tracking(1.0f);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
include(plasma_stick_alternating_blinkies.cmake)
|
||||
include(plasma_stick_encoder.cmake)
|
||||
include(plasma_stick_fire.cmake)
|
||||
include(plasma_stick_rainbows.cmake)
|
||||
include(plasma_stick_temperature_bme280.cmake)
|
|
@ -0,0 +1,12 @@
|
|||
set(OUTPUT_NAME plasma_stick_alternating_blinkies)
|
||||
add_executable(${OUTPUT_NAME} plasma_stick_alternating_blinkies.cpp)
|
||||
|
||||
target_link_libraries(${OUTPUT_NAME}
|
||||
pico_stdlib
|
||||
plasma_stick
|
||||
)
|
||||
|
||||
# enable usb output
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,54 @@
|
|||
#include "pico/stdlib.h"
|
||||
#include "plasma_stick.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
|
||||
/*
|
||||
This super simple example sets up two alternating colours, great for festive lights!
|
||||
*/
|
||||
|
||||
using namespace pimoroni;
|
||||
using namespace plasma;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint NUM_LEDS = 50;
|
||||
|
||||
// Pick two hues from the colour wheel (from 0-360°, try https://www.cssscript.com/demo/hsv-hsl-color-wheel-picker-reinvented/ )
|
||||
constexpr float HUE_1 = 40.0f;
|
||||
constexpr float HUE_2 = 285.0f;
|
||||
|
||||
// Set up brightness (between 0 and 1)
|
||||
constexpr float BRIGHTNESS = 0.5f;
|
||||
|
||||
// Set up speed (wait time between colour changes, in seconds)
|
||||
constexpr float SPEED = 1.0f;
|
||||
|
||||
// Set up the WS2812 / NeoPixel™ LEDs, with RGB color order to work with the LED wire that comes with Skully
|
||||
WS2812 led_strip(NUM_LEDS, pio0, 0, plasma_stick::DAT, WS2812::DEFAULT_SERIAL_FREQ, false, WS2812::COLOR_ORDER::RGB);
|
||||
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
// Start updating the LED strip
|
||||
led_strip.start();
|
||||
|
||||
while(true) {
|
||||
|
||||
for(auto i = 0u; i < NUM_LEDS; i++) {
|
||||
// the if statements below use a modulo operation to identify the even and odd numbered LEDs
|
||||
if((i % 2) == 0)
|
||||
led_strip.set_hsv(i, HUE_1 / 360, 1.0, BRIGHTNESS);
|
||||
else
|
||||
led_strip.set_hsv(i, HUE_2 / 360, 1.0, BRIGHTNESS);
|
||||
}
|
||||
sleep_ms(SPEED * 1000.0f);
|
||||
|
||||
for(auto i = 0u; i < NUM_LEDS; i++) {
|
||||
if((i % 2) == 0)
|
||||
led_strip.set_hsv(i, HUE_2 / 360, 1.0, BRIGHTNESS);
|
||||
else
|
||||
led_strip.set_hsv(i, HUE_1 / 360, 1.0, BRIGHTNESS);
|
||||
}
|
||||
sleep_ms(SPEED * 1000.0f);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
set(OUTPUT_NAME plasma_stick_encoder)
|
||||
add_executable(${OUTPUT_NAME} plasma_stick_encoder.cpp)
|
||||
|
||||
target_link_libraries(${OUTPUT_NAME}
|
||||
pico_stdlib
|
||||
plasma_stick
|
||||
breakout_encoder
|
||||
pimoroni_i2c
|
||||
)
|
||||
|
||||
# enable usb output
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,92 @@
|
|||
#include <stdio.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "plasma_stick.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "breakout_encoder.hpp"
|
||||
|
||||
/*
|
||||
Change the colour of your LEDs easily by hooking up an RGB Encoder Breakout!
|
||||
*/
|
||||
|
||||
using namespace pimoroni;
|
||||
using namespace plasma;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint NUM_LEDS = 50;
|
||||
|
||||
// Make this number bigger for more precise colour adjustments
|
||||
const uint STEPS_PER_REV = 24;
|
||||
|
||||
// Set up the WS2812 / NeoPixel™ LEDs, with RGB color order to work with the LED wire that comes with Skully
|
||||
WS2812 led_strip(NUM_LEDS, pio0, 0, plasma_stick::DAT, WS2812::DEFAULT_SERIAL_FREQ, false, WS2812::COLOR_ORDER::RGB);
|
||||
|
||||
I2C i2c(BOARD::PLASMA_STICK);
|
||||
BreakoutEncoder enc(&i2c);
|
||||
|
||||
// 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 count_changed(int16_t count) {
|
||||
printf("Count: %d\n", count);
|
||||
float h = (count % STEPS_PER_REV) / (float)STEPS_PER_REV;
|
||||
uint8_t r = 0, g = 0, b = 0;
|
||||
from_hsv(h, 1.0f, 1.0f, r, g, b);
|
||||
|
||||
// set the encoder LED colour
|
||||
enc.set_led(r, g, b);
|
||||
|
||||
for(auto i = 0u; i < NUM_LEDS; i++) {
|
||||
led_strip.set_rgb(i, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
led_strip.start();
|
||||
|
||||
if(enc.init()) {
|
||||
printf("Encoder found...\n");
|
||||
|
||||
enc.set_brightness(1.0f);
|
||||
//enc.set_direction(BreakoutEncoder::DIRECTION_CCW); // Uncomment this to flip the direction
|
||||
|
||||
count_changed(0);
|
||||
enc.clear_interrupt_flag();
|
||||
|
||||
while(true) {
|
||||
if(enc.get_interrupt_flag()) {
|
||||
int16_t count = enc.read();
|
||||
enc.clear_interrupt_flag();
|
||||
|
||||
while(count < 0) {
|
||||
count += STEPS_PER_REV;
|
||||
}
|
||||
|
||||
count_changed(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("Encoder not found :'(\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
set(OUTPUT_NAME plasma_stick_fire)
|
||||
add_executable(${OUTPUT_NAME} plasma_stick_fire.cpp)
|
||||
|
||||
target_link_libraries(${OUTPUT_NAME}
|
||||
pico_stdlib
|
||||
plasma_stick
|
||||
)
|
||||
|
||||
# enable usb output
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,38 @@
|
|||
#include "pico/stdlib.h"
|
||||
#include "plasma_stick.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
|
||||
/*
|
||||
A basic fire effect.
|
||||
*/
|
||||
|
||||
using namespace pimoroni;
|
||||
using namespace plasma;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint NUM_LEDS = 50;
|
||||
|
||||
// Set up the WS2812 / NeoPixel™ LEDs, with RGB color order to work with the LED wire that comes with Skully
|
||||
WS2812 led_strip(NUM_LEDS, pio0, 0, plasma_stick::DAT, WS2812::DEFAULT_SERIAL_FREQ, false, WS2812::COLOR_ORDER::RGB);
|
||||
|
||||
// Maps a value from one range to another
|
||||
float map(float x, float in_min, float in_max, float out_min, float out_max) {
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
// Start updating the LED strip
|
||||
led_strip.start();
|
||||
|
||||
while(true) {
|
||||
// fire effect! Random red/orange hue, full saturation, random brightness
|
||||
for(auto i = 0u; i < NUM_LEDS; ++i) {
|
||||
float hue = map((float)rand(), 0.0f, (float)RAND_MAX, 0.0, 50.0f / 360.0f);
|
||||
float brightness = map((float)rand(), 0.0f, (float)RAND_MAX, 0.0, 1.0f);
|
||||
led_strip.set_hsv(i, hue, 1.0, brightness);
|
||||
}
|
||||
sleep_ms(100);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
set(OUTPUT_NAME plasma_stick_rainbows)
|
||||
add_executable(${OUTPUT_NAME} plasma_stick_rainbows.cpp)
|
||||
|
||||
target_link_libraries(${OUTPUT_NAME}
|
||||
pico_stdlib
|
||||
plasma_stick
|
||||
)
|
||||
|
||||
# enable usb output
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,45 @@
|
|||
#include "pico/stdlib.h"
|
||||
#include "plasma_stick.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
|
||||
/*
|
||||
Make some rainbows!
|
||||
*/
|
||||
|
||||
using namespace pimoroni;
|
||||
using namespace plasma;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint NUM_LEDS = 50;
|
||||
|
||||
// The SPEED that the LEDs cycle at (1 - 255)
|
||||
const uint SPEED = 20;
|
||||
|
||||
// How many times the LEDs will be updated per second
|
||||
const uint UPDATES = 60;
|
||||
|
||||
// Set up the WS2812 / NeoPixel™ LEDs, with RGB color order to work with the LED wire that comes with Skully
|
||||
WS2812 led_strip(NUM_LEDS, pio0, 0, plasma_stick::DAT, WS2812::DEFAULT_SERIAL_FREQ, false, WS2812::COLOR_ORDER::RGB);
|
||||
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
// Start updating the LED strip
|
||||
led_strip.start(UPDATES);
|
||||
|
||||
float offset = 0.0f;
|
||||
|
||||
// Make rainbows
|
||||
while(true) {
|
||||
|
||||
offset += float(SPEED) / 2000.0f;
|
||||
|
||||
for(auto i = 0u; i < NUM_LEDS; ++i) {
|
||||
float hue = float(i) / NUM_LEDS;
|
||||
led_strip.set_hsv(i, hue + offset, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
sleep_ms(1000 / UPDATES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
set(OUTPUT_NAME plasma_stick_temperature_bme280)
|
||||
add_executable(${OUTPUT_NAME} plasma_stick_temperature_bme280.cpp)
|
||||
|
||||
target_link_libraries(${OUTPUT_NAME}
|
||||
pico_stdlib
|
||||
plasma_stick
|
||||
bme280
|
||||
pimoroni_i2c
|
||||
)
|
||||
|
||||
# enable usb output
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,66 @@
|
|||
#include <stdio.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "plasma_stick.hpp"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "bme280.hpp"
|
||||
|
||||
/*
|
||||
Reads the temperature from a BME280 breakout...
|
||||
...and changes the LED strip an appropriate colour.
|
||||
https://shop.pimoroni.com/products/bme280-breakout
|
||||
*/
|
||||
|
||||
using namespace pimoroni;
|
||||
using namespace plasma;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint NUM_LEDS = 50;
|
||||
|
||||
// Set up brightness (between 0 and 1)
|
||||
constexpr float BRIGHTNESS = 1.0f;
|
||||
|
||||
// The range of readings that we want to map to colours
|
||||
const uint MIN = 10;
|
||||
const uint MAX = 30;
|
||||
|
||||
// Pick what bits of the colour wheel to use (from 0-360°)
|
||||
// https://www.cssscript.com/demo/hsv-hsl-color-wheel-picker-reinvented/
|
||||
const uint HUE_START = 230; // blue
|
||||
const uint HUE_END = 359; // red
|
||||
|
||||
// Set up the WS2812 / NeoPixel™ LEDs, with RGB color order to work with the LED wire that comes with Skully
|
||||
WS2812 led_strip(NUM_LEDS, pio0, 0, plasma_stick::DAT, WS2812::DEFAULT_SERIAL_FREQ, false, WS2812::COLOR_ORDER::RGB);
|
||||
|
||||
I2C i2c(BOARD::PLASMA_STICK);
|
||||
BME280 bme(&i2c);
|
||||
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
led_strip.start();
|
||||
|
||||
if(bme.init()) {
|
||||
printf("BME280 found...\n");
|
||||
|
||||
while(true) {
|
||||
BME280::bme280_reading result = bme.read_forced();
|
||||
printf("%s %0.2lf deg C, %0.2lf hPa, %0.2lf%%\n", result.status == BME280_OK ? "OK" : "ER", result.temperature, result.pressure, result.humidity);
|
||||
|
||||
// calculates a colour
|
||||
float hue = HUE_START + ((float)(result.temperature - MIN) * (float)(HUE_END - HUE_START) / (float)(MAX - MIN));
|
||||
|
||||
// set the leds
|
||||
for(auto i = 0u; i < NUM_LEDS; i++) {
|
||||
led_strip.set_hsv(i, hue / 360.0f, 1.0, BRIGHTNESS);
|
||||
}
|
||||
|
||||
sleep_ms(500);
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("BME280 not found :'(\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
# Frequently Asked Questions (MicroPython) <!-- omit in toc -->
|
||||
|
||||
- [General MicroPython](#general-micropython)
|
||||
- [How do I get pirate-brand MicroPython onto my device?](#how-do-i-get-pirate-brand-micropython-onto-my-device)
|
||||
- [I flashed MicroPython to my device and it's not showing up as a drive anymore!](#i-flashed-micropython-to-my-device-and-its-not-showing-up-as-a-drive-anymore)
|
||||
- [OK, then how do I transfer files/edit code?](#ok-then-how-do-i-transfer-filesedit-code)
|
||||
- [How do I make my code run automatically?](#how-do-i-make-my-code-run-automatically)
|
||||
- [How do I do a factory reset?](#how-do-i-do-a-factory-reset)
|
||||
- [Thonny troubleshooting](#thonny-troubleshooting)
|
||||
- [I get a 'No module named 'insert Pimoroni module here' error when I try to run example code](#i-get-a-no-module-named-insert-pimoroni-module-here-error-when-i-try-to-run-example-code)
|
||||
- ['MicroPython - Raspberry Pi Pico' doesn't show up as an interpreter option](#micropython---raspberry-pi-pico-doesnt-show-up-as-an-interpreter-option)
|
||||
- [Couldn't find the device automatically?](#couldnt-find-the-device-automatically)
|
||||
- [Device is busy?](#device-is-busy)
|
||||
|
||||
## General MicroPython
|
||||
|
||||
### How do I get pirate-brand MicroPython onto my device?
|
||||
|
||||
Follow [these instructions!](setting-up-micropython.md)
|
||||
|
||||
### I flashed MicroPython to my device and it's not showing up as a drive anymore!
|
||||
|
||||
This is fine and expected - once your board restarts running MicroPython it will no longer show up as a drive.
|
||||
|
||||
### OK, then how do I transfer files/edit code?
|
||||
|
||||
To program it and to transfer files to and from it you'll need to use an interpreter, such as Thonny or Mu.
|
||||
|
||||
- [Download Thonny](https://thonny.org/)
|
||||
- [Download Mu](https://codewith.mu/)
|
||||
|
||||
If you're hardcore, you can also transfer files to boards running MicroPython using command line tools, like `mpremote`.
|
||||
|
||||
- https://docs.micropython.org/en/latest/reference/mpremote.html
|
||||
|
||||
To view the files on your device in Thonny you'll need to have the files window open - if you can't see it, go to 'View > Files'. Right click on a file and select 'Upload to /' or 'Download to /' to copy it to or from your board.
|
||||
|
||||
### How do I make my code run automatically?
|
||||
|
||||
Save your code as `main.py` to run it automatically when your board's powered up.
|
||||
|
||||
### How do I do a factory reset?
|
||||
|
||||
If you need to delete all your programs from your board's flash memory and start again from scratch, you can do that by downloading [this special .uf2 file](https://www.raspberrypi.org/documentation/pico/getting-started/static/6f6f31460c258138bd33cc96ddd76b91/flash_nuke.uf2) and copying it to your board whilst it's in bootloader mode (hold down 'bootsel', and tap 'reset'/plug in the USB cable). Once you've done that, you'll need to [install MicroPython](setting-up-micropython.md) again.
|
||||
|
||||
You may also find clearing the flash to be useful if you encounter problems after upgrading to the newest version of MicroPython - just make sure you save any code you've been working on to your computer first!
|
||||
|
||||
## Thonny troubleshooting
|
||||
|
||||
Having trouble getting Thonny talking to your MicroPython board? Here's some things that might help!
|
||||
|
||||
### I get a 'No module named 'insert Pimoroni module here' error when I try to run example code
|
||||
|
||||
First of all, check you downloaded and installed Pirate brand/our custom MicroPython. Standard MicroPython won't include our modules.
|
||||
|
||||
This error can also happen if you're trying to run code with Thonny's built in Python interpreter, instead of Pico/RP2040 flavoured MicroPython. Make sure you have 'MicroPython - Raspberry Pi Pico' selected as your interpreter in the box at the bottom right.
|
||||
|
||||
### 'MicroPython - Raspberry Pi Pico' doesn't show up as an interpreter option
|
||||
|
||||
Check you're running a recent version of Thonny - very old versions won't know what a Pico is. If you're using a Linux computer, bear in mind that some package managers don't install the most recent versions, best to download it from the [Thonny website](https://thonny.org/).
|
||||
|
||||
### Couldn't find the device automatically?
|
||||
|
||||
Some versions of Thonny seem to have problems finding devices that are running brand new Micropython v1.19. You might also run into this error if you have multiple RP2040 boards connected to your computer. To manually specify which port your device is using, click on **MicroPython (Raspberry Pi Pico)** at the bottom right, and select **Configure Interpreter**. Select your device under 'Port' - here, our board was showing up as 'COM20'.
|
||||
|
||||
![Selecting the correct COM port](couldnt_find_device_automatically.png)
|
||||
|
||||
If you're running a Windows computer, you should be able to see what COM ports your devices are using in Device Manager.
|
||||
|
||||
### Device is busy?
|
||||
|
||||
This error suggests your board is in the middle of doing something and you'll need to interrupt it by pressing the stop button in Thonny. If that doesn't get it talking, try resetting the board or disconnecting and reconnecting the USB cable. Restarting Thonny can also sometimes help!
|
||||
|
||||
If none of that helps, you might have a malfunctioning `main.py` - you can clear everything that's in the board's flash memory by following [these instructions](#how-do-i-get-micropython-onto-my-device). Note that this will delete the all the code saved on your device, so you should only do it as a last resort!
|
||||
|
|
@ -0,0 +1 @@
|
|||
include(galactic_unicorn.cmake)
|
|
@ -0,0 +1,254 @@
|
|||
# Galactic Unicorn <!-- omit in toc -->
|
||||
|
||||
Galactic Unicorn offers 53x11 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
|
||||
|
||||
## These are not your everyday RGB LEDs!
|
||||
|
||||
Internally Galactic Unicorn applies gamma correction to the supplied image data and updates the display with 14-bit precision resulting in extremely linear visual output - including at the low end.
|
||||
|
||||
The display is refreshed around 300 times per second (300fps!) allowing for rock solid stability even when being filmed, no smearing or flickering even when in motion.
|
||||
|
||||
No strobing or brightness stepping here folks - it's the perfect backdrop for your tricked out streaming setup!
|
||||
|
||||
## Getting started
|
||||
|
||||
The Galactic Unicorn library provides a collection of methods that allow you to easily access all of the features on the board.
|
||||
|
||||
Drawing is primarily handled via our [PicoGraphics](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_graphics) library which provides a comprehensive selection of drawing methods - once your drawing work is complete you pass the PicoGraphics object to Galactic Unicorn to have it displayed on the screen.
|
||||
|
||||
- [Example Program](#example-program)
|
||||
- [Interleaved framebuffer](#interleaved-framebuffer)
|
||||
- [Function Reference](#function-reference)
|
||||
- [System state](#system-state)
|
||||
- [`void init()`](#void-init)
|
||||
- [`void set_brightness(float value)`](#void-set_brightnessfloat-value)
|
||||
- [`float get_brightness()`](#float-get_brightness)
|
||||
- [`void adjust_brightness(float delta)`](#void-adjust_brightnessfloat-delta)
|
||||
- [`void set_volume(float value)`](#void-set_volumefloat-value)
|
||||
- [`float get_volume()`](#float-get_volume)
|
||||
- [`void adjust_volume(float delta)`](#void-adjust_volumefloat-delta)
|
||||
- [`uint16_t light()`](#uint16_t-light)
|
||||
- [`bool is_pressed(uint8_t button)`](#bool-is_presseduint8_t-button)
|
||||
- [Drawing](#drawing)
|
||||
- [`void update(PicoGraphics *graphics)`](#void-updatepicographics-graphics)
|
||||
- [`void clear()`](#void-clear)
|
||||
- [`void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b)`](#void-set_pixelint-x-int-y-uint8_t-r-uint8_t-g-uint8_t-b)
|
||||
- [Audio](#audio)
|
||||
- [`void play_sample(uint8_t *data, uint32_t length)`](#void-play_sampleuint8_t-data-uint32_t-length)
|
||||
- [`AudioChannel& synth_channel(uint channel)`](#audiochannel-synth_channeluint-channel)
|
||||
- [`void play_synth()`](#void-play_synth)
|
||||
- [`void stop_playing()`](#void-stop_playing)
|
||||
- [Constants](#constants)
|
||||
- [`WIDTH` & `HEIGHT`](#width--height)
|
||||
|
||||
# Example Program
|
||||
|
||||
The following example shows how to scroll a simple message across the display.
|
||||
|
||||
```c++
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
#include "galactic_unicorn.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
// create a PicoGraphics framebuffer to draw into
|
||||
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
|
||||
|
||||
// create our GalacticUnicorn object
|
||||
GalacticUnicorn galactic_unicorn;
|
||||
|
||||
// message to scroll
|
||||
std::string message = "Pirate. Monkey. Robot. Ninja.";
|
||||
|
||||
int main() {
|
||||
|
||||
stdio_init_all();
|
||||
|
||||
// initialise the GalacticUnicorn object
|
||||
galactic_unicorn.init();
|
||||
|
||||
// start position for scrolling (off the side of the display)
|
||||
float scroll = -53.0f;
|
||||
|
||||
while(true) {
|
||||
// determine the scroll position of the text
|
||||
int width = graphics.measure_text(message, 1);
|
||||
scroll += 0.25f;
|
||||
if(scroll > width) {
|
||||
scroll = -53.0f;
|
||||
}
|
||||
|
||||
// clear the graphics object
|
||||
graphics.set_pen(0, 0, 0);
|
||||
graphics.clear();
|
||||
|
||||
// draw the text
|
||||
graphics.set_pen(255, 255, 0); // a nice yellow
|
||||
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
|
||||
|
||||
// update the display
|
||||
galactic_unicorn.update(&graphics);
|
||||
|
||||
sleep_ms(10);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
# Interleaved framebuffer
|
||||
|
||||
Galactic Unicorn takes advantage of the RP2040's PIOs to drive screen updates - this is what gives it the performance it needs to render with 14-bit precision at over 300 frames per second.
|
||||
|
||||
The PIO is a powerful, but limited, tool. It has no way to access memory at random and minimal support for decision making and branching. All it can really do is process a stream of data/instructions in order.
|
||||
|
||||
This means that we need to be clever about the way we pass data into the PIO program, the information needs to be delivered in the exact order that the PIO will need to process it. To achieve this we "interleave" our framebuffer - each frame of BCM data is passed one after another with values for the current row, pixel count, and timing inserted as needed:
|
||||
|
||||
row 0 data:
|
||||
for each bcd frame:
|
||||
bit : data
|
||||
0: 00110110 // row pixel count (minus one)
|
||||
1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
|
||||
54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
|
||||
56: xxxxrrrr // row select bits
|
||||
57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
|
||||
|
||||
row 1 data:
|
||||
...
|
||||
|
||||
If you're working with our library then you don't need to worry about any of these details, they are handled for you.
|
||||
|
||||
# Function Reference
|
||||
|
||||
## System state
|
||||
|
||||
### `void init()`
|
||||
|
||||
Initialise the Galactic Unicorn hardware, interleaved framebuffer, and PIO programs. This function must be called before attempting to do anything else with Galactic Unicorn.
|
||||
|
||||
### `void set_brightness(float value)`
|
||||
|
||||
Set the brightness - `value` is supplied as a floating point value between `0..1`.
|
||||
|
||||
### `float get_brightness()`
|
||||
|
||||
Returns the current brightness as a value between `0..1`.
|
||||
|
||||
### `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..1`).
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
galactic.set_brightness(0.5f);
|
||||
galactic.adjust_brightness(0.1f); // brightness is now 0.6
|
||||
galactic.adjust_brightness(0.7f); // brightness is now 1.0
|
||||
galactic.adjust_brightness(-0.2f); // brightness is now 0.8
|
||||
```
|
||||
|
||||
### `void set_volume(float value)`
|
||||
|
||||
Set the volume - `value` is supplied as a floating point value between `0..1`.
|
||||
|
||||
### `float get_volume()`
|
||||
|
||||
Returns the current volume as a value between `0..1`.
|
||||
|
||||
### `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..1`).
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
galactic.set_volume(0.5f);
|
||||
galactic.adjust_volume(0.1f); // volume is now 0.6
|
||||
galactic.adjust_volume(0.7f); // volume is now 1.0
|
||||
galactic.adjust_volume(-0.2f); // volume is now 0.8
|
||||
```
|
||||
|
||||
### `uint16_t light()`
|
||||
|
||||
Get the current value seen by the onboard light sensor as a value between `0...4096`.
|
||||
|
||||
### `bool is_pressed(uint8_t button)`
|
||||
|
||||
Returns true if the requested `button` is currently pressed.
|
||||
|
||||
There are a set of constants on the GalacticUnicorn class that represent each of the buttons. The brightness, sleep, and volume buttons are not tied to hardware functions (they are implemented entirely in software) so can also be used for user functions if preferred.
|
||||
|
||||
```c++
|
||||
static const uint8_t SWITCH_A = 0;
|
||||
static const uint8_t SWITCH_B = 1;
|
||||
static const uint8_t SWITCH_C = 3;
|
||||
static const uint8_t SWITCH_D = 6;
|
||||
static const uint8_t SWITCH_SLEEP = 27;
|
||||
static const uint8_t SWITCH_VOLUME_UP = 7;
|
||||
static const uint8_t SWITCH_VOLUME_DOWN = 8;
|
||||
static const uint8_t SWITCH_BRIGHTNESS_UP = 21;
|
||||
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 26;
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
while(!galactic.is_pressed(GalacticUnicorn.SWITCH_A)) {
|
||||
// wait for switch A to be pressed
|
||||
}
|
||||
printf("We did it! We pressed switch A! Heck yeah!");
|
||||
```
|
||||
|
||||
## Drawing
|
||||
|
||||
### `void update(PicoGraphics *graphics)`
|
||||
|
||||
**This is our recommended way to update the image on Galactic Unicorn.** The PicoGraphics library provides a collection of powerful drawing methods to make things simple.
|
||||
|
||||
The image on the PicoGraphics object provided is copied to the interleaved framebuffer with gamma correction applied.
|
||||
|
||||
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 `update()` methods mentioned below.
|
||||
|
||||
### `void clear()`
|
||||
|
||||
Clear the contents of the interleaved framebuffer. 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 dispaly 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. Pixel coordinates go from `0..52` along the `x` axis and from `0..10` on the `y` axis. Colour values are specified as a `0..255` RGB triplet - the supplied colour will be gamma corrected automatically.
|
||||
|
||||
## 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 envelopes. It provides a similar set of functionality to the classic SID chip in the Commodore 64.
|
||||
|
||||
### `void play_sample(uint8_t *data, uint32_t length)`
|
||||
|
||||
Play the provided 16-bit audio sample. `data` must point to a buffer that contains 16-bit PCM data and `length` must be the number of samples.
|
||||
|
||||
### `AudioChannel& synth_channel(uint channel)`
|
||||
|
||||
Gets an `AudioChannel` object which can then be configured with voice, ADSR envelope, etc.
|
||||
|
||||
### `void play_synth()`
|
||||
|
||||
Start the synth playing.
|
||||
|
||||
### `void stop_playing()`
|
||||
|
||||
Stops any currently playing audio.
|
||||
|
||||
|
||||
## Constants
|
||||
|
||||
### `WIDTH` & `HEIGHT`
|
||||
|
||||
The width and height of Galactic Unicorn are available in constants `WIDTH` and `HEIGHT`.
|
||||
|
||||
For example:
|
||||
|
||||
```c++
|
||||
int num_pixels = GalacticUnicorn.WIDTH * GalacticUnicorn.HEIGHT;
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
;
|
||||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
|
||||
; Transmit a mono or stereo I2S audio stream as stereo
|
||||
; This is 16 bits per sample; can be altered by modifying the "set" params,
|
||||
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
|
||||
;
|
||||
; Autopull must be enabled, with threshold set to 32.
|
||||
; Since I2S is MSB-first, shift direction should be to left.
|
||||
; Hence the format of the FIFO word is:
|
||||
;
|
||||
; | 31 : 16 | 15 : 0 |
|
||||
; | sample ws=0 | sample ws=1 |
|
||||
;
|
||||
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
|
||||
; Fractional divider will probably be needed to get correct bit clock period,
|
||||
; but for common syslck freqs this should still give a constant word select period.
|
||||
;
|
||||
; One output pin is used for the data output.
|
||||
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
|
||||
|
||||
; Send 16 bit words to the PIO for mono, 32 bit words for stereo
|
||||
|
||||
.program audio_i2s
|
||||
.side_set 2
|
||||
|
||||
; /--- LRCLK
|
||||
; |/-- BCLK
|
||||
bitloop1: ; ||
|
||||
out pins, 1 side 0b10
|
||||
jmp x-- bitloop1 side 0b11
|
||||
out pins, 1 side 0b00
|
||||
set x, 14 side 0b01
|
||||
|
||||
bitloop0:
|
||||
out pins, 1 side 0b00
|
||||
jmp x-- bitloop0 side 0b01
|
||||
out pins, 1 side 0b10
|
||||
public entry_point:
|
||||
set x, 14 side 0b11
|
||||
|
||||
% c-sdk {
|
||||
|
||||
static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
|
||||
pio_sm_config sm_config = audio_i2s_program_get_default_config(offset);
|
||||
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, 32);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // clear pins
|
||||
|
||||
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point));
|
||||
}
|
||||
|
||||
%}
|
|
@ -0,0 +1,15 @@
|
|||
add_library(galactic_unicorn INTERFACE)
|
||||
|
||||
pico_generate_pio_header(galactic_unicorn ${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.pio)
|
||||
pico_generate_pio_header(galactic_unicorn ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pio)
|
||||
|
||||
|
||||
target_sources(galactic_unicorn INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/galactic_unicorn.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../pico_synth/pico_synth.cpp
|
||||
)
|
||||
|
||||
target_include_directories(galactic_unicorn INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(galactic_unicorn INTERFACE pico_stdlib 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 "galactic_unicorn.pio.h"
|
||||
#include "audio_i2s.pio.h"
|
||||
|
||||
#include "galactic_unicorn.hpp"
|
||||
|
||||
// pixel data is stored as a stream of bits delivered in the
|
||||
// order the PIO needs to manage the shift registers, row
|
||||
// selects, delays, and latching/blanking
|
||||
//
|
||||
// the 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: 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)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
static uint16_t r_gamma_lut[256] = {0};
|
||||
static uint16_t g_gamma_lut[256] = {0};
|
||||
static uint16_t b_gamma_lut[256] = {0};
|
||||
|
||||
static uint32_t dma_channel;
|
||||
static uint32_t dma_ctrl_channel;
|
||||
static uint32_t audio_dma_channel;
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
GalacticUnicorn* GalacticUnicorn::unicorn = nullptr;
|
||||
PIO GalacticUnicorn::bitstream_pio = pio0;
|
||||
uint GalacticUnicorn::bitstream_sm = 0;
|
||||
uint GalacticUnicorn::bitstream_sm_offset = 0;
|
||||
PIO GalacticUnicorn::audio_pio = pio0;
|
||||
uint GalacticUnicorn::audio_sm = 0;
|
||||
uint GalacticUnicorn::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 GalacticUnicorn::dma_complete() {
|
||||
if(unicorn != nullptr && dma_channel_get_irq0_status(audio_dma_channel)) {
|
||||
unicorn->next_audio_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
GalacticUnicorn::~GalacticUnicorn() {
|
||||
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, &galactic_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 GalacticUnicorn::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 GalacticUnicorn::light() {
|
||||
adc_select_input(2);
|
||||
return adc_read();
|
||||
}
|
||||
|
||||
void GalacticUnicorn::init() {
|
||||
|
||||
if(unicorn != nullptr) {
|
||||
// Tear down the old GU instance's hardware resources
|
||||
partial_teardown();
|
||||
}
|
||||
|
||||
|
||||
// create 14-bit gamma luts
|
||||
for(uint16_t v = 0; v < 256; v++) {
|
||||
// gamma correct the provided 0-255 brightness value onto a
|
||||
// 0-65535 range for the pwm counter
|
||||
float r_gamma = 1.8f;
|
||||
r_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, r_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
|
||||
float g_gamma = 1.8f;
|
||||
g_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, g_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
|
||||
float b_gamma = 1.8f;
|
||||
b_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, b_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
|
||||
}
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 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)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
|
||||
// initialise the bcd timing values and row selects in the bitstream
|
||||
for(uint8_t row = 0; row < HEIGHT; row++) {
|
||||
for(uint8_t frame = 0; frame < 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] = WIDTH - 1; // row pixel count
|
||||
p[56] = row; // row select
|
||||
|
||||
// set the number of bcd ticks for this frame
|
||||
uint32_t bcd_ticks = (1 << frame);
|
||||
p[57] = (bcd_ticks & 0xff) >> 0;
|
||||
p[58] = (bcd_ticks & 0xff00) >> 8;
|
||||
p[59] = (bcd_ticks & 0xff0000) >> 16;
|
||||
}
|
||||
}
|
||||
|
||||
// setup light sensor adc
|
||||
adc_init();
|
||||
adc_gpio_init(LIGHT_SENSOR);
|
||||
|
||||
gpio_init(COLUMN_CLOCK); gpio_set_dir(COLUMN_CLOCK, GPIO_OUT); gpio_put(COLUMN_CLOCK, false);
|
||||
gpio_init(COLUMN_DATA); gpio_set_dir(COLUMN_DATA, GPIO_OUT); gpio_put(COLUMN_DATA, false);
|
||||
gpio_init(COLUMN_LATCH); gpio_set_dir(COLUMN_LATCH, GPIO_OUT); gpio_put(COLUMN_LATCH, false);
|
||||
gpio_init(COLUMN_BLANK); gpio_set_dir(COLUMN_BLANK, GPIO_OUT); gpio_put(COLUMN_BLANK, true);
|
||||
|
||||
// initialise the row select, and set them to a non-visible row to avoid flashes during setup
|
||||
gpio_init(ROW_BIT_0); gpio_set_dir(ROW_BIT_0, GPIO_OUT); gpio_put(ROW_BIT_0, true);
|
||||
gpio_init(ROW_BIT_1); gpio_set_dir(ROW_BIT_1, GPIO_OUT); gpio_put(ROW_BIT_1, true);
|
||||
gpio_init(ROW_BIT_2); gpio_set_dir(ROW_BIT_2, GPIO_OUT); gpio_put(ROW_BIT_2, true);
|
||||
gpio_init(ROW_BIT_3); gpio_set_dir(ROW_BIT_3, GPIO_OUT); gpio_put(ROW_BIT_3, true);
|
||||
|
||||
sleep_ms(100);
|
||||
|
||||
// configure full output current in register 2
|
||||
|
||||
uint16_t reg1 = 0b1111111111001110;
|
||||
|
||||
// clock the register value to the first 9 driver chips
|
||||
for(int j = 0; j < 9; 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, &galactic_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 = galactic_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 GalacticUnicorn::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 GalacticUnicorn::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 GalacticUnicorn::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 GalacticUnicorn::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 GalacticUnicorn::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 GalacticUnicorn::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 GalacticUnicorn::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& GalacticUnicorn::synth_channel(uint channel) {
|
||||
assert(channel < PicoSynth::CHANNEL_COUNT);
|
||||
return synth.channels[channel];
|
||||
}
|
||||
|
||||
void GalacticUnicorn::set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
|
||||
|
||||
// make those coordinates sane
|
||||
x = (WIDTH - 1) - x;
|
||||
y = (HEIGHT - 1) - y;
|
||||
|
||||
r = (r * this->brightness) >> 8;
|
||||
g = (g * this->brightness) >> 8;
|
||||
b = (b * this->brightness) >> 8;
|
||||
|
||||
uint16_t gamma_r = r_gamma_lut[r];
|
||||
uint16_t gamma_g = g_gamma_lut[g];
|
||||
uint16_t gamma_b = b_gamma_lut[b];
|
||||
|
||||
// for each row:
|
||||
// for each bcd frame:
|
||||
// 0: 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)
|
||||
//
|
||||
// .. and back to the start
|
||||
|
||||
// set the appropriate bits in the separate bcd frames
|
||||
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
|
||||
uint8_t *p = &bitstream[y * ROW_BYTES + (BCD_FRAME_BYTES * frame) + 1 + x];
|
||||
|
||||
uint8_t red_bit = gamma_r & 0b1;
|
||||
uint8_t green_bit = gamma_g & 0b1;
|
||||
uint8_t blue_bit = gamma_b & 0b1;
|
||||
|
||||
*p = (blue_bit << 0) | (green_bit << 1) | (red_bit << 2);
|
||||
|
||||
gamma_r >>= 1;
|
||||
gamma_g >>= 1;
|
||||
gamma_b >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void GalacticUnicorn::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 GalacticUnicorn::get_brightness() {
|
||||
return this->brightness / 255.0f;
|
||||
}
|
||||
|
||||
void GalacticUnicorn::adjust_brightness(float delta) {
|
||||
this->set_brightness(this->get_brightness() + delta);
|
||||
}
|
||||
|
||||
void GalacticUnicorn::set_volume(float value) {
|
||||
value = value < 0.0f ? 0.0f : value;
|
||||
value = value > 1.0f ? 1.0f : value;
|
||||
this->volume = floor(value * 255.0f);
|
||||
}
|
||||
|
||||
float GalacticUnicorn::get_volume() {
|
||||
return this->volume / 255.0f;
|
||||
}
|
||||
|
||||
void GalacticUnicorn::adjust_volume(float delta) {
|
||||
this->set_volume(this->get_volume() + delta);
|
||||
}
|
||||
|
||||
void GalacticUnicorn::update(PicoGraphics *graphics) {
|
||||
if(unicorn == this) {
|
||||
if(graphics->pen_type == PicoGraphics::PEN_RGB888) {
|
||||
uint32_t *p = (uint32_t *)graphics->frame_buffer;
|
||||
for(size_t j = 0; j < 53 * 11; j++) {
|
||||
int x = j % 53;
|
||||
int y = j / 53;
|
||||
|
||||
uint32_t col = *p;
|
||||
uint8_t r = (col & 0xff0000) >> 16;
|
||||
uint8_t g = (col & 0x00ff00) >> 8;
|
||||
uint8_t b = (col & 0x0000ff) >> 0;
|
||||
p++;
|
||||
|
||||
set_pixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
else if(graphics->pen_type == PicoGraphics::PEN_RGB565) {
|
||||
uint16_t *p = (uint16_t *)graphics->frame_buffer;
|
||||
for(size_t j = 0; j < 53 * 11; j++) {
|
||||
int x = j % 53;
|
||||
int y = j / 53;
|
||||
|
||||
uint16_t col = __builtin_bswap16(*p);
|
||||
uint8_t r = (col & 0b1111100000000000) >> 8;
|
||||
uint8_t g = (col & 0b0000011111100000) >> 3;
|
||||
uint8_t b = (col & 0b0000000000011111) << 3;
|
||||
p++;
|
||||
|
||||
set_pixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GalacticUnicorn::is_pressed(uint8_t button) {
|
||||
return !gpio_get(button);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
#pragma once
|
||||
|
||||
#include "hardware/pio.h"
|
||||
#include "pico_graphics.hpp"
|
||||
#include "../pico_synth/pico_synth.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
class GalacticUnicorn {
|
||||
public:
|
||||
static const int WIDTH = 53;
|
||||
static const int HEIGHT = 11;
|
||||
|
||||
// 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 = 11;
|
||||
static const uint32_t BCD_FRAME_COUNT = 14;
|
||||
static const uint32_t BCD_FRAME_BYTES = 60;
|
||||
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 GalacticUnicorn* 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:
|
||||
~GalacticUnicorn();
|
||||
|
||||
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);
|
||||
|
||||
private:
|
||||
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
|
||||
public:
|
||||
|
||||
uint16_t light();
|
||||
|
||||
bool is_pressed(uint8_t button);
|
||||
|
||||
void play_sample(uint8_t *data, uint32_t length);
|
||||
void play_synth();
|
||||
void stop_playing();
|
||||
AudioChannel& synth_channel(uint channel);
|
||||
|
||||
private:
|
||||
void partial_teardown();
|
||||
void dma_safe_abort(uint channel);
|
||||
void next_audio_sequence();
|
||||
void populate_next_synth();
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
.program galactic_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: 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)
|
||||
;
|
||||
; .. and back to the start
|
||||
|
||||
|
||||
.wrap_target
|
||||
|
||||
; loop over row pixels
|
||||
out y, 8 ; get row pixel count (minus 1 because test is pre decrement)
|
||||
pixels:
|
||||
|
||||
; red bit
|
||||
out x, 1 side 0 [1] ; pull in blue bit from OSR into register x, clear clock
|
||||
set pins, 0b100 ; clear data bit, blank high
|
||||
jmp !x endb ; if bit was zero jump
|
||||
set pins, 0b101 ; set data bit, blank high
|
||||
endb:
|
||||
nop side 1 [2] ; clock in bit
|
||||
|
||||
; green bit
|
||||
out x, 1 side 0 [1] ; pull in green bit from OSR into register X, clear clock
|
||||
set pins, 0b100 ; clear data bit, blank high
|
||||
jmp !x endg ; if bit was zero jump
|
||||
set pins, 0b101 ; set data bit, blank high
|
||||
endg:
|
||||
nop side 1 [2] ; clock in bit
|
||||
|
||||
; blue bit
|
||||
out x, 1 side 0 [1] ; pull in red bit from OSR into register X, clear clock
|
||||
set pins, 0b100 ; clear data bit, blank high
|
||||
jmp !x endr ; if bit was zero jump
|
||||
set pins, 0b101 ; set data bit, blank high
|
||||
endr:
|
||||
out null, 5 side 1 [2] ; clock in bit
|
||||
|
||||
;out null, 5 side 0 ; discard the five dummy bits for this pixel
|
||||
|
||||
jmp y-- pixels
|
||||
|
||||
out null, 16 ; discard dummy bytes
|
||||
|
||||
out pins, 8 ; output row select
|
||||
|
||||
set pins, 0b110 [5] ; latch high, blank high
|
||||
set pins, 0b000 ; blank low (enable output)
|
||||
|
||||
; loop over bcd delay period
|
||||
out y, 24 ; get bcd delay counter value
|
||||
bcd_delay:
|
||||
jmp y-- bcd_delay
|
||||
|
||||
set pins 0b100 ; blank high (disable output)
|
||||
|
||||
.wrap
|
|
@ -142,7 +142,7 @@ namespace pimoroni {
|
|||
|
||||
// Display an image that fills the screen (286*128)
|
||||
void InkyFrame::image(const uint8_t* data) {
|
||||
image(data, WIDTH, 0, 0, WIDTH, HEIGHT, 0, 0);
|
||||
image(data, width, 0, 0, width, height, 0, 0);
|
||||
}
|
||||
|
||||
// Display an image smaller than the screen (sw*sh) at dx, dy
|
||||
|
|
|
@ -89,14 +89,26 @@ namespace pimoroni {
|
|||
I2C i2c;
|
||||
PCF85063A rtc;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
|
||||
[[deprecated("Use instance variable width.")]]
|
||||
static const int WIDTH = 600;
|
||||
[[deprecated("Use instance variable height.")]]
|
||||
static const int HEIGHT = 448;
|
||||
|
||||
InkyFrame() :
|
||||
PicoGraphics_Pen3Bit(WIDTH, HEIGHT, nullptr),
|
||||
uc8159(WIDTH, HEIGHT, {spi0, EINK_CS, CLK, MOSI, PIN_UNUSED, EINK_DC, PIN_UNUSED}),
|
||||
// Default 5.7" constructor
|
||||
InkyFrame() : InkyFrame(600, 448) {};
|
||||
|
||||
// 600x448 for 5.7"
|
||||
// 640x400 for 4.0"
|
||||
InkyFrame(int width, int height) :
|
||||
PicoGraphics_Pen3Bit(width, height, nullptr),
|
||||
uc8159(width, height, {spi0, EINK_CS, CLK, MOSI, PIN_UNUSED, EINK_DC, PIN_UNUSED}),
|
||||
i2c(4, 5),
|
||||
rtc(&i2c) {
|
||||
rtc(&i2c),
|
||||
width(width),
|
||||
height(height) {
|
||||
}
|
||||
|
||||
void init();
|
||||
|
|
|
@ -276,8 +276,6 @@ namespace pimoroni {
|
|||
void PicoGraphics::line(Point p1, Point p2) {
|
||||
// fast horizontal line
|
||||
if(p1.y == p2.y) {
|
||||
p1 = p1.clamp(clip);
|
||||
p2 = p2.clamp(clip);
|
||||
int32_t start = std::min(p1.x, p2.x);
|
||||
int32_t end = std::max(p1.x, p2.x);
|
||||
pixel_span(Point(start, p1.y), end - start);
|
||||
|
@ -286,13 +284,11 @@ namespace pimoroni {
|
|||
|
||||
// fast vertical line
|
||||
if(p1.x == p2.x) {
|
||||
p1 = p1.clamp(clip);
|
||||
p2 = p2.clamp(clip);
|
||||
int32_t start = std::min(p1.y, p2.y);
|
||||
int32_t length = std::max(p1.y, p2.y) - start;
|
||||
Point dest(p1.x, start);
|
||||
while(length--) {
|
||||
set_pixel(dest);
|
||||
pixel(dest);
|
||||
dest.y++;
|
||||
}
|
||||
return;
|
||||
|
@ -314,7 +310,7 @@ namespace pimoroni {
|
|||
int32_t y = p1.y << 16;
|
||||
while(s--) {
|
||||
Point p(x, y >> 16);
|
||||
if(clip.contains(p)) set_pixel(p);
|
||||
pixel(p);
|
||||
y += sy;
|
||||
x += sx;
|
||||
}
|
||||
|
@ -327,7 +323,7 @@ namespace pimoroni {
|
|||
int32_t x = p1.x << 16;
|
||||
while(s--) {
|
||||
Point p(x >> 16, y);
|
||||
if(clip.contains(p)) set_pixel(p);
|
||||
pixel(p);
|
||||
y += sy;
|
||||
x += sx;
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ namespace pimoroni {
|
|||
}
|
||||
}
|
||||
void PicoGraphics_PenRGB888::set_pen(uint c) {
|
||||
color = RGB(c, c, c).to_rgb888();
|
||||
color = c;
|
||||
}
|
||||
void PicoGraphics_PenRGB888::set_pen(uint8_t r, uint8_t g, uint8_t b) {
|
||||
color = RGB(r, g, b).to_rgb888();
|
||||
src_color = {r, g, b};
|
||||
color = src_color.to_rgb888();
|
||||
}
|
||||
int PicoGraphics_PenRGB888::create_pen(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return RGB(r, g, b).to_rgb888();
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
#include "pico_synth.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
uint32_t prng_xorshift_state = 0x32B71700;
|
||||
|
||||
uint32_t prng_xorshift_next() {
|
||||
uint32_t x = prng_xorshift_state;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
prng_xorshift_state = x;
|
||||
return x;
|
||||
}
|
||||
|
||||
int32_t prng_normal() {
|
||||
// rough approximation of a normal distribution
|
||||
uint32_t r0 = prng_xorshift_next();
|
||||
uint32_t r1 = prng_xorshift_next();
|
||||
uint32_t n = ((r0 & 0xffff) + (r1 & 0xffff) + (r0 >> 16) + (r1 >> 16)) / 2;
|
||||
return n - 0xffff;
|
||||
}
|
||||
|
||||
|
||||
void AudioChannel::trigger_attack() {
|
||||
adsr_frame = 0;
|
||||
adsr_phase = ADSRPhase::ATTACK;
|
||||
adsr_end_frame = (attack_ms * sample_rate) / 1000;
|
||||
adsr_step = (int32_t(0xffffff) - int32_t(adsr_level)) / int32_t(adsr_end_frame);
|
||||
}
|
||||
|
||||
void AudioChannel::trigger_decay() {
|
||||
adsr_frame = 0;
|
||||
adsr_phase = ADSRPhase::DECAY;
|
||||
adsr_end_frame = (decay_ms * sample_rate) / 1000;
|
||||
adsr_step = (int32_t(sustain << 8) - int32_t(adsr_level)) / int32_t(adsr_end_frame);
|
||||
}
|
||||
|
||||
void AudioChannel::trigger_sustain() {
|
||||
adsr_frame = 0;
|
||||
adsr_phase = ADSRPhase::SUSTAIN;
|
||||
adsr_end_frame = 0;
|
||||
adsr_step = 0;
|
||||
}
|
||||
|
||||
void AudioChannel::trigger_release() {
|
||||
adsr_frame = 0;
|
||||
adsr_phase = ADSRPhase::RELEASE;
|
||||
adsr_end_frame = (release_ms * sample_rate) / 1000;
|
||||
adsr_step = (int32_t(0) - int32_t(adsr_level)) / int32_t(adsr_end_frame);
|
||||
}
|
||||
|
||||
void AudioChannel::off() {
|
||||
adsr_frame = 0;
|
||||
adsr_phase = ADSRPhase::OFF;
|
||||
adsr_step = 0;
|
||||
}
|
||||
|
||||
void AudioChannel::restore() {
|
||||
// Put all the parameters back to their initial values
|
||||
waveforms = 0;
|
||||
frequency = 660;
|
||||
volume = 0xffff;
|
||||
|
||||
attack_ms = 2;
|
||||
decay_ms = 6;
|
||||
sustain = 0xffff;
|
||||
release_ms = 1;
|
||||
pulse_width = 0x7fff;
|
||||
noise = 0;
|
||||
}
|
||||
|
||||
bool PicoSynth::is_audio_playing() {
|
||||
if(volume == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_channel_playing = false;
|
||||
for(uint c = 0; c < CHANNEL_COUNT; c++) {
|
||||
if(channels[c].volume > 0 && channels[c].adsr_phase != ADSRPhase::OFF) {
|
||||
any_channel_playing = true;
|
||||
}
|
||||
}
|
||||
|
||||
return any_channel_playing;
|
||||
}
|
||||
|
||||
int16_t PicoSynth::get_audio_frame() {
|
||||
int32_t sample = 0; // used to combine channel output
|
||||
|
||||
for(uint c = 0; c < CHANNEL_COUNT; c++) {
|
||||
|
||||
auto &channel = channels[c];
|
||||
|
||||
// increment the waveform position counter. this provides an
|
||||
// Q16 fixed point value representing how far through
|
||||
// the current waveform we are
|
||||
channel.waveform_offset += ((channel.frequency * 256) << 8) / sample_rate;
|
||||
|
||||
if(channel.adsr_phase == ADSRPhase::OFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if((channel.adsr_frame >= channel.adsr_end_frame) && (channel.adsr_phase != ADSRPhase::SUSTAIN)) {
|
||||
switch (channel.adsr_phase) {
|
||||
case ADSRPhase::ATTACK:
|
||||
channel.trigger_decay();
|
||||
break;
|
||||
case ADSRPhase::DECAY:
|
||||
channel.trigger_sustain();
|
||||
break;
|
||||
case ADSRPhase::RELEASE:
|
||||
channel.off();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
channel.adsr_level += channel.adsr_step;
|
||||
channel.adsr_frame++;
|
||||
|
||||
if(channel.waveform_offset & 0x10000) {
|
||||
// if the waveform offset overflows then generate a new
|
||||
// random noise sample
|
||||
channel.noise = prng_normal();
|
||||
}
|
||||
|
||||
channel.waveform_offset &= 0xffff;
|
||||
|
||||
// check if any waveforms are active for this channel
|
||||
if(channel.waveforms) {
|
||||
uint8_t waveform_count = 0;
|
||||
int32_t channel_sample = 0;
|
||||
|
||||
if(channel.waveforms & Waveform::NOISE) {
|
||||
channel_sample += channel.noise;
|
||||
waveform_count++;
|
||||
}
|
||||
|
||||
if(channel.waveforms & Waveform::SAW) {
|
||||
channel_sample += (int32_t)channel.waveform_offset - 0x7fff;
|
||||
waveform_count++;
|
||||
}
|
||||
|
||||
// creates a triangle wave of ^
|
||||
if(channel.waveforms & Waveform::TRIANGLE) {
|
||||
if(channel.waveform_offset < 0x7fff) { // initial quarter up slope
|
||||
channel_sample += int32_t(channel.waveform_offset * 2) - int32_t(0x7fff);
|
||||
}
|
||||
else { // final quarter up slope
|
||||
channel_sample += int32_t(0x7fff) - ((int32_t(channel.waveform_offset) - int32_t(0x7fff)) * 2);
|
||||
}
|
||||
waveform_count++;
|
||||
}
|
||||
|
||||
if(channel.waveforms & Waveform::SQUARE) {
|
||||
channel_sample += (channel.waveform_offset < channel.pulse_width) ? 0x7fff : -0x7fff;
|
||||
waveform_count++;
|
||||
}
|
||||
|
||||
if(channel.waveforms & Waveform::SINE) {
|
||||
// the sine_waveform sample contains 256 samples in
|
||||
// total so we'll just use the most significant bits
|
||||
// of the current waveform position to index into it
|
||||
channel_sample += sine_waveform[channel.waveform_offset >> 8];
|
||||
waveform_count++;
|
||||
}
|
||||
|
||||
if(channel.waveforms & Waveform::WAVE) {
|
||||
channel_sample += channel.wave_buffer[channel.wave_buf_pos];
|
||||
if(++channel.wave_buf_pos == 64) {
|
||||
channel.wave_buf_pos = 0;
|
||||
if(channel.wave_buffer_callback)
|
||||
channel.wave_buffer_callback(channel);
|
||||
}
|
||||
waveform_count++;
|
||||
}
|
||||
|
||||
channel_sample = channel_sample / waveform_count;
|
||||
|
||||
channel_sample = (int64_t(channel_sample) * int32_t(channel.adsr_level >> 8)) >> 16;
|
||||
|
||||
// apply channel volume
|
||||
channel_sample = (int64_t(channel_sample) * int32_t(channel.volume)) >> 16;
|
||||
|
||||
// apply channel filter
|
||||
//if (channel.filter_enable) {
|
||||
//float filter_epow = 1 - expf(-(1.0f / 22050.0f) * 2.0f * pi * int32_t(channel.filter_cutoff_frequency));
|
||||
//channel_sample += (channel_sample - channel.filter_last_sample) * filter_epow;
|
||||
//}
|
||||
|
||||
//channel.filter_last_sample = channel_sample;
|
||||
|
||||
// combine channel sample into the final sample
|
||||
sample += channel_sample;
|
||||
}
|
||||
}
|
||||
|
||||
sample = (int64_t(sample) * int32_t(volume)) >> 16;
|
||||
|
||||
// clip result to 16-bit
|
||||
sample = sample <= -0x8000 ? -0x8000 : (sample > 0x7fff ? 0x7fff : sample);
|
||||
return sample;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
#pragma once
|
||||
|
||||
//#include <string>
|
||||
//#include <array>
|
||||
//#include <cstdint>
|
||||
//#include <algorithm>
|
||||
//#include <vector>
|
||||
//#include <functional>
|
||||
|
||||
#include "common/pimoroni_common.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
// The duration a note is played is determined by the amount of attack,
|
||||
// decay, and release, combined with the length of the note as defined by
|
||||
// the user.
|
||||
//
|
||||
// - Attack: number of milliseconds it takes for a note to hit full volume
|
||||
// - Decay: number of milliseconds it takes for a note to settle to sustain volume
|
||||
// - Sustain: percentage of full volume that the note sustains at (duration implied by other factors)
|
||||
// - Release: number of milliseconds it takes for a note to reduce to zero volume after it has ended
|
||||
//
|
||||
// Attack (750ms) - Decay (500ms) -------- Sustain ----- Release (250ms)
|
||||
//
|
||||
// + + + +
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// v v v v
|
||||
// 0ms 1000ms 2000ms 3000ms 4000ms
|
||||
//
|
||||
// | XXXX | | | |
|
||||
// | X X|XX | | |
|
||||
// | X | XXX | | |
|
||||
// | X | XXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX| |
|
||||
// | X | | |X |
|
||||
// | X | | |X |
|
||||
// | X | | | X |
|
||||
// | X | | | X |
|
||||
// | X | | | X |
|
||||
// | X | | | X |
|
||||
// | X | | | X |
|
||||
// | X | | | X |
|
||||
// | X + + + | + + + | + + + | + + + | +
|
||||
// | X | | | | | | | | | | | | | | | | |
|
||||
// |X | | | | | | | | | | | | | | | | |
|
||||
// +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+--->
|
||||
|
||||
enum Waveform {
|
||||
NOISE = 128,
|
||||
SQUARE = 64,
|
||||
SAW = 32,
|
||||
TRIANGLE = 16,
|
||||
SINE = 8,
|
||||
WAVE = 1
|
||||
};
|
||||
|
||||
enum class ADSRPhase : uint8_t {
|
||||
ATTACK,
|
||||
DECAY,
|
||||
SUSTAIN,
|
||||
RELEASE,
|
||||
OFF
|
||||
};
|
||||
|
||||
const int16_t sine_waveform[256] = {-32768,-32758,-32729,-32679,-32610,-32522,-32413,-32286,-32138,-31972,-31786,-31581,-31357,-31114,-30853,-30572,-30274,-29957,-29622,-29269,-28899,-28511,-28106,-27684,-27246,-26791,-26320,-25833,-25330,-24812,-24279,-23732,-23170,-22595,-22006,-21403,-20788,-20160,-19520,-18868,-18205,-17531,-16846,-16151,-15447,-14733,-14010,-13279,-12540,-11793,-11039,-10279,-9512,-8740,-7962,-7180,-6393,-5602,-4808,-4011,-3212,-2411,-1608,-804,0,804,1608,2411,3212,4011,4808,5602,6393,7180,7962,8740,9512,10279,11039,11793,12540,13279,14010,14733,15447,16151,16846,17531,18205,18868,19520,20160,20788,21403,22006,22595,23170,23732,24279,24812,25330,25833,26320,26791,27246,27684,28106,28511,28899,29269,29622,29957,30274,30572,30853,31114,31357,31581,31786,31972,32138,32286,32413,32522,32610,32679,32729,32758,32767,32758,32729,32679,32610,32522,32413,32286,32138,31972,31786,31581,31357,31114,30853,30572,30274,29957,29622,29269,28899,28511,28106,27684,27246,26791,26320,25833,25330,24812,24279,23732,23170,22595,22006,21403,20788,20160,19520,18868,18205,17531,16846,16151,15447,14733,14010,13279,12540,11793,11039,10279,9512,8740,7962,7180,6393,5602,4808,4011,3212,2411,1608,804,0,-804,-1608,-2411,-3212,-4011,-4808,-5602,-6393,-7180,-7962,-8740,-9512,-10279,-11039,-11793,-12540,-13279,-14010,-14733,-15447,-16151,-16846,-17531,-18205,-18868,-19520,-20160,-20788,-21403,-22006,-22595,-23170,-23732,-24279,-24812,-25330,-25833,-26320,-26791,-27246,-27684,-28106,-28511,-28899,-29269,-29622,-29957,-30274,-30572,-30853,-31114,-31357,-31581,-31786,-31972,-32138,-32286,-32413,-32522,-32610,-32679,-32729,-32758};
|
||||
const uint32_t sample_rate = 22050;
|
||||
|
||||
struct AudioChannel {
|
||||
uint8_t waveforms = 0; // bitmask for enabled waveforms (see Waveform enum for values)
|
||||
uint16_t frequency = 660; // frequency of the voice (Hz)
|
||||
uint16_t volume = UINT16_MAX; // channel volume
|
||||
|
||||
uint16_t attack_ms = 2; // attack period (cannot be zero)
|
||||
uint16_t decay_ms = 6; // decay period (cannot be zero)
|
||||
uint16_t sustain = UINT16_MAX; // sustain volume
|
||||
uint16_t release_ms = 1; // release period
|
||||
uint16_t pulse_width = 0x7fff; // duty cycle of square wave (default 50%)
|
||||
int16_t noise = 0; // current noise value
|
||||
|
||||
uint32_t waveform_offset = 0; // voice offset (Q8)
|
||||
|
||||
int32_t filter_last_sample = 0;
|
||||
bool filter_enable = false;
|
||||
uint16_t filter_cutoff_frequency = 0;
|
||||
|
||||
uint32_t adsr_frame = 0; // number of frames into the current ADSR phase
|
||||
uint32_t adsr_end_frame = 0; // frame target at which the ADSR changes to the next phase
|
||||
uint32_t adsr_level = 0; // the output level at the current frame of the ADSR phase
|
||||
int32_t adsr_step = 0; // the amount to increment the level with each frame
|
||||
ADSRPhase adsr_phase = ADSRPhase::OFF;
|
||||
|
||||
uint8_t wave_buf_pos = 0; //
|
||||
int16_t wave_buffer[64]; // buffer for arbitrary waveforms. small as it's filled by user callback
|
||||
|
||||
void *user_data = nullptr;
|
||||
void (*wave_buffer_callback)(AudioChannel &channel);
|
||||
|
||||
void trigger_attack();
|
||||
void trigger_decay();
|
||||
void trigger_sustain();
|
||||
void trigger_release();
|
||||
void off();
|
||||
void restore();
|
||||
};
|
||||
|
||||
class PicoSynth {
|
||||
public:
|
||||
const uint16_t volume = 0x2fff;
|
||||
|
||||
static const uint CHANNEL_COUNT = 8;
|
||||
AudioChannel channels[CHANNEL_COUNT];
|
||||
|
||||
int16_t get_audio_frame();
|
||||
bool is_audio_playing();
|
||||
};
|
||||
|
||||
constexpr float pi = 3.14159265358979323846f;
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
include(plasma_stick.cmake)
|
|
@ -0,0 +1,6 @@
|
|||
add_library(plasma_stick INTERFACE)
|
||||
|
||||
target_include_directories(plasma_stick INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(plasma_stick INTERFACE pico_stdlib plasma)
|
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "ws2812.hpp"
|
||||
|
||||
namespace plasma {
|
||||
namespace plasma_stick {
|
||||
const uint DAT = 15; // Used for WS2812
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
# Automation 2040 W Micropython Examples <!-- omit in toc -->
|
||||
|
||||
- [Function Examples](#function-examples)
|
||||
- [PWM Outputs](#pwm-outputs)
|
||||
- [Read ADCs](#read-adcs)
|
||||
- [Read Inputs](#read-inputs)
|
||||
- [Toggle Relays](#toggle-relays)
|
||||
|
@ -13,16 +14,23 @@
|
|||
|
||||
## Function Examples
|
||||
|
||||
These examples will work with Automation 2040 W and Automation 2040 W Mini. If you have an Automation 2040 W Mini, initialise your board with `board = Automation2040WMini` to see the correct numbers of inputs, outputs and relays!
|
||||
|
||||
### PWM Outputs
|
||||
[pwm_outputs.py](pwm_outputs.py)
|
||||
|
||||
Shows how to PWM the output terminals of Automation 2040 W.
|
||||
|
||||
### Read ADCs
|
||||
[read_adcs.py](read_adcs.py)
|
||||
|
||||
Shows how to read the 3 ADC terminals of Automation 2040 W.
|
||||
Shows how to read the ADC terminals of Automation 2040 W.
|
||||
|
||||
|
||||
### Read Inputs
|
||||
[read_inputs.py](read_inputs.py)
|
||||
|
||||
Shows how to read the 3 Input terminals of Automation 2040 W.
|
||||
Shows how to read the input terminals of Automation 2040 W.
|
||||
|
||||
|
||||
### Toggle Relays
|
||||
|
@ -50,11 +58,11 @@ A simple program that resets Automation 2040 W, turning off its Relays, Outputs,
|
|||
|
||||
## Wireless Examples
|
||||
|
||||
The wireless examples need `network_manager.py` and `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).
|
||||
The wireless examples need `network_manager.py` and `WIFI_CONFIG.py` from the `micropython/examples/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).
|
||||
|
||||
### Web IO Interface
|
||||
[web_io_interface/](web_io_interface/)
|
||||
|
||||
Provides a basic web interface for all your Automation 2040W features.
|
||||
|
||||
Needs `lib/tinyweb` from `common`!
|
||||
Needs `lib/tinyweb` from `micropython/examples/common`!
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import time
|
||||
from automation import Automation2040W, SWITCH_A
|
||||
|
||||
# Uncomment for Automation2040W Mini and comment out above import
|
||||
# from automation import Automation2040WMini, SWITCH_A
|
||||
|
||||
"""
|
||||
Demonstrates how to toggle each of Automation 2040 W's output terminals.
|
||||
|
||||
Press "A" to exit the program.
|
||||
"""
|
||||
|
||||
TIME_PER_TOGGLE = 0.5 # How much time to wait between each toggle (in seconds)
|
||||
OUTPUT_NAMES = ("O1", "O2", "O3") # The friendly names to give each digital output
|
||||
|
||||
# Create a new Automation2040W
|
||||
board = Automation2040W()
|
||||
# Uncomment for Automation2040W Mini
|
||||
# board = Automation2040WMini()
|
||||
|
||||
# Enable the LED of the switch used to exit the loop
|
||||
board.switch_led(SWITCH_A, 50) # Half Brightness
|
||||
|
||||
toggle = True
|
||||
index = 0
|
||||
|
||||
# Toggle the outputs until the user switch is pressed
|
||||
while not board.switch_pressed(SWITCH_A):
|
||||
|
||||
# Toggle an output
|
||||
for output_percentage in range(101):
|
||||
|
||||
if toggle is True:
|
||||
board.output(index, output_percentage)
|
||||
else:
|
||||
board.output(index, 100.0 - output_percentage)
|
||||
|
||||
# Print the state of all outputs
|
||||
for i in range(board.NUM_OUTPUTS):
|
||||
print(OUTPUT_NAMES[i], " = ", board.output_percent(i), sep="", end=", ")
|
||||
|
||||
# Print a new line
|
||||
print()
|
||||
time.sleep(0.01)
|
||||
|
||||
index += 1 # Move on to the next output
|
||||
if index >= board.NUM_OUTPUTS:
|
||||
index = 0 # Go back to the first output
|
||||
toggle = not toggle # Invert the toggle value
|
||||
|
||||
time.sleep(TIME_PER_TOGGLE)
|
||||
|
||||
# Put the board back into a safe state
|
||||
board.reset()
|
|
@ -1,5 +1,5 @@
|
|||
import time
|
||||
from automation import Automation2040W, SWITCH_A, NUM_ADCS
|
||||
from automation import Automation2040W, SWITCH_A
|
||||
|
||||
"""
|
||||
Shows how to read the 3 ADC terminals of Automation 2040 W.
|
||||
|
@ -20,7 +20,7 @@ board.switch_led(SWITCH_A, 50) # Half Brightness
|
|||
while not board.switch_pressed(SWITCH_A):
|
||||
|
||||
# Read each ADC in turn and print its voltage
|
||||
for i in range(NUM_ADCS):
|
||||
for i in range(board.NUM_ADCS):
|
||||
voltage = board.read_adc(i)
|
||||
print(ADC_NAMES[i], " = ", round(voltage, 3), sep="", end=", ")
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import time
|
||||
from automation import Automation2040W, SWITCH_A, NUM_INPUTS
|
||||
from automation import Automation2040W, SWITCH_A
|
||||
|
||||
# Uncomment for Automation2040W Mini and comment out above import
|
||||
# from automation import Automation2040WMini, SWITCH_A
|
||||
|
||||
"""
|
||||
Shows how to read the 3 Input terminals of Automation 2040 W.
|
||||
|
@ -12,6 +15,8 @@ INPUT_NAMES = ("I1", "I2", "I3", "I4") # The friendly names to give each digita
|
|||
|
||||
# Create a new Automation2040W
|
||||
board = Automation2040W()
|
||||
# Uncomment for Automation2040W Mini
|
||||
# board = Automation2040WMini()
|
||||
|
||||
# Enable the LED of the switch used to exit the loop
|
||||
board.switch_led(SWITCH_A, 50) # Half Brightness
|
||||
|
@ -20,7 +25,7 @@ board.switch_led(SWITCH_A, 50) # Half Brightness
|
|||
while not board.switch_pressed(SWITCH_A):
|
||||
|
||||
# Read each input in turn and print its value
|
||||
for i in range(NUM_INPUTS):
|
||||
for i in range(board.NUM_INPUTS):
|
||||
value = board.read_input(i)
|
||||
print(INPUT_NAMES[i], " = ", value, sep="", end=", ")
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import time
|
||||
from automation import Automation2040W, SWITCH_A, SWITCH_B, NUM_SWITCHES
|
||||
from automation import Automation2040W, SWITCH_A, SWITCH_B
|
||||
|
||||
"""
|
||||
An example of the user switches and LEDs on Automation 2040 W.
|
||||
|
@ -22,7 +22,7 @@ led_brightnesses = [0.0, 0.0]
|
|||
# Interact with the switches and LEDs until both are pressed simultaneously
|
||||
while not board.switch_pressed(SWITCH_A) or not board.switch_pressed(SWITCH_B):
|
||||
|
||||
for i in range(NUM_SWITCHES):
|
||||
for i in range(board.NUM_SWITCHES):
|
||||
# Change the LED brightness based on switch's state
|
||||
if board.switch_pressed(i):
|
||||
print(SWITCH_NAMES[i], " = Pressed", sep="", end=", ")
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import time
|
||||
from automation import Automation2040W, SWITCH_A, NUM_OUTPUTS
|
||||
from automation import Automation2040W, SWITCH_A
|
||||
|
||||
# Uncomment for Automation2040W Mini and comment out above import
|
||||
# from automation import Automation2040WMini, SWITCH_A
|
||||
|
||||
"""
|
||||
Demonstrates how to toggle each of Automation 2040 W's output terminals.
|
||||
|
@ -12,6 +15,8 @@ OUTPUT_NAMES = ("O1", "O2", "O3") # The friendly names to give each digital ou
|
|||
|
||||
# Create a new Automation2040W
|
||||
board = Automation2040W()
|
||||
# Uncomment for Automation2040W Mini
|
||||
# board = Automation2040WMini()
|
||||
|
||||
# Enable the LED of the switch used to exit the loop
|
||||
board.switch_led(SWITCH_A, 50) # Half Brightness
|
||||
|
@ -26,14 +31,14 @@ while not board.switch_pressed(SWITCH_A):
|
|||
board.output(index, toggle)
|
||||
|
||||
# Print the state of all outputs
|
||||
for i in range(NUM_OUTPUTS):
|
||||
print(OUTPUT_NAMES[i], " = ", board.output(i), sep="", end=", ")
|
||||
for i in range(board.NUM_OUTPUTS):
|
||||
print(OUTPUT_NAMES[i], " = ", bool(board.output(i)), sep="", end=", ")
|
||||
|
||||
# Print a new line
|
||||
print()
|
||||
|
||||
index += 1 # Move on to the next output
|
||||
if index >= NUM_OUTPUTS:
|
||||
if index >= board.NUM_OUTPUTS:
|
||||
index = 0 # Go back to the first output
|
||||
toggle = not toggle # Invert the toggle value
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import time
|
||||
from automation import Automation2040W, SWITCH_A, NUM_RELAYS
|
||||
from automation import Automation2040W, SWITCH_A
|
||||
|
||||
# Uncomment for Automation2040W Mini and comment out above import
|
||||
# from automation import Automation2040WMini, SWITCH_A
|
||||
|
||||
"""
|
||||
Demonstrates how to toggle the actuation state of each of Automation 2040 W's relays.
|
||||
|
@ -12,6 +15,8 @@ RELAY_NAMES = ("R1", "R2", "R3") # The friendly names to give each relay
|
|||
|
||||
# Create a new Automation2040W
|
||||
board = Automation2040W()
|
||||
# Uncomment for Automation2040W Mini
|
||||
# board = Automation2040WMini()
|
||||
|
||||
# Enable the LED of the switch used to exit the loop
|
||||
board.switch_led(SWITCH_A, 50) # Half Brightness
|
||||
|
@ -23,17 +28,23 @@ index = 0
|
|||
while not board.switch_pressed(SWITCH_A):
|
||||
|
||||
# Toggle a relay
|
||||
board.relay(index, toggle)
|
||||
if board.NUM_RELAYS == 1:
|
||||
board.relay(toggle)
|
||||
|
||||
# Print the state of all relays
|
||||
for i in range(NUM_RELAYS):
|
||||
print(RELAY_NAMES[i], " = ", board.relay(i), sep="", end=", ")
|
||||
# Print the state of the relay
|
||||
print(RELAY_NAMES[0], " = ", board.relay(), sep="", end=", ")
|
||||
else:
|
||||
board.relay(index, toggle)
|
||||
|
||||
# Print the state of all relays
|
||||
for i in range(board.NUM_RELAYS):
|
||||
print(RELAY_NAMES[i], " = ", board.relay(i), sep="", end=", ")
|
||||
|
||||
# Print a new line
|
||||
print()
|
||||
|
||||
index += 1 # Move on to the next relay
|
||||
if index >= NUM_RELAYS:
|
||||
if index >= board.NUM_RELAYS:
|
||||
index = 0 # Go back to the first relay
|
||||
toggle = not toggle # Invert the toggle value
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ def status_handler(mode, status, ip):
|
|||
|
||||
print("Network: {}".format(WIFI_CONFIG.SSID))
|
||||
status_text = "Connecting..."
|
||||
board.conn_led(20)
|
||||
board.conn_led(20.0)
|
||||
if status is not None:
|
||||
if status:
|
||||
status_text = "Connection successful!"
|
||||
|
@ -119,12 +119,12 @@ class outputs:
|
|||
|
||||
def get(self, data):
|
||||
if 'one' in data.keys():
|
||||
board.output(0, int(data['one']))
|
||||
board.output(0, bool(data['one']))
|
||||
if 'two' in data.keys():
|
||||
board.output(1, int(data['two']))
|
||||
board.output(1, bool(data['two']))
|
||||
if 'three' in data.keys():
|
||||
board.output(2, int(data['three']))
|
||||
return {"one": board.output(0), "two": board.output(1), "three": board.output(2)}, 201
|
||||
board.output(2, bool(data['three']))
|
||||
return {"one": bool(board.output(0)), "two": bool(board.output(1)), "three": bool(board.output(2))}, 201
|
||||
|
||||
def post(self, data):
|
||||
if 'one' in data.keys():
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Drop this function into your code to set your board's time/date from your RV3028 RTC breakout!
|
||||
"""
|
||||
|
||||
|
||||
def set_pico_time():
|
||||
# the function sets the Pico's RTC from a RV3028 RTC breakout
|
||||
# to setup breakout (and set the time) run this first:
|
||||
# https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/breakout_rtc/set-time.py
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
from breakout_rtc import BreakoutRTC
|
||||
from machine import RTC
|
||||
|
||||
# set up I2C
|
||||
PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} # i2c pins 4, 5 for Breakout Garden
|
||||
# for boards that use the alternate I2C pins use the line below instead
|
||||
# PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} # Default i2c pins for Pico Explorer
|
||||
|
||||
i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
|
||||
# for boards that use the alternate I2C pins use the line below instead
|
||||
# i2c = PimoroniI2C(**PINS_PICO_EXPLORER)
|
||||
|
||||
# set up the RTC breakout
|
||||
RV3028 = BreakoutRTC(i2c)
|
||||
|
||||
# set the Pico's RTC from the RTC breakout
|
||||
RV3028.update_time()
|
||||
RTC().datetime([RV3028.get_year(), RV3028.get_month(), RV3028.get_date(),
|
||||
RV3028.get_weekday(), RV3028.get_hours(), RV3028.get_minutes(),
|
||||
RV3028.get_seconds(), 0])
|
||||
print(f"Pico RTC set to breakout time: {RV3028.string_date()} {RV3028.string_time()}")
|
||||
|
||||
|
||||
# call the function
|
||||
set_pico_time()
|
|
@ -0,0 +1,230 @@
|
|||
# Clock example with NTP synchronization
|
||||
#
|
||||
# Create a secrets.py with your Wifi details to be able to get the time
|
||||
# when the Galactic 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 galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_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 galactic object and graphics surface for drawing
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
# create the rtc object
|
||||
rtc = machine.RTC()
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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()
|
||||
gu.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(GalacticUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP)
|
||||
down_button = machine.Pin(GalacticUnicorn.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 += utc_offset
|
||||
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)
|
||||
|
||||
# set the font
|
||||
graphics.set_font("bitmap8")
|
||||
|
||||
# calculate text position so that it is centred
|
||||
w = graphics.measure_text(clock, 1)
|
||||
x = int(width / 2 - w / 2 + 1)
|
||||
y = 2
|
||||
|
||||
outline_text(clock, x, y)
|
||||
|
||||
last_second = second
|
||||
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
sync_time()
|
||||
|
||||
while True:
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.adjust_brightness(-0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
sync_time()
|
||||
|
||||
redraw_display_if_reqd()
|
||||
|
||||
# update the display
|
||||
gu.update(graphics)
|
||||
|
||||
time.sleep(0.01)
|
|
@ -0,0 +1,72 @@
|
|||
import time
|
||||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
colour = (230, 150, 0)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def setup():
|
||||
global width, height, lifetime, age
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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)
|
||||
|
||||
gu.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()
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.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,117 @@
|
|||
import time
|
||||
import math
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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) / 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("bitmap8")
|
||||
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
|
||||
w = graphics.measure_text(text, 1)
|
||||
|
||||
x = int(53 / 2 - w / 2 + 1)
|
||||
y = 2
|
||||
|
||||
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)
|
||||
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
time_ms = time.ticks_ms()
|
||||
test = (time_ms // 1000) % 5
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.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 gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
text = "Button A"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
text = "Button B"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
text = "Button C"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
text = "Button D"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
|
||||
text = "Louder!"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
|
||||
text = "Quieter"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
text = "Brighter!"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
text = "Darker"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
|
||||
text = "Zzz... zzz..."
|
||||
|
||||
outline_text(text)
|
||||
|
||||
gu.update(graphics)
|
|
@ -0,0 +1,348 @@
|
|||
import gc
|
||||
import time
|
||||
import math
|
||||
from machine import Timer
|
||||
from galactic import GalacticUnicorn, Channel
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gc.collect()
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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 = [gu.synth_channel(i) for i in range(len(notes))]
|
||||
|
||||
|
||||
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("bitmap8")
|
||||
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
|
||||
w = graphics.measure_text(text, 1)
|
||||
|
||||
x = int(53 / 2 - w / 2 + 1)
|
||||
y = 2
|
||||
|
||||
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)
|
||||
|
||||
|
||||
gu.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 gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
if not was_a_pressed:
|
||||
# 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)
|
||||
|
||||
# If the synth is not already playing, init the first beat
|
||||
if not synthing:
|
||||
beat = 0
|
||||
next_beat()
|
||||
|
||||
gu.play_synth()
|
||||
synthing = True
|
||||
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
|
||||
|
||||
was_a_pressed = True
|
||||
else:
|
||||
was_a_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
if not was_b_pressed:
|
||||
# Configure the synth to play our notes, but with only one channel audable
|
||||
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
|
||||
attack=0.016,
|
||||
decay=0.168,
|
||||
sustain=0,
|
||||
release=0.168,
|
||||
volume=0)
|
||||
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=0)
|
||||
channels[3].configure(waveforms=Channel.NOISE,
|
||||
attack=0.005,
|
||||
decay=0.005,
|
||||
sustain=8000 / 65535,
|
||||
release=0.040,
|
||||
volume=0)
|
||||
channels[4].configure(waveforms=Channel.SQUARE,
|
||||
attack=0.010,
|
||||
decay=0.100,
|
||||
sustain=0,
|
||||
release=0.500,
|
||||
volume=0)
|
||||
|
||||
# If the synth is not already playing, init the first beat
|
||||
if not synthing:
|
||||
beat = 0
|
||||
next_beat()
|
||||
|
||||
gu.play_synth()
|
||||
synthing = True
|
||||
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
|
||||
|
||||
was_b_pressed = True
|
||||
else:
|
||||
was_b_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
if not was_c_pressed:
|
||||
# Stop synth (if running) and play Tone A
|
||||
timer.deinit()
|
||||
tone_a = 400
|
||||
channels[0].play_tone(tone_a, 0.06)
|
||||
|
||||
gu.play_synth()
|
||||
synthing = False
|
||||
|
||||
was_c_pressed = True
|
||||
else:
|
||||
was_c_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
if not was_c_pressed:
|
||||
# Stop synth (if running) and play Tone B
|
||||
timer.deinit()
|
||||
tone_b = 600
|
||||
|
||||
channels[1].play_tone(tone_b, 0.06, attack=0.5)
|
||||
|
||||
gu.play_synth()
|
||||
synthing = False
|
||||
|
||||
was_d_pressed = True
|
||||
else:
|
||||
was_d_pressed = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
if tone_b > 0: # Zero means tone not playing
|
||||
# Increase Tone B
|
||||
tone_b = min(tone_b + 10, 20000)
|
||||
channels[1].frequency(tone_b)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
if tone_b > 0: # Zero means tone not playing
|
||||
# Decrease Tone B
|
||||
tone_b = max(tone_b - 10, 10)
|
||||
channels[1].frequency(max(tone_b, 10))
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
|
||||
if tone_a > 0: # Zero means tone not playing
|
||||
# Increase Tone A
|
||||
tone_a = min(tone_a + 10, 20000)
|
||||
channels[0].frequency(tone_a)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
|
||||
if tone_a > 0: # Zero means tone not playing
|
||||
# Decrease Tone A
|
||||
tone_a = max(tone_a - 10, 10)
|
||||
channels[0].frequency(tone_a)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
|
||||
if not was_d_pressed:
|
||||
# Stop synth and both tones
|
||||
tone_a = 0
|
||||
tone_b = 0
|
||||
gu.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 gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
text = "Play Synth"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
text = "Solo Synth"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
text = "Tone A"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
text = "Tone B"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
|
||||
text = "Raise A"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
|
||||
text = "Lower A"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
text = "Raise B"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
text = "Power B"
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
|
||||
text = "Stop"
|
||||
|
||||
outline_text(text)
|
||||
|
||||
gu.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,138 @@
|
|||
import time
|
||||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
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 setup_landscape():
|
||||
global width, height, heat, fire_spawns, damping_factor
|
||||
width = GalacticUnicorn.WIDTH + 2
|
||||
height = GalacticUnicorn.HEIGHT + 4
|
||||
heat = [[0.0 for y in range(height)] for x in range(width)]
|
||||
fire_spawns = 5
|
||||
damping_factor = 0.97
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def setup_portrait():
|
||||
global width, height, heat, fire_spawns, damping_factor
|
||||
width = GalacticUnicorn.HEIGHT + 2
|
||||
height = GalacticUnicorn.WIDTH + 4
|
||||
heat = [[0.0 for y in range(height)] for x in range(width)]
|
||||
fire_spawns = 2
|
||||
damping_factor = 0.99
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def update():
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw_landscape():
|
||||
for y in range(GalacticUnicorn.HEIGHT):
|
||||
for x in range(GalacticUnicorn.WIDTH):
|
||||
value = heat[x + 1][y]
|
||||
if value < 0.15:
|
||||
graphics.set_pen(fire_colours[0])
|
||||
elif value < 0.25:
|
||||
graphics.set_pen(fire_colours[1])
|
||||
elif value < 0.35:
|
||||
graphics.set_pen(fire_colours[2])
|
||||
elif value < 0.45:
|
||||
graphics.set_pen(fire_colours[3])
|
||||
else:
|
||||
graphics.set_pen(fire_colours[4])
|
||||
graphics.pixel(x, y)
|
||||
|
||||
gu.update(graphics)
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw_portrait():
|
||||
for y in range(GalacticUnicorn.WIDTH):
|
||||
for x in range(GalacticUnicorn.HEIGHT):
|
||||
value = heat[x + 1][y]
|
||||
if value < 0.15:
|
||||
graphics.set_pen(fire_colours[0])
|
||||
elif value < 0.25:
|
||||
graphics.set_pen(fire_colours[1])
|
||||
elif value < 0.35:
|
||||
graphics.set_pen(fire_colours[2])
|
||||
elif value < 0.45:
|
||||
graphics.set_pen(fire_colours[3])
|
||||
else:
|
||||
graphics.set_pen(fire_colours[4])
|
||||
graphics.pixel(y, x)
|
||||
|
||||
gu.update(graphics)
|
||||
|
||||
|
||||
landscape = True
|
||||
setup_landscape()
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.adjust_brightness(-0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
landscape = True
|
||||
setup_landscape()
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
landscape = False
|
||||
setup_portrait()
|
||||
|
||||
start = time.ticks_ms()
|
||||
|
||||
update()
|
||||
if landscape:
|
||||
draw_landscape()
|
||||
else:
|
||||
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,11 @@
|
|||
# Galactic Paint
|
||||
|
||||
Galactic Paint lets you paint pixels onto your Galatic 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 galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
from WIFI_CONFIG import SSID, PSK
|
||||
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
mv_graphics = memoryview(graphics)
|
||||
gu.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("galactic_paint/index.html")
|
||||
|
||||
|
||||
@server.route("/static/<path:path>", methods=["GET"])
|
||||
def route_static(request, path):
|
||||
return send_file(f"galactic_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":
|
||||
gu.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>Galactic 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>Galactic 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: 976px;
|
||||
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,214 @@
|
|||
'use strict';
|
||||
|
||||
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 < 11; y++) {
|
||||
var row = $('<tr></tr>');
|
||||
for (var x = 0; x < 53; 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);
|
||||
});
|
||||
});
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,80 @@
|
|||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
# setup heat value buffer and fire parameters
|
||||
width = GalacticUnicorn.WIDTH + 2
|
||||
height = GalacticUnicorn.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 firey 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(GalacticUnicorn.HEIGHT):
|
||||
for x in range(GalacticUnicorn.WIDTH):
|
||||
graphics.set_pen(pen_from_value(heat[x + 1][y]))
|
||||
graphics.pixel(x, y)
|
||||
|
||||
|
||||
def test():
|
||||
print("A")
|
|
@ -0,0 +1,113 @@
|
|||
import time
|
||||
import machine
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
# overclock to 200Mhz
|
||||
machine.freq(200000000)
|
||||
|
||||
# create galactic object and graphics surface for drawing
|
||||
galactic = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
brightness = 0.5
|
||||
|
||||
|
||||
# returns the id of the button that is currently pressed or
|
||||
# None if none are
|
||||
def pressed():
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
return GalacticUnicorn.SWITCH_A
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
return GalacticUnicorn.SWITCH_B
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
return GalacticUnicorn.SWITCH_C
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_D):
|
||||
return GalacticUnicorn.SWITCH_D
|
||||
return None
|
||||
|
||||
|
||||
# wait for a button to be pressed and load that effect
|
||||
while True:
|
||||
graphics.set_font("bitmap6")
|
||||
graphics.set_pen(graphics.create_pen(0, 0, 0))
|
||||
graphics.clear()
|
||||
graphics.set_pen(graphics.create_pen(155, 155, 155))
|
||||
graphics.text("PRESS", 12, -1, -1, 1)
|
||||
graphics.text("A B C OR D!", 2, 5, -1, 1)
|
||||
|
||||
# brightness up/down
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
brightness += 0.01
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
brightness -= 0.01
|
||||
brightness = max(min(brightness, 1.0), 0.0)
|
||||
|
||||
galactic.set_brightness(brightness)
|
||||
galactic.update(graphics)
|
||||
|
||||
if pressed() == GalacticUnicorn.SWITCH_A:
|
||||
import fire as effect
|
||||
break
|
||||
if pressed() == GalacticUnicorn.SWITCH_B:
|
||||
import supercomputer as effect # noqa: F811
|
||||
break
|
||||
if pressed() == GalacticUnicorn.SWITCH_C:
|
||||
import rainbow as effect # noqa: F811
|
||||
break
|
||||
if pressed() == GalacticUnicorn.SWITCH_D:
|
||||
import retroprompt 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 = galactic.is_pressed(GalacticUnicorn.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
|
||||
galactic.set_brightness(galactic.get_brightness() - 0.01)
|
||||
|
||||
if galactic.get_brightness() > 0.0:
|
||||
effect.draw()
|
||||
|
||||
# update the display
|
||||
galactic.update(graphics)
|
||||
else:
|
||||
effect.draw()
|
||||
|
||||
# update the display
|
||||
galactic.update(graphics)
|
||||
|
||||
# brightness up/down
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
brightness += 0.01
|
||||
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
brightness -= 0.01
|
||||
brightness = max(min(brightness, 1.0), 0.0)
|
||||
|
||||
galactic.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 galactic import GalacticUnicorn
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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,100 @@
|
|||
import time
|
||||
|
||||
graphics = None
|
||||
palette = None
|
||||
|
||||
|
||||
c64 = [
|
||||
" ",
|
||||
" ",
|
||||
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX ",
|
||||
" OO OO OO OOOO OO OO OO OO XXXXXXX ",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
|
||||
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX ",
|
||||
" OOOO OO OO OO OO OO OO XXXXXXX ",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
|
||||
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX ",
|
||||
" XXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_C64 = (230, 210, 250)
|
||||
BACKGROUND_C64 = (20, 20, 120)
|
||||
|
||||
spectrum = [
|
||||
" ",
|
||||
" ",
|
||||
" O OOOO OOOO OOOOO O O O O XXXXXXXX ",
|
||||
" O O O O O O O O O O O X XXXXXX ",
|
||||
" O O O O O O O X XXXXXX ",
|
||||
" O O O OOOOOO O O X XXXXXX ",
|
||||
" O O O O O O O X XXXXXX ",
|
||||
" OOOOOO OOOO O O OOOOO X XXXXXX ",
|
||||
" X X ",
|
||||
" XXXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_SPECTRUM = (0, 0, 0)
|
||||
BACKGROUND_SPECTRUM = (180, 150, 150)
|
||||
|
||||
bbc_micro = [
|
||||
" ",
|
||||
" ",
|
||||
" OOOOO OO OOOO OOO OOOO O ",
|
||||
" O O O O O O O O O O ",
|
||||
" O O O O O O O O ",
|
||||
" OOOOO O O OOOO O O O ",
|
||||
" O O OOOOOO O O O O ",
|
||||
" O O O O O O O O O O ",
|
||||
" OOOOO O O OOOO OOO 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
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
@micropython.native # noqa: F821
|
||||
def draw():
|
||||
time_ms = time.ticks_ms()
|
||||
prompt = (time_ms // 3000) % 3
|
||||
|
||||
if prompt == PROMPT_C64:
|
||||
image = c64
|
||||
fg = FOREGROUND_C64
|
||||
bg = BACKGROUND_C64
|
||||
elif prompt == PROMPT_SPECTRUM:
|
||||
image = spectrum
|
||||
fg = FOREGROUND_SPECTRUM
|
||||
bg = BACKGROUND_SPECTRUM
|
||||
elif prompt == PROMPT_BBC_MICRO:
|
||||
image = bbc_micro
|
||||
fg = FOREGROUND_BBC_MICRO
|
||||
bg = BACKGROUND_BBC_MICRO
|
||||
|
||||
fg_pen = graphics.create_pen(fg[0], fg[1], fg[2])
|
||||
bg_pen = graphics.create_pen(bg[0], bg[1], bg[2])
|
||||
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, y)
|
|
@ -0,0 +1,40 @@
|
|||
import random
|
||||
from galactic import GalacticUnicorn
|
||||
|
||||
graphics = None
|
||||
|
||||
colour = (230, 150, 0)
|
||||
|
||||
|
||||
def init():
|
||||
global width, height, lifetime, age
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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,145 @@
|
|||
import time
|
||||
import random
|
||||
import math
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
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 = GalacticUnicorn.HEIGHT
|
||||
height = GalacticUnicorn.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)
|
||||
|
||||
gu.update(graphics)
|
||||
|
||||
|
||||
setup_portrait()
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.adjust_brightness(-0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.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,114 @@
|
|||
import time
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
|
||||
c64 = [
|
||||
" ",
|
||||
" ",
|
||||
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX ",
|
||||
" OO OO OO OOOO OO OO OO OO XXXXXXX ",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
|
||||
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX ",
|
||||
" OOOO OO OO OO OO OO OO XXXXXXX ",
|
||||
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
|
||||
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX ",
|
||||
" XXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_C64 = (230, 210, 250)
|
||||
BACKGROUND_C64 = (20, 20, 120)
|
||||
|
||||
spectrum = [
|
||||
" ",
|
||||
" ",
|
||||
" O OOOO OOOO OOOOO O O O O XXXXXXXX ",
|
||||
" O O O O O O O O O O O X XXXXXX ",
|
||||
" O O O O O O O X XXXXXX ",
|
||||
" O O O OOOOOO O O X XXXXXX ",
|
||||
" O O O O O O O X XXXXXX ",
|
||||
" OOOOOO OOOO O O OOOOO X XXXXXX ",
|
||||
" X X ",
|
||||
" XXXXXXXX ",
|
||||
" "
|
||||
]
|
||||
FOREGROUND_SPECTRUM = (0, 0, 0)
|
||||
BACKGROUND_SPECTRUM = (180, 150, 150)
|
||||
|
||||
bbc_micro = [
|
||||
" ",
|
||||
" ",
|
||||
" OOOOO OO OOOO OOO OOOO O ",
|
||||
" O O O O O O O O O O ",
|
||||
" O O O O O O O O ",
|
||||
" OOOOO O O OOOO O O O ",
|
||||
" O O OOOOOO O O O O ",
|
||||
" O O O O O O O O O O ",
|
||||
" OOOOO O O OOOO OOO 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])
|
||||
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, y)
|
||||
|
||||
gu.update(graphics)
|
||||
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
while True:
|
||||
|
||||
time_ms = time.ticks_ms()
|
||||
prompt = (time_ms // 3000) % 3
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.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,106 @@
|
|||
import time
|
||||
import math
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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)
|
||||
|
||||
gu.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
|
||||
|
||||
gu.set_brightness(0.5)
|
||||
|
||||
phase = 0
|
||||
while True:
|
||||
|
||||
if animate:
|
||||
phase += speed
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
|
||||
hue_offset += 0.01
|
||||
hue_offset = 1.0 if hue_offset > 1.0 else hue_offset
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
|
||||
hue_offset -= 0.01
|
||||
hue_offset = 0.0 if hue_offset < 0.0 else hue_offset
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.adjust_brightness(-0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
|
||||
animate = False
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
|
||||
speed += 0.05
|
||||
speed = 10.0 if speed > 10.0 else speed
|
||||
animate = True
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
|
||||
speed -= 0.05
|
||||
speed = 0.0 if speed < 0.0 else speed
|
||||
animate = True
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
|
||||
stripe_width += 0.05
|
||||
stripe_width = 10.0 if stripe_width > 10.0 else stripe_width
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.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,90 @@
|
|||
import time
|
||||
from galactic import GalacticUnicorn
|
||||
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
|
||||
|
||||
# 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 galactic object and graphics surface for drawing
|
||||
gu = GalacticUnicorn()
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
|
||||
width = GalacticUnicorn.WIDTH
|
||||
height = GalacticUnicorn.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)
|
||||
|
||||
|
||||
gu.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 gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
|
||||
gu.adjust_brightness(+0.01)
|
||||
|
||||
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
|
||||
gu.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=2)
|
||||
|
||||
# update the display
|
||||
gu.update(graphics)
|
||||
|
||||
# pause for a moment (important or the USB serial device will fail)
|
||||
time.sleep(0.001)
|
|
@ -0,0 +1,85 @@
|
|||
# This example shows you a simple, non-interrupt way of reading Inky Frame's buttons with a loop that checks to see if buttons are pressed.
|
||||
|
||||
from pimoroni import ShiftRegister
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
from machine import Pin
|
||||
|
||||
display = PicoGraphics(display=DISPLAY)
|
||||
|
||||
display.set_font("bitmap8")
|
||||
|
||||
# Inky Frame uses a shift register to read the buttons
|
||||
SR_CLOCK = 8
|
||||
SR_LATCH = 9
|
||||
SR_OUT = 10
|
||||
|
||||
sr = ShiftRegister(SR_CLOCK, SR_LATCH, SR_OUT)
|
||||
|
||||
# set up the button LEDs
|
||||
button_a_led = Pin(11, Pin.OUT)
|
||||
button_b_led = Pin(12, Pin.OUT)
|
||||
button_c_led = Pin(13, Pin.OUT)
|
||||
button_d_led = Pin(14, Pin.OUT)
|
||||
button_e_led = Pin(15, Pin.OUT)
|
||||
|
||||
|
||||
# a handy function we can call to clear the screen
|
||||
# display.set_pen(1) is white and display.set_pen(0) is black
|
||||
def clear():
|
||||
display.set_pen(1)
|
||||
display.clear()
|
||||
|
||||
|
||||
# set up
|
||||
clear()
|
||||
display.set_pen(0)
|
||||
display.text("Press any button!", 10, 10, scale=4)
|
||||
display.update()
|
||||
|
||||
while True:
|
||||
button_a_led.off()
|
||||
button_b_led.off()
|
||||
button_c_led.off()
|
||||
button_d_led.off()
|
||||
button_e_led.off()
|
||||
|
||||
# read the shift register
|
||||
# we can tell which button has been pressed by checking if a specific bit is 0 or 1
|
||||
result = sr.read()
|
||||
button_a = sr[7]
|
||||
button_b = sr[6]
|
||||
button_c = sr[5]
|
||||
button_d = sr[4]
|
||||
button_e = sr[3]
|
||||
|
||||
if button_a == 1: # if a button press is detected then...
|
||||
button_a_led.on()
|
||||
clear() # clear to white
|
||||
display.set_pen(4) # change the pen colour
|
||||
display.text("Button A pressed", 10, 10, scale=4) # display some text on the screen
|
||||
display.update() # update the display
|
||||
elif button_b == 1:
|
||||
button_b_led.on()
|
||||
clear()
|
||||
display.set_pen(6)
|
||||
display.text("Button B pressed", 10, 50, scale=4)
|
||||
display.update()
|
||||
elif button_c == 1:
|
||||
button_c_led.on()
|
||||
clear()
|
||||
display.set_pen(5)
|
||||
display.text("Button C pressed", 10, 90, scale=4)
|
||||
display.update()
|
||||
elif button_d == 1:
|
||||
button_d_led.on()
|
||||
clear()
|
||||
display.set_pen(2)
|
||||
display.text("Button D pressed", 10, 130, scale=4)
|
||||
display.update()
|
||||
elif button_e == 1:
|
||||
button_e_led.on()
|
||||
clear()
|
||||
display.set_pen(3)
|
||||
display.text("Button E pressed", 10, 170, scale=4)
|
||||
display.update()
|
|
@ -1,12 +1,10 @@
|
|||
# This example shows you a simple, non-interrupt way of reading Inky Frame's buttons with a loop that checks to see if buttons are pressed.
|
||||
# This example allows you to test Inky Frame's buttons
|
||||
# It does not update the screen.
|
||||
|
||||
from pimoroni import ShiftRegister
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
|
||||
from machine import Pin
|
||||
import time
|
||||
|
||||
display = PicoGraphics(display=DISPLAY_INKY_FRAME)
|
||||
|
||||
display.set_font("bitmap8")
|
||||
|
||||
# Inky Frame uses a shift register to read the buttons
|
||||
SR_CLOCK = 8
|
||||
|
@ -15,70 +13,47 @@ SR_OUT = 10
|
|||
|
||||
sr = ShiftRegister(SR_CLOCK, SR_LATCH, SR_OUT)
|
||||
|
||||
# set up the button LEDs
|
||||
button_a_led = Pin(11, Pin.OUT)
|
||||
button_b_led = Pin(12, Pin.OUT)
|
||||
button_c_led = Pin(13, Pin.OUT)
|
||||
button_d_led = Pin(14, Pin.OUT)
|
||||
button_e_led = Pin(15, Pin.OUT)
|
||||
|
||||
# Simple class to debounce button input and handle LED
|
||||
class Button:
|
||||
def __init__(self, idx, led, debounce=50):
|
||||
self.led = Pin(led, Pin.OUT) # LEDs are just regular IOs
|
||||
self.led.on()
|
||||
self._idx = idx
|
||||
self._debounce_time = debounce
|
||||
self._changed = time.ticks_ms()
|
||||
self._last_value = None
|
||||
|
||||
def debounced(self):
|
||||
return time.ticks_ms() - self._changed > self._debounce_time
|
||||
|
||||
def get(self, sr):
|
||||
value = sr[self._idx]
|
||||
if value != self._last_value and self.debounced():
|
||||
self._last_value = value
|
||||
self._changed = time.ticks_ms()
|
||||
return value
|
||||
|
||||
|
||||
# a handy function we can call to clear the screen
|
||||
# display.set_pen(1) is white and display.set_pen(0) is black
|
||||
def clear():
|
||||
display.set_pen(1)
|
||||
display.clear()
|
||||
button_a = Button(7, 11)
|
||||
button_b = Button(6, 12)
|
||||
button_c = Button(5, 13)
|
||||
button_d = Button(4, 14)
|
||||
button_e = Button(3, 15)
|
||||
|
||||
|
||||
# set up
|
||||
clear()
|
||||
display.set_pen(0)
|
||||
display.text("Press any button!", 10, 10, scale=4)
|
||||
display.update()
|
||||
|
||||
while True:
|
||||
button_a_led.off()
|
||||
button_b_led.off()
|
||||
button_c_led.off()
|
||||
button_d_led.off()
|
||||
button_e_led.off()
|
||||
sr.read()
|
||||
|
||||
# read the shift register
|
||||
# we can tell which button has been pressed by checking if a specific bit is 0 or 1
|
||||
result = sr.read()
|
||||
button_a = sr[7]
|
||||
button_b = sr[6]
|
||||
button_c = sr[5]
|
||||
button_d = sr[4]
|
||||
button_e = sr[3]
|
||||
if button_a.get(sr):
|
||||
button_a.led.toggle()
|
||||
if button_b.get(sr):
|
||||
button_b.led.toggle()
|
||||
if button_c.get(sr):
|
||||
button_c.led.toggle()
|
||||
if button_d.get(sr):
|
||||
button_d.led.toggle()
|
||||
if button_e.get(sr):
|
||||
button_e.led.toggle()
|
||||
|
||||
if button_a == 1: # if a button press is detected then...
|
||||
button_a_led.on()
|
||||
clear() # clear to white
|
||||
display.set_pen(4) # change the pen colour
|
||||
display.text("Button A pressed", 10, 10, scale=4) # display some text on the screen
|
||||
display.update() # update the display
|
||||
elif button_b == 1:
|
||||
button_b_led.on()
|
||||
clear()
|
||||
display.set_pen(6)
|
||||
display.text("Button B pressed", 10, 50, scale=4)
|
||||
display.update()
|
||||
elif button_c == 1:
|
||||
button_c_led.on()
|
||||
clear()
|
||||
display.set_pen(5)
|
||||
display.text("Button C pressed", 10, 90, scale=4)
|
||||
display.update()
|
||||
elif button_d == 1:
|
||||
button_d_led.on()
|
||||
clear()
|
||||
display.set_pen(2)
|
||||
display.text("Button D pressed", 10, 130, scale=4)
|
||||
display.update()
|
||||
elif button_e == 1:
|
||||
button_e_led.on()
|
||||
clear()
|
||||
display.set_pen(3)
|
||||
display.text("Button E pressed", 10, 170, scale=4)
|
||||
display.update()
|
||||
time.sleep(1.0 / 60) # Poll 60 times/second
|
||||
|
|
|
@ -17,11 +17,11 @@ Pop an SD card into your computer to copy the images across. (Alternatively, you
|
|||
|
||||
This example requires `sdcard.mpy` from `common/lib` - copy this file into `lib` on your Pico W.
|
||||
|
||||
- [/micropython/examples/common](../common)
|
||||
- [/micropython/examples/common](../../common)
|
||||
|
||||
## Image Credits
|
||||
|
||||
Sample images from the Webb Space Telescope (credit: NASA, ESA, CSA, and STScI).
|
||||
Find more gorgeous images and info @ https://webbtelescope.org/
|
||||
|
||||
... and Raspberry Pi <3
|
||||
... and Raspberry Pi <3
|
||||
|
|
|
@ -4,7 +4,8 @@ from network_manager import NetworkManager
|
|||
import uasyncio
|
||||
import ujson
|
||||
from urllib import urequest
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
from machine import Pin
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
from pcf85063a import PCF85063A
|
||||
|
@ -38,7 +39,7 @@ def status_handler(mode, status, ip):
|
|||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
|
||||
gc.collect()
|
||||
graphics = PicoGraphics(DISPLAY_INKY_FRAME)
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
WIDTH, HEIGHT = graphics.get_bounds()
|
||||
gc.collect()
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio
|
||||
from urllib import urequest
|
||||
|
@ -27,7 +28,7 @@ URL = "http://feeds.bbci.co.uk/news/technology/rss.xml"
|
|||
# Frequent updates will reduce battery life!
|
||||
UPDATE_INTERVAL = 60 * 1
|
||||
|
||||
graphics = PicoGraphics(DISPLAY_INKY_FRAME)
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
WIDTH, HEIGHT = graphics.get_bounds()
|
||||
graphics.set_font("bitmap8")
|
||||
code = qrcode.QRCode()
|
||||
|
|
|
@ -8,7 +8,8 @@ import sdcard
|
|||
import WIFI_CONFIG
|
||||
from urllib import urequest
|
||||
from network_manager import NetworkManager
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
|
||||
"""
|
||||
random placekitten (from a very small set)
|
||||
|
|
|
@ -5,7 +5,8 @@ import uasyncio
|
|||
import WIFI_CONFIG
|
||||
from urllib import urequest
|
||||
from network_manager import NetworkManager
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
|
||||
|
||||
ENDPOINT = "https://en.wikiquote.org/w/api.php?format=json&action=expandtemplates&prop=wikitext&text={{{{Wikiquote:Quote%20of%20the%20day/{3}%20{2},%20{0}}}}}"
|
||||
|
@ -31,7 +32,7 @@ def status_handler(mode, status, ip):
|
|||
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
|
||||
|
||||
gc.collect()
|
||||
graphics = PicoGraphics(DISPLAY_INKY_FRAME)
|
||||
graphics = PicoGraphics(DISPLAY)
|
||||
WIDTH, HEIGHT = graphics.get_bounds()
|
||||
graphics.set_font("bitmap8")
|
||||
gc.collect()
|
||||
|
|
|
@ -6,7 +6,8 @@ import jpegdec
|
|||
import WIFI_CONFIG
|
||||
import uasyncio
|
||||
from network_manager import NetworkManager
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
from urllib import urequest
|
||||
|
||||
|
||||
|
@ -27,7 +28,7 @@ WIDTH, HEIGHT = graphics.get_bounds()
|
|||
FILENAME = "/sd/random-joke.jpg"
|
||||
|
||||
JOKE_IDS = "https://pimoroni.github.io/feed2image/jokeapi-ids.txt"
|
||||
JOKE_IMG = "https://pimoroni.github.io/feed2image/jokeapi-{}-600x448.jpg"
|
||||
JOKE_IMG = "https://pimoroni.github.io/feed2image/jokeapi-{}-{}x{}.jpg"
|
||||
|
||||
import sdcard # noqa: E402 - putting this at the top causes an MBEDTLS OOM error!?
|
||||
sd_spi = machine.SPI(0, sck=machine.Pin(18, machine.Pin.OUT), mosi=machine.Pin(19, machine.Pin.OUT), miso=machine.Pin(16, machine.Pin.OUT))
|
||||
|
@ -59,7 +60,7 @@ socket.close()
|
|||
|
||||
print("Random joke ID: {}".format(random_joke_id))
|
||||
|
||||
url = JOKE_IMG.format(random_joke_id)
|
||||
url = JOKE_IMG.format(random_joke_id, WIDTH, HEIGHT)
|
||||
|
||||
socket = urequest.urlopen(url)
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import gc
|
||||
import uos
|
||||
import random
|
||||
import machine
|
||||
import jpegdec
|
||||
import uasyncio
|
||||
|
@ -8,7 +7,8 @@ import sdcard
|
|||
import WIFI_CONFIG
|
||||
from urllib import urequest
|
||||
from network_manager import NetworkManager
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY
|
||||
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
|
||||
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
|
||||
|
||||
"""
|
||||
xkcd daily
|
||||
|
@ -46,7 +46,10 @@ uos.mount(sd, "/sd")
|
|||
gc.collect() # Claw back some RAM!
|
||||
|
||||
|
||||
url = ENDPOINT.format(WIDTH, HEIGHT + random.randint(0, 10))
|
||||
url = ENDPOINT
|
||||
|
||||
if (WIDTH, HEIGHT) != (600, 448):
|
||||
url = url.replace("xkcd-", f"xkcd-{WIDTH}x{HEIGHT}-")
|
||||
|
||||
socket = urequest.urlopen(url)
|
||||
|
||||
|
|
|
@ -18,10 +18,16 @@ led_e = PWM(Pin(15))
|
|||
|
||||
led_activity.freq(1000)
|
||||
|
||||
leds = [led_activity, led_connect, led_a, led_b, led_c, led_d, led_e]
|
||||
n = 0
|
||||
|
||||
while True:
|
||||
for duty in range(65025):
|
||||
led_activity.duty_u16(duty)
|
||||
sleep(0.0001)
|
||||
for duty in range(65025, 0, -1):
|
||||
led_activity.duty_u16(duty)
|
||||
sleep(0.0001)
|
||||
for _ in range(2):
|
||||
for duty in range(65025, 2):
|
||||
leds[n].duty_u16(duty)
|
||||
sleep(0.0001)
|
||||
for duty in range(65025, 0, -2):
|
||||
leds[n].duty_u16(duty)
|
||||
sleep(0.0001)
|
||||
n += 1
|
||||
n %= len(leds)
|
||||
|
|
|
@ -22,6 +22,9 @@ TEMPERATURE_OFFSET = 3
|
|||
|
||||
BRIGHTNESS = 0.8
|
||||
|
||||
# change this to adjust pressure based on your altitude
|
||||
altitude = 0
|
||||
|
||||
# light the LED red if the gas reading is less than 50%
|
||||
GAS_ALERT = 0.5
|
||||
|
||||
|
@ -46,6 +49,18 @@ def graphic_equaliser():
|
|||
led.set_rgb(0, ms, 0)
|
||||
|
||||
|
||||
def adjust_to_sea_pressure(pressure_hpa, temperature, altitude):
|
||||
"""
|
||||
Adjust pressure based on your altitude.
|
||||
|
||||
credits to @cubapp https://gist.github.com/cubapp/23dd4e91814a995b8ff06f406679abcf
|
||||
"""
|
||||
|
||||
# Adjusted-to-the-sea barometric pressure
|
||||
adjusted_hpa = pressure_hpa + ((pressure_hpa * 9.80665 * altitude) / (287 * (273 + temperature + (altitude / 400))))
|
||||
return adjusted_hpa
|
||||
|
||||
|
||||
def describe_pressure(pressure_hpa):
|
||||
"""Convert pressure into barometer-type description."""
|
||||
if pressure_hpa < 970:
|
||||
|
@ -213,6 +228,9 @@ while True:
|
|||
# convert pressure into hpa
|
||||
pressure_hpa = pressure / 100
|
||||
|
||||
# correct pressure
|
||||
pressure_hpa = adjust_to_sea_pressure(pressure_hpa, corrected_temperature, altitude)
|
||||
|
||||
# read LTR559
|
||||
ltr_reading = ltr.get_reading()
|
||||
lux = ltr_reading[BreakoutLTR559.LUX]
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
# Plasma Stick MicroPython Examples <!-- omit in toc -->
|
||||
|
||||
- [About Plasma Stick](#about-plasma-stick)
|
||||
- [Plasma Library](#plasma-library)
|
||||
- [Using Breakouts](#using-breakouts)
|
||||
- [Basic Examples](#basic-examples)
|
||||
- [Alternating Blinkies](#alternating-blinkies)
|
||||
- [Fire](#fire)
|
||||
- [Moon](#moon)
|
||||
- [Rainbows](#rainbows)
|
||||
- [Thermometer](#thermometer)
|
||||
- [Advanced Examples](#advanced-examples)
|
||||
- [CO2](#co2)
|
||||
- [Encoder](#encoder)
|
||||
- [Moon (RTC)](#moon-rtc)
|
||||
- [PIR](#pir)
|
||||
- [Thermometer (BME280)](#thermometer-bme280)
|
||||
- [Wireless Examples](#wireless-examples)
|
||||
- [Cheerlights](#cheerlights)
|
||||
- [Weather](#weather)
|
||||
|
||||
## About Plasma Stick
|
||||
|
||||
Plasma Stick 2040 W is a compact controller for WS2812 strip, powered by Raspberry Pi Pico W and perfect for easy, seasonal lighting. It has built in ✨wireless connectivity✨, courtesy of the onboard Pico W.
|
||||
|
||||
You can buy one on its own or in a kit:
|
||||
|
||||
- [Plasma Stick 2040 W](https://shop.pimoroni.com/products/plasma-stick-2040-w)
|
||||
|
||||
- [Wireless Plasma Kit](https://shop.pimoroni.com/products/wireless-plasma-kit)
|
||||
|
||||
Plasma Stick ships with MicroPython firmware pre-loaded, but you can download the most recent version at the link below (you'll want the `pimoroni-picow` image).
|
||||
|
||||
- [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases)
|
||||
|
||||
## Plasma Library
|
||||
|
||||
You can control your WS2812 / NeoPixel™ strip using our handy MicroPython Plasma library.
|
||||
|
||||
- [PicoGraphics MicroPython function reference](../../modules/plasma)
|
||||
|
||||
Note that the examples in this directory default to a RGB colour order to match the LEDs in the Wireless Plasma Kit. If you're using different LEDs you may need to adjust the colour order in your code - most of the other addressable LEDs we sell are GRB.
|
||||
|
||||
## Using Breakouts
|
||||
|
||||
Plasma Stick has a Qw/ST (Qwiic/STEMMA QT) connector. 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)
|
||||
|
||||
Plasma Stick 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 PINS_BREAKOUT_GARDEN
|
||||
|
||||
i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)
|
||||
```
|
||||
|
||||
Alternatively, you can specify the pin numbers directly:
|
||||
|
||||
```python
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
|
||||
i2c = PimoroniI2C(sda=(4), scl=(5))
|
||||
```
|
||||
|
||||
## Basic Examples
|
||||
|
||||
### Alternating Blinkies
|
||||
|
||||
[alternating-blinkies.py](alternating-blinkies.py)
|
||||
|
||||
A simple example with two alternating colours, great for festive lights!
|
||||
|
||||
### Fire
|
||||
|
||||
[fire.py](fire.py)
|
||||
|
||||
A simple 🔥 fire effect example 🤘 (warning, flashy).
|
||||
|
||||
### Moon
|
||||
|
||||
[moon.py](moon.py)
|
||||
|
||||
Spooky moon simulator - the LEDs will get brighter as midnight approaches!
|
||||
Needs to be run from Thonny to get the correct time.
|
||||
|
||||
### Rainbows
|
||||
|
||||
[rainbows.py](rainbows.py)
|
||||
|
||||
Some good old fashioned rainbows!
|
||||
|
||||
### Thermometer
|
||||
|
||||
[thermometer_pico.py](thermometer_pico.py)
|
||||
|
||||
Reads the temperature from the Pico W's internal temperature sensor and changes the LED strip an appropriate colour.
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
These examples require additional hardware.
|
||||
|
||||
### CO2
|
||||
|
||||
[co2.py](co2.py)
|
||||
|
||||
Reads CO2 level from a [SCD41 CO2 breakout](https://shop.pimoroni.com/products/scd41-co2-sensor-breakout) and turns the LED strip an appropriate colour.
|
||||
|
||||
### Encoder
|
||||
|
||||
[encoder.py](encoder.py)
|
||||
|
||||
Adjust the colour of your LEDs easily with an [RGB Encoder breakout](https://shop.pimoroni.com/products/rgb-encoder-breakout?variant=32236590399571).
|
||||
|
||||
### Moon (RTC)
|
||||
|
||||
[moon_rtc.py](moon_rtc.py)
|
||||
|
||||
Spooky moon simulator - the LEDs will get brighter as midnight approaches!
|
||||
Gets the time from a [RV3028 RTC breakout](https://shop.pimoroni.com/products/rv3028-real-time-clock-rtc-breakout).
|
||||
|
||||
### PIR
|
||||
|
||||
[pir.py](pir.py)
|
||||
|
||||
Connect a PIR motion sensor and trigger some ominous effects. We like [these ones](https://shop.pimoroni.com/products/micro-pir-motion-sensor-2-pcs) - we connected ours to the QwST connector using [this cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609846355) and some [socket to socket](https://shop.pimoroni.com/products/jumper-jerky-junior?variant=1076482185) jumper jerky.
|
||||
|
||||
### Thermometer (BME280)
|
||||
|
||||
[thermometer_bme280.py](thermometer_bme280.py)
|
||||
|
||||
Reads the temperature from a [BME280 breakout](https://shop.pimoroni.com/products/bme280-breakout) and changes the LED strip an appropriate colour.
|
||||
|
||||
## Wireless Examples
|
||||
|
||||
The wireless examples need `network_manager.py` and `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).
|
||||
|
||||
- [micropython/examples/common](../../examples/common)
|
||||
|
||||
### Cheerlights
|
||||
|
||||
[cheerlights.py](cheerlights.py)
|
||||
|
||||
Sets your LED strip to the current #cheerlights colour.
|
||||
Find out more about the Cheerlights API at [https://cheerlights.com/].
|
||||
|
||||
### Weather
|
||||
|
||||
[weather.py](weather.py)
|
||||
|
||||
This Plasma Stick example connects to Open Meteo to access the current weather conditions.
|
||||
It then does some cool weather appropriate stuff with LEDs.
|
||||
Find out more about the Open Meteo API at [https://open-meteo.com].
|
|
@ -0,0 +1,42 @@
|
|||
import plasma
|
||||
from plasma import plasma_stick
|
||||
import time
|
||||
|
||||
"""
|
||||
This super simple example sets up two alternating colours, great for festive lights!
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
# Pick two hues from the colour wheel (from 0-360°, try https://www.cssscript.com/demo/hsv-hsl-color-wheel-picker-reinvented/ )
|
||||
HUE_1 = 40
|
||||
HUE_2 = 285
|
||||
|
||||
# Set up brightness (between 0 and 1)
|
||||
BRIGHTNESS = 0.5
|
||||
|
||||
# Set up speed (wait time between colour changes, in seconds)
|
||||
SPEED = 1
|
||||
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
while True:
|
||||
for i in range(NUM_LEDS):
|
||||
# the if statements below use a modulo operation to identify the even and odd numbered LEDs
|
||||
if (i % 2) == 0:
|
||||
led_strip.set_hsv(i, HUE_1 / 360, 1.0, BRIGHTNESS)
|
||||
else:
|
||||
led_strip.set_hsv(i, HUE_2 / 360, 1.0, BRIGHTNESS)
|
||||
time.sleep(SPEED)
|
||||
|
||||
for i in range(NUM_LEDS):
|
||||
if (i % 2) == 0:
|
||||
led_strip.set_hsv(i, HUE_2 / 360, 1.0, BRIGHTNESS)
|
||||
else:
|
||||
led_strip.set_hsv(i, HUE_1 / 360, 1.0, BRIGHTNESS)
|
||||
time.sleep(SPEED)
|
|
@ -0,0 +1,119 @@
|
|||
import WIFI_CONFIG
|
||||
from network_manager import NetworkManager
|
||||
import uasyncio
|
||||
import urequests
|
||||
import time
|
||||
import plasma
|
||||
from plasma import plasma_stick
|
||||
from machine import Pin
|
||||
|
||||
'''
|
||||
This Plasma Stick example sets your LED strip to the current #cheerlights colour.
|
||||
Find out more about the Cheerlights API at https://cheerlights.com/
|
||||
'''
|
||||
|
||||
URL = 'http://api.thingspeak.com/channels/1417/field/2/last.json'
|
||||
UPDATE_INTERVAL = 120 # refresh interval in secs. Be nice to free APIs!
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
|
||||
def status_handler(mode, status, ip):
|
||||
# reports wifi connection status
|
||||
print(mode, status, ip)
|
||||
print('Connecting to wifi...')
|
||||
# flash while connecting
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_rgb(i, 255, 255, 255)
|
||||
time.sleep(0.02)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_rgb(i, 0, 0, 0)
|
||||
if status is not None:
|
||||
if status:
|
||||
print('Wifi connection successful!')
|
||||
else:
|
||||
print('Wifi connection failed!')
|
||||
# if no wifi connection, you get spooky rainbows. Bwahahaha!
|
||||
spooky_rainbows()
|
||||
|
||||
|
||||
def spooky_rainbows():
|
||||
print('SPOOKY RAINBOWS!')
|
||||
HUE_START = 30 # orange
|
||||
HUE_END = 140 # green
|
||||
SPEED = 0.3 # bigger = faster (harder, stronger)
|
||||
|
||||
distance = 0.0
|
||||
direction = SPEED
|
||||
while True:
|
||||
for i in range(NUM_LEDS):
|
||||
# generate a triangle wave that moves up and down the LEDs
|
||||
j = max(0, 1 - abs(distance - i) / (NUM_LEDS / 3))
|
||||
hue = HUE_START + j * (HUE_END - HUE_START)
|
||||
|
||||
led_strip.set_hsv(i, hue / 360, 1.0, 0.8)
|
||||
|
||||
# reverse direction at the end of colour segment to avoid an abrupt change
|
||||
distance += direction
|
||||
if distance > NUM_LEDS:
|
||||
direction = - SPEED
|
||||
if distance < 0:
|
||||
direction = SPEED
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
def hex_to_rgb(hex):
|
||||
# converts a hex colour code into RGB
|
||||
h = hex.lstrip('#')
|
||||
r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
|
||||
return r, g, b
|
||||
|
||||
|
||||
# set up the Pico W's onboard LED
|
||||
pico_led = Pin('LED', Pin.OUT)
|
||||
|
||||
# set up the WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
# 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}')
|
||||
# if no wifi, then you get...
|
||||
spooky_rainbows()
|
||||
|
||||
while True:
|
||||
# open the json file
|
||||
print(f'Requesting URL: {URL}')
|
||||
r = urequests.get(URL)
|
||||
# open the json data
|
||||
j = r.json()
|
||||
print('Data obtained!')
|
||||
r.close()
|
||||
|
||||
# flash the onboard LED after getting data
|
||||
pico_led.value(True)
|
||||
time.sleep(0.2)
|
||||
pico_led.value(False)
|
||||
|
||||
# extract hex colour from the data
|
||||
hex = j['field2']
|
||||
|
||||
# and convert it to RGB
|
||||
r, g, b = hex_to_rgb(hex)
|
||||
|
||||
# light up the LEDs
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_rgb(i, r, g, b)
|
||||
print(f'LEDs set to {hex}')
|
||||
|
||||
# sleep
|
||||
print(f'Sleeping for {UPDATE_INTERVAL} seconds.')
|
||||
time.sleep(UPDATE_INTERVAL)
|
|
@ -0,0 +1,55 @@
|
|||
import plasma
|
||||
from plasma import plasma_stick
|
||||
import breakout_scd41 as scd
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
|
||||
"""
|
||||
Reads CO2 level from an SCD41 breakout...
|
||||
... and changes the LED strip an appropriate colour.
|
||||
https://shop.pimoroni.com/products/scd41-co2-sensor-breakout
|
||||
"""
|
||||
|
||||
# set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
BRIGHTNESS = 1.0
|
||||
|
||||
# the range of readings to map to colours
|
||||
# https://www.kane.co.uk/knowledge-centre/what-are-safe-levels-of-co-and-co2-in-rooms
|
||||
MIN = 400
|
||||
MAX = 2000
|
||||
|
||||
# pick what bits of the colour wheel to use (from 0-360°)
|
||||
# https://www.cssscript.com/demo/hsv-hsl-color-wheel-picker-reinvented/
|
||||
HUE_START = 100 # green
|
||||
HUE_END = 0 # red
|
||||
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
# set up I2C
|
||||
i2c = PimoroniI2C(plasma_stick.SDA, plasma_stick.SCL)
|
||||
|
||||
# set up SCD41 breakout
|
||||
scd.init(i2c)
|
||||
scd.start()
|
||||
|
||||
print("Waiting for SCD41 to be ready")
|
||||
|
||||
while True:
|
||||
if scd.ready():
|
||||
co2, temperature, humidity = scd.measure()
|
||||
print(f"""
|
||||
CO2: {co2} ppm
|
||||
Temperature: {temperature:.2f} °C
|
||||
Humidity: {humidity:.2f} %""")
|
||||
|
||||
# calculates a colour
|
||||
hue = HUE_START + ((co2 - MIN) * (HUE_END - HUE_START) / (MAX - MIN))
|
||||
|
||||
# set the leds
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, hue / 360, 1.0, BRIGHTNESS)
|
|
@ -0,0 +1,78 @@
|
|||
from pimoroni_i2c import PimoroniI2C
|
||||
from breakout_encoder import BreakoutEncoder
|
||||
import plasma
|
||||
from plasma import plasma_stick
|
||||
|
||||
"""
|
||||
Change the colour of your LEDs easily by hooking up an RGB Encoder Breakout!
|
||||
"""
|
||||
|
||||
# set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
# make this number bigger for more precise colour adjustments
|
||||
STEPS_PER_REV = 24
|
||||
|
||||
i2c = PimoroniI2C(plasma_stick.SDA, plasma_stick.SCL)
|
||||
enc = BreakoutEncoder(i2c)
|
||||
|
||||
enc.set_brightness(1.0)
|
||||
# enc.set_direction(BreakoutEncoder.DIRECTION_CCW) # Uncomment this to flip the direction
|
||||
|
||||
|
||||
def hsv_to_rgb(h, s, v):
|
||||
# From CPython Lib/colorsys.py
|
||||
if s == 0.0:
|
||||
return v, v, v
|
||||
i = int(h * 6.0)
|
||||
f = (h * 6.0) - i
|
||||
p = v * (1.0 - s)
|
||||
q = v * (1.0 - s * f)
|
||||
t = v * (1.0 - s * (1.0 - f))
|
||||
i = i % 6
|
||||
if i == 0:
|
||||
return v, t, p
|
||||
if i == 1:
|
||||
return q, v, p
|
||||
if i == 2:
|
||||
return p, v, t
|
||||
if i == 3:
|
||||
return p, q, v
|
||||
if i == 4:
|
||||
return t, p, v
|
||||
if i == 5:
|
||||
return v, p, q
|
||||
|
||||
|
||||
def count_changed(count):
|
||||
print("Count: ", count, sep="")
|
||||
h = ((count % STEPS_PER_REV) * 360.0) / STEPS_PER_REV # Convert the count to a colour hue
|
||||
r, g, b = [int(255 * c) for c in hsv_to_rgb(h / 360.0, 1.0, 1.0)] # rainbow magic
|
||||
# set the encoder LED colour
|
||||
enc.set_led(r, g, b)
|
||||
# set the led strip to match
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_rgb(i, r, g, b)
|
||||
|
||||
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
count = 0
|
||||
|
||||
count_changed(count)
|
||||
|
||||
enc.clear_interrupt_flag()
|
||||
|
||||
while True:
|
||||
if enc.get_interrupt_flag():
|
||||
count = enc.read()
|
||||
enc.clear_interrupt_flag()
|
||||
|
||||
while count < 0:
|
||||
count += STEPS_PER_REV
|
||||
|
||||
count_changed(count)
|
|
@ -0,0 +1,23 @@
|
|||
import plasma
|
||||
from plasma import plasma_stick
|
||||
import time
|
||||
from random import random, uniform
|
||||
|
||||
"""
|
||||
A basic fire effect.
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
while True:
|
||||
# fire effect! Random red/orange hue, full saturation, random brightness
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, uniform(0.0, 50 / 360), 1.0, random())
|
||||
time.sleep(0.1)
|
|
@ -0,0 +1,56 @@
|
|||
import time
|
||||
import plasma
|
||||
from plasma import plasma_stick
|
||||
from machine import RTC
|
||||
|
||||
"""
|
||||
Spooky moon simulator!
|
||||
The LEDs will get brighter as midnight approaches!
|
||||
It won't do much in the day...
|
||||
Needs to be run from Thonny to get the right time.
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
# pick a hue (0 - 360° on the colour wheel)
|
||||
# warm white moon - 60, blue moon - 230 , blood moon - 0
|
||||
HUE = 60
|
||||
SATURATION = 0.2 # increase this for a more colourful moon (max 1.0)
|
||||
|
||||
# when to start counting down from, in seconds before midnight
|
||||
# eg from 10pm = 2 hours = 2 * 60 * 60 = 7200
|
||||
COUNT_FROM = 14400
|
||||
|
||||
# set up the WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
while True:
|
||||
# get the time from Pico RTC
|
||||
year, month, day, _, hour, minute, second, _ = RTC().datetime()
|
||||
print(f'Time is {hour:02d}:{minute:02d}')
|
||||
|
||||
# calculate how long to go until midnight
|
||||
if hour >= 12:
|
||||
hours_to_go = 23 - hour
|
||||
minutes_to_go = 59 - minute
|
||||
seconds_to_go = 59 - second
|
||||
total_seconds = hours_to_go * 60 * 60 + minutes_to_go * 60 + seconds_to_go
|
||||
print(f'{total_seconds} seconds until midnight')
|
||||
# or, if it's after midnight
|
||||
else:
|
||||
hours_since = 0 + hour
|
||||
minutes_since = 0 + minute
|
||||
seconds_since = 0 + second
|
||||
total_seconds = hours_since * 60 * 60 + minutes_since * 60 + seconds_since
|
||||
print(f'{total_seconds} seconds since midnight')
|
||||
|
||||
# gets brighter as witching hour approacheth
|
||||
brightness = max(0, (COUNT_FROM - total_seconds) / COUNT_FROM)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE / 360.0, SATURATION, brightness)
|
||||
print(f'Brightness - {brightness * 100} %')
|
||||
time.sleep(10.0)
|
|
@ -0,0 +1,83 @@
|
|||
import time
|
||||
import plasma
|
||||
from plasma import plasma_stick
|
||||
from machine import RTC
|
||||
|
||||
"""
|
||||
Spooky moon simulator!
|
||||
The LEDs will get brighter as midnight approaches!
|
||||
It won't do much in the day...
|
||||
Gets the time from a connected RV3028 RTC breakout:
|
||||
https://shop.pimoroni.com/products/rv3028-real-time-clock-rtc-breakout
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
# pick a hue (0 - 360° on the colour wheel)
|
||||
# warm white moon - 60, blue moon - 230 , blood moon - 0
|
||||
HUE = 60
|
||||
SATURATION = 0.2 # increase this for a more colourful moon (max 1.0)
|
||||
|
||||
# when to start counting down from, in seconds before midnight
|
||||
# eg from 10pm = 2 hours = 2 * 60 * 60 = 7200
|
||||
COUNT_FROM = 14400
|
||||
|
||||
|
||||
def set_pico_time():
|
||||
# the function sets the Pico's RTC from a RV3028 RTC breakout
|
||||
# to setup breakout (and set the time) run this first:
|
||||
# https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/breakout_rtc/set-time.py
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
from breakout_rtc import BreakoutRTC
|
||||
from machine import RTC
|
||||
|
||||
# set up I2C
|
||||
i2c = PimoroniI2C(plasma_stick.SDA, plasma_stick.SCL)
|
||||
|
||||
# set up the RTC breakout
|
||||
RV3028 = BreakoutRTC(i2c)
|
||||
|
||||
# set the Pico's RTC from the RTC breakout
|
||||
RV3028.update_time()
|
||||
RTC().datetime([RV3028.get_year(), RV3028.get_month(), RV3028.get_date(),
|
||||
RV3028.get_weekday(), RV3028.get_hours(), RV3028.get_minutes(),
|
||||
RV3028.get_seconds(), 0])
|
||||
print(f"Pico RTC set to breakout time: {RV3028.string_date()} {RV3028.string_time()}")
|
||||
|
||||
|
||||
# set up the WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
# call our function to set the Pico's RTC
|
||||
set_pico_time()
|
||||
|
||||
while True:
|
||||
# get the time from Pico RTC
|
||||
year, month, day, _, hour, minute, second, _ = RTC().datetime()
|
||||
print(f'Time is {hour:02d}:{minute:02d}')
|
||||
|
||||
# calculate how long to go until midnight
|
||||
if hour >= 12:
|
||||
hours_to_go = 23 - hour
|
||||
minutes_to_go = 59 - minute
|
||||
seconds_to_go = 59 - second
|
||||
total_seconds = hours_to_go * 60 * 60 + minutes_to_go * 60 + seconds_to_go
|
||||
print(f'{total_seconds} seconds until midnight')
|
||||
# or, if it's after midnight
|
||||
else:
|
||||
hours_since = 0 + hour
|
||||
minutes_since = 0 + minute
|
||||
seconds_since = 0 + second
|
||||
total_seconds = hours_since * 60 * 60 + minutes_since * 60 + seconds_since
|
||||
print(f'{total_seconds} seconds since midnight')
|
||||
|
||||
# gets brighter as witching hour approacheth
|
||||
brightness = max(0, (COUNT_FROM - total_seconds) / COUNT_FROM)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE / 360.0, SATURATION, brightness)
|
||||
print(f'Brightness - {brightness * 100} %')
|
||||
time.sleep(10.0)
|
|
@ -0,0 +1,102 @@
|
|||
import plasma
|
||||
from plasma import plasma_stick
|
||||
from machine import Pin
|
||||
import time
|
||||
from random import random, uniform, choice
|
||||
|
||||
"""
|
||||
This example uses a PIR sensor to trigger some spooky effects.
|
||||
We connected ours up to our QW/ST connector.
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
BRIGHTNESS = 0.8
|
||||
|
||||
HUE = 0.8
|
||||
|
||||
# randomly pick a different colour every time an effect fires
|
||||
RANDOM = True
|
||||
|
||||
|
||||
def spooky_flash():
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, BRIGHTNESS / 2)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, 0.0)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, BRIGHTNESS / 2)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, 0.0)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, BRIGHTNESS / 2)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, 0.0)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, BRIGHTNESS)
|
||||
time.sleep(3)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, BRIGHTNESS / 2)
|
||||
time.sleep(0.1)
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, 0.0)
|
||||
time.sleep(0.1)
|
||||
# uncomment next line to increase tension
|
||||
# time.sleep(randrange(0, 15))
|
||||
|
||||
|
||||
def fire():
|
||||
while pir.value() == 1:
|
||||
# fire effect! Random red/orange hue, full saturation, random brightness
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, uniform(0.0, 50 / 360), 1.0, random())
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def all_on():
|
||||
while pir.value == 1:
|
||||
# light up a solid colour while movement is detected
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, HUE, 1.0, BRIGHTNESS)
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
# set up the hardware
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# the pin the signal line of our PIR sensor is connected to
|
||||
# if you're using one of our qw/st > DuPont cables the blue wire is SDA (GP4) and the yellow wire is SCL (GP5)
|
||||
pir = Pin(plasma_stick.SCL, Pin.IN, Pin.PULL_UP)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
while True:
|
||||
# on movement
|
||||
if pir.value() == 1:
|
||||
print("Movement detected!")
|
||||
# pick a random colour
|
||||
if RANDOM is True:
|
||||
HUE = random()
|
||||
|
||||
# pick a random effect
|
||||
effects = [spooky_flash, fire, all_on]
|
||||
choice(effects)()
|
||||
|
||||
# if you want a specific effect, comment the lines above and uncomment one of these:
|
||||
# spooky_flash()
|
||||
# fire()
|
||||
# all_on()
|
||||
|
||||
else:
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, 1.0, 1.0, 0.0)
|
||||
time.sleep(0.1)
|
|
@ -0,0 +1,36 @@
|
|||
import plasma
|
||||
from plasma import plasma_stick
|
||||
import time
|
||||
|
||||
"""
|
||||
Make some rainbows!
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
# The SPEED that the LEDs cycle at (1 - 255)
|
||||
SPEED = 20
|
||||
|
||||
# How many times the LEDs will be updated per second
|
||||
UPDATES = 60
|
||||
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
offset = 0.0
|
||||
|
||||
# Make rainbows
|
||||
while True:
|
||||
|
||||
SPEED = min(255, max(1, SPEED))
|
||||
offset += float(SPEED) / 2000.0
|
||||
|
||||
for i in range(NUM_LEDS):
|
||||
hue = float(i) / NUM_LEDS
|
||||
led_strip.set_hsv(i, hue + offset, 1.0, 1.0)
|
||||
|
||||
time.sleep(1.0 / UPDATES)
|
|
@ -0,0 +1,54 @@
|
|||
import plasma
|
||||
from plasma import plasma_stick
|
||||
import time
|
||||
from breakout_bme280 import BreakoutBME280
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
|
||||
"""
|
||||
Reads the temperature from a BME280 breakout...
|
||||
...and changes the LED strip an appropriate colour.
|
||||
https://shop.pimoroni.com/products/bme280-breakout
|
||||
"""
|
||||
|
||||
# Set how many LEDs you have
|
||||
NUM_LEDS = 50
|
||||
|
||||
BRIGHTNESS = 1.0
|
||||
|
||||
# The range of readings that we want to map to colours
|
||||
MIN = 10
|
||||
MAX = 30
|
||||
|
||||
# pick what bits of the colour wheel to use (from 0-360°)
|
||||
# https://www.cssscript.com/demo/hsv-hsl-color-wheel-picker-reinvented/
|
||||
HUE_START = 230 # blue
|
||||
HUE_END = 359 # red
|
||||
|
||||
# WS2812 / NeoPixel™ LEDs
|
||||
led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma_stick.DAT, color_order=plasma.COLOR_ORDER_RGB)
|
||||
|
||||
# Start updating the LED strip
|
||||
led_strip.start()
|
||||
|
||||
# set up I2C
|
||||
i2c = PimoroniI2C(plasma_stick.SDA, plasma_stick.SCL)
|
||||
|
||||
# set up BME280 breakout
|
||||
bme = BreakoutBME280(i2c)
|
||||
|
||||
while True:
|
||||
temperature, pressure, humidity = bme.read()
|
||||
print(f"""
|
||||
Temperature: {temperature:.2f} °C
|
||||
Humidity: {humidity:.2f} %
|
||||
Pressure: {pressure / 100:.2f} hPa
|
||||
""")
|
||||
|
||||
# calculates a colour
|
||||
hue = HUE_START + ((temperature - MIN) * (HUE_END - HUE_START) / (MAX - MIN))
|
||||
|
||||
# set the leds
|
||||
for i in range(NUM_LEDS):
|
||||
led_strip.set_hsv(i, hue / 360, 1.0, BRIGHTNESS)
|
||||
|
||||
time.sleep(0.5)
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue