Compare commits
34 Commits
41439a4a1a
...
85b12380f3
Author | SHA1 | Date |
---|---|---|
Nicolau Leal Werneck | 85b12380f3 | |
Connor Linfoot | 32c10482d9 | |
Philip Howard | 4c44b77193 | |
Phil Howard | 5510c82564 | |
Philip Howard | 3a10b29f54 | |
Phil Howard | 8cf276b992 | |
Philip Howard | f1ea35fbbf | |
Philip Howard | c066325ca0 | |
Philip Howard | fd4eb165f8 | |
Phil Howard | 8fc8a8ee06 | |
Phil Howard | 3bfb548686 | |
Philip Howard | 9edcdcc126 | |
Philip Howard | e8e550b18b | |
thirdr | cdb7b4bf2c | |
Philip Howard | 4fc3095433 | |
Phil Howard | 9c5b529754 | |
ZodiusInfuser | a87d5581aa | |
ZodiusInfuser | 44d7875f7e | |
ZodiusInfuser | a90c31fb3b | |
ZodiusInfuser | 458b0ac209 | |
Phil Howard | a537672dd4 | |
Phil Howard | d34e692f51 | |
Phil Howard | 27b913124c | |
Phil Howard | c7b788cd1d | |
Philip Howard | c386b3e9cf | |
Philip Howard | a7a2e2bee0 | |
Phil Howard | 19fa8864cf | |
Phil Howard | 964cf5eedf | |
Phil Howard | eab1595352 | |
Phil Howard | 5dd76ed31b | |
Nicolau Leal Werneck | 4dd6a378a2 | |
Nicolau Leal Werneck | 7bf03d9242 | |
Nicolau Leal Werneck | 0e772f06cd | |
Nicolau Leal Werneck | fb4f7ff27c |
|
@ -25,7 +25,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Compiler Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /home/runner/.ccache
|
||||
key: ccache-cmake-${{github.ref}}-${{matrix.board}}-${{github.sha}}
|
||||
|
@ -34,13 +34,13 @@ jobs:
|
|||
ccache-cmake-${{github.ref}}
|
||||
ccache-cmake
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
# Check out the Pico SDK
|
||||
- name: Checkout Pico SDK
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: raspberrypi/pico-sdk
|
||||
path: pico-sdk
|
||||
|
@ -48,7 +48,7 @@ jobs:
|
|||
|
||||
# Check out the Pico Extras
|
||||
- name: Checkout Pico Extras
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: raspberrypi/pico-extras
|
||||
path: pico-extras
|
||||
|
|
|
@ -13,6 +13,7 @@ jobs:
|
|||
build:
|
||||
name: ${{ matrix.name }} (${{ matrix.board }})
|
||||
runs-on: ubuntu-20.04
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
|
@ -20,7 +21,7 @@ jobs:
|
|||
board: RPI_PICO
|
||||
- name: picow
|
||||
board: RPI_PICO_W
|
||||
- name: tiny2040
|
||||
- name: tiny2040_8mb
|
||||
board: PIMORONI_TINY2040
|
||||
- name: picolipo_4mb
|
||||
board: PIMORONI_PICOLIPO_4MB
|
||||
|
@ -52,7 +53,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Compiler Cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /home/runner/.ccache
|
||||
key: ccache-micropython-${{ matrix.name }}-${{ github.ref }}-${{ github.sha }}
|
||||
|
@ -111,7 +112,7 @@ jobs:
|
|||
cmake_build
|
||||
|
||||
- name: Store .uf2 as artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.RELEASE_FILE }}.uf2
|
||||
path: build-${{ matrix.name }}/${{ env.RELEASE_FILE }}.uf2
|
||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
|||
name: Python Linting
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Python Deps
|
||||
run: python3 -m pip install flake8
|
||||
|
|
|
@ -44,6 +44,10 @@ You can find MicroPython examples for supported sensors, packs and bases in the
|
|||
|
||||
* [MicroPython Examples](micropython/examples)
|
||||
|
||||
You can also install MicroPython stubs into Visual Studio Code to give you auto-complete, see:
|
||||
|
||||
* [MicroPython Stubs](https://github.com/pimoroni/pimoroni-pico-stubs)
|
||||
|
||||
# C/C++
|
||||
|
||||
Advanced users that want to unleash the full power of Pico can use our C++ libraries. If you know what you're doing and want to build your own Pimoroni Pico project then start with the [Pimoroni Pico SDK Boilerplate](https://github.com/pimoroni/pico-boilerplate).
|
||||
|
|
|
@ -96,6 +96,24 @@ namespace pimoroni {
|
|||
191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,
|
||||
222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255};
|
||||
|
||||
inline constexpr uint16_t GAMMA_12BIT[256] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 47, 48, 50,
|
||||
52, 53, 55, 57, 59, 61, 63, 65, 68, 70, 72, 75, 78, 80, 83, 86,
|
||||
89, 92, 95, 98, 102, 105, 109, 112, 116, 120, 124, 128, 132, 137, 141, 146,
|
||||
150, 155, 160, 165, 170, 175, 181, 186, 192, 198, 204, 210, 216, 222, 228, 235,
|
||||
242, 249, 256, 263, 270, 277, 285, 293, 301, 309, 317, 325, 334, 342, 351, 360,
|
||||
369, 379, 388, 398, 408, 418, 428, 438, 449, 459, 470, 481, 493, 504, 516, 527,
|
||||
539, 552, 564, 576, 589, 602, 615, 629, 642, 656, 670, 684, 698, 713, 727, 742,
|
||||
758, 773, 789, 804, 820, 837, 853, 870, 887, 904, 921, 939, 957, 975, 993, 1011,
|
||||
1030, 1049, 1068, 1088, 1107, 1127, 1147, 1168, 1189, 1209, 1231, 1252, 1274, 1296, 1318, 1340,
|
||||
1363, 1386, 1409, 1432, 1456, 1480, 1504, 1529, 1554, 1579, 1604, 1630, 1656, 1682, 1708, 1735,
|
||||
1762, 1789, 1817, 1845, 1873, 1901, 1930, 1959, 1988, 2018, 2048, 2078, 2109, 2139, 2171, 2202,
|
||||
2234, 2266, 2298, 2331, 2364, 2397, 2430, 2464, 2498, 2533, 2568, 2603, 2638, 2674, 2710, 2747,
|
||||
2784, 2821, 2858, 2896, 2934, 2973, 3011, 3050, 3090, 3130, 3170, 3210, 3251, 3292, 3334, 3376,
|
||||
3418, 3461, 3504, 3547, 3591, 3635, 3679, 3724, 3769, 3814, 3860, 3906, 3953, 4000, 4047, 4095};
|
||||
|
||||
/* Moved from pico_unicorn.cpp
|
||||
v = (uint16_t)(powf((float)(n) / 255.0f, 2.2) * 16383.0f + 0.5f) */
|
||||
inline constexpr uint16_t GAMMA_14BIT[256] = {
|
||||
|
@ -147,4 +165,4 @@ namespace pimoroni {
|
|||
bool_pair() : first(false), second(false) {}
|
||||
bool_pair(bool first, bool second) : first(first), second(second) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,9 @@ namespace pimoroni {
|
|||
return !(sr.read() & 128);
|
||||
}
|
||||
|
||||
void Inky73::busy_wait() {
|
||||
while(is_busy()) {
|
||||
void Inky73::busy_wait(uint timeout_ms) {
|
||||
absolute_time_t timeout = make_timeout_time_ms(timeout_ms);
|
||||
while(is_busy() && !time_reached(timeout)) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace pimoroni {
|
|||
// Methods
|
||||
//--------------------------------------------------
|
||||
public:
|
||||
void busy_wait();
|
||||
void busy_wait(uint timeout_ms=45000);
|
||||
void reset();
|
||||
void power_off();
|
||||
|
||||
|
|
|
@ -133,8 +133,6 @@ namespace pimoroni {
|
|||
|
||||
void ST7789::configure_display(Rotation rotate) {
|
||||
|
||||
bool rotate180 = rotate == ROTATE_180 || rotate == ROTATE_90;
|
||||
|
||||
if(rotate == ROTATE_90 || rotate == ROTATE_270) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
|
@ -185,20 +183,30 @@ namespace pimoroni {
|
|||
// Pico Display
|
||||
if(width == 240 && height == 135) {
|
||||
caset[0] = 40; // 240 cols
|
||||
caset[1] = 279;
|
||||
raset[0] = 53; // 135 rows
|
||||
raset[1] = 187;
|
||||
madctl = rotate180 ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER;
|
||||
caset[1] = 40 + width - 1;
|
||||
raset[0] = 52; // 135 rows
|
||||
raset[1] = 52 + height - 1;
|
||||
if (rotate == ROTATE_0) {
|
||||
raset[0] += 1;
|
||||
raset[1] += 1;
|
||||
}
|
||||
madctl = rotate == ROTATE_180 ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER;
|
||||
madctl |= MADCTL::SWAP_XY | MADCTL::SCAN_ORDER;
|
||||
}
|
||||
|
||||
// Pico Display at 90 degree rotation
|
||||
if(width == 135 && height == 240) {
|
||||
caset[0] = 52; // 135 cols
|
||||
caset[1] = 186;
|
||||
caset[1] = 52 + width - 1;
|
||||
raset[0] = 40; // 240 rows
|
||||
raset[1] = 279;
|
||||
madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
|
||||
raset[1] = 40 + height - 1;
|
||||
madctl = 0;
|
||||
if (rotate == ROTATE_90) {
|
||||
caset[0] += 1;
|
||||
caset[1] += 1;
|
||||
madctl = MADCTL::COL_ORDER | MADCTL::ROW_ORDER;
|
||||
}
|
||||
madctl = rotate == ROTATE_90 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
|
||||
}
|
||||
|
||||
// Pico Display 2.0
|
||||
|
@ -207,7 +215,7 @@ namespace pimoroni {
|
|||
caset[1] = 319;
|
||||
raset[0] = 0;
|
||||
raset[1] = 239;
|
||||
madctl = rotate180 ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER;
|
||||
madctl = (rotate == ROTATE_180 || rotate == ROTATE_90) ? MADCTL::ROW_ORDER : MADCTL::COL_ORDER;
|
||||
madctl |= MADCTL::SWAP_XY | MADCTL::SCAN_ORDER;
|
||||
}
|
||||
|
||||
|
@ -217,7 +225,7 @@ namespace pimoroni {
|
|||
caset[1] = 239;
|
||||
raset[0] = 0;
|
||||
raset[1] = 319;
|
||||
madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
|
||||
madctl = (rotate == ROTATE_180 || rotate == ROTATE_90) ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
|
||||
}
|
||||
|
||||
// Byte swap the 16bit rows/cols values
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoUnicorn pico_unicorn;
|
||||
PicoUnicorn<14,1,0, uint16_t, pimoroni::GAMMA_14BIT> pico_unicorn;
|
||||
|
||||
int main() {
|
||||
bool a_pressed = false;
|
||||
|
|
|
@ -22,7 +22,7 @@ BSD License
|
|||
|
||||
using namespace pimoroni;
|
||||
|
||||
PicoUnicorn pico_unicorn;
|
||||
PicoUnicorn<14,1,0, uint16_t, pimoroni::GAMMA_14BIT> pico_unicorn;
|
||||
|
||||
// Sine table to speed up execution
|
||||
static const int8_t sinetab[256] = {
|
||||
|
@ -95,7 +95,7 @@ void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
|||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
pico_unicorn.init();
|
||||
// pico_unicorn.init();
|
||||
pico_unicorn.clear();
|
||||
|
||||
|
||||
|
|
|
@ -494,11 +494,14 @@ namespace pimoroni {
|
|||
void CosmicUnicorn::set_brightness(float value) {
|
||||
value = value < 0.0f ? 0.0f : value;
|
||||
value = value > 1.0f ? 1.0f : value;
|
||||
// Max brightness is - in fact - 256 since it's applied with:
|
||||
// result = (channel * brightness) >> 8
|
||||
// eg: (255 * 256) >> 8 == 255
|
||||
this->brightness = floor(value * 256.0f);
|
||||
}
|
||||
|
||||
float CosmicUnicorn::get_brightness() {
|
||||
return this->brightness / 255.0f;
|
||||
return this->brightness / 256.0f;
|
||||
}
|
||||
|
||||
void CosmicUnicorn::adjust_brightness(float delta) {
|
||||
|
|
|
@ -488,11 +488,14 @@ namespace pimoroni {
|
|||
void GalacticUnicorn::set_brightness(float value) {
|
||||
value = value < 0.0f ? 0.0f : value;
|
||||
value = value > 1.0f ? 1.0f : value;
|
||||
// Max brightness is - in fact - 256 since it's applied with:
|
||||
// result = (channel * brightness) >> 8
|
||||
// eg: (255 * 256) >> 8 == 255
|
||||
this->brightness = floor(value * 256.0f);
|
||||
}
|
||||
|
||||
float GalacticUnicorn::get_brightness() {
|
||||
return this->brightness / 255.0f;
|
||||
return this->brightness / 256.0f;
|
||||
}
|
||||
|
||||
void GalacticUnicorn::adjust_brightness(float delta) {
|
||||
|
|
|
@ -1,349 +1,5 @@
|
|||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
|
||||
#ifndef NO_QSTR
|
||||
#include "pico_unicorn.pio.h"
|
||||
#endif
|
||||
#include "pico_unicorn.hpp"
|
||||
|
||||
// pixel data is stored as a stream of bits delivered in the
|
||||
// order the PIO needs to manage the shift registers, row
|
||||
// selects, delays, and latching/blanking
|
||||
//
|
||||
// the data consists of 7 rows each of which has 14 frames of
|
||||
// bcd timing data
|
||||
//
|
||||
// each row looks like this:
|
||||
//
|
||||
// 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # pixel data
|
||||
// 0b00000000, # dummy byte to 32-bit align the frame (could be used to extend row select in future)
|
||||
// 0b01111111, # row 0 select (7-bit row address, 1-bit dummy data)
|
||||
// 0b00001111, 0b11111111, # bcd tick count (0-65536)
|
||||
//
|
||||
// .. next BCD frame for this row (repeat for 14 frames)
|
||||
//
|
||||
// .. next row (repeat for 7 rows)
|
||||
//
|
||||
// pixels are encoded as 4 bits: r, g, b, dummy to conveniently
|
||||
// pack them into nibbles
|
||||
|
||||
enum pin {
|
||||
LED_DATA = 8,
|
||||
LED_CLOCK = 9,
|
||||
LED_LATCH = 10,
|
||||
LED_BLANK = 11,
|
||||
ROW_0 = 22,
|
||||
ROW_1 = 21,
|
||||
ROW_2 = 20,
|
||||
ROW_3 = 19,
|
||||
ROW_4 = 18,
|
||||
ROW_5 = 17,
|
||||
ROW_6 = 16,
|
||||
A = 12,
|
||||
B = 13,
|
||||
X = 14,
|
||||
Y = 15,
|
||||
};
|
||||
|
||||
static uint32_t dma_channel;
|
||||
static uint32_t dma_ctrl_channel;
|
||||
|
||||
namespace pimoroni {
|
||||
PicoUnicorn* PicoUnicorn::unicorn = nullptr;
|
||||
PIO PicoUnicorn::bitstream_pio = pio0;
|
||||
uint PicoUnicorn::bitstream_sm = 0;
|
||||
uint PicoUnicorn::bitstream_sm_offset = 0;
|
||||
|
||||
PicoUnicorn::~PicoUnicorn() {
|
||||
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, &unicorn_program, bitstream_sm_offset);
|
||||
|
||||
unicorn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PicoUnicorn::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 = 0b1111111 << ROW_6;
|
||||
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_safe_abort(dma_channel);
|
||||
}
|
||||
|
||||
[[deprecated("Handled by constructor.")]]
|
||||
void PicoUnicorn::init() {
|
||||
return;
|
||||
}
|
||||
|
||||
PicoUnicorn::PicoUnicorn() {
|
||||
if(unicorn != nullptr) {
|
||||
partial_teardown();
|
||||
}
|
||||
|
||||
// setup pins
|
||||
gpio_init(pin::LED_DATA); gpio_set_dir(pin::LED_DATA, GPIO_OUT);
|
||||
gpio_init(pin::LED_CLOCK); gpio_set_dir(pin::LED_CLOCK, GPIO_OUT);
|
||||
gpio_init(pin::LED_LATCH); gpio_set_dir(pin::LED_LATCH, GPIO_OUT);
|
||||
gpio_init(pin::LED_BLANK); gpio_set_dir(pin::LED_BLANK, GPIO_OUT);
|
||||
|
||||
gpio_init(pin::ROW_0); gpio_set_dir(pin::ROW_0, GPIO_OUT);
|
||||
gpio_init(pin::ROW_1); gpio_set_dir(pin::ROW_1, GPIO_OUT);
|
||||
gpio_init(pin::ROW_2); gpio_set_dir(pin::ROW_2, GPIO_OUT);
|
||||
gpio_init(pin::ROW_3); gpio_set_dir(pin::ROW_3, GPIO_OUT);
|
||||
gpio_init(pin::ROW_4); gpio_set_dir(pin::ROW_4, GPIO_OUT);
|
||||
gpio_init(pin::ROW_5); gpio_set_dir(pin::ROW_5, GPIO_OUT);
|
||||
gpio_init(pin::ROW_6); gpio_set_dir(pin::ROW_6, GPIO_OUT);
|
||||
|
||||
// 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_FRAMES; frame++) {
|
||||
// determine offset in the buffer for this row/frame
|
||||
uint16_t offset = (row * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame);
|
||||
|
||||
uint16_t row_select_offset = offset + 9;
|
||||
uint16_t bcd_offset = offset + 10;
|
||||
|
||||
// the last bcd frame is used to allow the fets to discharge to avoid ghosting
|
||||
if(frame == BCD_FRAMES - 1) {
|
||||
bitstream[row_select_offset] = 0b11111111;
|
||||
|
||||
uint16_t bcd_ticks = 65535;
|
||||
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
|
||||
bitstream[bcd_offset] = (bcd_ticks & 0xff);
|
||||
|
||||
for(uint8_t col = 0; col < 6; col++) {
|
||||
bitstream[offset + col] = 0xff;
|
||||
}
|
||||
}else{
|
||||
uint8_t row_select_mask = ~(1 << (7 - row));
|
||||
bitstream[row_select_offset] = row_select_mask;
|
||||
|
||||
uint16_t bcd_ticks = 1 << frame;
|
||||
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
|
||||
bitstream[bcd_offset] = (bcd_ticks & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setup button inputs
|
||||
gpio_set_function(pin::A, GPIO_FUNC_SIO); gpio_set_dir(pin::A, GPIO_IN); gpio_pull_up(pin::A);
|
||||
gpio_set_function(pin::B, GPIO_FUNC_SIO); gpio_set_dir(pin::B, GPIO_IN); gpio_pull_up(pin::B);
|
||||
gpio_set_function(pin::X, GPIO_FUNC_SIO); gpio_set_dir(pin::X, GPIO_IN); gpio_pull_up(pin::X);
|
||||
gpio_set_function(pin::Y, GPIO_FUNC_SIO); gpio_set_dir(pin::Y, GPIO_IN); gpio_pull_up(pin::Y);
|
||||
|
||||
// setup the pio
|
||||
bitstream_pio = pio0;
|
||||
if(unicorn == nullptr) {
|
||||
bitstream_sm = pio_claim_unused_sm(bitstream_pio, true);
|
||||
bitstream_sm_offset = pio_add_program(bitstream_pio, &unicorn_program);
|
||||
}
|
||||
|
||||
pio_gpio_init(bitstream_pio, pin::LED_DATA);
|
||||
pio_gpio_init(bitstream_pio, pin::LED_CLOCK);
|
||||
pio_gpio_init(bitstream_pio, pin::LED_LATCH);
|
||||
pio_gpio_init(bitstream_pio, pin::LED_BLANK);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_0);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_1);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_2);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_3);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_4);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_5);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_6);
|
||||
|
||||
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::LED_DATA, 4, true);
|
||||
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::ROW_6, 7, true);
|
||||
|
||||
pio_sm_config c = unicorn_program_get_default_config(bitstream_sm_offset);
|
||||
|
||||
// osr shifts right, autopull on, autopull threshold 8
|
||||
sm_config_set_out_shift(&c, true, false, 32);
|
||||
|
||||
// configure out, set, and sideset pins
|
||||
sm_config_set_out_pins(&c, pin::ROW_6, 7);
|
||||
sm_config_set_sideset_pins(&c, pin::LED_CLOCK);
|
||||
sm_config_set_set_pins(&c, pin::LED_DATA, 4);
|
||||
|
||||
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
// setup chained dma transfer for pixel data to the pio
|
||||
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);
|
||||
|
||||
unicorn = this;
|
||||
}
|
||||
|
||||
void PicoUnicorn::clear() {
|
||||
for(uint8_t y = 0; y < HEIGHT; y++) {
|
||||
for(uint8_t x = 0; x < WIDTH; x++) {
|
||||
set_pixel(x, y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PicoUnicorn::set_pixel(uint8_t x, uint8_t 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;
|
||||
|
||||
// work out the byte offset of this pixel
|
||||
uint8_t byte_offset = x / 2;
|
||||
|
||||
// check if it's the high or low nibble and create mask and shift value
|
||||
uint8_t shift = x % 2 == 0 ? 0 : 4;
|
||||
uint8_t nibble_mask = 0b00001111 << shift;
|
||||
|
||||
uint16_t gr = pimoroni::GAMMA_14BIT[r];
|
||||
uint16_t gg = pimoroni::GAMMA_14BIT[g];
|
||||
uint16_t gb = pimoroni::GAMMA_14BIT[b];
|
||||
|
||||
// set the appropriate bits in the separate bcd frames
|
||||
for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) {
|
||||
// determine offset in the buffer for this row/frame
|
||||
uint16_t offset = (y * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame);
|
||||
|
||||
uint8_t rgbd = ((gr & 0b1) << 1) | ((gg & 0b1) << 3) | ((gb & 0b1) << 2);
|
||||
|
||||
// shift to correct nibble
|
||||
rgbd <<= shift;
|
||||
|
||||
// clear existing data
|
||||
bitstream[offset + byte_offset] &= ~nibble_mask;
|
||||
|
||||
// set new data
|
||||
bitstream[offset + byte_offset] |= rgbd;
|
||||
|
||||
gr >>= 1;
|
||||
gg >>= 1;
|
||||
gb >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void PicoUnicorn::set_pixel(uint8_t x, uint8_t y, uint8_t v) {
|
||||
set_pixel(x, y, v, v, v);
|
||||
}
|
||||
|
||||
bool PicoUnicorn::is_pressed(uint8_t button) {
|
||||
return !gpio_get(button);
|
||||
}
|
||||
|
||||
void PicoUnicorn::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 PicoUnicorn::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 < HEIGHT; y++) {
|
||||
for(int x = 0; x < WIDTH; 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 < HEIGHT; y++) {
|
||||
for(int x = 0; x < WIDTH; 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 % WIDTH;
|
||||
int y = offset / WIDTH;
|
||||
|
||||
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++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
template class pimoroni::PicoUnicorn<14,1,0, uint16_t, pimoroni::GAMMA_14BIT>;
|
||||
template class pimoroni::PicoUnicorn<12,1,4, uint16_t, pimoroni::GAMMA_12BIT>;
|
||||
template class pimoroni::PicoUnicorn<12,6,4, uint16_t, pimoroni::GAMMA_12BIT>;
|
||||
|
|
|
@ -1,10 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#ifndef NO_QSTR
|
||||
#include "pico_unicorn.pio.h"
|
||||
#endif
|
||||
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "common/pimoroni_common.hpp"
|
||||
|
||||
#include "pico_graphics.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
// pixel data is stored as a stream of bits delivered in the
|
||||
// order the PIO needs to manage the shift registers, row
|
||||
// selects, delays, and latching/blanking
|
||||
//
|
||||
// the data consists of 7 rows each of which has 14 frames of
|
||||
// bcd timing data
|
||||
//
|
||||
// each row looks like this:
|
||||
//
|
||||
// 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # pixel data
|
||||
// 0b00000000, # dummy byte to 32-bit align the frame (could be used to extend row select in future)
|
||||
// 0b01111111, # row 0 select (7-bit row address, 1-bit dummy data)
|
||||
// 0b00001111, 0b11111111, # bcd tick count (0-65536)
|
||||
//
|
||||
// .. next BCD frame for this row (repeat for 14 frames)
|
||||
//
|
||||
// .. next row (repeat for 7 rows)
|
||||
//
|
||||
// pixels are encoded as 4 bits: r, g, b, dummy to conveniently
|
||||
// pack them into nibbles
|
||||
|
||||
enum pin {
|
||||
LED_DATA = 8,
|
||||
LED_CLOCK = 9,
|
||||
LED_LATCH = 10,
|
||||
LED_BLANK = 11,
|
||||
ROW_0 = 22,
|
||||
ROW_1 = 21,
|
||||
ROW_2 = 20,
|
||||
ROW_3 = 19,
|
||||
ROW_4 = 18,
|
||||
ROW_5 = 17,
|
||||
ROW_6 = 16,
|
||||
A = 12,
|
||||
B = 13,
|
||||
X = 14,
|
||||
Y = 15,
|
||||
};
|
||||
|
||||
static uint32_t dma_channel;
|
||||
static uint32_t dma_ctrl_channel;
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
|
||||
class PicoUnicorn {
|
||||
public:
|
||||
static const int WIDTH = 16;
|
||||
|
@ -16,35 +67,343 @@ namespace pimoroni {
|
|||
|
||||
static const uint32_t ROW_COUNT = 7;
|
||||
static const uint32_t ROW_BYTES = 12;
|
||||
static const uint32_t BCD_FRAMES = 15; // includes fet discharge frame
|
||||
static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * BCD_FRAMES);
|
||||
// // static const uint32_t BCD_FRAMES = 14; // Original version
|
||||
// static const uint32_t BCD_FRAMES = 12;
|
||||
// // static const uint32_t DISCHARGE_FRAMES = 1;
|
||||
// static const uint32_t DISCHARGE_FRAMES = 6;
|
||||
static const uint32_t DISCHARGE_TICKS = 65535; // how long to run the discharge frame
|
||||
// // static const uint16_t FRAME_DELAY = 0; // Original version
|
||||
// static const uint16_t FRAME_DELAY = 4;
|
||||
static const uint16_t TOTAL_FRAMES = BCD_FRAMES + DISCHARGE_FRAMES;
|
||||
static const uint32_t BITSTREAM_LENGTH = (ROW_COUNT * ROW_BYTES * TOTAL_FRAMES);
|
||||
|
||||
private:
|
||||
static PIO bitstream_pio;
|
||||
static uint bitstream_sm;
|
||||
static uint bitstream_sm_offset;
|
||||
|
||||
|
||||
// must be aligned for 32bit dma transfer
|
||||
alignas(4) uint8_t bitstream[BITSTREAM_LENGTH] = {0};
|
||||
const uint32_t bitstream_addr = (uint32_t)bitstream;
|
||||
static PicoUnicorn* unicorn;
|
||||
|
||||
public:
|
||||
PicoUnicorn();
|
||||
~PicoUnicorn();
|
||||
PicoUnicorn(){
|
||||
if(unicorn != nullptr) {
|
||||
partial_teardown();
|
||||
}
|
||||
|
||||
void init();
|
||||
// setup pins
|
||||
gpio_init(pin::LED_DATA); gpio_set_dir(pin::LED_DATA, GPIO_OUT);
|
||||
gpio_init(pin::LED_CLOCK); gpio_set_dir(pin::LED_CLOCK, GPIO_OUT);
|
||||
gpio_init(pin::LED_LATCH); gpio_set_dir(pin::LED_LATCH, GPIO_OUT);
|
||||
gpio_init(pin::LED_BLANK); gpio_set_dir(pin::LED_BLANK, GPIO_OUT);
|
||||
|
||||
void clear();
|
||||
void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b);
|
||||
void set_pixel(uint8_t x, uint8_t y, uint8_t v);
|
||||
gpio_init(pin::ROW_0); gpio_set_dir(pin::ROW_0, GPIO_OUT);
|
||||
gpio_init(pin::ROW_1); gpio_set_dir(pin::ROW_1, GPIO_OUT);
|
||||
gpio_init(pin::ROW_2); gpio_set_dir(pin::ROW_2, GPIO_OUT);
|
||||
gpio_init(pin::ROW_3); gpio_set_dir(pin::ROW_3, GPIO_OUT);
|
||||
gpio_init(pin::ROW_4); gpio_set_dir(pin::ROW_4, GPIO_OUT);
|
||||
gpio_init(pin::ROW_5); gpio_set_dir(pin::ROW_5, GPIO_OUT);
|
||||
gpio_init(pin::ROW_6); gpio_set_dir(pin::ROW_6, GPIO_OUT);
|
||||
|
||||
bool is_pressed(uint8_t button);
|
||||
// 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 < TOTAL_FRAMES; frame++) {
|
||||
// determine offset in the buffer for this row/frame
|
||||
uint16_t offset = (row * ROW_BYTES * TOTAL_FRAMES) + (ROW_BYTES * frame);
|
||||
|
||||
uint16_t row_select_offset = offset + 9;
|
||||
uint16_t bcd_offset = offset + 10;
|
||||
|
||||
// the last bcd frame is used to allow the fets to discharge to avoid ghosting
|
||||
if(frame >= BCD_FRAMES) {
|
||||
bitstream[row_select_offset] = 0b11111111;
|
||||
|
||||
uint16_t bcd_ticks = DISCHARGE_TICKS;
|
||||
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
|
||||
bitstream[bcd_offset] = (bcd_ticks & 0xff);
|
||||
|
||||
for(uint8_t col = 0; col < 6; col++) {
|
||||
bitstream[offset + col] = 0xff;
|
||||
}
|
||||
} else {
|
||||
uint8_t row_select_mask = ~(1 << (7 - row));
|
||||
bitstream[row_select_offset] = row_select_mask;
|
||||
|
||||
// uint16_t frameperiod = std::max(uint16_t(1), FRAME_DELAY) << frame;
|
||||
// uint16_t bcd_ticks = frameperiod - FRAME_DELAY;
|
||||
uint16_t bcd_ticks = 1 << frame;
|
||||
|
||||
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
|
||||
bitstream[bcd_offset] = (bcd_ticks & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setup button inputs
|
||||
gpio_set_function(pin::A, GPIO_FUNC_SIO); gpio_set_dir(pin::A, GPIO_IN); gpio_pull_up(pin::A);
|
||||
gpio_set_function(pin::B, GPIO_FUNC_SIO); gpio_set_dir(pin::B, GPIO_IN); gpio_pull_up(pin::B);
|
||||
gpio_set_function(pin::X, GPIO_FUNC_SIO); gpio_set_dir(pin::X, GPIO_IN); gpio_pull_up(pin::X);
|
||||
gpio_set_function(pin::Y, GPIO_FUNC_SIO); gpio_set_dir(pin::Y, GPIO_IN); gpio_pull_up(pin::Y);
|
||||
|
||||
// setup the pio
|
||||
bitstream_pio = pio0;
|
||||
if(unicorn == nullptr) {
|
||||
bitstream_sm = pio_claim_unused_sm(bitstream_pio, true);
|
||||
bitstream_sm_offset = pio_add_program(bitstream_pio, &unicorn_program);
|
||||
}
|
||||
|
||||
pio_gpio_init(bitstream_pio, pin::LED_DATA);
|
||||
pio_gpio_init(bitstream_pio, pin::LED_CLOCK);
|
||||
pio_gpio_init(bitstream_pio, pin::LED_LATCH);
|
||||
pio_gpio_init(bitstream_pio, pin::LED_BLANK);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_0);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_1);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_2);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_3);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_4);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_5);
|
||||
pio_gpio_init(bitstream_pio, pin::ROW_6);
|
||||
|
||||
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::LED_DATA, 4, true);
|
||||
pio_sm_set_consecutive_pindirs(bitstream_pio, bitstream_sm, pin::ROW_6, 7, true);
|
||||
|
||||
pio_sm_config c = unicorn_program_get_default_config(bitstream_sm_offset);
|
||||
|
||||
// osr shifts right, autopull on, autopull threshold 8
|
||||
sm_config_set_out_shift(&c, true, false, 32);
|
||||
|
||||
// configure out, set, and sideset pins
|
||||
sm_config_set_out_pins(&c, pin::ROW_6, 7);
|
||||
sm_config_set_sideset_pins(&c, pin::LED_CLOCK);
|
||||
sm_config_set_set_pins(&c, pin::LED_DATA, 4);
|
||||
|
||||
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
// setup chained dma transfer for pixel data to the pio
|
||||
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);
|
||||
|
||||
unicorn = this;
|
||||
}
|
||||
|
||||
~PicoUnicorn(){
|
||||
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, &unicorn_program, bitstream_sm_offset);
|
||||
|
||||
unicorn = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
[[deprecated("Handled by constructor.")]]
|
||||
void init() {
|
||||
return;
|
||||
}
|
||||
|
||||
void clear(){
|
||||
for(uint8_t y = 0; y < HEIGHT; y++) {
|
||||
for(uint8_t x = 0; x < WIDTH; x++) {
|
||||
set_pixel(x, y, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) {
|
||||
L gr = COLORLUT[r];
|
||||
L gg = COLORLUT[g];
|
||||
L gb = COLORLUT[b];
|
||||
|
||||
set_pixel_(x, y, gr, gg, gb);
|
||||
}
|
||||
|
||||
void set_pixel(uint8_t x, uint8_t y, float r, float g, float b) {
|
||||
return set_pixel(x, y,
|
||||
uint8_t(std::round(r)),
|
||||
uint8_t(std::round(g)),
|
||||
uint8_t(std::round(b))
|
||||
);
|
||||
}
|
||||
void set_pixel_(uint8_t x, uint8_t y, L gr, L gg, L gb) {
|
||||
if(x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
|
||||
|
||||
// make those coordinates sane
|
||||
x = (WIDTH - 1) - x;
|
||||
|
||||
// work out the byte offset of this pixel
|
||||
uint8_t byte_offset = x / 2;
|
||||
|
||||
// check if it's the high or low nibble and create mask and shift value
|
||||
uint8_t shift = x % 2 == 0 ? 0 : 4;
|
||||
uint8_t nibble_mask = 0b00001111 << shift;
|
||||
|
||||
// set the appropriate bits in the separate bcd frames
|
||||
for(uint8_t frame = 0; frame < TOTAL_FRAMES; frame++) {
|
||||
// determine offset in the buffer for this row/frame
|
||||
uint16_t offset = (y * ROW_BYTES * TOTAL_FRAMES) + (ROW_BYTES * frame);
|
||||
|
||||
uint8_t rgbd = ((gr & 0b1) << 1) | ((gg & 0b1) << 3) | ((gb & 0b1) << 2);
|
||||
|
||||
// shift to correct nibble
|
||||
rgbd <<= shift;
|
||||
|
||||
// clear existing data
|
||||
uint16_t othernibble = bitstream[offset + byte_offset] & ~nibble_mask;
|
||||
|
||||
// set new data
|
||||
bitstream[offset + byte_offset] = othernibble | rgbd;
|
||||
|
||||
gr >>= 1;
|
||||
gg >>= 1;
|
||||
gb >>= 1;
|
||||
}
|
||||
}
|
||||
// void set_pixel(uint8_t x, uint8_t y, int r, int g, int b);
|
||||
void set_pixel(uint8_t x, uint8_t y, uint8_t v){
|
||||
set_pixel(x, y, v, v, v);
|
||||
}
|
||||
|
||||
bool is_pressed(uint8_t button){
|
||||
return !gpio_get(button);
|
||||
}
|
||||
|
||||
void 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 < HEIGHT; y++) {
|
||||
for(int x = 0; x < WIDTH; 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 < HEIGHT; y++) {
|
||||
for(int x = 0; x < WIDTH; 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 % WIDTH;
|
||||
int y = offset / WIDTH;
|
||||
|
||||
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++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update(PicoGraphics *graphics);
|
||||
private:
|
||||
void partial_teardown();
|
||||
void dma_safe_abort(uint channel);
|
||||
void 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 = 0b1111111 << ROW_6;
|
||||
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_safe_abort(dma_channel);
|
||||
}
|
||||
|
||||
void 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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
|
||||
PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>* PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::unicorn = nullptr;
|
||||
|
||||
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
|
||||
PIO PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::bitstream_pio = pio0;
|
||||
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
|
||||
uint PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::bitstream_sm = 0;
|
||||
template<uint32_t BCD_FRAMES, uint32_t DISCHARGE_FRAMES, uint16_t FRAME_DELAY, typename L, const L* COLORLUT>
|
||||
uint PicoUnicorn<BCD_FRAMES, DISCHARGE_FRAMES, FRAME_DELAY,L,COLORLUT>::bitstream_sm_offset = 0;
|
||||
|
||||
}
|
||||
|
|
|
@ -485,11 +485,14 @@ namespace pimoroni {
|
|||
void StellarUnicorn::set_brightness(float value) {
|
||||
value = value < 0.0f ? 0.0f : value;
|
||||
value = value > 1.0f ? 1.0f : value;
|
||||
// Max brightness is - in fact - 256 since it's applied with:
|
||||
// result = (channel * brightness) >> 8
|
||||
// eg: (255 * 256) >> 8 == 255
|
||||
this->brightness = floor(value * 256.0f);
|
||||
}
|
||||
|
||||
float StellarUnicorn::get_brightness() {
|
||||
return this->brightness / 255.0f;
|
||||
return this->brightness / 256.0f;
|
||||
}
|
||||
|
||||
void StellarUnicorn::adjust_brightness(float delta) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import time
|
||||
from machine import Pin
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
from breakout_ltr559 import BreakoutLTR559
|
||||
|
||||
PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5}
|
||||
PINS_PICO_EXPLORER = {"sda": 20, "scl": 21}
|
||||
PIN_INTERRUPT = 22 # 3 for Breakout Garden
|
||||
|
||||
i2c = PimoroniI2C(**PINS_PICO_EXPLORER)
|
||||
ltr = BreakoutLTR559(i2c, interrupt=PIN_INTERRUPT)
|
||||
interrupt = Pin(PIN_INTERRUPT, Pin.IN, Pin.PULL_DOWN)
|
||||
|
||||
ltr.light_threshold(0, 10) # COUNTS, NOT LUX!!!
|
||||
ltr.proximity_threshold(0, 10)
|
||||
|
||||
|
||||
def read(pin):
|
||||
reading = ltr.get_reading()
|
||||
if reading is not None:
|
||||
print("T: ", time.ticks_ms(), " Lux: ", reading[BreakoutLTR559.LUX], " Prox: ", reading[BreakoutLTR559.PROXIMITY])
|
||||
|
||||
|
||||
interrupt.irq(trigger=Pin.IRQ_RISING, handler=read)
|
||||
|
||||
part_id = ltr.part_id()
|
||||
print("Found LTR559. Part ID: 0x", '{:02x}'.format(part_id), sep="")
|
||||
|
||||
while True:
|
||||
pass
|
|
@ -95,7 +95,7 @@ def draw():
|
|||
y += line_space
|
||||
x = default_x
|
||||
|
||||
graphics.text(letter.upper(), x, y, 640, scale, spacing)
|
||||
graphics.text(letter.upper(), x, y, 640, scale=scale, spacing=spacing)
|
||||
x += letter_space
|
||||
|
||||
graphics.update()
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
- [Read ADCs](#read-adcs)
|
||||
- [Read GPIOs](#read-gpios)
|
||||
- [Read Encoders](#read-encoders)
|
||||
- [Read Speeds](#read-speeds)
|
||||
- [LED Rainbow](#led-rainbow)
|
||||
- [Reset Inventor](#reset-inventor)
|
||||
- [Motor Examples](#motor-examples)
|
||||
|
@ -22,13 +23,14 @@
|
|||
- [Velocity Tuning](#velocity-tuning)
|
||||
- [Position on Velocity Tuning](#position-on-velocity-tuning)
|
||||
- [Servo Examples](#servo-examples)
|
||||
- [Single Servos](#single-servo)
|
||||
- [Single Servo](#single-servo)
|
||||
- [Multiple Servos](#multiple-servos)
|
||||
- [Simple Easing](#simple-easing)
|
||||
- [Servo Wave](#servo-wave)
|
||||
- [Calibration](#calibration)
|
||||
- [Audio Examples](#audio-examples)
|
||||
- [Tone Song](#tone-song)
|
||||
- [Motor Song](#motor-song)
|
||||
|
||||
## Function Examples
|
||||
|
||||
|
@ -50,6 +52,12 @@ Shows how to initialise and read the 6 GPIO headers of Inventor 2040 W.
|
|||
Demonstrates how to read the angles of Inventor 2040 W's two encoders.
|
||||
|
||||
|
||||
### Read Speeds
|
||||
[read_speeds.py](read_speeds.py)
|
||||
|
||||
Demonstrates how to read the speeds of Inventor 2040 W's two encoders.
|
||||
|
||||
|
||||
### LED Rainbow
|
||||
[led_rainbow.py](led_rainbow.py)
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import time
|
||||
from inventor import Inventor2040W, NUM_MOTORS # , MOTOR_A, MOTOR_B
|
||||
# from pimoroni import REVERSED_DIR
|
||||
|
||||
"""
|
||||
Demonstrates how to read the speeds of Inventor 2040 W's two encoders.
|
||||
|
||||
Press "User" to exit the program.
|
||||
"""
|
||||
|
||||
# Wheel friendly names
|
||||
NAMES = ["LEFT", "RIGHT"]
|
||||
|
||||
# Constants
|
||||
GEAR_RATIO = 50 # The gear ratio of the motor
|
||||
SPEED = 1.0 # The speed to drive the motors at
|
||||
SLEEP = 0.1 # The time to sleep between each capture
|
||||
|
||||
# Create a new Inventor2040W
|
||||
board = Inventor2040W(motor_gear_ratio=GEAR_RATIO)
|
||||
|
||||
# Uncomment the below lines (and the top imports) to
|
||||
# reverse the counting direction of an encoder
|
||||
# encoders[MOTOR_A].direction(REVERSED_DIR)
|
||||
# encoders[MOTOR_B].direction(REVERSED_DIR)
|
||||
|
||||
# Set both motors driving
|
||||
for motor in board.motors:
|
||||
motor.speed(SPEED)
|
||||
|
||||
# Variables for storing encoder captures
|
||||
captures = [None] * NUM_MOTORS
|
||||
|
||||
# Read the encoders until the user button is pressed
|
||||
while not board.switch_pressed():
|
||||
|
||||
# Capture the state of all the encoders since the last capture, SLEEP seconds ago
|
||||
for i in range(NUM_MOTORS):
|
||||
captures[i] = board.encoders[i].capture()
|
||||
|
||||
# Print out the speeds from each encoder
|
||||
for i in range(NUM_MOTORS):
|
||||
print(NAMES[i], "=", captures[i].revolutions_per_second, end=", ")
|
||||
print()
|
||||
|
||||
time.sleep(SLEEP)
|
|
@ -6,6 +6,7 @@ This library offers an `Encoder` class that uses Programmable IO (PIO) hardware
|
|||
|
||||
|
||||
## Table of Content
|
||||
- [Table of Content](#table-of-content)
|
||||
- [Encoder](#encoder)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Count and Angle](#count-and-angle)
|
||||
|
@ -102,7 +103,22 @@ degrees_per_second
|
|||
radians_per_second
|
||||
```
|
||||
|
||||
Internally `.capture()` does the same up-front reading of values but does so more optimally within the underlying C++ driver. As an added bonus, it calculates encoder speeds too, by using the captured `delta` along with timing information returned by the PIO, more accurately than estimating a speed from the `delta` alone.
|
||||
Internally `.capture()` does the same up-front reading of values but does so more optimally within the underlying C++ driver. It calculates encoder speeds too, by using the difference between the current `count` and the **last capture's** `count` (aka the `delta`), along with timing information returned by the PIO. This produces speed readings that are more accurate than estimating a speed from the `delta` alone.
|
||||
|
||||
:information_source: **It is recommended to perform captures frequently and at a consistent rate.** If this is not possible for your project, consider performing a dummy capture at the start of the time window you actually wish to measure the encoder's speed over.
|
||||
|
||||
```python
|
||||
# Perform a dummy capture to clear the encoder
|
||||
enc.capture()
|
||||
|
||||
# Wait for the capture time to pass
|
||||
time.sleep(CAPTURE_TIME)
|
||||
|
||||
# Perform a capture and read the measured speed
|
||||
capture = enc.capture()
|
||||
|
||||
print("Speed =", capture.revolutions_per_second)
|
||||
```
|
||||
|
||||
|
||||
### State
|
||||
|
|
|
@ -84,6 +84,7 @@ The available display settings are listed here:
|
|||
* 32 x 32 Matrix - `DISPLAY_INTERSTATE75_32X32`
|
||||
* 64 x 32 Matrix - `DISPLAY_INTERSTATE75_64X32`
|
||||
* 96 x 32 Matrix - `DISPLAY_INTERSTATE75_96X32`
|
||||
* 96 x 48 Matrix - `DISPLAY_INTERSTATE75_96X48`
|
||||
* 128 x 32 Matrix - `DISPLAY_INTERSTATE75_128X32`
|
||||
* 64 x 64 Matrix - `DISPLAY_INTERSTATE75_64X64`
|
||||
* 128 x 64 Matrix - `DISPLAY_INTERSTATE75_128X64`
|
||||
|
|
|
@ -145,6 +145,7 @@ STATIC const mp_map_elem_t picographics_globals_table[] = {
|
|||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_32X32), MP_ROM_INT(DISPLAY_INTERSTATE75_32X32) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_64X32), MP_ROM_INT(DISPLAY_INTERSTATE75_64X32) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_96X32), MP_ROM_INT(DISPLAY_INTERSTATE75_96X32) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_96X48), MP_ROM_INT(DISPLAY_INTERSTATE75_96X48) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_128X32), MP_ROM_INT(DISPLAY_INTERSTATE75_128X32) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_64X64), MP_ROM_INT(DISPLAY_INTERSTATE75_64X64) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_128X64), MP_ROM_INT(DISPLAY_INTERSTATE75_128X64) },
|
||||
|
|
|
@ -155,6 +155,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_INTERSTATE75_96X48:
|
||||
width = 96;
|
||||
height = 48;
|
||||
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_INTERSTATE75_128X32:
|
||||
width = 128;
|
||||
height = 32;
|
||||
|
|
|
@ -19,6 +19,7 @@ enum PicoGraphicsDisplay {
|
|||
DISPLAY_INTERSTATE75_32X32,
|
||||
DISPLAY_INTERSTATE75_64X32,
|
||||
DISPLAY_INTERSTATE75_96X32,
|
||||
DISPLAY_INTERSTATE75_96X48,
|
||||
DISPLAY_INTERSTATE75_128X32,
|
||||
DISPLAY_INTERSTATE75_64X64,
|
||||
DISPLAY_INTERSTATE75_128X64,
|
||||
|
|
|
@ -49,6 +49,7 @@ STATIC const mp_map_elem_t PNG_globals_table[] = {
|
|||
{ MP_ROM_QSTR(MP_QSTR_PNG_POSTERISE), MP_ROM_INT(0) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_PNG_DITHER), MP_ROM_INT(1) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_PNG_COPY), MP_ROM_INT(2) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_PNG_PEN), MP_ROM_INT(3) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_PNG_globals, PNG_globals_table);
|
||||
|
|
|
@ -25,6 +25,7 @@ typedef struct _PNG_decode_target {
|
|||
Rect source = {0, 0, 0, 0};
|
||||
Point scale = {1, 1};
|
||||
int rotation = 0;
|
||||
uint8_t palette_offset = 0;
|
||||
} _PNG_decode_target;
|
||||
|
||||
typedef struct _PNG_obj_t {
|
||||
|
@ -44,6 +45,7 @@ enum DECODE_MODE : uint8_t {
|
|||
MODE_POSTERIZE = 0u,
|
||||
MODE_DITHER = 1u,
|
||||
MODE_COPY = 2u,
|
||||
MODE_PEN = 3u,
|
||||
};
|
||||
|
||||
void *pngdec_open_callback(const char *filename, int32_t *size) {
|
||||
|
@ -125,6 +127,7 @@ mp_event_handle_nowait();
|
|||
PicoGraphics *current_graphics = (PicoGraphics *)target->target;
|
||||
Point current_position = target->position;
|
||||
uint8_t current_mode = target->mode;
|
||||
uint8_t current_palette_offset = target->palette_offset;
|
||||
Point scale = target->scale;
|
||||
int rotation = target->rotation;
|
||||
Point step = {0, 0};
|
||||
|
@ -158,29 +161,79 @@ mp_event_handle_nowait();
|
|||
break;
|
||||
}
|
||||
|
||||
//mp_printf(&mp_plat_print, "Drawing scanline at %d, %dbpp, type: %d, width: %d pitch: %d alpha: %d\n", y, pDraw->iBpp, pDraw->iPixelType, pDraw->iWidth, pDraw->iPitch, pDraw->iHasAlpha);
|
||||
//mp_printf(&mp_plat_print, "Drawing scanline at %d, %dbpp, type: %d, width: %d pitch: %d alpha: %d\n", pDraw->y , pDraw->iBpp, pDraw->iPixelType, pDraw->iWidth, pDraw->iPitch, pDraw->iHasAlpha);
|
||||
uint8_t *pixel = (uint8_t *)pDraw->pPixels;
|
||||
if(pDraw->iPixelType == PNG_PIXEL_TRUECOLOR ) {
|
||||
if(pDraw->iPixelType == PNG_PIXEL_TRUECOLOR || pDraw->iPixelType == PNG_PIXEL_TRUECOLOR_ALPHA) {
|
||||
for(int x = 0; x < pDraw->iWidth; x++) {
|
||||
uint8_t r = *pixel++;
|
||||
uint8_t g = *pixel++;
|
||||
uint8_t b = *pixel++;
|
||||
if(x < target->source.x || x >= target->source.x + target->source.w) continue;
|
||||
current_graphics->set_pen(r, g, b);
|
||||
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
|
||||
current_position += step;
|
||||
}
|
||||
} else if (pDraw->iPixelType == PNG_PIXEL_TRUECOLOR_ALPHA) {
|
||||
for(int x = 0; x < pDraw->iWidth; x++) {
|
||||
uint8_t r = *pixel++;
|
||||
uint8_t g = *pixel++;
|
||||
uint8_t b = *pixel++;
|
||||
uint8_t a = *pixel++;
|
||||
uint8_t a = 1;
|
||||
if (pDraw->iHasAlpha) {
|
||||
a = *pixel++;
|
||||
}
|
||||
if(x < target->source.x || x >= target->source.x + target->source.w) continue;
|
||||
if (a) {
|
||||
current_graphics->set_pen(r, g, b);
|
||||
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
|
||||
}
|
||||
current_position += step;
|
||||
}
|
||||
} else if (pDraw->iPixelType == PNG_PIXEL_GRAYSCALE) {
|
||||
for(int x = 0; x < pDraw->iWidth; x++) {
|
||||
uint8_t i = 0;
|
||||
if(pDraw->iBpp == 8) { // 8bpp
|
||||
i = *pixel++; // Already 8bpc
|
||||
} else if (pDraw->iBpp == 4) { // 4bpp
|
||||
i = *pixel;
|
||||
i >>= (x & 0b1) ? 0 : 4;
|
||||
i &= 0xf;
|
||||
if (x & 1) pixel++;
|
||||
// Just copy the colour into the upper and lower nibble
|
||||
if(current_mode != MODE_COPY) {
|
||||
i = (i << 4) | i;
|
||||
}
|
||||
} else if (pDraw->iBpp == 2) { // 2bpp
|
||||
i = *pixel;
|
||||
i >>= 6 - ((x & 0b11) << 1);
|
||||
i &= 0x3;
|
||||
if ((x & 0b11) == 0b11) pixel++;
|
||||
// Evenly spaced 4-colour palette
|
||||
if(current_mode != MODE_COPY) {
|
||||
i = (0xFFB86800 >> (i * 8)) & 0xFF;
|
||||
}
|
||||
} else { // 1bpp
|
||||
i = *pixel;
|
||||
i >>= 7 - (x & 0b111);
|
||||
i &= 0b1;
|
||||
if ((x & 0b111) == 0b111) pixel++;
|
||||
if(current_mode != MODE_COPY) {
|
||||
i = i ? 255 : 0;
|
||||
}
|
||||
}
|
||||
if(x < target->source.x || x >= target->source.x + target->source.w) continue;
|
||||
|
||||
//mp_printf(&mp_plat_print, "Drawing pixel at %dx%d, %dbpp, value %d\n", current_position.x, current_position.y, pDraw->iBpp, i);
|
||||
if (current_mode != MODE_PEN) {
|
||||
// Allow greyscale PNGs to be copied just like an indexed PNG
|
||||
// since we might want to offset and recolour them.
|
||||
if(current_mode == MODE_COPY
|
||||
&& (current_graphics->pen_type == PicoGraphics::PEN_P8
|
||||
|| current_graphics->pen_type == PicoGraphics::PEN_P4
|
||||
|| current_graphics->pen_type == PicoGraphics::PEN_3BIT
|
||||
|| current_graphics->pen_type == PicoGraphics::PEN_INKY7)) {
|
||||
if(current_palette_offset > 0) {
|
||||
i = ((int16_t)(i) + current_palette_offset) & 0xff;
|
||||
}
|
||||
current_graphics->set_pen(i);
|
||||
} else {
|
||||
current_graphics->set_pen(i, i, i);
|
||||
}
|
||||
}
|
||||
if (current_mode != MODE_PEN || i == 0) {
|
||||
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
|
||||
}
|
||||
|
||||
current_position += step;
|
||||
}
|
||||
} else if (pDraw->iPixelType == PNG_PIXEL_INDEXED) {
|
||||
|
@ -231,6 +284,9 @@ mp_event_handle_nowait();
|
|||
|
||||
// Copy raw palette indexes over
|
||||
if(current_mode == MODE_COPY) {
|
||||
if(current_palette_offset > 0) {
|
||||
i = ((int16_t)(i) + current_palette_offset) & 0xff;
|
||||
}
|
||||
current_graphics->set_pen(i);
|
||||
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
|
||||
// Posterized output to the available palete
|
||||
|
@ -326,7 +382,7 @@ mp_obj_t _PNG_openRAM(mp_obj_t self_in, mp_obj_t buffer) {
|
|||
|
||||
// decode
|
||||
mp_obj_t _PNG_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_self, ARG_x, ARG_y, ARG_scale, ARG_mode, ARG_source, ARG_rotate };
|
||||
enum { ARG_self, ARG_x, ARG_y, ARG_scale, ARG_mode, ARG_source, ARG_rotate, ARG_palette_offset };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
||||
{ MP_QSTR_x, MP_ARG_INT, {.u_int = 0} },
|
||||
|
@ -335,6 +391,7 @@ mp_obj_t _PNG_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
|
|||
{ MP_QSTR_mode, MP_ARG_INT, {.u_int = 0} },
|
||||
{ MP_QSTR_source, MP_ARG_OBJ, {.u_obj = nullptr} },
|
||||
{ MP_QSTR_rotate, MP_ARG_INT, {.u_int = 0} },
|
||||
{ MP_QSTR_palette_offset, MP_ARG_INT, {.u_int = 0} },
|
||||
};
|
||||
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
|
@ -394,6 +451,8 @@ mp_obj_t _PNG_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
|
|||
|
||||
self->decode_target->position = {args[ARG_x].u_int, args[ARG_y].u_int};
|
||||
|
||||
self->decode_target->palette_offset = args[ARG_palette_offset].u_int;
|
||||
|
||||
// Just-in-time open of the filename/buffer we stored in self->file via open_RAM or open_file
|
||||
|
||||
// Source is a filename
|
||||
|
|
|
@ -32,6 +32,7 @@ You can choose the HUB75 matrix display size that you wish to use by defining `d
|
|||
DISPLAY_INTERSTATE75_32X32
|
||||
DISPLAY_INTERSTATE75_64X32
|
||||
DISPLAY_INTERSTATE75_96X32
|
||||
DISPLAY_INTERSTATE75_96X48
|
||||
DISPLAY_INTERSTATE75_128X32
|
||||
DISPLAY_INTERSTATE75_64X64
|
||||
DISPLAY_INTERSTATE75_128X64
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from pimoroni import RGBLED, Button
|
||||
from picographics import PicoGraphics, DISPLAY_INTERSTATE75_32X32, DISPLAY_INTERSTATE75_64X32, DISPLAY_INTERSTATE75_96X32, DISPLAY_INTERSTATE75_128X32, DISPLAY_INTERSTATE75_64X64, DISPLAY_INTERSTATE75_128X64, DISPLAY_INTERSTATE75_192X64, DISPLAY_INTERSTATE75_256X64
|
||||
from picographics import PicoGraphics, DISPLAY_INTERSTATE75_32X32, DISPLAY_INTERSTATE75_64X32, DISPLAY_INTERSTATE75_96X32, DISPLAY_INTERSTATE75_96X48, DISPLAY_INTERSTATE75_128X32, DISPLAY_INTERSTATE75_64X64, DISPLAY_INTERSTATE75_128X64, DISPLAY_INTERSTATE75_192X64, DISPLAY_INTERSTATE75_256X64
|
||||
from pimoroni_i2c import PimoroniI2C
|
||||
import hub75
|
||||
import sys
|
||||
|
@ -23,6 +23,7 @@ class Interstate75:
|
|||
DISPLAY_INTERSTATE75_32X32 = DISPLAY_INTERSTATE75_32X32
|
||||
DISPLAY_INTERSTATE75_64X32 = DISPLAY_INTERSTATE75_64X32
|
||||
DISPLAY_INTERSTATE75_96X32 = DISPLAY_INTERSTATE75_96X32
|
||||
DISPLAY_INTERSTATE75_96X48 = DISPLAY_INTERSTATE75_96X48
|
||||
DISPLAY_INTERSTATE75_128X32 = DISPLAY_INTERSTATE75_128X32
|
||||
DISPLAY_INTERSTATE75_64X64 = DISPLAY_INTERSTATE75_64X64
|
||||
DISPLAY_INTERSTATE75_128X64 = DISPLAY_INTERSTATE75_128X64
|
||||
|
|
Loading…
Reference in New Issue