Initial setup for StellarUnicorn build

pull/786/head
ZodiusInfuser 2023-05-30 10:32:25 +01:00
rodzic bd4238945d
commit aabe789f21
22 zmienionych plików z 2250 dodań i 1 usunięć

Wyświetl plik

@ -86,6 +86,8 @@ jobs:
board: PICO_W
- name: cosmic_unicorn
board: PICO_W
- name: stellar_unicorn
board: PICO_W
- name: inky_frame
board: PICO_W_INKY
patch: true

Wyświetl plik

@ -61,3 +61,4 @@ add_subdirectory(encoder)
add_subdirectory(galactic_unicorn)
add_subdirectory(gfx_pack)
add_subdirectory(cosmic_unicorn)
add_subdirectory(stellar_unicorn)

Wyświetl plik

@ -42,3 +42,4 @@ add_subdirectory(galactic_unicorn)
add_subdirectory(gfx_pack)
add_subdirectory(interstate75)
add_subdirectory(cosmic_unicorn)
add_subdirectory(stellar_unicorn)

Wyświetl plik

@ -0,0 +1 @@
include(stellar_unicorn.cmake)

Wyświetl plik

@ -0,0 +1,259 @@
# Stellar Unicorn (C/C++)<!-- omit in toc -->
Stellar Unicorn offers 16x16 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
You can buy one here: https://shop.pimoroni.com/products/stellar-unicorn
## These are not your everyday RGB LEDs!
Internally Stellar Unicorn applies gamma correction to the supplied image data and updates the display with 14-bit precision resulting in extremely linear visual output - including at the low end.
The display is refreshed around 300 times per second (300fps!) allowing for rock solid stability even when being filmed, no smearing or flickering even when in motion.
No strobing or brightness stepping here folks - it's the perfect backdrop for your tricked out streaming setup!
## Getting started
The Stellar Unicorn library provides a collection of methods that allow you to easily access all of the features on the board.
Drawing is primarily handled via our [PicoGraphics](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_graphics) library which provides a comprehensive selection of drawing methods - once your drawing work is complete you pass the PicoGraphics object to Stellar Unicorn to have it displayed on the screen.
- [Example Program](#example-program)
- [Interleaved Framebuffer](#interleaved-framebuffer)
- [Function Reference](#function-reference)
- [System State](#system-state)
- [`void init()`](#void-init)
- [`void set_brightness(float value)`](#void-set_brightnessfloat-value)
- [`float get_brightness()`](#float-get_brightness)
- [`void adjust_brightness(float delta)`](#void-adjust_brightnessfloat-delta)
- [`void set_volume(float value)`](#void-set_volumefloat-value)
- [`float get_volume()`](#float-get_volume)
- [`void adjust_volume(float delta)`](#void-adjust_volumefloat-delta)
- [`uint16_t light()`](#uint16_t-light)
- [`bool is_pressed(uint8_t button)`](#bool-is_presseduint8_t-button)
- [Drawing](#drawing)
- [`void update(PicoGraphics *graphics)`](#void-updatepicographics-graphics)
- [`void clear()`](#void-clear)
- [`void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b)`](#void-set_pixelint-x-int-y-uint8_t-r-uint8_t-g-uint8_t-b)
- [Audio](#audio)
- [`void play_sample(uint8_t *data, uint32_t length)`](#void-play_sampleuint8_t-data-uint32_t-length)
- [`AudioChannel& synth_channel(uint channel)`](#audiochannel-synth_channeluint-channel)
- [`void play_synth()`](#void-play_synth)
- [`void stop_playing()`](#void-stop_playing)
- [Constants](#constants)
- [`WIDTH` \& `HEIGHT`](#width--height)
# Example Program
The following example shows how to scroll a simple message across the display.
```c++
#include <stdio.h>
#include <stdlib.h>
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "stellar_unicorn.hpp"
using namespace pimoroni;
// create a PicoGraphics framebuffer to draw into
PicoGraphics_PenRGB888 graphics(StellarUnicorn::WIDTH, StellarUnicorn::HEIGHT, nullptr);
// create our StellarUnicorn object
StellarUnicorn stellar_unicorn;
// message to scroll
std::string message = "Pirate. Monkey. Robot. Ninja.";
int main() {
stdio_init_all();
// initialise the StellarUnicorn object
stellar_unicorn.init();
// start position for scrolling (off the side of the display)
float scroll = -(float)StellarUnicorn::WIDTH;
while(true) {
// determine the scroll position of the text
int width = graphics.measure_text(message, 1);
scroll += 0.25f;
if(scroll > width) {
scroll = -(float)StellarUnicorn::WIDTH;
}
// clear the graphics object
graphics.set_pen(0, 0, 0);
graphics.clear();
// draw the text
graphics.set_pen(255, 255, 0); // a nice yellow
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
// update the display
stellar_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}
```
# Interleaved Framebuffer
Stellar Unicorn takes advantage of the RP2040's PIOs to drive screen updates - this is what gives it the performance it needs to render with 14-bit precision at over 300 frames per second.
The PIO is a powerful, but limited, tool. It has no way to access memory at random and minimal support for decision making and branching. All it can really do is process a stream of data/instructions in order.
This means that we need to be clever about the way we pass data into the PIO program, the information needs to be delivered in the exact order that the PIO will need to process it. To achieve this we "interleave" our framebuffer - each frame of BCM data is passed one after another with values for the current row, pixel count, and timing inserted as needed:
row 0 data:
for each bcd frame:
bit : data
0: 00110110 // row pixel count (minus one)
1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
56: xxxxrrrr // row select bits
57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
row 1 data:
...
If you're working with our library then you don't need to worry about any of these details, they are handled for you.
# Function Reference
## System State
### `void init()`
Initialise the Stellar Unicorn hardware, interleaved framebuffer, and PIO programs. This function must be called before attempting to do anything else with Stellar Unicorn.
### `void set_brightness(float value)`
Set the brightness - `value` is supplied as a floating point value between `0.0` and `1.0`.
### `float get_brightness()`
Returns the current brightness as a value between `0.0` to `1.0`.
### `void adjust_brightness(float delta)`
Adjust the brightness of the display - `delta` is supplied as a floating point value and will be added to the current brightness (and then clamped to the range `0.0` to `1.0`).
For example:
```c++
stellar.set_brightness(0.5f);
stellar.adjust_brightness(0.1f); // brightness is now 0.6
stellar.adjust_brightness(0.7f); // brightness is now 1.0
stellar.adjust_brightness(-0.2f); // brightness is now 0.8
```
### `void set_volume(float value)`
Set the volume - `value` is supplied as a floating point value between `0.0` and `1.0`.
### `float get_volume()`
Returns the current volume as a value between `0.0` and `1.0`.
### `void adjust_volume(float delta)`
Adjust the volume - `delta` is supplied as a floating point value and will be added to the current volume (and then clamped to the range `0.0` to `1.0`).
For example:
```c++
stellar.set_volume(0.5f);
stellar.adjust_volume(0.1f); // volume is now 0.6
stellar.adjust_volume(0.7f); // volume is now 1.0
stellar.adjust_volume(-0.2f); // volume is now 0.8
```
### `uint16_t light()`
Get the current value seen by the onboard light sensor as a value between `0` and `4095`.
### `bool is_pressed(uint8_t button)`
Returns true if the requested `button` is currently pressed.
There are a set of constants on the StellarUnicorn class that represent each of the buttons. The brightness, sleep, and volume buttons are not tied to hardware functions (they are implemented entirely in software) so can also be used for user functions if preferred.
```c++
static const uint8_t SWITCH_A = 0;
static const uint8_t SWITCH_B = 1;
static const uint8_t SWITCH_C = 3;
static const uint8_t SWITCH_D = 6;
static const uint8_t SWITCH_SLEEP = 27;
static const uint8_t SWITCH_VOLUME_UP = 7;
static const uint8_t SWITCH_VOLUME_DOWN = 8;
static const uint8_t SWITCH_BRIGHTNESS_UP = 21;
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 26;
```
For example:
```c++
while(!stellar.is_pressed(StellarUnicorn::SWITCH_A)) {
// wait for switch A to be pressed
}
printf("We did it! We pressed switch A! Heck yeah!");
```
## Drawing
### `void update(PicoGraphics *graphics)`
**This is our recommended way to update the image on Stellar Unicorn.** The PicoGraphics library provides a collection of powerful drawing methods to make things simple.
The image on the PicoGraphics object provided is copied to the interleaved framebuffer with gamma correction applied. This lets you have multiple PicoGraphics objects on the go at once and switch between them by changing which gets passed into this function.
If however you'd rather twiddle individual pixels (for example you're producing some sort of algorithmic output) then you can simply use the `clear()` and `set_pixel()` methods mentioned below.
### `void clear()`
Clear the contents of the interleaved framebuffer. This will make your Stellar Unicorn display turn off when the next frame is displayed.
If you're using PicoGraphics to build your image (recommended!) then you won't need to call this method as you'll overwrite the entire display when you call `update()` anyway.
### `void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b)`
Set a single pixel to the specified colour. The newly set colour will be shown at the next frame. Pixel coordinates go from `0` to `52` along the `x` axis and from `0` to `10` on the `y` axis. Colour values are specified as a `0` to `255` RGB triplet - the supplied colour will be gamma corrected automatically.
When drawing a full image it's recommended that you keep the time between each `set_pixel` call short to ensure your image gets displayed on the next frame. Otherwise you can get scanning-like visual artefacts (unless that is your intention of course!)
## Audio
Audio functionality is supported by our [PicoSynth library](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_synth) which allows you to create multiple voice channels with ADSR (attack decay sustain release) envelopes. It provides a similar set of functionality to the classic SID chip in the Commodore 64.
### `void play_sample(uint8_t *data, uint32_t length)`
Play the provided 16-bit audio sample. `data` must point to a buffer that contains 16-bit PCM data and `length` must be the number of samples.
### `AudioChannel& synth_channel(uint channel)`
Gets an `AudioChannel` object which can then be configured with voice, ADSR envelope, etc.
### `void play_synth()`
Start the synth playing.
### `void stop_playing()`
Stops any currently playing audio.
## Constants
### `WIDTH` & `HEIGHT`
The width and height of Stellar Unicorn are available in constants `WIDTH` and `HEIGHT`.
For example:
```c++
int num_pixels = StellarUnicorn::WIDTH * StellarUnicorn::HEIGHT;
```

Wyświetl plik

@ -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));
}
%}

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,595 @@
#include <math.h>
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/adc.h"
#include "hardware/clocks.h"
#include "stellar_unicorn.pio.h"
#include "audio_i2s.pio.h"
#include "stellar_unicorn.hpp"
// pixel data is stored as a stream of bits delivered in the
// order the PIO needs to manage the shift registers, row
// selects, delays, and latching/blanking
//
// the pins used are:
//
// - 13: column clock (sideset)
// - 14: column data (out base)
// - 15: column latch
// - 16: column blank
// - 17: row select bit 0
// - 18: row select bit 1
// - 19: row select bit 2
// - 20: row select bit 3
//
// the framebuffer data is structured like this:
//
// for each row:
// for each bcd frame:
// 0: 00111111 // row pixel count (minus one)
// 1: xxxxrrrr // row select bits
// 2 - 65: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
// 66 - 67: xxxxxxxx, xxxxxxxx, // dummy bytes to dword align
// 68 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
//
// .. and back to the start
static 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 {
StellarUnicorn* StellarUnicorn::unicorn = nullptr;
PIO StellarUnicorn::bitstream_pio = pio0;
uint StellarUnicorn::bitstream_sm = 0;
uint StellarUnicorn::bitstream_sm_offset = 0;
PIO StellarUnicorn::audio_pio = pio0;
uint StellarUnicorn::audio_sm = 0;
uint StellarUnicorn::audio_sm_offset = 0;
// once the dma transfer of the scanline is complete we move to the
// next scanline (or quit if we're finished)
void __isr StellarUnicorn::dma_complete() {
if(unicorn != nullptr && dma_channel_get_irq0_status(audio_dma_channel)) {
unicorn->next_audio_sequence();
}
}
StellarUnicorn::~StellarUnicorn() {
if(unicorn == this) {
partial_teardown();
dma_channel_unclaim(dma_ctrl_channel); // This works now the teardown behaves correctly
dma_channel_unclaim(dma_channel); // This works now the teardown behaves correctly
pio_sm_unclaim(bitstream_pio, bitstream_sm);
pio_remove_program(bitstream_pio, &stellar_unicorn_program, bitstream_sm_offset);
dma_channel_unclaim(audio_dma_channel); // This works now the teardown behaves correctly
pio_sm_unclaim(audio_pio, audio_sm);
pio_remove_program(audio_pio, &audio_i2s_program, audio_sm_offset);
irq_remove_handler(DMA_IRQ_0, dma_complete);
unicorn = nullptr;
}
}
void StellarUnicorn::partial_teardown() {
// Stop the bitstream SM
pio_sm_set_enabled(bitstream_pio, bitstream_sm, false);
// Make sure the display is off and switch it to an invisible row, to be safe
const uint pins_to_set = 1 << COLUMN_BLANK | 0b1111 << ROW_BIT_0;
pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set);
dma_hw->ch[dma_ctrl_channel].al1_ctrl = (dma_hw->ch[dma_ctrl_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_ctrl_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
dma_hw->ch[dma_channel].al1_ctrl = (dma_hw->ch[dma_channel].al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
// Abort any in-progress DMA transfer
dma_safe_abort(dma_ctrl_channel);
//dma_channel_abort(dma_ctrl_channel);
//dma_channel_abort(dma_channel);
dma_safe_abort(dma_channel);
// Stop the audio SM
pio_sm_set_enabled(audio_pio, audio_sm, false);
// Reset the I2S pins to avoid popping when audio is suddenly stopped
const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK;
pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear);
// Abort any in-progress DMA transfer
dma_safe_abort(audio_dma_channel);
}
uint16_t StellarUnicorn::light() {
adc_select_input(2);
return adc_read();
}
void StellarUnicorn::init() {
if(unicorn != nullptr) {
// Tear down the old GU instance's hardware resources
partial_teardown();
}
// create 14-bit gamma luts
for(uint16_t v = 0; v < 256; v++) {
// gamma correct the provided 0-255 brightness value onto a
// 0-65535 range for the pwm counter
float r_gamma = 1.8f;
r_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, r_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
float g_gamma = 1.8f;
g_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, g_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
float b_gamma = 1.8f;
b_gamma_lut[v] = (uint16_t)(powf((float)(v) / 255.0f, b_gamma) * (float(1U << (BCD_FRAME_COUNT)) - 1.0f) + 0.5f);
}
// for each row:
// for each bcd frame:
// 0: 00111111 // row pixel count (minus one)
// 1 - 64: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
// 65 - 67: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
// 68: xxxrrrrr // row select bits
// 69 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
//
// .. and back to the start
// initialise the bcd timing values and row selects in the bitstream
for(uint8_t row = 0; row < 16; row++) {
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
// find the offset of this row and frame in the bitstream
uint8_t *p = &bitstream[row * ROW_BYTES + (BCD_FRAME_BYTES * frame)];
p[ 0] = 64 - 1; // row pixel count
p[ 1] = row; // row select
// set the number of bcd ticks for this frame
uint32_t bcd_ticks = (1 << frame);
p[68] = (bcd_ticks & 0xff) >> 0;
p[69] = (bcd_ticks & 0xff00) >> 8;
p[70] = (bcd_ticks & 0xff0000) >> 16;
p[71] = (bcd_ticks & 0xff000000) >> 24;
}
}
// setup light sensor adc
adc_init();
adc_gpio_init(LIGHT_SENSOR);
gpio_init(COLUMN_CLOCK); gpio_set_dir(COLUMN_CLOCK, GPIO_OUT); gpio_put(COLUMN_CLOCK, false);
gpio_init(COLUMN_DATA); gpio_set_dir(COLUMN_DATA, GPIO_OUT); gpio_put(COLUMN_DATA, false);
gpio_init(COLUMN_LATCH); gpio_set_dir(COLUMN_LATCH, GPIO_OUT); gpio_put(COLUMN_LATCH, false);
gpio_init(COLUMN_BLANK); gpio_set_dir(COLUMN_BLANK, GPIO_OUT); gpio_put(COLUMN_BLANK, true);
// initialise the row select, and set them to a non-visible row to avoid flashes during setup
gpio_init(ROW_BIT_0); gpio_set_dir(ROW_BIT_0, GPIO_OUT); gpio_put(ROW_BIT_0, true);
gpio_init(ROW_BIT_1); gpio_set_dir(ROW_BIT_1, GPIO_OUT); gpio_put(ROW_BIT_1, true);
gpio_init(ROW_BIT_2); gpio_set_dir(ROW_BIT_2, GPIO_OUT); gpio_put(ROW_BIT_2, true);
gpio_init(ROW_BIT_3); gpio_set_dir(ROW_BIT_3, GPIO_OUT); gpio_put(ROW_BIT_3, true);
sleep_ms(100);
// configure full output current in register 2
uint16_t reg1 = 0b1111111111001110;
// clock the register value to the first 11 driver chips
for(int j = 0; j < 11; j++) {
for(int i = 0; i < 16; i++) {
if(reg1 & (1U << (15 - i))) {
gpio_put(COLUMN_DATA, true);
}else{
gpio_put(COLUMN_DATA, false);
}
sleep_us(10);
gpio_put(COLUMN_CLOCK, true);
sleep_us(10);
gpio_put(COLUMN_CLOCK, false);
}
}
// clock the last chip and latch the value
for(int i = 0; i < 16; i++) {
if(reg1 & (1U << (15 - i))) {
gpio_put(COLUMN_DATA, true);
}else{
gpio_put(COLUMN_DATA, false);
}
sleep_us(10);
gpio_put(COLUMN_CLOCK, true);
sleep_us(10);
gpio_put(COLUMN_CLOCK, false);
if(i == 4) {
gpio_put(COLUMN_LATCH, true);
}
}
gpio_put(COLUMN_LATCH, false);
// reapply the blank as the above seems to cause a slight glow.
// Note, this will produce a brief flash if a visible row is selected (which it shouldn't be)
gpio_put(COLUMN_BLANK, false);
sleep_us(10);
gpio_put(COLUMN_BLANK, true);
gpio_init(MUTE); gpio_set_dir(MUTE, GPIO_OUT); gpio_put(MUTE, true);
// setup button inputs
gpio_init(SWITCH_A); gpio_pull_up(SWITCH_A);
gpio_init(SWITCH_B); gpio_pull_up(SWITCH_B);
gpio_init(SWITCH_C); gpio_pull_up(SWITCH_C);
gpio_init(SWITCH_D); gpio_pull_up(SWITCH_D);
gpio_init(SWITCH_SLEEP); gpio_pull_up(SWITCH_SLEEP);
gpio_init(SWITCH_BRIGHTNESS_UP); gpio_pull_up(SWITCH_BRIGHTNESS_UP);
gpio_init(SWITCH_BRIGHTNESS_DOWN); gpio_pull_up(SWITCH_BRIGHTNESS_DOWN);
gpio_init(SWITCH_VOLUME_UP); gpio_pull_up(SWITCH_VOLUME_UP);
gpio_init(SWITCH_VOLUME_DOWN); gpio_pull_up(SWITCH_VOLUME_DOWN);
// setup the pio if it has not previously been set up
bitstream_pio = pio0;
if(unicorn == nullptr) {
bitstream_sm = pio_claim_unused_sm(bitstream_pio, true);
bitstream_sm_offset = pio_add_program(bitstream_pio, &stellar_unicorn_program);
}
pio_gpio_init(bitstream_pio, COLUMN_CLOCK);
pio_gpio_init(bitstream_pio, COLUMN_DATA);
pio_gpio_init(bitstream_pio, COLUMN_LATCH);
pio_gpio_init(bitstream_pio, COLUMN_BLANK);
pio_gpio_init(bitstream_pio, ROW_BIT_0);
pio_gpio_init(bitstream_pio, ROW_BIT_1);
pio_gpio_init(bitstream_pio, ROW_BIT_2);
pio_gpio_init(bitstream_pio, ROW_BIT_3);
// set the blank and row pins to be high, then set all led driving pins as outputs.
// This order is important to avoid a momentary flash
const uint pins_to_set = 1 << COLUMN_BLANK | 0b1111 << ROW_BIT_0;
pio_sm_set_pins_with_mask(bitstream_pio, bitstream_sm, pins_to_set, pins_to_set);
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, COLUMN_CLOCK, 8, true);
pio_sm_config c = stellar_unicorn_program_get_default_config(bitstream_sm_offset);
// osr shifts right, autopull on, autopull threshold 8
sm_config_set_out_shift(&c, true, true, 32);
// configure out, set, and sideset pins
sm_config_set_out_pins(&c, ROW_BIT_0, 4);
sm_config_set_set_pins(&c, COLUMN_DATA, 3);
sm_config_set_sideset_pins(&c, COLUMN_CLOCK);
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// setup dma transfer for pixel data to the pio
//if(unicorn == nullptr) {
dma_channel = dma_claim_unused_channel(true);
dma_ctrl_channel = dma_claim_unused_channel(true);
//}
dma_channel_config ctrl_config = dma_channel_get_default_config(dma_ctrl_channel);
channel_config_set_transfer_data_size(&ctrl_config, DMA_SIZE_32);
channel_config_set_read_increment(&ctrl_config, false);
channel_config_set_write_increment(&ctrl_config, false);
channel_config_set_chain_to(&ctrl_config, dma_channel);
dma_channel_configure(
dma_ctrl_channel,
&ctrl_config,
&dma_hw->ch[dma_channel].read_addr,
&bitstream_addr,
1,
false
);
dma_channel_config config = dma_channel_get_default_config(dma_channel);
channel_config_set_transfer_data_size(&config, DMA_SIZE_32);
channel_config_set_bswap(&config, false); // byte swap to reverse little endian
channel_config_set_dreq(&config, pio_get_dreq(bitstream_pio, bitstream_sm, true));
channel_config_set_chain_to(&config, dma_ctrl_channel);
dma_channel_configure(
dma_channel,
&config,
&bitstream_pio->txf[bitstream_sm],
NULL,
BITSTREAM_LENGTH / 4,
false);
pio_sm_init(bitstream_pio, bitstream_sm, bitstream_sm_offset, &c);
pio_sm_set_enabled(bitstream_pio, bitstream_sm, true);
// start the control channel
dma_start_channel_mask(1u << dma_ctrl_channel);
// setup audio pio program
audio_pio = pio0;
if(unicorn == nullptr) {
audio_sm = pio_claim_unused_sm(audio_pio, true);
audio_sm_offset = pio_add_program(audio_pio, &audio_i2s_program);
}
pio_gpio_init(audio_pio, I2S_DATA);
pio_gpio_init(audio_pio, I2S_BCLK);
pio_gpio_init(audio_pio, I2S_LRCLK);
audio_i2s_program_init(audio_pio, audio_sm, audio_sm_offset, I2S_DATA, I2S_BCLK);
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
uint32_t divider = system_clock_frequency * 4 / SYSTEM_FREQ; // avoid arithmetic overflow
pio_sm_set_clkdiv_int_frac(audio_pio, audio_sm, divider >> 8u, divider & 0xffu);
audio_dma_channel = dma_claim_unused_channel(true);
dma_channel_config audio_config = dma_channel_get_default_config(audio_dma_channel);
channel_config_set_transfer_data_size(&audio_config, DMA_SIZE_16);
//channel_config_set_bswap(&audio_config, false); // byte swap to reverse little endian
channel_config_set_dreq(&audio_config, pio_get_dreq(audio_pio, audio_sm, true));
dma_channel_configure(audio_dma_channel, &audio_config, &audio_pio->txf[audio_sm], NULL, 0, false);
dma_channel_set_irq0_enabled(audio_dma_channel, true);
if(unicorn == nullptr) {
irq_add_shared_handler(DMA_IRQ_0, dma_complete, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
irq_set_enabled(DMA_IRQ_0, true);
}
unicorn = this;
}
void StellarUnicorn::clear() {
if(unicorn == this) {
for(uint8_t y = 0; y < HEIGHT; y++) {
for(uint8_t x = 0; x < WIDTH; x++) {
set_pixel(x, y, 0, 0, 0);
}
}
}
}
void StellarUnicorn::dma_safe_abort(uint channel) {
// Tear down the DMA channel.
// This is copied from: https://github.com/raspberrypi/pico-sdk/pull/744/commits/5e0e8004dd790f0155426e6689a66e08a83cd9fc
uint32_t irq0_save = dma_hw->inte0 & (1u << channel);
hw_clear_bits(&dma_hw->inte0, irq0_save);
dma_hw->abort = 1u << channel;
// To fence off on in-flight transfers, the BUSY bit should be polled
// rather than the ABORT bit, because the ABORT bit can clear prematurely.
while (dma_hw->ch[channel].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS) tight_loop_contents();
// Clear the interrupt (if any) and restore the interrupt masks.
dma_hw->ints0 = 1u << channel;
hw_set_bits(&dma_hw->inte0, irq0_save);
}
void StellarUnicorn::play_sample(uint8_t *data, uint32_t length) {
stop_playing();
if(unicorn == this) {
// Restart the audio SM and start a new DMA transfer
pio_sm_set_enabled(audio_pio, audio_sm, true);
dma_channel_transfer_from_buffer_now(audio_dma_channel, data, length / 2);
play_mode = PLAYING_BUFFER;
}
}
void StellarUnicorn::play_synth() {
if(play_mode != PLAYING_SYNTH) {
stop_playing();
}
if(unicorn == this && play_mode == NOT_PLAYING) {
// Nothing is playing, so we can set up the first buffer straight away
current_buffer = 0;
populate_next_synth();
// Restart the audio SM and start a new DMA transfer
pio_sm_set_enabled(audio_pio, audio_sm, true);
play_mode = PLAYING_SYNTH;
next_audio_sequence();
}
}
void StellarUnicorn::next_audio_sequence() {
// Clear any interrupt request caused by our channel
//dma_channel_acknowledge_irq0(audio_dma_channel);
// NOTE Temporary replacement of the above until this reaches pico-sdk main:
// https://github.com/raspberrypi/pico-sdk/issues/974
dma_hw->ints0 = 1u << audio_dma_channel;
if(play_mode == PLAYING_SYNTH) {
dma_channel_transfer_from_buffer_now(audio_dma_channel, tone_buffers[current_buffer], TONE_BUFFER_SIZE);
current_buffer = (current_buffer + 1) % NUM_TONE_BUFFERS;
populate_next_synth();
}
else {
play_mode = NOT_PLAYING;
}
}
void StellarUnicorn::populate_next_synth() {
int16_t *samples = tone_buffers[current_buffer];
for(uint i = 0; i < TONE_BUFFER_SIZE; i++) {
samples[i] = synth.get_audio_frame();
}
}
void StellarUnicorn::stop_playing() {
if(unicorn == this) {
// Stop the audio SM
pio_sm_set_enabled(audio_pio, audio_sm, false);
// Reset the I2S pins to avoid popping when audio is suddenly stopped
const uint pins_to_clear = 1 << I2S_DATA | 1 << I2S_BCLK | 1 << I2S_LRCLK;
pio_sm_set_pins_with_mask(audio_pio, audio_sm, 0, pins_to_clear);
// Abort any in-progress DMA transfer
dma_safe_abort(audio_dma_channel);
play_mode = NOT_PLAYING;
}
}
AudioChannel& StellarUnicorn::synth_channel(uint channel) {
assert(channel < PicoSynth::CHANNEL_COUNT);
return synth.channels[channel];
}
void StellarUnicorn::set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
x = (WIDTH - 1) - x;
y = (HEIGHT - 1) - y;
// map coordinates into display space
if(y < 16) {
// move to top half of display (which is actually the right half of the framebuffer)
x += 32;
}else{
// remap y coordinate
y -= 16;
}
r = (r * this->brightness) >> 8;
g = (g * this->brightness) >> 8;
b = (b * this->brightness) >> 8;
uint16_t gamma_r = r_gamma_lut[r];
uint16_t gamma_g = g_gamma_lut[g];
uint16_t gamma_b = b_gamma_lut[b];
// for each row:
// for each bcd frame:
// 0: 00111111 // row pixel count (minus one)
// 1 - 64: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
// 65 - 67: xxxxxxxx, xxxxxxxx, xxxxxxxx // dummy bytes to dword align
// 68: xxxxrrrr // row select bits
// 69 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
//
// .. and back to the start
// set the appropriate bits in the separate bcd frames
for(uint8_t frame = 0; frame < BCD_FRAME_COUNT; frame++) {
uint8_t *p = &bitstream[y * ROW_BYTES + (BCD_FRAME_BYTES * frame) + 2 + x];
uint8_t red_bit = gamma_r & 0b1;
uint8_t green_bit = gamma_g & 0b1;
uint8_t blue_bit = gamma_b & 0b1;
*p = (blue_bit << 0) | (green_bit << 1) | (red_bit << 2);
gamma_r >>= 1;
gamma_g >>= 1;
gamma_b >>= 1;
}
}
void StellarUnicorn::set_brightness(float value) {
value = value < 0.0f ? 0.0f : value;
value = value > 1.0f ? 1.0f : value;
this->brightness = floor(value * 256.0f);
}
float StellarUnicorn::get_brightness() {
return this->brightness / 255.0f;
}
void StellarUnicorn::adjust_brightness(float delta) {
this->set_brightness(this->get_brightness() + delta);
}
void StellarUnicorn::set_volume(float value) {
value = value < 0.0f ? 0.0f : value;
value = value > 1.0f ? 1.0f : value;
this->volume = floor(value * 255.0f);
this->synth.volume = this->volume * 255.0f;
}
float StellarUnicorn::get_volume() {
return this->volume / 255.0f;
}
void StellarUnicorn::adjust_volume(float delta) {
this->set_volume(this->get_volume() + delta);
}
void StellarUnicorn::update(PicoGraphics *graphics) {
if(unicorn == this) {
if(graphics->pen_type == PicoGraphics::PEN_RGB888) {
uint32_t *p = (uint32_t *)graphics->frame_buffer;
for(int y = 0; y < 32; y++) {
for(int x = 0; x < 32; x++) {
uint32_t col = *p;
uint8_t r = (col & 0xff0000) >> 16;
uint8_t g = (col & 0x00ff00) >> 8;
uint8_t b = (col & 0x0000ff) >> 0;
p++;
set_pixel(x, y, r, g, b);
}
}
}
else if(graphics->pen_type == PicoGraphics::PEN_RGB565) {
uint16_t *p = (uint16_t *)graphics->frame_buffer;
for(int y = 0; y < 32; y++) {
for(int x = 0; x < 32; x++) {
uint16_t col = __builtin_bswap16(*p);
uint8_t r = (col & 0b1111100000000000) >> 8;
uint8_t g = (col & 0b0000011111100000) >> 3;
uint8_t b = (col & 0b0000000000011111) << 3;
p++;
set_pixel(x, y, r, g, b);
}
}
}
else if(graphics->pen_type == PicoGraphics::PEN_P8 || graphics->pen_type == PicoGraphics::PEN_P4) {
int offset = 0;
graphics->frame_convert(PicoGraphics::PEN_RGB888, [this, offset](void *data, size_t length) mutable {
uint32_t *p = (uint32_t *)data;
for(auto i = 0u; i < length / 4; i++) {
int x = offset % 32;
int y = offset / 32;
uint32_t col = *p;
uint8_t r = (col & 0xff0000) >> 16;
uint8_t g = (col & 0x00ff00) >> 8;
uint8_t b = (col & 0x0000ff) >> 0;
set_pixel(x, y, r, g, b);
offset++;
p++;
}
});
}
}
}
bool StellarUnicorn::is_pressed(uint8_t button) {
return !gpio_get(button);
}
}

Wyświetl plik

@ -0,0 +1,125 @@
#pragma once
#include "hardware/pio.h"
#include "pico_graphics.hpp"
#include "../pico_synth/pico_synth.hpp"
namespace pimoroni {
class StellarUnicorn {
public:
static const int WIDTH = 16;
static const int HEIGHT = 16;
// pin assignments
static const uint8_t COLUMN_CLOCK = 13;
static const uint8_t COLUMN_DATA = 14;
static const uint8_t COLUMN_LATCH = 15;
static const uint8_t COLUMN_BLANK = 16;
static const uint8_t ROW_BIT_0 = 17;
static const uint8_t ROW_BIT_1 = 18;
static const uint8_t ROW_BIT_2 = 19;
static const uint8_t ROW_BIT_3 = 20;
static const uint8_t LIGHT_SENSOR = 28;
static const uint8_t MUTE = 22;
static const uint8_t I2S_DATA = 9;
static const uint8_t I2S_BCLK = 10;
static const uint8_t I2S_LRCLK = 11;
static const uint8_t I2C_SDA = 4;
static const uint8_t I2C_SCL = 5;
static const uint8_t SWITCH_A = 0;
static const uint8_t SWITCH_B = 1;
static const uint8_t SWITCH_C = 3;
static const uint8_t SWITCH_D = 6;
static const uint8_t SWITCH_SLEEP = 27;
static const uint8_t SWITCH_VOLUME_UP = 7;
static const uint8_t SWITCH_VOLUME_DOWN = 8;
static const uint8_t SWITCH_BRIGHTNESS_UP = 21;
static const uint8_t SWITCH_BRIGHTNESS_DOWN = 26;
private:
static const uint32_t ROW_COUNT = 16;
static const uint32_t BCD_FRAME_COUNT = 14;
static const uint32_t BCD_FRAME_BYTES = 72;
static const uint32_t ROW_BYTES = BCD_FRAME_COUNT * BCD_FRAME_BYTES;
static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES);
static const uint SYSTEM_FREQ = 22050;
private:
static PIO bitstream_pio;
static uint bitstream_sm;
static uint bitstream_sm_offset;
static PIO audio_pio;
static uint audio_sm;
static uint audio_sm_offset;
uint16_t brightness = 256;
uint16_t volume = 127;
// must be aligned for 32bit dma transfer
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
const uint32_t bitstream_addr = (uint32_t)bitstream;
static StellarUnicorn* unicorn;
static void dma_complete();
static const uint NUM_TONE_BUFFERS = 2;
static const uint TONE_BUFFER_SIZE = 4;
int16_t tone_buffers[NUM_TONE_BUFFERS][TONE_BUFFER_SIZE] = {0};
uint current_buffer = 0;
PicoSynth synth;
enum PlayMode {
PLAYING_BUFFER,
//PLAYING_TONE,
PLAYING_SYNTH,
NOT_PLAYING
};
PlayMode play_mode = NOT_PLAYING;
public:
~StellarUnicorn();
void init();
static inline void pio_program_init(PIO pio, uint sm, uint offset);
void clear();
void update(PicoGraphics *graphics);
void set_brightness(float value);
float get_brightness();
void adjust_brightness(float delta);
void set_volume(float value);
float get_volume();
void adjust_volume(float delta);
void set_pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b);
uint16_t light();
bool is_pressed(uint8_t button);
void play_sample(uint8_t *data, uint32_t length);
void play_synth();
void stop_playing();
AudioChannel& synth_channel(uint channel);
private:
void partial_teardown();
void dma_safe_abort(uint channel);
void next_audio_sequence();
void populate_next_synth();
};
}

Wyświetl plik

@ -0,0 +1,79 @@
.program stellar_unicorn
.side_set 1 opt
; out pins:
;
; - 3: row select bit 0
; - 4: row select bit 1
; - 5: row select bit 2
; - 6: row select bit 3
; set pins:
;
; - 0: column data (base)
; - 1: column latch
; - 2: column blank
; sideset pin:
;
; - 0: column clock
; for each row:
; for each bcd frame:
; 0: 00111111 // row pixel count (minus one)
; 1: xxxxrrrr // row select bits
; 2 - 65: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
; 66 - 67: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
; 68 - 71: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
;
; .. and back to the start
.wrap_target
; loop over row pixels
out y, 8 ; get row pixel count (minus 1 because test is pre decrement)
out pins, 8 ; output row select
pixels:
; red bit
out x, 1 side 0 [1] ; pull in blue bit from OSR into register x, clear clock
set pins, 0b100 ; clear data bit, blank high
jmp !x endb ; if bit was zero jump
set pins, 0b101 ; set data bit, blank high
endb:
nop side 1 [2] ; clock in bit
; green bit
out x, 1 side 0 [1] ; pull in green bit from OSR into register X, clear clock
set pins, 0b100 ; clear data bit, blank high
jmp !x endg ; if bit was zero jump
set pins, 0b101 ; set data bit, blank high
endg:
nop side 1 [2] ; clock in bit
; blue bit
out x, 1 side 0 [1] ; pull in red bit from OSR into register X, clear clock
set pins, 0b100 ; clear data bit, blank high
jmp !x endr ; if bit was zero jump
set pins, 0b101 ; set data bit, blank high
endr:
out null, 5 side 1 [2] ; clock in bit
;out null, 5 side 0 ; discard the five dummy bits for this pixel
jmp y-- pixels
out null, 16 ; discard dummy bytes
set pins, 0b110 [5] ; latch high, blank high
set pins, 0b000 ; blank low (enable output)
; loop over bcd delay period
out y, 32 ; get bcd delay counter value
bcd_delay:
jmp y-- bcd_delay
set pins 0b100 ; blank high (disable output)
.wrap

Wyświetl plik

@ -0,0 +1,40 @@
include_directories(${CMAKE_CURRENT_LIST_DIR}/../../)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../")
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Essential
include(pimoroni_i2c/micropython)
include(pimoroni_bus/micropython)
# Pico Graphics Essential
include(hershey_fonts/micropython)
include(bitmap_fonts/micropython)
include(picographics/micropython)
# Pico Graphics Extra
include(jpegdec/micropython)
include(qrcode/micropython/micropython)
# Sensors & Breakouts
include(micropython-common-breakouts)
include(pcf85063a/micropython)
# Utility
include(adcfft/micropython)
# LEDs & Matrices
include(stellar_unicorn/micropython)
# ULAB
include(micropython-common-ulab)
enable_ulab()
include(modules_py/modules_py)
# C++ Magic Memory
include(cppmem/micropython)

Wyświetl plik

@ -72,6 +72,7 @@ Bear in mind that MicroPython has only 192K of RAM available- a 320x240 pixel di
* Galactic Unicorn - 53x11 LED Matrix - `DISPLAY_GALACTIC_UNICORN`
* Interstate75 and 75W - HUB75 Matrix driver - `DISPLAY_INTERSTATE75_SIZEOFMATRIX` please read below!
* Cosmic Unicorn - 32x32 LED Matrix - `DISPLAY_COSMIC_UNICORN`
* Stellar Unicorn - 16x16 LED Matrix - `DISPLAY_STELLAR_UNICORN`
#### Interstate75 and Interstate75W Display modes

Wyświetl plik

@ -152,6 +152,7 @@ STATIC const mp_map_elem_t picographics_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_256X64), MP_ROM_INT(DISPLAY_INTERSTATE75_256X64) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME_7), MP_ROM_INT(DISPLAY_INKY_FRAME_7) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_COSMIC_UNICORN), MP_ROM_INT(DISPLAY_COSMIC_UNICORN) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_STELLAR_UNICORN), MP_ROM_INT(DISPLAY_STELLAR_UNICORN) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_UNICORN_PACK), MP_ROM_INT(DISPLAY_UNICORN_PACK) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_SCROLL_PACK), MP_ROM_INT(DISPLAY_SCROLL_PACK) },

Wyświetl plik

@ -210,6 +210,14 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height,
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
if(pen_type == -1) pen_type = PEN_RGB888;
break;
case DISPLAY_STELLAR_UNICORN:
width = 16;
height = 16;
bus_type = BUS_PIO;
// Portrait to match labelling
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
if(pen_type == -1) pen_type = PEN_RGB888;
break;
case DISPLAY_UNICORN_PACK:
width = 16;
height = 7;
@ -354,6 +362,7 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
|| display == DISPLAY_INTERSTATE75_64X32
|| display == DISPLAY_GALACTIC_UNICORN
|| display == DISPLAY_COSMIC_UNICORN
|| display == DISPLAY_STELLAR_UNICORN
|| display == DISPLAY_UNICORN_PACK
|| display == DISPLAY_SCROLL_PACK) {
// Create a dummy display driver

Wyświetl plik

@ -26,6 +26,7 @@ enum PicoGraphicsDisplay {
DISPLAY_INTERSTATE75_256X64,
DISPLAY_INKY_FRAME_7,
DISPLAY_COSMIC_UNICORN,
DISPLAY_STELLAR_UNICORN,
DISPLAY_UNICORN_PACK,
DISPLAY_SCROLL_PACK
};

Wyświetl plik

@ -0,0 +1,315 @@
# Stellar Unicorn (MicroPython) <!-- omit in toc -->
Stellar Unicorn offers 16x16 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
You can buy one here: https://shop.pimoroni.com/products/stellar-unicorn
## These are not your everyday RGB LEDs!
Internally Stellar Unicorn applies gamma correction to the supplied image data and updates the display with 14-bit precision resulting in extremely linear visual output - including at the low end.
The display is refreshed around 300 times per second (300fps!) allowing for rock solid stability even when being filmed, no smearing or flickering even when in motion.
No strobing or brightness stepping here folks - it's the perfect backdrop for your tricked out streaming setup!
## Getting started
The Stellar Unicorn library provides a collection of methods that allow you to easily access all of the features on the board.
Drawing is primarily handled via our [PicoGraphics](https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/modules/picographics) library which provides a comprehensive selection of drawing methods - once your drawing work is complete you pass the PicoGraphics object to Stellar Unicorn to have it displayed on the screen.
- [Example Program](#example-program)
- [Interleaved Framebuffer](#interleaved-framebuffer)
- [Function Reference](#function-reference)
- [Imports and Objects](#imports-and-objects)
- [System State](#system-state)
- [`set_brightness(value)`](#set_brightnessvalue)
- [`get_brightness()`](#get_brightness)
- [`adjust_brightness(delta)`](#adjust_brightnessdelta)
- [`set_volume(value)`](#set_volumevalue)
- [`get_volume()`](#get_volume)
- [`adjust_volume(delta)`](#adjust_volumedelta)
- [`light()`](#light)
- [`is_pressed(button)`](#is_pressedbutton)
- [Drawing](#drawing)
- [`update(PicoGraphics)`](#updatepicographics)
- [`clear()`](#clear)
- [Audio](#audio)
- [`play_sample(data)`](#play_sampledata)
- [`synth_channel(channel)`](#synth_channelchannel)
- [`play_synth()`](#play_synth)
- [`stop_playing()`](#stop_playing)
- [Channel Reference](#channel-reference)
- [Constants](#constants)
- [`WIDTH` \& `HEIGHT`](#width--height)
- [Using Breakouts](#using-breakouts)
# Example Program
The following example shows how to scroll a simple message across the display.
```python
from stellar import StellarUnicorn
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN
import time
# create a PicoGraphics framebuffer to draw into
graphics = PicoGraphics(display=DISPLAY_STELLAR_UNICORN)
# create our StellarUnicorn object
su = StellarUnicorn()
# start position for scrolling (off the side of the display)
scroll = float(-StellarUnicorn.WIDTH)
# message to scroll
MESSAGE = "Pirate. Monkey. Robot. Ninja."
# pen colours to draw with
BLACK = graphics.create_pen(0, 0, 0)
YELLOW = graphics.create_pen(255, 255, 0)
while True:
# determine the scroll position of the text
width = graphics.measure_text(MESSAGE, 1)
scroll += 0.25
if scroll > width:
scroll = float(-StellarUnicorn.WIDTH)
# clear the graphics object
graphics.set_pen(BLACK)
graphics.clear()
# draw the text
graphics.set_pen(YELLOW)
graphics.text(MESSAGE, round(0 - scroll), 2, -1, 0.55)
# update the display
su.update(graphics)
time.sleep(0.02)
```
# Interleaved Framebuffer
Stellar Unicorn takes advantage of the RP2040's PIOs to drive screen updates - this is what gives it the performance it needs to render with 14-bit precision at over 300 frames per second.
The PIO is a powerful, but limited, tool. It has no way to access memory at random and minimal support for decision making and branching. All it can really do is process a stream of data/instructions in order.
This means that we need to be clever about the way we pass data into the PIO program, the information needs to be delivered in the exact order that the PIO will need to process it. To achieve this we "interleave" our framebuffer - each frame of BCM data is passed one after another with values for the current row, pixel count, and timing inserted as needed:
row 0 data:
for each bcd frame:
bit : data
0: 00110110 // row pixel count (minus one)
1 - 53: xxxxxbgr, xxxxxbgr, xxxxxbgr, ... // pixel data
54 - 55: xxxxxxxx, xxxxxxxx // dummy bytes to dword align
56: xxxxrrrr // row select bits
57 - 59: tttttttt, tttttttt, tttttttt // bcd tick count (0-65536)
row 1 data:
...
If you're working with our library then you don't need to worry about any of these details, they are handled for you.
# Function Reference
## Imports and Objects
To access these functions, you'll need to first `import` the relevant libraries and then set up a Stellar Unicorn object:
```python
from stellar import StellarUnicorn
su = StellarUnicorn()
```
or (with PicoGraphics):
```python
from stellar import StellarUnicorn
from picographics import PicoGraphics, DISPLAY_STELLAR_UNICORN
su = StellarUnicorn()
graphics = PicoGraphics(display=DISPLAY_STELLAR_UNICORN)
```
## System State
### `set_brightness(value)`
Set the brightness - `value` is supplied as a floating point value between `0.0` and `1.0`.
### `get_brightness()`
Returns the current brightness as a value between `0.0` and `1.0`.
### `adjust_brightness(delta)`
Adjust the brightness of the display - `delta` is supplied as a floating point value and will be added to the current brightness (and then clamped to the range `0.0` to `1.0`).
For example:
```python
su.set_brightness(0.5)
su.adjust_brightness(0.1) # brightness is now 0.6
su.adjust_brightness(0.7) # brightness is now 1.0
su.adjust_brightness(-0.2) # brightness is now 0.8
```
### `set_volume(value)`
Set the volume - `value` is supplied as a floating point value between `0.0` and `1.0`.
### `get_volume()`
Returns the current volume as a value between `0.0` and `1.0`.
### `adjust_volume(delta)`
Adjust the volume - `delta` is supplied as a floating point value and will be added to the current volume (and then clamped to the range `0.0` to `1.0`).
For example:
```python
su.set_volume(0.5)
su.set_volume(0.1) # volume is now 0.6
su.adjust_volume(0.7) # volume is now 1.0
su.adjust_volume(-0.2) # volume is now 0.8
```
### `light()`
Get the current value seen by the onboard light sensor as a value between `0` and `4095`.
### `is_pressed(button)`
Returns true if the requested `button` is currently pressed.
There are a set of constants in the StellarUnicorn class that represent each of the buttons. The brightness, sleep, and volume buttons are not tied to hardware functions (they are implemented entirely in software) so can also be used for user functions if preferred. Here's a list of the constants and their associated pin numbers:
```python
SWITCH_A = 0
SWITCH_B = 1
SWITCH_C = 3
SWITCH_D = 6
SWITCH_SLEEP = 27
SWITCH_VOLUME_UP = 7
SWITCH_VOLUME_DOWN = 8
SWITCH_BRIGHTNESS_UP = 21
SWITCH_BRIGHTNESS_DOWN = 26
```
For example:
```python
while not su.is_pressed(StellarUnicorn.SWITCH_A):
# wait for switch A to be pressed
pass
print("We did it! We pressed switch A! Heck yeah!")
```
## Drawing
### `update(PicoGraphics)`
The PicoGraphics library provides a collection of powerful drawing methods to make things simple.
The image on the PicoGraphics object provided is copied to the interleaved framebuffer with gamma correction applied.
For example (assuming you've set up your Stellar Unicorn and PicoGraphics objects up [as we did above](#imports-and-objects)):
```python
su.update(graphics)
```
⚠️ If you've used PicoGraphics on our other boards note that this `update` function works a little differently. Here it's a Stellar Unicorn function to which you need to pass a PicoGraphics object to.
### `clear()`
Clear the contents of the interleaved framebuffer. This will make your Stellar Unicorn display turn off. To show an image again, call the `update()` function as described above.
## Audio
Audio functionality is supported by our [PicoSynth library](https://github.com/pimoroni/pimoroni-pico/tree/main/libraries/pico_synth) which allows you to create multiple voice channels with ADSR (attack decay sustain release) envelopes. It provides a similar set of functionality to the classic SID chip in the Commodore 64.
### `play_sample(data)`
Play the provided 16-bit audio sample. `data` must point to a `bytearray` that contains 16-bit PCM data. The number of samples is retrieved from the array's length.
### `synth_channel(channel)`
Gets a `Channel` object which can then be configured with voice, ADSR envelope, etc.
### `play_synth()`
Start the synth playing.
### `stop_playing()`
Stops any currently playing audio.
### Channel Reference
```python
configure(waveforms=None, frequency=None, volume=None,
attack=None, decay=None, sustain=None,
release=None, pulse_width=None)
restore()
waveforms()
waveforms(waveforms)
frequency()
frequency(frequency)
volume()
volume(volume)
attack_duration()
attack_duration(duration)
decay_duration()
decay_duration(duration)
sustain_level()
sustain_level(level)
release_duration()
release_duration(duration)
pulse_width()
pulse_width(width)
trigger_attack() # start the channel playing
trigger_release() # stop the channel playing
play_tone(frequency, volume=None, attack=None, release=None)
```
## Constants
### `WIDTH` & `HEIGHT`
The width and height of Stellar Unicorn are available in constants `WIDTH` and `HEIGHT`.
For example:
```python
num_pixels = StellarUnicorn.WIDTH * StellarUnicorn.HEIGHT
print(num_pixels)
```
## Using Breakouts
Stellar Unicorn has two Qw/ST (Qwiic/STEMMA QT) connectors. Breakouts with Qw/ST connectors, can be plugged straight in with a [JST-SH to JST-SH cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609813587). You can connect I2C Breakout Garden breakouts without Qw/ST connectors using a [JST-SH to JST-SH cable](https://shop.pimoroni.com/products/jst-sh-cable-qwiic-stemma-qt-compatible?variant=31910609813587) and a [Qw/ST to Breakout Garden adaptor](https://shop.pimoroni.com/products/stemma-qt-qwiic-to-breakout-garden-adapter).
- [List of breakouts currently supported in our C++/MicroPython build](https://github.com/pimoroni/pimoroni-pico#breakouts)
Stellar Unicorn uses GP4 and GP5 for its I2C interface. You can use the constants in the shared `pimoroni` module to set up the I2C interface:
```python
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
```
Alternatively, you can specify the pin numbers directly:
```python
from pimoroni_i2c import PimoroniI2C
i2c = PimoroniI2C(sda=4, scl=5)
```

Wyświetl plik

@ -0,0 +1,24 @@
set(MOD_NAME stellar_unicorn)
string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER)
add_library(usermod_${MOD_NAME} INTERFACE)
target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/stellar_unicorn/stellar_unicorn.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_synth/pico_synth.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_rgb888.cpp
)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/stellar_unicorn/stellar_unicorn.pio)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/stellar_unicorn/audio_i2s.pio)
target_include_directories(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/
)
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
MODULE_STELLAR_ENABLED=1
)
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})

Wyświetl plik

@ -0,0 +1,150 @@
#include "stellar_unicorn.h"
/***** Methods *****/
MP_DEFINE_CONST_FUN_OBJ_1(Channel___del___obj, Channel___del__);
MP_DEFINE_CONST_FUN_OBJ_KW(Channel_configure_obj, 1, Channel_configure);
MP_DEFINE_CONST_FUN_OBJ_1(Channel_restore_obj, Channel_restore);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_waveforms_obj, 1, 2, Channel_waveforms);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_frequency_obj, 1, 2, Channel_frequency);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_volume_obj, 1, 2, Channel_volume);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_attack_duration_obj, 1, 2, Channel_attack_duration);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_decay_duration_obj, 1, 2, Channel_decay_duration);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_sustain_level_obj, 1, 2, Channel_sustain_level);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_release_duration_obj, 1, 2, Channel_release_duration);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(Channel_pulse_width_obj, 1, 2, Channel_pulse_width);
MP_DEFINE_CONST_FUN_OBJ_1(Channel_trigger_attack_obj, Channel_trigger_attack);
MP_DEFINE_CONST_FUN_OBJ_1(Channel_trigger_release_obj, Channel_trigger_release);
MP_DEFINE_CONST_FUN_OBJ_KW(Channel_play_tone_obj, 2, Channel_play_tone);
//MP_DEFINE_CONST_FUN_OBJ_1(Channel_stop_playing_obj, Channel_stop_playing);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn___del___obj, StellarUnicorn___del__);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_clear_obj, StellarUnicorn_clear);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_update_obj, StellarUnicorn_update);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_set_brightness_obj, StellarUnicorn_set_brightness);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_get_brightness_obj, StellarUnicorn_get_brightness);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_adjust_brightness_obj, StellarUnicorn_adjust_brightness);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_set_volume_obj, StellarUnicorn_set_volume);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_get_volume_obj, StellarUnicorn_get_volume);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_adjust_volume_obj, StellarUnicorn_adjust_volume);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_light_obj, StellarUnicorn_light);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_is_pressed_obj, StellarUnicorn_is_pressed);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_play_sample_obj, StellarUnicorn_play_sample);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_play_synth_obj, StellarUnicorn_play_synth);
MP_DEFINE_CONST_FUN_OBJ_1(StellarUnicorn_stop_playing_obj, StellarUnicorn_stop_playing);
MP_DEFINE_CONST_FUN_OBJ_2(StellarUnicorn_synth_channel_obj, StellarUnicorn_synth_channel);
/***** Binding of Methods *****/
STATIC const mp_rom_map_elem_t Channel_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Channel___del___obj) },
{ MP_ROM_QSTR(MP_QSTR_configure), MP_ROM_PTR(&Channel_configure_obj) },
{ MP_ROM_QSTR(MP_QSTR_restore), MP_ROM_PTR(&Channel_restore_obj) },
{ MP_ROM_QSTR(MP_QSTR_waveforms), MP_ROM_PTR(&Channel_waveforms_obj) },
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&Channel_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_volume), MP_ROM_PTR(&Channel_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_attack_duration), MP_ROM_PTR(&Channel_attack_duration_obj) },
{ MP_ROM_QSTR(MP_QSTR_decay_duration), MP_ROM_PTR(&Channel_decay_duration_obj) },
{ MP_ROM_QSTR(MP_QSTR_sustain_level), MP_ROM_PTR(&Channel_sustain_level_obj) },
{ MP_ROM_QSTR(MP_QSTR_release_duration), MP_ROM_PTR(&Channel_release_duration_obj) },
{ MP_ROM_QSTR(MP_QSTR_pulse_width), MP_ROM_PTR(&Channel_pulse_width_obj) },
{ MP_ROM_QSTR(MP_QSTR_trigger_attack), MP_ROM_PTR(&Channel_trigger_attack_obj) },
{ MP_ROM_QSTR(MP_QSTR_trigger_release), MP_ROM_PTR(&Channel_trigger_release_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_tone), MP_ROM_PTR(&Channel_play_tone_obj) },
{ MP_ROM_QSTR(MP_QSTR_NOISE), MP_ROM_INT(128) },
{ MP_ROM_QSTR(MP_QSTR_SQUARE), MP_ROM_INT(64) },
{ MP_ROM_QSTR(MP_QSTR_SAW), MP_ROM_INT(32) },
{ MP_ROM_QSTR(MP_QSTR_TRIANGLE), MP_ROM_INT(16) },
{ MP_ROM_QSTR(MP_QSTR_SINE), MP_ROM_INT(8) },
{ MP_ROM_QSTR(MP_QSTR_WAVE), MP_ROM_INT(1) },
};
STATIC const mp_rom_map_elem_t StellarUnicorn_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&StellarUnicorn___del___obj) },
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&StellarUnicorn_clear_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&StellarUnicorn_update_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&StellarUnicorn_set_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_brightness), MP_ROM_PTR(&StellarUnicorn_get_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_adjust_brightness), MP_ROM_PTR(&StellarUnicorn_adjust_brightness_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_volume), MP_ROM_PTR(&StellarUnicorn_set_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_volume), MP_ROM_PTR(&StellarUnicorn_get_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_adjust_volume), MP_ROM_PTR(&StellarUnicorn_adjust_volume_obj) },
{ MP_ROM_QSTR(MP_QSTR_light), MP_ROM_PTR(&StellarUnicorn_light_obj) },
{ MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&StellarUnicorn_is_pressed_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_sample), MP_ROM_PTR(&StellarUnicorn_play_sample_obj) },
{ MP_ROM_QSTR(MP_QSTR_play_synth), MP_ROM_PTR(&StellarUnicorn_play_synth_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop_playing), MP_ROM_PTR(&StellarUnicorn_stop_playing_obj) },
{ MP_ROM_QSTR(MP_QSTR_synth_channel), MP_ROM_PTR(&StellarUnicorn_synth_channel_obj) },
{ MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(16) },
{ MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(16) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_A), MP_ROM_INT(0) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_B), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_C), MP_ROM_INT(3) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_D), MP_ROM_INT(6) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_SLEEP), MP_ROM_INT(27) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_VOLUME_UP), MP_ROM_INT(7) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_VOLUME_DOWN), MP_ROM_INT(8) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_BRIGHTNESS_UP), MP_ROM_INT(21) },
{ MP_ROM_QSTR(MP_QSTR_SWITCH_BRIGHTNESS_DOWN), MP_ROM_INT(26) },
};
STATIC MP_DEFINE_CONST_DICT(Channel_locals_dict, Channel_locals_dict_table);
STATIC MP_DEFINE_CONST_DICT(StellarUnicorn_locals_dict, StellarUnicorn_locals_dict_table);
/***** Class Definition *****/
#ifdef MP_DEFINE_CONST_OBJ_TYPE
MP_DEFINE_CONST_OBJ_TYPE(
Channel_type,
MP_QSTR_Channel,
MP_TYPE_FLAG_NONE,
make_new, Channel_make_new,
print, Channel_print,
locals_dict, (mp_obj_dict_t*)&Channel_locals_dict
);
MP_DEFINE_CONST_OBJ_TYPE(
StellarUnicorn_type,
MP_QSTR_StellarUnicorn,
MP_TYPE_FLAG_NONE,
make_new, StellarUnicorn_make_new,
print, StellarUnicorn_print,
locals_dict, (mp_obj_dict_t*)&StellarUnicorn_locals_dict
);
#else
const mp_obj_type_t Channel_type = {
{ &mp_type_type },
.name = MP_QSTR_Channel,
.print = Channel_print,
.make_new = Channel_make_new,
.locals_dict = (mp_obj_dict_t*)&Channel_locals_dict,
};
const mp_obj_type_t StellarUnicorn_type = {
{ &mp_type_type },
.name = MP_QSTR_StellarUnicorn,
.print = StellarUnicorn_print,
.make_new = StellarUnicorn_make_new,
.locals_dict = (mp_obj_dict_t*)&StellarUnicorn_locals_dict,
};
#endif
/***** Globals Table *****/
STATIC const mp_map_elem_t stellar_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_stellar) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_Channel), (mp_obj_t)&Channel_type },
{ MP_OBJ_NEW_QSTR(MP_QSTR_StellarUnicorn), (mp_obj_t)&StellarUnicorn_type },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_stellar_globals, stellar_globals_table);
/***** Module Definition *****/
const mp_obj_module_t stellar_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_stellar_globals,
};
#if MICROPY_VERSION <= 70144
MP_REGISTER_MODULE(MP_QSTR_stellar, stellar_user_cmodule, MODULE_STELLAR_ENABLED);
#else
MP_REGISTER_MODULE(MP_QSTR_stellar, stellar_user_cmodule);
#endif

