kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Plasma 2040 library & examples
Library: Includes classes for driving WS2812 and APA102 LEDs and defines for Plasma features. Encoder Example: Supports connecting a Rotary Encoder via the Qw'St connector. Works with APA102 or WS281X pixels. Pressing A will cycle between: 1. Colour change 2. Brightness change 3. Cycle delay Pressing B will switch back into auto-cycle mode. Turning the encoder at any time will switch out of auto cycle mode into parameter adjust mode. Also includes a bugfix to Rotary Encoder for getting the interrupt correctly. Rainbow Example: Basic rainbow cycle, press B to speed up and A to slow down.pull/181/head
rodzic
bcde0b6784
commit
815e784625
|
@ -743,6 +743,13 @@ namespace pimoroni {
|
|||
return encoder_offset[channel] + value;
|
||||
}
|
||||
|
||||
void IOExpander::clear_rotary_encoder(uint8_t channel) {
|
||||
channel -= 1;
|
||||
encoder_last[channel] = 0;
|
||||
uint8_t reg = ENC_COUNT[channel];
|
||||
i2c->reg_write_uint8(address, reg, 0);
|
||||
}
|
||||
|
||||
uint8_t IOExpander::get_bit(uint8_t reg, uint8_t bit) {
|
||||
// Returns the specified bit (nth position from right) from a register
|
||||
return i2c->reg_read_uint8(address, reg) & (1 << bit);
|
||||
|
|
|
@ -214,6 +214,7 @@ namespace pimoroni {
|
|||
|
||||
void setup_rotary_encoder(uint8_t channel, uint8_t pin_a, uint8_t pin_b, uint8_t pin_c = 0, bool count_microsteps = false);
|
||||
int16_t read_rotary_encoder(uint8_t channel);
|
||||
void clear_rotary_encoder(uint8_t channel);
|
||||
|
||||
private:
|
||||
uint8_t i2c_reg_read_uint8(uint8_t reg);
|
||||
|
|
|
@ -16,6 +16,8 @@ add_subdirectory(breakout_msa301)
|
|||
add_subdirectory(breakout_bme688)
|
||||
add_subdirectory(breakout_bmp280)
|
||||
add_subdirectory(breakout_bme280)
|
||||
add_subdirectory(breakout_as7262)
|
||||
add_subdirectory(breakout_bh1745)
|
||||
|
||||
add_subdirectory(pico_display)
|
||||
add_subdirectory(pico_unicorn)
|
||||
|
@ -30,5 +32,5 @@ add_subdirectory(pico_tof_display)
|
|||
add_subdirectory(pico_trackball_display)
|
||||
add_subdirectory(pico_audio)
|
||||
add_subdirectory(pico_wireless)
|
||||
add_subdirectory(breakout_as7262)
|
||||
add_subdirectory(breakout_bh1745)
|
||||
|
||||
add_subdirectory(plasma_2040)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
include(plasma2040_rotary.cmake)
|
||||
include(plasma2040_rainbow.cmake)
|
|
@ -0,0 +1,10 @@
|
|||
add_executable(plasma2040_rainbow plasma2040_rainbow.cpp)
|
||||
|
||||
target_link_libraries(plasma2040_rainbow
|
||||
pico_stdlib
|
||||
plasma2040
|
||||
rgbled
|
||||
button
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(plasma2040_rainbow)
|
|
@ -0,0 +1,66 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "plasma2040.hpp"
|
||||
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "rgbled.hpp"
|
||||
#include "button.hpp"
|
||||
|
||||
/*
|
||||
Press "B" to speed up the LED cycling effect.
|
||||
Press "A" to slow it down again.
|
||||
*/
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint N_LEDS = 30;
|
||||
|
||||
// Pick *one* LED type by uncommenting the relevant line below:
|
||||
|
||||
// APA102-style LEDs with Data/Clock lines. AKA DotStar
|
||||
//plasma::APA102 led_strip(N_LEDS, pio0, 0, plasma::PIN_DAT, plasma::PIN_CLK);
|
||||
|
||||
// WS28X-style LEDs with a single signal line. AKA NeoPixel
|
||||
plasma::WS2812 led_strip(N_LEDS, pio0, 0, plasma::PIN_DAT);
|
||||
|
||||
|
||||
Button button_a(plasma::BUTTON_A, Polarity::ACTIVE_LOW, 50);
|
||||
Button button_b(plasma::BUTTON_B, Polarity::ACTIVE_LOW, 50);
|
||||
RGBLED led(plasma::LED_R, plasma::LED_G, plasma::LED_B);
|
||||
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
led_strip.start(60);
|
||||
|
||||
int speed = 10;
|
||||
float offset = 0.0f;
|
||||
|
||||
while (true) {
|
||||
bool a = button_a.read();
|
||||
bool b = button_b.read();
|
||||
|
||||
if(a) speed--;
|
||||
if(b) speed++;
|
||||
speed = std::min((int)255, std::max((int)1, speed));
|
||||
|
||||
offset += float(speed) / 2000.0f;
|
||||
|
||||
for(auto i = 0u; i < led_strip.num_leds; ++i) {
|
||||
float hue = float(i) / led_strip.num_leds;
|
||||
led_strip.set_hsv(i, hue + offset, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
led.set_rgb(speed, 0, 255 - speed);
|
||||
|
||||
// Sleep time controls the rate at which the LED buffer is updated
|
||||
// but *not* the actual framerate at which the buffer is sent to the LEDs
|
||||
sleep_ms(1000 / 60);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
add_executable(plasma2040_rotary plasma2040_rotary.cpp)
|
||||
|
||||
target_link_libraries(plasma2040_rotary
|
||||
pico_stdlib
|
||||
plasma2040
|
||||
breakout_encoder
|
||||
pimoroni_i2c
|
||||
rgbled
|
||||
button
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(plasma2040_rotary)
|
|
@ -0,0 +1,152 @@
|
|||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "plasma2040.hpp"
|
||||
|
||||
#include "common/pimoroni_common.hpp"
|
||||
#include "breakout_encoder.hpp"
|
||||
#include "rgbled.hpp"
|
||||
#include "button.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
// Set how many LEDs you have
|
||||
const uint N_LEDS = 30;
|
||||
|
||||
// Pick *one* LED type by uncommenting the relevant line below:
|
||||
|
||||
// APA102-style LEDs with Data/Clock lines. AKA DotStar
|
||||
//plasma::APA102 led_strip(N_LEDS, pio0, 0, plasma::PIN_DAT, plasma::PIN_CLK);
|
||||
|
||||
// WS28X-style LEDs with a single signal line. AKA NeoPixel
|
||||
plasma::WS2812 led_strip(N_LEDS, pio0, 0, plasma::PIN_DAT);
|
||||
|
||||
|
||||
|
||||
Button button_a(plasma::BUTTON_A);
|
||||
Button button_b(plasma::BUTTON_B);
|
||||
|
||||
RGBLED led(plasma::LED_R, plasma::LED_G, plasma::LED_B);
|
||||
|
||||
I2C i2c(BOARD::PICO_EXPLORER);
|
||||
BreakoutEncoder enc(&i2c);
|
||||
|
||||
enum ENCODER_MODE {
|
||||
COLOUR,
|
||||
ANGLE,
|
||||
BRIGHTNESS,
|
||||
TIME
|
||||
};
|
||||
|
||||
|
||||
void colour_cycle(float hue, float t, float angle) {
|
||||
t /= 200.0f;
|
||||
|
||||
for (auto i = 0u; i < led_strip.num_leds; ++i) {
|
||||
float offset = (M_PI * i) / led_strip.num_leds;
|
||||
offset = sinf(offset + t) * angle;
|
||||
led_strip.set_hsv(i, (hue + offset) / 360.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void gauge(uint v, uint vmax = 100) {
|
||||
uint light_pixels = led_strip.num_leds * v / vmax;
|
||||
|
||||
for (auto i = 0u; i < led_strip.num_leds; ++i) {
|
||||
if(i < light_pixels) {
|
||||
led_strip.set_rgb(i, 0, 255, 0);
|
||||
} else {
|
||||
led_strip.set_rgb(i, 255, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
led_strip.start(60);
|
||||
|
||||
bool encoder_detected = enc.init();
|
||||
enc.clear_interrupt_flag();
|
||||
|
||||
int speed = 50;
|
||||
float hue = 0;
|
||||
int angle = 120;
|
||||
int8_t brightness = 16;
|
||||
bool cycle = true;
|
||||
ENCODER_MODE mode = ENCODER_MODE::COLOUR;
|
||||
while (true) {
|
||||
uint32_t t = millis();
|
||||
if(encoder_detected) {
|
||||
if(enc.get_interrupt_flag()) {
|
||||
int count = enc.read();
|
||||
enc.clear_interrupt_flag();
|
||||
enc.clear();
|
||||
|
||||
cycle = false;
|
||||
switch(mode) {
|
||||
case ENCODER_MODE::COLOUR:
|
||||
hue += count;
|
||||
brightness = std::min((int8_t)359, brightness);
|
||||
brightness = std::max((int8_t)0, brightness);
|
||||
colour_cycle(hue, 0, (float)angle);
|
||||
break;
|
||||
case ENCODER_MODE::ANGLE:
|
||||
angle += count;
|
||||
angle = std::min((int)359, angle);
|
||||
angle = std::max((int)0, angle);
|
||||
colour_cycle(hue, 0, (float)angle);
|
||||
break;
|
||||
case ENCODER_MODE::BRIGHTNESS:
|
||||
brightness += count;
|
||||
brightness = std::min((int8_t)31, brightness);
|
||||
brightness = std::max((int8_t)0, brightness);
|
||||
led_strip.set_brightness(brightness);
|
||||
gauge(brightness, 31);
|
||||
break;
|
||||
case ENCODER_MODE::TIME:
|
||||
speed += count;
|
||||
speed = std::min((int)100, speed);
|
||||
speed = std::max((int)0, speed);
|
||||
gauge(speed, 100);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool a_pressed = button_a.read();
|
||||
bool b_pressed = button_b.read();
|
||||
|
||||
if(b_pressed) cycle = true;
|
||||
|
||||
switch(mode) {
|
||||
case ENCODER_MODE::COLOUR:
|
||||
led.set_rgb(255, 0, 0);
|
||||
if(a_pressed) mode = ENCODER_MODE::ANGLE;
|
||||
break;
|
||||
case ENCODER_MODE::ANGLE:
|
||||
led.set_rgb(255, 255, 0);
|
||||
if(a_pressed) mode = ENCODER_MODE::BRIGHTNESS;
|
||||
break;
|
||||
case ENCODER_MODE::BRIGHTNESS:
|
||||
led.set_rgb(0, 255, 0);
|
||||
if(a_pressed) mode = ENCODER_MODE::TIME;
|
||||
break;
|
||||
case ENCODER_MODE::TIME:
|
||||
led.set_rgb(0, 0, 255);
|
||||
if(a_pressed) mode = ENCODER_MODE::COLOUR;
|
||||
break;
|
||||
}
|
||||
|
||||
if(cycle) colour_cycle(hue, t * speed / 100, (float)angle);
|
||||
|
||||
auto first_led = led_strip.get(0);
|
||||
enc.set_led(first_led.r, first_led.g, first_led.b);
|
||||
|
||||
// Sleep time controls the rate at which the LED buffer is updated
|
||||
// but *not* the actual framerate at which the buffer is sent to the LEDs
|
||||
sleep_ms(1000 / 60);
|
||||
}
|
||||
}
|
|
@ -22,3 +22,4 @@ add_subdirectory(pico_scroll)
|
|||
add_subdirectory(pico_explorer)
|
||||
add_subdirectory(pico_rgb_keypad)
|
||||
add_subdirectory(pico_wireless)
|
||||
add_subdirectory(plasma2040)
|
||||
|
|
|
@ -87,6 +87,10 @@ namespace pimoroni {
|
|||
return (ioe.get_interrupt_flag() > 0);
|
||||
}
|
||||
|
||||
void BreakoutEncoder::clear() {
|
||||
ioe.clear_rotary_encoder(ENC_CHANNEL);
|
||||
}
|
||||
|
||||
int16_t BreakoutEncoder::read() {
|
||||
int16_t count = ioe.read_rotary_encoder(ENC_CHANNEL);
|
||||
if(direction != DIRECTION_CW)
|
||||
|
|
|
@ -23,7 +23,6 @@ namespace pimoroni {
|
|||
static const uint8_t DEFAULT_I2C_ADDRESS = 0x0F;
|
||||
static constexpr float DEFAULT_BRIGHTNESS = 1.0f; //Effectively the maximum fraction of the period that the LED will be on
|
||||
static const Direction DEFAULT_DIRECTION = DIRECTION_CW;
|
||||
static const uint8_t PIN_UNUSED = UINT8_MAX;
|
||||
static const uint32_t DEFAULT_TIMEOUT = 1;
|
||||
|
||||
private:
|
||||
|
@ -90,6 +89,7 @@ namespace pimoroni {
|
|||
|
||||
bool available();
|
||||
int16_t read();
|
||||
void clear();
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
include(plasma2040.cmake)
|
|
@ -0,0 +1,90 @@
|
|||
#include "apa102.hpp"
|
||||
|
||||
namespace plasma {
|
||||
|
||||
APA102::APA102(uint num_leds, PIO pio, uint sm, uint pin_dat, uint pin_clk, uint freq) : num_leds(num_leds), pio(pio), sm(sm) {
|
||||
uint offset = pio_add_program(pio, &apa102_program);
|
||||
|
||||
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_clk) | (1u << pin_dat));
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, (1u << pin_clk) | (1u << pin_dat));
|
||||
pio_gpio_init(pio, pin_clk);
|
||||
pio_gpio_init(pio, pin_dat);
|
||||
|
||||
pio_sm_config c = apa102_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, pin_dat, 1);
|
||||
sm_config_set_sideset_pins(&c, pin_clk);
|
||||
|
||||
sm_config_set_out_shift(&c, false, true, 32);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
// The PIO program transmits 1 bit every 2 execution cycles
|
||||
float div = (float)clock_get_hz(clk_sys) / (2 * freq);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
|
||||
dma_channel = dma_claim_unused_channel(true);
|
||||
dma_channel_config config = dma_channel_get_default_config(dma_channel);
|
||||
channel_config_set_bswap(&config, true);
|
||||
channel_config_set_dreq(&config, pio_get_dreq(pio, sm, true));
|
||||
channel_config_set_transfer_data_size(&config, DMA_SIZE_32);
|
||||
channel_config_set_read_increment(&config, true);
|
||||
dma_channel_configure(dma_channel, &config, &pio->txf[sm], NULL, 0, false);
|
||||
|
||||
buffer = new RGB[num_leds];
|
||||
}
|
||||
|
||||
bool APA102::dma_timer_callback(struct repeating_timer *t) {
|
||||
((APA102*)t->user_data)->update();
|
||||
return true;
|
||||
}
|
||||
|
||||
void APA102::update(bool blocking) {
|
||||
while(dma_channel_is_busy(dma_channel)) {}; // Block waiting for DMA finish
|
||||
pio->txf[sm] = 0x00000000; // Output the APA102 start-of-frame bytes
|
||||
dma_channel_set_trans_count(dma_channel, num_leds, false);
|
||||
dma_channel_set_read_addr(dma_channel, buffer, true);
|
||||
if (!blocking) return;
|
||||
while(dma_channel_is_busy(dma_channel)) {}; // Block waiting for DMA finish
|
||||
}
|
||||
|
||||
bool APA102::start(uint fps) {
|
||||
add_repeating_timer_ms(-(1000 / fps), dma_timer_callback, (void*)this, &timer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APA102::stop() {
|
||||
dma_channel_unclaim(dma_channel);
|
||||
return cancel_repeating_timer(&timer);
|
||||
}
|
||||
|
||||
void APA102::set_hsv(uint32_t index, float h, float s, float v) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: buffer[index].rgb(v, t, p); break;
|
||||
case 1: buffer[index].rgb(q, v, p); break;
|
||||
case 2: buffer[index].rgb(p, v, t); break;
|
||||
case 3: buffer[index].rgb(p, q, v); break;
|
||||
case 4: buffer[index].rgb(t, p, v); break;
|
||||
case 5: buffer[index].rgb(v, p, q); break;
|
||||
}
|
||||
}
|
||||
|
||||
void APA102::set_rgb(uint32_t index, uint8_t r, uint8_t g, uint8_t b) {
|
||||
buffer[index].rgb(r, g, b);
|
||||
}
|
||||
|
||||
void APA102::set_brightness(uint8_t b) {
|
||||
for (auto i = 0u; i < num_leds; ++i) {
|
||||
buffer[i].brightness(b);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/*
|
||||
This code is significantly modified from the PIO apa102 example
|
||||
found here: https://github.com/raspberrypi/pico-examples/tree/master/pio/apa102
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "apa102.pio.h"
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/timer.h"
|
||||
|
||||
namespace plasma {
|
||||
|
||||
class APA102 {
|
||||
public:
|
||||
static const uint DEFAULT_SERIAL_FREQ = 20 * 1000 * 1000; // 20MHz
|
||||
#pragma pack(push, 1)
|
||||
union alignas(4) RGB {
|
||||
struct {
|
||||
uint8_t sof = 0b11101111;
|
||||
uint8_t b;
|
||||
uint8_t g;
|
||||
uint8_t r;
|
||||
} ;
|
||||
uint32_t srgb;
|
||||
void operator=(uint32_t v) {
|
||||
srgb = v;
|
||||
};
|
||||
void brightness(uint8_t b) {
|
||||
sof = 0b11100000 | b;
|
||||
};
|
||||
void rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||
this->r = r;
|
||||
this->g = g;
|
||||
this->b = b;
|
||||
}
|
||||
RGB() {}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
RGB *buffer;
|
||||
uint32_t num_leds;
|
||||
|
||||
APA102(uint num_leds, PIO pio, uint sm, uint pin_dat, uint pin_clk, uint freq=DEFAULT_SERIAL_FREQ);
|
||||
bool start(uint fps=60);
|
||||
bool stop();
|
||||
void update(bool blocking=false);
|
||||
void set_hsv(uint32_t index, float h, float s, float v);
|
||||
void set_rgb(uint32_t index, uint8_t r, uint8_t g, uint8_t b);
|
||||
void set_brightness(uint8_t b);
|
||||
RGB get(uint32_t index) {return buffer[index];};
|
||||
|
||||
static bool dma_timer_callback(struct repeating_timer *t);
|
||||
|
||||
private:
|
||||
uint32_t fps;
|
||||
PIO pio;
|
||||
uint sm;
|
||||
int dma_channel;
|
||||
struct repeating_timer timer;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
;
|
||||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
|
||||
.program apa102
|
||||
.side_set 1
|
||||
out pins, 1 side 0 ; Stall here when no data (still asserts clock low)
|
||||
nop side 1
|
|
@ -0,0 +1,17 @@
|
|||
add_library(plasma2040 INTERFACE)
|
||||
|
||||
target_sources(plasma2040 INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/apa102.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ws2812.cpp
|
||||
)
|
||||
|
||||
target_include_directories(plasma2040 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
target_link_libraries(plasma2040 INTERFACE
|
||||
pico_stdlib
|
||||
hardware_pio
|
||||
hardware_dma
|
||||
)
|
||||
|
||||
pico_generate_pio_header(plasma2040 ${CMAKE_CURRENT_LIST_DIR}/apa102.pio)
|
||||
pico_generate_pio_header(plasma2040 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "apa102.hpp"
|
||||
#include "ws2812.hpp"
|
||||
|
||||
namespace plasma {
|
||||
const uint LED_R = 16;
|
||||
const uint LED_G = 17;
|
||||
const uint LED_B = 18;
|
||||
|
||||
const uint BUTTON_A = 12;
|
||||
const uint BUTTON_B = 13;
|
||||
|
||||
const uint PIN_CLK = 14; // Used only for APA102
|
||||
const uint PIN_DAT = 15; // Used for both APA102 and WS2812
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
#include "ws2812.hpp"
|
||||
|
||||
namespace plasma {
|
||||
|
||||
WS2812::WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq) : num_leds(num_leds), pio(pio), sm(sm) {
|
||||
uint offset = pio_add_program(pio, &ws2812_program);
|
||||
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
|
||||
pio_sm_config c = ws2812_program_get_default_config(offset);
|
||||
sm_config_set_sideset_pins(&c, pin);
|
||||
|
||||
sm_config_set_out_shift(&c, false, true, 24); // Discard first (APA102 global brightness) byte. TODO support RGBW WS281X LEDs
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
|
||||
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
|
||||
dma_channel = dma_claim_unused_channel(true);
|
||||
dma_channel_config config = dma_channel_get_default_config(dma_channel);
|
||||
channel_config_set_bswap(&config, true);
|
||||
channel_config_set_dreq(&config, pio_get_dreq(pio, sm, true));
|
||||
channel_config_set_transfer_data_size(&config, DMA_SIZE_32);
|
||||
channel_config_set_read_increment(&config, true);
|
||||
dma_channel_set_trans_count(dma_channel, num_leds, false);
|
||||
dma_channel_set_read_addr(dma_channel, (uint32_t *)buffer, false);
|
||||
dma_channel_configure(dma_channel, &config, &pio->txf[sm], NULL, 0, false);
|
||||
|
||||
buffer = new RGB[num_leds];
|
||||
}
|
||||
|
||||
bool WS2812::dma_timer_callback(struct repeating_timer *t) {
|
||||
((WS2812*)t->user_data)->update();
|
||||
return true;
|
||||
}
|
||||
|
||||
void WS2812::update(bool blocking) {
|
||||
while(dma_channel_is_busy(dma_channel)) {}; // Block waiting for DMA finish
|
||||
dma_channel_set_trans_count(dma_channel, num_leds, false);
|
||||
dma_channel_set_read_addr(dma_channel, buffer, true);
|
||||
if (!blocking) return;
|
||||
while(dma_channel_is_busy(dma_channel)) {}; // Block waiting for DMA finish
|
||||
}
|
||||
|
||||
bool WS2812::start(uint fps) {
|
||||
add_repeating_timer_ms(-(1000 / fps), dma_timer_callback, (void*)this, &timer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WS2812::stop() {
|
||||
dma_channel_unclaim(dma_channel);
|
||||
return cancel_repeating_timer(&timer);
|
||||
}
|
||||
|
||||
void WS2812::set_hsv(uint32_t index, float h, float s, float v) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: buffer[index].rgb(v, t, p); break;
|
||||
case 1: buffer[index].rgb(q, v, p); break;
|
||||
case 2: buffer[index].rgb(p, v, t); break;
|
||||
case 3: buffer[index].rgb(p, q, v); break;
|
||||
case 4: buffer[index].rgb(t, p, v); break;
|
||||
case 5: buffer[index].rgb(v, p, q); break;
|
||||
}
|
||||
}
|
||||
|
||||
void WS2812::set_rgb(uint32_t index, uint8_t r, uint8_t g, uint8_t b) {
|
||||
buffer[index].rgb(r, g, b);
|
||||
}
|
||||
|
||||
void WS2812::set_brightness(uint8_t b) {
|
||||
// WS2812 LEDs have no global brightness
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/*
|
||||
This code is significantly modified from the PIO apa102 example
|
||||
found here: https://github.com/raspberrypi/pico-examples/tree/master/pio/ws2812
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "ws2812.pio.h"
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/timer.h"
|
||||
|
||||
namespace plasma {
|
||||
|
||||
class WS2812 {
|
||||
public:
|
||||
static const uint SERIAL_FREQ_400KHZ = 400000;
|
||||
static const uint SERIAL_FREQ_800KHZ = 800000;
|
||||
static const uint DEFAULT_SERIAL_FREQ = SERIAL_FREQ_400KHZ;
|
||||
#pragma pack(push, 1)
|
||||
union alignas(4) RGB {
|
||||
struct {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t w = 0b00000000;
|
||||
} ;
|
||||
uint32_t srgb;
|
||||
void operator=(uint32_t v) {
|
||||
srgb = v;
|
||||
};
|
||||
void brightness(uint8_t b) {};;
|
||||
void rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||
this->r = r;
|
||||
this->g = g;
|
||||
this->b = b;
|
||||
}
|
||||
RGB() {};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
RGB *buffer;
|
||||
uint32_t num_leds;
|
||||
|
||||
WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq=DEFAULT_SERIAL_FREQ);
|
||||
bool start(uint fps=60);
|
||||
bool stop();
|
||||
void update(bool blocking=false);
|
||||
void set_hsv(uint32_t index, float h, float s, float v);
|
||||
void set_rgb(uint32_t index, uint8_t r, uint8_t g, uint8_t b);
|
||||
void set_brightness(uint8_t b);
|
||||
RGB get(uint32_t index) {return buffer[index];};
|
||||
|
||||
static bool dma_timer_callback(struct repeating_timer *t);
|
||||
|
||||
private:
|
||||
uint32_t fps;
|
||||
PIO pio;
|
||||
uint sm;
|
||||
int dma_channel;
|
||||
struct repeating_timer timer;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
;
|
||||
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
;
|
||||
; SPDX-License-Identifier: BSD-3-Clause
|
||||
;
|
||||
|
||||
.program ws2812
|
||||
.side_set 1
|
||||
|
||||
.define public T1 2
|
||||
.define public T2 5
|
||||
.define public T3 3
|
||||
|
||||
.lang_opt python sideset_init = pico.PIO.OUT_HIGH
|
||||
.lang_opt python out_init = pico.PIO.OUT_HIGH
|
||||
.lang_opt python out_shiftdir = 1
|
||||
|
||||
.wrap_target
|
||||
bitloop:
|
||||
out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
|
||||
jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
|
||||
do_one:
|
||||
jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
|
||||
do_zero:
|
||||
nop side 0 [T2 - 1] ; Or drive low, for a short pulse
|
||||
.wrap
|
Ładowanie…
Reference in New Issue