Wyświetl plik

@ -0,0 +1,516 @@
#include "libraries/stellar_unicorn/stellar_unicorn.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "micropython/modules/util.hpp"
#include <cstdio>
#include <cfloat>
using namespace pimoroni;
extern "C" {
#include "stellar_unicorn.h"
#include "micropython/modules/pimoroni_i2c/pimoroni_i2c.h"
#include "py/builtin.h"
/********** Channel **********/
/***** Variables Struct *****/
typedef struct _Channel_obj_t {
mp_obj_base_t base;
AudioChannel* channel;
} _Channel_obj_t;
/***** Print *****/
void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind; //Unused input parameter
//_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
//AudioChannel* channel = self->channel;
mp_print_str(print, "Channel(");
mp_print_str(print, ")");
}
/***** Constructor *****/
mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
mp_raise_msg(&mp_type_RuntimeError, "Cannot create Channel objects. They can only be accessed from StellarUnicorn.synth_channel()");
return mp_const_none;
}
/***** Destructor ******/
mp_obj_t Channel___del__(mp_obj_t self_in) {
return mp_const_none;
}
/***** Helper Functions *****/
void set_channel_waveforms(AudioChannel& channel, mp_obj_t in) {
int waveforms = mp_obj_get_int(in);
const int mask = (NOISE | SQUARE | SAW | TRIANGLE | SINE | WAVE);
if(waveforms < 0 || (waveforms & mask) == 0) {
mp_raise_ValueError("waveforms invalid. Expected a combination of NOISE, SQUARE, SAW, TRIANGLE, SINE, or WAVE");
}
channel.waveforms = (uint8_t)waveforms;
}
void set_channel_frequency(AudioChannel& channel, mp_obj_t in) {
int freq = mp_obj_get_int(in);
if(freq <= 0 || freq > UINT16_MAX) {
mp_raise_ValueError("frequency out of range. Expected greater than 0Hz to 65535Hz");
}
channel.frequency = (uint16_t)freq;
}
void set_channel_volume(AudioChannel& channel, mp_obj_t in) {
float volume = mp_obj_get_float(in);
if(volume < 0.0f || volume > 1.0f) {
mp_raise_ValueError("volume out of range. Expected 0.0 to 1.0");
}
channel.volume = (uint16_t)(volume * UINT16_MAX);
}
void set_channel_attack(AudioChannel& channel, mp_obj_t in) {
int attack_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(attack_ms < 0 || attack_ms > UINT16_MAX) {
mp_raise_ValueError("attack out of range. Expected 0.0s to 65.5s");
}
channel.attack_ms = MAX(attack_ms, 1);
}
void set_channel_decay(AudioChannel& channel, mp_obj_t in) {
int decay_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(decay_ms < 0 || decay_ms > UINT16_MAX) {
mp_raise_ValueError("decay out of range. Expected 0.0s to 65.5s");
}
channel.decay_ms = MAX(decay_ms, 1);
}
void set_channel_sustain(AudioChannel& channel, mp_obj_t in) {
float sustain = mp_obj_get_float(in);
if(sustain < 0.0f || sustain > 1.0f) {
mp_raise_ValueError("sustain out of range. Expected 0.0 to 1.0");
}
channel.sustain = (uint16_t)(sustain * UINT16_MAX);
}
void set_channel_release(AudioChannel& channel, mp_obj_t in) {
int release_ms = (int)(mp_obj_get_float(in) * 1000.0f);
if(release_ms < 0 || release_ms > UINT16_MAX) {
mp_raise_ValueError("release out of range. Expected 0.0s to 65.5s");
}
channel.release_ms = MAX(release_ms, 1);
}
void set_channel_pulse_width(AudioChannel& channel, mp_obj_t in) {
float pulse_width = mp_obj_get_float(in);
if(pulse_width < 0.0f || pulse_width > 1.0f) {
mp_raise_ValueError("pulse_width out of range. Expected 0.0 to 1.0");
}
channel.pulse_width = (uint16_t)(pulse_width * UINT16_MAX);
}
/***** Methods *****/
mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_waveforms, ARG_frequency, ARG_volume, ARG_attack, ARG_decay, ARG_sustain, ARG_release, ARG_pulse_width };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_waveforms, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_frequency, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_decay, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_sustain, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_pulse_width, MP_ARG_OBJ, {.u_obj = mp_const_none} }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
mp_obj_t waveforms = args[ARG_waveforms].u_obj;
if(waveforms != mp_const_none) {
set_channel_waveforms(*self->channel, waveforms);
}
mp_obj_t frequency = args[ARG_frequency].u_obj;
if(frequency != mp_const_none) {
set_channel_frequency(*self->channel, frequency);
}
mp_obj_t volume = args[ARG_volume].u_obj;
if(volume != mp_const_none) {
set_channel_volume(*self->channel, volume);
}
mp_obj_t attack = args[ARG_attack].u_obj;
if(attack != mp_const_none) {
set_channel_attack(*self->channel, attack);
}
mp_obj_t decay = args[ARG_decay].u_obj;
if(decay != mp_const_none) {
set_channel_decay(*self->channel, decay);
}
mp_obj_t sustain = args[ARG_sustain].u_obj;
if(sustain != mp_const_none) {
set_channel_sustain(*self->channel, sustain);
}
mp_obj_t release = args[ARG_release].u_obj;
if(release != mp_const_none) {
set_channel_release(*self->channel, release);
}
mp_obj_t pulse_width = args[ARG_pulse_width].u_obj;
if(pulse_width != mp_const_none) {
set_channel_pulse_width(*self->channel, pulse_width);
}
return mp_const_none;
}
mp_obj_t Channel_restore(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->restore();
return mp_const_none;
}
mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_int(self->channel->waveforms);
}
set_channel_waveforms(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_int(self->channel->frequency);
}
set_channel_frequency(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->volume / UINT16_MAX);
}
set_channel_volume(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->attack_ms / 1000.0f);
}
set_channel_attack(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->decay_ms / 1000.0f);
}
set_channel_decay(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->sustain / UINT16_MAX);
}
set_channel_sustain(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->release_ms / 1000.0f);
}
set_channel_release(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[0], _Channel_obj_t);
if(n_args == 1) {
return mp_obj_new_float((float)self->channel->pulse_width / 0xffff);
}
set_channel_pulse_width(*self->channel, args[1]);
return mp_const_none;
}
mp_obj_t Channel_trigger_attack(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->trigger_attack();
return mp_const_none;
}
mp_obj_t Channel_trigger_release(mp_obj_t self_in) {
_Channel_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Channel_obj_t);
self->channel->trigger_release();
return mp_const_none;
}
mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_freq, ARG_volume, ARG_fade_in, ARG_fade_out };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_frequency, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_volume, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_attack, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_release, MP_ARG_OBJ, {.u_obj = mp_const_none} },
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_Channel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Channel_obj_t);
set_channel_frequency(*self->channel, args[ARG_freq].u_obj);
mp_obj_t volume = args[ARG_volume].u_obj;
if(volume != mp_const_none) {
set_channel_volume(*self->channel, volume);
}
else {
self->channel->volume = UINT16_MAX;
}
mp_obj_t attack_ms = args[ARG_fade_in].u_obj;
if(attack_ms != mp_const_none) {
set_channel_attack(*self->channel, attack_ms);
}
else {
self->channel->attack_ms = 1;
}
mp_obj_t release_ms = args[ARG_fade_out].u_obj;
if(release_ms != mp_const_none) {
set_channel_release(*self->channel, release_ms);
}
else {
self->channel->release_ms = 1;
}
self->channel->waveforms = Waveform::SINE;
self->channel->decay_ms = 1;
self->channel->sustain = UINT16_MAX;
self->channel->trigger_attack();
return mp_const_none;
}
/********** StellarUnicorn **********/
/***** Variables Struct *****/
typedef struct _StellarUnicorn_obj_t {
mp_obj_base_t base;
StellarUnicorn* stellar;
} _StellarUnicorn_obj_t;
typedef struct _ModPicoGraphics_obj_t {
mp_obj_base_t base;
PicoGraphics *graphics;
DisplayDriver *display;
void *spritedata;
void *buffer;
_PimoroniI2C_obj_t *i2c;
//mp_obj_t scanline_callback; // Not really feasible in MicroPython
} ModPicoGraphics_obj_t;
/***** Print *****/
void StellarUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind; //Unused input parameter
//_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
mp_print_str(print, "StellarUnicorn()");
}
/***** Constructor *****/
mp_obj_t StellarUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
_StellarUnicorn_obj_t *self = nullptr;
enum { ARG_pio, ARG_sm, ARG_pins, ARG_common_pin, ARG_direction, ARG_counts_per_rev, ARG_count_microsteps, ARG_freq_divider };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_pio, MP_ARG_INT },
{ MP_QSTR_sm, MP_ARG_INT }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
int pio_int = args[ARG_pio].u_int;
if(pio_int < 0 || pio_int > (int)NUM_PIOS) {
mp_raise_ValueError("pio out of range. Expected 0 to 1");
}
//PIO pio = pio_int == 0 ? pio0 : pio1;
int sm = args[ARG_sm].u_int;
if(sm < 0 || sm > (int)NUM_PIO_STATE_MACHINES) {
mp_raise_ValueError("sm out of range. Expected 0 to 3");
}
StellarUnicorn *stellar = m_new_class(StellarUnicorn);
stellar->init();
self = m_new_obj_with_finaliser(_StellarUnicorn_obj_t);
self->base.type = &StellarUnicorn_type;
self->stellar = stellar;
return MP_OBJ_FROM_PTR(self);
}
/***** Destructor ******/
mp_obj_t StellarUnicorn___del__(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
m_del_class(StellarUnicorn, self->stellar);
return mp_const_none;
}
/***** Methods *****/
extern mp_obj_t StellarUnicorn_clear(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->clear();
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
ModPicoGraphics_obj_t *picographics = MP_OBJ_TO_PTR2(graphics_in, ModPicoGraphics_obj_t);
self->stellar->update(picographics->graphics);
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->set_brightness(mp_obj_get_float(value));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_get_brightness(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_float(self->stellar->get_brightness());
}
extern mp_obj_t StellarUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->adjust_brightness(mp_obj_get_float(delta));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->set_volume(mp_obj_get_float(value));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_get_volume(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_float(self->stellar->get_volume());
}
extern mp_obj_t StellarUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->adjust_volume(mp_obj_get_float(delta));
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_light(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_float(self->stellar->light());
}
extern mp_obj_t StellarUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
return mp_obj_new_bool(self->stellar->is_pressed((uint8_t)mp_obj_get_int(button)));
}
extern mp_obj_t StellarUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_RW);
if(bufinfo.len < 1) {
mp_raise_ValueError("Supplied buffer is too small!");
}
self->stellar->play_sample((uint8_t *)bufinfo.buf, bufinfo.len);
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_play_synth(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->play_synth();
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_stop_playing(mp_obj_t self_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
self->stellar->stop_playing();
return mp_const_none;
}
extern mp_obj_t StellarUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in) {
_StellarUnicorn_obj_t *self = MP_OBJ_TO_PTR2(self_in, _StellarUnicorn_obj_t);
// Check that the channel is valid
int channel = mp_obj_get_int(channel_in);
if(channel < 0 || channel >= (int)PicoSynth::CHANNEL_COUNT) {
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("channel out of range. Expected 0 to %d"), PicoSynth::CHANNEL_COUNT - 1);
}
// NOTE This seems to work, in that it give MP access to the calibration object
// Could very easily mess up in weird ways once object deletion is considered?
_Channel_obj_t *channel_obj = m_new_obj_with_finaliser(_Channel_obj_t);
channel_obj->base.type = &Channel_type;
channel_obj->channel = &self->stellar->synth_channel(channel);
return MP_OBJ_FROM_PTR(channel_obj);
}
}

Wyświetl plik

@ -0,0 +1,50 @@
// Include MicroPython API.
#include "py/runtime.h"
/***** Extern of Class Definition *****/
extern const mp_obj_type_t Channel_type;
extern const mp_obj_type_t StellarUnicorn_type;
/***** Extern of Class Methods *****/
extern void Channel_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
extern mp_obj_t Channel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t Channel___del__(mp_obj_t self_in);
extern mp_obj_t Channel_configure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t Channel_restore(mp_obj_t self_in);
extern mp_obj_t Channel_waveforms(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_frequency(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_volume(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_attack_duration(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_decay_duration(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_sustain_level(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_release_duration(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_pulse_width(size_t n_args, const mp_obj_t *args);
extern mp_obj_t Channel_trigger_attack(mp_obj_t self_in);
extern mp_obj_t Channel_trigger_release(mp_obj_t self_in);
extern mp_obj_t Channel_play_tone(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t Channel_stop_playing(mp_obj_t self_in);
extern void StellarUnicorn_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
extern mp_obj_t StellarUnicorn_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t StellarUnicorn___del__(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_clear(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_update(mp_obj_t self_in, mp_obj_t graphics_in);
extern mp_obj_t StellarUnicorn_set_brightness(mp_obj_t self_in, mp_obj_t value);
extern mp_obj_t StellarUnicorn_get_brightness(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_adjust_brightness(mp_obj_t self_in, mp_obj_t delta);
extern mp_obj_t StellarUnicorn_set_volume(mp_obj_t self_in, mp_obj_t value);
extern mp_obj_t StellarUnicorn_get_volume(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_adjust_volume(mp_obj_t self_in, mp_obj_t delta);
extern mp_obj_t StellarUnicorn_light(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_is_pressed(mp_obj_t self_in, mp_obj_t button);
extern mp_obj_t StellarUnicorn_play_sample(mp_obj_t self_in, mp_obj_t data);
extern mp_obj_t StellarUnicorn_play_synth(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_stop_playing(mp_obj_t self_in);
extern mp_obj_t StellarUnicorn_synth_channel(mp_obj_t self_in, mp_obj_t channel_in);

@ -1 +1 @@
Subproject commit ac2e9954edc185ec212e437e73e4b5e4290de35b
Subproject commit e68bb707b20ee326d84ab75fc9fb35f2e85b87e3

Wyświetl plik

@ -30,6 +30,7 @@ On the releases page you'll find a bunch of different .uf2 files for use on diff
| Badger 2040 W | **pimoroni-badger2040w-vx.x.x-micropython.uf2** or **pimoroni-badger2040w-v0.0.1-micropython-with-badger-os.uf2** | :warning: Badger OS will overwrite your files!
| Badger 2040 | **pimoroni-badger2040-vx.x.x-micropython.uf2** or **pimoroni-badger2040-v0.0.1-micropython-with-badger-os.uf2** | :warning: Badger OS will overwrite your files!
| Cosmic Unicorn | **pimoroni-picow_cosmic_unicorn-vx.x.x-micropython.uf2** | |
| Stellar Unicorn | **pimoroni-picow_stellar_unicorn-vx.x.x-micropython.uf2** | |
## Entering DFU/bootloader mode