From b72e0a1bb92ea416fe324742aa0801df8945e67c Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Fri, 20 Aug 2021 18:17:39 +0100 Subject: [PATCH 01/23] Added Plasma2040 example that displays data from the BME68x --- drivers/bme68x/bme68x.cpp | 2 +- examples/plasma2040/CMakeLists.txt | 1 + examples/plasma2040/plasma2040_bme68x.cmake | 17 ++ examples/plasma2040/plasma2040_bme68x.cpp | 165 ++++++++++++++++++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 examples/plasma2040/plasma2040_bme68x.cmake create mode 100644 examples/plasma2040/plasma2040_bme68x.cpp diff --git a/drivers/bme68x/bme68x.cpp b/drivers/bme68x/bme68x.cpp index 106960f5..59cfb8ee 100644 --- a/drivers/bme68x/bme68x.cpp +++ b/drivers/bme68x/bme68x.cpp @@ -33,7 +33,7 @@ namespace pimoroni { } bool BME68X::configure(uint8_t filter, uint8_t odr, uint8_t os_humidity, uint8_t os_pressure, uint8_t os_temp) { - int8_t result; + int8_t result = 0; conf.filter = filter; conf.odr = odr; diff --git a/examples/plasma2040/CMakeLists.txt b/examples/plasma2040/CMakeLists.txt index af288df9..cff73730 100644 --- a/examples/plasma2040/CMakeLists.txt +++ b/examples/plasma2040/CMakeLists.txt @@ -1,3 +1,4 @@ +include(plasma2040_bme68x.cmake) include(plasma2040_rotary.cmake) include(plasma2040_rainbow.cmake) include(plasma2040_stacker.cmake) \ No newline at end of file diff --git a/examples/plasma2040/plasma2040_bme68x.cmake b/examples/plasma2040/plasma2040_bme68x.cmake new file mode 100644 index 00000000..7e77c4b2 --- /dev/null +++ b/examples/plasma2040/plasma2040_bme68x.cmake @@ -0,0 +1,17 @@ +set(OUTPUT_NAME plasma2040_bme68x) +add_executable(${OUTPUT_NAME} plasma2040_bme68x.cpp) + +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + plasma2040 + bme68x + hardware_i2c + pimoroni_i2c + rgbled + button + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/plasma2040/plasma2040_bme68x.cpp b/examples/plasma2040/plasma2040_bme68x.cpp new file mode 100644 index 00000000..e0ac2727 --- /dev/null +++ b/examples/plasma2040/plasma2040_bme68x.cpp @@ -0,0 +1,165 @@ +#include +#include +#include + +#include "pico/stdlib.h" + +#include "plasma2040.hpp" + +#include "common/pimoroni_common.hpp" +#include "bme68x.hpp" +#include "rgbled.hpp" +#include "button.hpp" + +using namespace pimoroni; +using namespace plasma; + +// Set how many LEDs you have +const uint N_LEDS = 30; + +// How many times the LEDs will be updated per second +const uint UPDATES = 60; + + +constexpr float TEMPERATURE_C_MIN = 20.0f; +constexpr float TEMPERATURE_C_MAX = 35.0f; + +constexpr float PRESSURE_PA_MIN = 87000.0f; +constexpr float PRESSURE_PA_MAX = 108500.0f; + +constexpr float HUMIDITY_MIN = 0.0f; +constexpr float HUMIDITY_MAX = 100.0f; + +// Pick *one* LED type by uncommenting the relevant line below: + +// APA102-style LEDs with Data/Clock lines. AKA DotStar +APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); + +// WS28X-style LEDs with a single signal line. AKA NeoPixel +//WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); + + + +Button button_a(plasma2040::BUTTON_A); +Button button_b(plasma2040::BUTTON_B); + +RGBLED led(plasma2040::LED_R, plasma2040::LED_G, plasma2040::LED_B); + +I2C i2c(BOARD::PICO_EXPLORER); +BME68X bme68x(&i2c); + +enum DisplayMode { + ALL, + TEMPERATURE, + PRESSURE, + HUMIDITY +}; + + +float map(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +void colour_gauge(float percent, uint start_led, uint end_led, float start_hue, float end_hue) { + if(end_led > start_led) { + uint range = end_led - start_led; + float light_pixels = percent * range; + + for(uint i = 0; i < range; i++) { + float h = map(i, 0.0f, (float)range - 1, start_hue, end_hue); + + uint i2 = i + 1; + if(i2 <= light_pixels) { + led_strip.set_hsv(i + start_led, h, 1.0f, 1.0f); + } + else if(i <= light_pixels) { + float scale = map(light_pixels, (float)i, (float)i2, 0.0f, 1.0f); + led_strip.set_hsv(i + start_led, h, 1.0f, scale); + } + else { + led_strip.set_hsv(i + start_led, 0.0f, 0.0f, 0.0f); + } + } + } +} + +int main() { + stdio_init_all(); + + led_strip.start(UPDATES); + + bool bme_detected = bme68x.init(); + + + uint first_third = led_strip.num_leds / 3; + uint second_third = (led_strip.num_leds * 2) / 3; + + float t = 0.0f; + DisplayMode mode = DisplayMode::ALL; + while(true) { + if(bme_detected) { + bme68x_data data; + + auto result = bme68x.read_forced(&data); + (void)result; + + printf("%.2f, %.2f, %.2f\n", data.temperature, data.pressure, data.humidity); + + switch(mode) { + case DisplayMode::ALL: + t = map(data.temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0f, 1.0f); + colour_gauge(t, 0, first_third, 0.667f, 1.0f); + + t = map(data.pressure, PRESSURE_PA_MIN, PRESSURE_PA_MAX, 0.0f, 1.0f); + colour_gauge(t, first_third, second_third, 0.333f, 0.0f); + + t = map(data.humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0f, 1.0f); + colour_gauge(t, second_third, led_strip.num_leds, 0.333f, 0.667f); + break; + + case DisplayMode::TEMPERATURE: + t = map(data.temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0f, 1.0f); + colour_gauge(t, 0, led_strip.num_leds, 0.667f, 1.0f); + break; + + case DisplayMode::PRESSURE: + t = map(data.pressure, PRESSURE_PA_MIN, PRESSURE_PA_MAX, 0.0f, 1.0f); + colour_gauge(t, 0, led_strip.num_leds, 0.333f, 0.0f); + break; + + case DisplayMode::HUMIDITY: + t = map(data.humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0f, 1.0f); + colour_gauge(t, 0, led_strip.num_leds, 0.333f, 0.667f); + break; + } + } + bool a_pressed = button_a.read(); + bool b_pressed = button_b.read(); + + switch(mode) { + case DisplayMode::ALL: + led.set_rgb(127, 127, 127); + if(a_pressed) mode = DisplayMode::TEMPERATURE; + else if(b_pressed) mode = DisplayMode::HUMIDITY; + break; + + case DisplayMode::TEMPERATURE: + led.set_rgb(255, 0, 255); + if(a_pressed) mode = DisplayMode::PRESSURE; + else if(b_pressed) mode = DisplayMode::ALL; + break; + + case DisplayMode::PRESSURE: + led.set_rgb(255, 255, 0); + if(a_pressed) mode = DisplayMode::HUMIDITY; + else if(b_pressed) mode = DisplayMode::TEMPERATURE; + break; + + case DisplayMode::HUMIDITY: + led.set_rgb(0, 255, 255); + if(a_pressed) mode = DisplayMode::ALL; + else if(b_pressed) mode = DisplayMode::PRESSURE; + break; + } + } +} From bfcb688264d302ba69cc4586ebefe8a9bc7da807 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Mon, 23 Aug 2021 14:58:38 +0100 Subject: [PATCH 02/23] Moved some variables to constants --- examples/plasma2040/plasma2040_bme68x.cpp | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/examples/plasma2040/plasma2040_bme68x.cpp b/examples/plasma2040/plasma2040_bme68x.cpp index e0ac2727..27aad76e 100644 --- a/examples/plasma2040/plasma2040_bme68x.cpp +++ b/examples/plasma2040/plasma2040_bme68x.cpp @@ -20,16 +20,32 @@ const uint N_LEDS = 30; // How many times the LEDs will be updated per second const uint UPDATES = 60; - +// The temperature range to show (in degrees celsius) constexpr float TEMPERATURE_C_MIN = 20.0f; constexpr float TEMPERATURE_C_MAX = 35.0f; +// The pressure range to show (in pascals) constexpr float PRESSURE_PA_MIN = 87000.0f; constexpr float PRESSURE_PA_MAX = 108500.0f; +// The humidity range to show (in percent) constexpr float HUMIDITY_MIN = 0.0f; constexpr float HUMIDITY_MAX = 100.0f; + +// The start and end hues for the temperature range +constexpr float TEMPERATURE_HUE_START = 0.667f; +constexpr float TEMPERATURE_HUE_END = 1.0f; + +// The start and end hues for the pressure range +constexpr float PRESSURE_HUE_START = 0.333f; +constexpr float PRESSURE_HUE_END = 0.0f; + +// The start and end hues for the humidity range +constexpr float HUMIDITY_HUE_START = 0.333f; +constexpr float HUMIDITY_HUE_END = 0.667f; + + // Pick *one* LED type by uncommenting the relevant line below: // APA102-style LEDs with Data/Clock lines. AKA DotStar @@ -55,11 +71,12 @@ enum DisplayMode { HUMIDITY }; - +// Maps a value from one range to another float map(float x, float in_min, float in_max, float out_min, float out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } +// Sets a section of the led strip to show a hue gradient based on the provided percent void colour_gauge(float percent, uint start_led, uint end_led, float start_hue, float end_hue) { if(end_led > start_led) { uint range = end_led - start_led; @@ -108,28 +125,28 @@ int main() { switch(mode) { case DisplayMode::ALL: t = map(data.temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0f, 1.0f); - colour_gauge(t, 0, first_third, 0.667f, 1.0f); + colour_gauge(t, 0, first_third, TEMPERATURE_HUE_START, TEMPERATURE_HUE_END); t = map(data.pressure, PRESSURE_PA_MIN, PRESSURE_PA_MAX, 0.0f, 1.0f); - colour_gauge(t, first_third, second_third, 0.333f, 0.0f); + colour_gauge(t, first_third, second_third, PRESSURE_HUE_START, PRESSURE_HUE_END); t = map(data.humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0f, 1.0f); - colour_gauge(t, second_third, led_strip.num_leds, 0.333f, 0.667f); + colour_gauge(t, second_third, led_strip.num_leds, HUMIDITY_HUE_START, HUMIDITY_HUE_END); break; case DisplayMode::TEMPERATURE: t = map(data.temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0f, 1.0f); - colour_gauge(t, 0, led_strip.num_leds, 0.667f, 1.0f); + colour_gauge(t, 0, led_strip.num_leds, TEMPERATURE_HUE_START, TEMPERATURE_HUE_END); break; case DisplayMode::PRESSURE: t = map(data.pressure, PRESSURE_PA_MIN, PRESSURE_PA_MAX, 0.0f, 1.0f); - colour_gauge(t, 0, led_strip.num_leds, 0.333f, 0.0f); + colour_gauge(t, 0, led_strip.num_leds, PRESSURE_HUE_START, PRESSURE_HUE_END); break; case DisplayMode::HUMIDITY: t = map(data.humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0f, 1.0f); - colour_gauge(t, 0, led_strip.num_leds, 0.333f, 0.667f); + colour_gauge(t, 0, led_strip.num_leds, HUMIDITY_HUE_START, HUMIDITY_HUE_END); break; } } From 88a9449e0a7ac660b21c79a430165b9f551f6307 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Mon, 23 Aug 2021 16:21:38 +0100 Subject: [PATCH 03/23] Added micropython version of bme example, and backported some fixes --- examples/plasma2040/plasma2040_bme68x.cpp | 15 +- micropython/examples/plasma2040/bme68x.py | 147 ++++++++++++++++++ .../{plasma_2040 => plasma2040}/rainbow.py | 0 .../rgb-led-and-buttons.py | 0 4 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 micropython/examples/plasma2040/bme68x.py rename micropython/examples/{plasma_2040 => plasma2040}/rainbow.py (100%) rename micropython/examples/{plasma_2040 => plasma2040}/rgb-led-and-buttons.py (100%) diff --git a/examples/plasma2040/plasma2040_bme68x.cpp b/examples/plasma2040/plasma2040_bme68x.cpp index 27aad76e..fab5fb84 100644 --- a/examples/plasma2040/plasma2040_bme68x.cpp +++ b/examples/plasma2040/plasma2040_bme68x.cpp @@ -11,6 +11,11 @@ #include "rgbled.hpp" #include "button.hpp" +/* +Press "A" to cycle to the next mode. +Press "B" to cycle to the previous mode. +*/ + using namespace pimoroni; using namespace plasma; @@ -49,15 +54,15 @@ constexpr float HUMIDITY_HUE_END = 0.667f; // Pick *one* LED type by uncommenting the relevant line below: // APA102-style LEDs with Data/Clock lines. AKA DotStar -APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); +//APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); // WS28X-style LEDs with a single signal line. AKA NeoPixel -//WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); +WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); -Button button_a(plasma2040::BUTTON_A); -Button button_b(plasma2040::BUTTON_B); +Button button_a(plasma2040::BUTTON_A, ACTIVE_LOW, 0); +Button button_b(plasma2040::BUTTON_B, ACTIVE_LOW, 0); RGBLED led(plasma2040::LED_R, plasma2040::LED_G, plasma2040::LED_B); @@ -120,7 +125,7 @@ int main() { auto result = bme68x.read_forced(&data); (void)result; - printf("%.2f, %.2f, %.2f\n", data.temperature, data.pressure, data.humidity); + printf("%.2fc, %.2fPa, %.2f%%\n", data.temperature, data.pressure, data.humidity); switch(mode) { case DisplayMode::ALL: diff --git a/micropython/examples/plasma2040/bme68x.py b/micropython/examples/plasma2040/bme68x.py new file mode 100644 index 00000000..50e8fc62 --- /dev/null +++ b/micropython/examples/plasma2040/bme68x.py @@ -0,0 +1,147 @@ +import plasma +from plasma import plasma2040 +import time + +# Import helpers for RGB LEDs, Buttons, and Analog +from pimoroni import RGBLED, Button, Analog + +# Import bme68x and I2C helper +from breakout_bme68x import BreakoutBME68X, STATUS_HEATER_STABLE +from pimoroni_i2c import PimoroniI2C + +# Press "A" to cycle to the next mode. +# Press "B" to cycle to the previous mode. + +# Set how many LEDs you have +NUM_LEDS = 30 + +# How many times the LEDs will be updated per second +UPDATES = 60 + +# The temperature range to show (in degrees celsius) +TEMPERATURE_C_MIN = 20.0 +TEMPERATURE_C_MAX = 35.0 + +# The pressure range to show (in pascals) +PRESSURE_PA_MIN = 87000.0 +PRESSURE_PA_MAX = 108500.0 + +# The humidity range to show (in percent) +HUMIDITY_MIN = 0.0 +HUMIDITY_MAX = 100.0 + + +# The start and end hues for the temperature range +TEMPERATURE_HUE_START = 0.667 +TEMPERATURE_HUE_END = 1.0 + +# The start and end hues for the pressure range +PRESSURE_HUE_START = 0.333 +PRESSURE_HUE_END = 0.0 + +# The start and end hues for the humidity range +HUMIDITY_HUE_START = 0.333 +HUMIDITY_HUE_END = 0.667 + + +# Pick *one* LED type by uncommenting the relevant line below: + +# APA102 / DotStar™ LEDs +# led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) + +# WS2812 / NeoPixel™ LEDs +# led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) + + +button_a = Button(plasma2040.BUTTON_A, repeat_time=0) +button_b = Button(plasma2040.BUTTON_B, repeat_time=0) +led = RGBLED(plasma2040.LED_R, plasma2040.LED_G, plasma2040.LED_B) + +PINS_PLASMA2040 = {"sda": 20, "scl": 21} + +i2c = PimoroniI2C(**PINS_PLASMA2040) +bme = BreakoutBME68X(i2c) + + +ALL, TEMPERATURE, PRESSURE, HUMIDITY = range(4) + +# Maps a value from one range to another +def map(x, in_min, in_max, out_min, out_max): + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + +# Sets a section of the led strip to show a hue gradient based on the provided percent +def colour_gauge(percent, start_led, end_led, start_hue, end_hue): + if end_led > start_led: + length = end_led - start_led + light_pixels = percent * float(length) + + for i in range(length): + h = map(i, 0.0, float(length - 1), start_hue, end_hue) + + i2 = i + 1 + if i2 <= light_pixels: + led_strip.set_hsv(i + start_led, h, 1.0, 1.0) + elif i <= light_pixels: + scale = map(light_pixels, float(i), float(i2), 0.0, 1.0) + led_strip.set_hsv(i + start_led, h, 1.0, scale) + else: + led_strip.set_hsv(i + start_led, 0.0, 0.0, 0.0) + + +first_third = int(NUM_LEDS / 3) +second_third = int((NUM_LEDS * 2) / 3) + +mode = ALL + +# Start updating the LED strip +led_strip.start() + +while True: + temperature, pressure, humidity, _, _, _, _ = bme.read() + print("{:0.2f}c, {:0.2f}Pa, {:0.2f}%".format( + temperature, pressure, humidity)) + + if mode == ALL: + t = map(temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0, 1.0) + colour_gauge(t, 0, first_third, TEMPERATURE_HUE_START, TEMPERATURE_HUE_END) + + t = map(pressure, PRESSURE_PA_MIN, PRESSURE_PA_MAX, 0.0, 1.0) + colour_gauge(t, first_third, second_third, PRESSURE_HUE_START, PRESSURE_HUE_END) + + t = map(humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0, 1.0) + colour_gauge(t, second_third, NUM_LEDS, HUMIDITY_HUE_START, HUMIDITY_HUE_END) + + elif mode == TEMPERATURE: + t = map(temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0, 1.0) + colour_gauge(t, 0, NUM_LEDS, TEMPERATURE_HUE_START, TEMPERATURE_HUE_END) + + elif mode == PRESSURE: + t = map(pressure, PRESSURE_PA_MIN, PRESSURE_PA_MAX, 0.0, 1.0) + colour_gauge(t, 0, NUM_LEDS, PRESSURE_HUE_START, PRESSURE_HUE_END) + + elif mode == HUMIDITY: + t = map(humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0, 1.0) + colour_gauge(t, 0, NUM_LEDS, HUMIDITY_HUE_START, HUMIDITY_HUE_END) + + a_pressed = button_a.read() + b_pressed = button_b.read() + + if mode == ALL: + led.set_rgb(127, 127, 127) + if a_pressed: mode = TEMPERATURE + elif b_pressed: mode = HUMIDITY + + elif mode == TEMPERATURE: + led.set_rgb(255, 0, 255) + if a_pressed: mode = PRESSURE + elif b_pressed: mode = ALL + + elif mode == PRESSURE: + led.set_rgb(255, 255, 0) + if a_pressed: mode = HUMIDITY + elif b_pressed: mode = TEMPERATURE + + elif mode == HUMIDITY: + led.set_rgb(0, 255, 255) + if a_pressed: mode = ALL + elif b_pressed: mode = PRESSURE diff --git a/micropython/examples/plasma_2040/rainbow.py b/micropython/examples/plasma2040/rainbow.py similarity index 100% rename from micropython/examples/plasma_2040/rainbow.py rename to micropython/examples/plasma2040/rainbow.py diff --git a/micropython/examples/plasma_2040/rgb-led-and-buttons.py b/micropython/examples/plasma2040/rgb-led-and-buttons.py similarity index 100% rename from micropython/examples/plasma_2040/rgb-led-and-buttons.py rename to micropython/examples/plasma2040/rgb-led-and-buttons.py From a98252993dd9b55fd36f14f592af98ee37f161f2 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Mon, 23 Aug 2021 16:28:27 +0100 Subject: [PATCH 04/23] Linting fixes --- micropython/examples/plasma2040/bme68x.py | 40 ++++++++++++++--------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/micropython/examples/plasma2040/bme68x.py b/micropython/examples/plasma2040/bme68x.py index 50e8fc62..cca27ef1 100644 --- a/micropython/examples/plasma2040/bme68x.py +++ b/micropython/examples/plasma2040/bme68x.py @@ -2,11 +2,11 @@ import plasma from plasma import plasma2040 import time -# Import helpers for RGB LEDs, Buttons, and Analog -from pimoroni import RGBLED, Button, Analog +# Import helpers for RGB LEDs and Buttons +from pimoroni import RGBLED, Button # Import bme68x and I2C helper -from breakout_bme68x import BreakoutBME68X, STATUS_HEATER_STABLE +from breakout_bme68x import BreakoutBME68X from pimoroni_i2c import PimoroniI2C # Press "A" to cycle to the next mode. @@ -50,7 +50,7 @@ HUMIDITY_HUE_END = 0.667 # led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) # WS2812 / NeoPixel™ LEDs -# led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) +led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) button_a = Button(plasma2040.BUTTON_A, repeat_time=0) @@ -65,10 +65,12 @@ bme = BreakoutBME68X(i2c) ALL, TEMPERATURE, PRESSURE, HUMIDITY = range(4) + # Maps a value from one range to another def map(x, in_min, in_max, out_min, out_max): return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + # Sets a section of the led strip to show a hue gradient based on the provided percent def colour_gauge(percent, start_led, end_led, start_hue, end_hue): if end_led > start_led: @@ -95,12 +97,12 @@ mode = ALL # Start updating the LED strip led_strip.start() - + while True: temperature, pressure, humidity, _, _, _, _ = bme.read() print("{:0.2f}c, {:0.2f}Pa, {:0.2f}%".format( temperature, pressure, humidity)) - + if mode == ALL: t = map(temperature, TEMPERATURE_C_MIN, TEMPERATURE_C_MAX, 0.0, 1.0) colour_gauge(t, 0, first_third, TEMPERATURE_HUE_START, TEMPERATURE_HUE_END) @@ -122,26 +124,34 @@ while True: elif mode == HUMIDITY: t = map(humidity, HUMIDITY_MIN, HUMIDITY_MAX, 0.0, 1.0) colour_gauge(t, 0, NUM_LEDS, HUMIDITY_HUE_START, HUMIDITY_HUE_END) - + a_pressed = button_a.read() b_pressed = button_b.read() if mode == ALL: led.set_rgb(127, 127, 127) - if a_pressed: mode = TEMPERATURE - elif b_pressed: mode = HUMIDITY + if a_pressed: + mode = TEMPERATURE + elif b_pressed: + mode = HUMIDITY elif mode == TEMPERATURE: led.set_rgb(255, 0, 255) - if a_pressed: mode = PRESSURE - elif b_pressed: mode = ALL + if a_pressed: + mode = PRESSURE + elif b_pressed: + mode = ALL elif mode == PRESSURE: led.set_rgb(255, 255, 0) - if a_pressed: mode = HUMIDITY - elif b_pressed: mode = TEMPERATURE + if a_pressed: + mode = HUMIDITY + elif b_pressed: + mode = TEMPERATURE elif mode == HUMIDITY: led.set_rgb(0, 255, 255) - if a_pressed: mode = ALL - elif b_pressed: mode = PRESSURE + if a_pressed: + mode = ALL + elif b_pressed: + mode = PRESSURE From 5b40899e44a46add3215ea38eb91d940e59c9ea3 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Mon, 23 Aug 2021 16:30:03 +0100 Subject: [PATCH 05/23] Linting fixes --- micropython/examples/plasma2040/bme68x.py | 1 - 1 file changed, 1 deletion(-) diff --git a/micropython/examples/plasma2040/bme68x.py b/micropython/examples/plasma2040/bme68x.py index cca27ef1..318df518 100644 --- a/micropython/examples/plasma2040/bme68x.py +++ b/micropython/examples/plasma2040/bme68x.py @@ -1,6 +1,5 @@ import plasma from plasma import plasma2040 -import time # Import helpers for RGB LEDs and Buttons from pimoroni import RGBLED, Button From c8f90ea84629f0184fcf07fed5933d56113411ac Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Mon, 23 Aug 2021 17:18:34 +0100 Subject: [PATCH 06/23] Changed default color order to GRB to match our strips --- drivers/plasma/ws2812.hpp | 2 +- micropython/modules/plasma/plasma.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/plasma/ws2812.hpp b/drivers/plasma/ws2812.hpp index 3da8722a..3d57cabb 100644 --- a/drivers/plasma/ws2812.hpp +++ b/drivers/plasma/ws2812.hpp @@ -63,7 +63,7 @@ namespace plasma { uint32_t num_leds; COLOR_ORDER color_order; - WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq=DEFAULT_SERIAL_FREQ, bool rgbw=false, COLOR_ORDER color_order=COLOR_ORDER::RGB, RGB* buffer=nullptr); + WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq=DEFAULT_SERIAL_FREQ, bool rgbw=false, COLOR_ORDER color_order=COLOR_ORDER::GRB, RGB* buffer=nullptr); ~WS2812() { stop(); clear(); diff --git a/micropython/modules/plasma/plasma.cpp b/micropython/modules/plasma/plasma.cpp index d3d319c0..96f11c37 100644 --- a/micropython/modules/plasma/plasma.cpp +++ b/micropython/modules/plasma/plasma.cpp @@ -73,7 +73,7 @@ mp_obj_t PlasmaWS2812_make_new(const mp_obj_type_t *type, size_t n_args, size_t { MP_QSTR_freq, MP_ARG_INT, {.u_int = WS2812::DEFAULT_SERIAL_FREQ} }, { MP_QSTR_buffer, MP_ARG_OBJ, {.u_obj = nullptr} }, { MP_QSTR_rgbw, MP_ARG_BOOL, {.u_bool = false} }, - { MP_QSTR_color_order, MP_ARG_INT, {.u_int = (uint8_t)WS2812::COLOR_ORDER::RGB} }, + { MP_QSTR_color_order, MP_ARG_INT, {.u_int = (uint8_t)WS2812::COLOR_ORDER::GRB} }, }; // Parse args. From a5d9fcf48ace5f6f65a3e1ba6ad865b96e993a8f Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 11:41:56 +0100 Subject: [PATCH 07/23] Renamed bme68x plasma example to Monitor --- examples/plasma2040/CMakeLists.txt | 4 ++-- .../{plasma2040_bme68x.cmake => plasma2040_monitor.cmake} | 4 ++-- .../{plasma2040_bme68x.cpp => plasma2040_monitor.cpp} | 1 + micropython/examples/plasma2040/{bme68x.py => monitor.py} | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) rename examples/plasma2040/{plasma2040_bme68x.cmake => plasma2040_monitor.cmake} (75%) rename examples/plasma2040/{plasma2040_bme68x.cpp => plasma2040_monitor.cpp} (98%) rename micropython/examples/plasma2040/{bme68x.py => monitor.py} (97%) diff --git a/examples/plasma2040/CMakeLists.txt b/examples/plasma2040/CMakeLists.txt index cff73730..30abd500 100644 --- a/examples/plasma2040/CMakeLists.txt +++ b/examples/plasma2040/CMakeLists.txt @@ -1,4 +1,4 @@ -include(plasma2040_bme68x.cmake) -include(plasma2040_rotary.cmake) +include(plasma2040_monitor.cmake) include(plasma2040_rainbow.cmake) +include(plasma2040_rotary.cmake) include(plasma2040_stacker.cmake) \ No newline at end of file diff --git a/examples/plasma2040/plasma2040_bme68x.cmake b/examples/plasma2040/plasma2040_monitor.cmake similarity index 75% rename from examples/plasma2040/plasma2040_bme68x.cmake rename to examples/plasma2040/plasma2040_monitor.cmake index 7e77c4b2..e3291ff4 100644 --- a/examples/plasma2040/plasma2040_bme68x.cmake +++ b/examples/plasma2040/plasma2040_monitor.cmake @@ -1,5 +1,5 @@ -set(OUTPUT_NAME plasma2040_bme68x) -add_executable(${OUTPUT_NAME} plasma2040_bme68x.cpp) +set(OUTPUT_NAME plasma2040_monitor) +add_executable(${OUTPUT_NAME} plasma2040_monitor.cpp) target_link_libraries(${OUTPUT_NAME} pico_stdlib diff --git a/examples/plasma2040/plasma2040_bme68x.cpp b/examples/plasma2040/plasma2040_monitor.cpp similarity index 98% rename from examples/plasma2040/plasma2040_bme68x.cpp rename to examples/plasma2040/plasma2040_monitor.cpp index fab5fb84..362299fc 100644 --- a/examples/plasma2040/plasma2040_bme68x.cpp +++ b/examples/plasma2040/plasma2040_monitor.cpp @@ -12,6 +12,7 @@ #include "button.hpp" /* +Uses a BME68x to monitor the ambient temperature, pressure and humidity, and show them as bars on an LED strip. Press "A" to cycle to the next mode. Press "B" to cycle to the previous mode. */ diff --git a/micropython/examples/plasma2040/bme68x.py b/micropython/examples/plasma2040/monitor.py similarity index 97% rename from micropython/examples/plasma2040/bme68x.py rename to micropython/examples/plasma2040/monitor.py index 318df518..1f6a47d3 100644 --- a/micropython/examples/plasma2040/bme68x.py +++ b/micropython/examples/plasma2040/monitor.py @@ -8,6 +8,7 @@ from pimoroni import RGBLED, Button from breakout_bme68x import BreakoutBME68X from pimoroni_i2c import PimoroniI2C +# Uses a BME68x to monitor the ambient temperature, pressure and humidity, and show them as bars on an LED strip. # Press "A" to cycle to the next mode. # Press "B" to cycle to the previous mode. From a46ea3b097a678e34203352eabaab3b838d41447 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 14:15:58 +0100 Subject: [PATCH 08/23] Bug fix and tweaks to rotary example --- examples/plasma2040/plasma2040_rotary.cpp | 261 ++++++++++++++-------- 1 file changed, 162 insertions(+), 99 deletions(-) diff --git a/examples/plasma2040/plasma2040_rotary.cpp b/examples/plasma2040/plasma2040_rotary.cpp index 7ee59c42..4ab222cd 100644 --- a/examples/plasma2040/plasma2040_rotary.cpp +++ b/examples/plasma2040/plasma2040_rotary.cpp @@ -11,27 +11,47 @@ #include "rgbled.hpp" #include "button.hpp" +/* +Press "B" to enable cycling. +Press "A" to change the encoder mode. +Press "Boot" to reset the effects back to default. +*/ + using namespace pimoroni; using namespace plasma; // Set how many LEDs you have const uint N_LEDS = 30; +// The speed that the LEDs will start cycling at +const int16_t DEFAULT_SPEED = 20; + +// The hue (in degrees) that the LEDs will start at +const int16_t DEFAULT_HUE = 0; + +// The angle (in degrees) from the hue, that the LEDs will end at +const int16_t DEFAULT_ANGLE = 120; + +// The brightness (between 0 and 31) to set the LEDs to +const int16_t DEFAULT_BRIGHTNESS = 16; + + + // How many times the LEDs will be updated per second const uint UPDATES = 60; // Pick *one* LED type by uncommenting the relevant line below: // APA102-style LEDs with Data/Clock lines. AKA DotStar -//APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); +APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); // WS28X-style LEDs with a single signal line. AKA NeoPixel -WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); +//WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); - -Button button_a(plasma2040::BUTTON_A); -Button button_b(plasma2040::BUTTON_B); +Button user_sw(plasma2040::USER_SW, Polarity::ACTIVE_LOW, 0); +Button button_a(plasma2040::BUTTON_A, Polarity::ACTIVE_LOW, 0); +Button button_b(plasma2040::BUTTON_B, Polarity::ACTIVE_LOW, 0); RGBLED led(plasma2040::LED_R, plasma2040::LED_G, plasma2040::LED_B); @@ -39,118 +59,161 @@ I2C i2c(BOARD::PICO_EXPLORER); BreakoutEncoder enc(&i2c); enum ENCODER_MODE { - COLOUR, - ANGLE, - BRIGHTNESS, - TIME + COLOUR, + ANGLE, + BRIGHTNESS, + TIME }; +float wrap(float v, float min, float max) { + if(v <= min) + v += (max - min); + + if(v > max) + v -= (max - min); + + return v; +} + void colour_cycle(float hue, float t, float angle) { - t /= 200.0f; + 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); - } + for(auto i = 0u; i < led_strip.num_leds; ++i) { + float percent_along = (float)i / led_strip.num_leds; + float offset = sinf((percent_along + 0.5f + t) * M_PI) * angle; + float h = wrap((hue + offset) / 360.0f, 0.0f, 1.0f); + led_strip.set_hsv(i, h, 1.0f, 1.0f); + } } -void gauge(uint v, uint vmax = 100) { - uint light_pixels = led_strip.num_leds * v / vmax; +void speed_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); - } + 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); + } + } +} + +void brightness_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, 64, 64, 64); + } + else { + led_strip.set_rgb(i, 0, 0, 0); + } + } } int main() { - stdio_init_all(); + stdio_init_all(); - led_strip.start(UPDATES); + led_strip.start(UPDATES); - bool encoder_detected = enc.init(); - enc.clear_interrupt_flag(); + 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(); + //Initialise the default values + int16_t speed = DEFAULT_SPEED; + int16_t hue = DEFAULT_HUE; + int16_t angle = DEFAULT_ANGLE; + int16_t brightness = DEFAULT_BRIGHTNESS; - 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; + bool cycle = true; + ENCODER_MODE mode = ENCODER_MODE::COLOUR; + uint32_t start_time = millis(); + while(true) { + uint32_t t = millis() - start_time; + if(encoder_detected) { + if(enc.get_interrupt_flag()) { + int16_t count = enc.read(); + enc.clear_interrupt_flag(); + enc.clear(); + cycle = false; 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; + case ENCODER_MODE::COLOUR: + hue += count; + hue = std::min((int16_t)359, std::max((int16_t)0, hue)); + colour_cycle((float)hue, 0, (float)angle); + break; + + case ENCODER_MODE::ANGLE: + angle += count; + angle = std::min((int16_t)359, std::max((int16_t)0, angle)); + colour_cycle((float)hue, 0, (float)angle); + break; + + case ENCODER_MODE::BRIGHTNESS: + brightness += count; + brightness = std::min((int16_t)31, std::max((int16_t)0, brightness)); + led_strip.set_brightness((uint8_t)brightness); + brightness_gauge(brightness, 31); + break; + + case ENCODER_MODE::TIME: + speed += count; + speed = std::min((int16_t)100, std::max((int16_t)0, speed)); + speed_gauge(speed, 100); + 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 / UPDATES); + } } + bool sw_pressed = user_sw.read(); + bool a_pressed = button_a.read(); + bool b_pressed = button_b.read(); + + if(sw_pressed) { + speed = DEFAULT_SPEED; + hue = DEFAULT_HUE; + angle = DEFAULT_ANGLE; + brightness = DEFAULT_BRIGHTNESS; + } + + if(b_pressed) { + if(!cycle) + start_time = millis(); + 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, (float)(t * speed) / 100.0f, (float)angle); + + auto first_led = led_strip.get(led_strip.num_leds / 2); + 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 / UPDATES); + } } From a347eb468ff59f39fecd4a048e2fbf7e18d235c0 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 14:16:27 +0100 Subject: [PATCH 09/23] Fixed indentation of rainbow example --- examples/plasma2040/plasma2040_rainbow.cpp | 74 +++++++++++----------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/examples/plasma2040/plasma2040_rainbow.cpp b/examples/plasma2040/plasma2040_rainbow.cpp index 48895954..6c6b21ec 100644 --- a/examples/plasma2040/plasma2040_rainbow.cpp +++ b/examples/plasma2040/plasma2040_rainbow.cpp @@ -47,46 +47,46 @@ Analog sense(plasma2040::CURRENT_SENSE, plasma2040::ADC_GAIN, plasma2040::SHUNT_ int main() { - stdio_init_all(); + stdio_init_all(); - led_strip.start(UPDATES); + led_strip.start(UPDATES); - int speed = DEFAULT_SPEED; - float offset = 0.0f; + int speed = DEFAULT_SPEED; + float offset = 0.0f; - uint count = 0; - while (true) { - bool sw = user_sw.read(); - bool a = button_a.read(); - bool b = button_b.read(); + uint count = 0; + while(true) { + bool sw_pressed = user_sw.read(); + bool a_pressed = button_a.read(); + bool b_pressed = button_b.read(); - if(sw) { - speed = DEFAULT_SPEED; - } - else { - 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); - - count += 1; - if(count >= UPDATES) { - // Display the current value once every second - printf("Current = %f A\n", sense.read_current()); - count = 0; - } - - // 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 / UPDATES); + if(sw_pressed) { + speed = DEFAULT_SPEED; } + else { + if(a_pressed) speed--; + if(b_pressed) 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); + + count += 1; + if(count >= UPDATES) { + // Display the current value once every second + printf("Current = %f A\n", sense.read_current()); + count = 0; + } + + // 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 / UPDATES); + } } From dc7da7d07a44c9d914068c1713e726e24a9abaff Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 14:26:35 +0100 Subject: [PATCH 10/23] Switch rotary back to ws2812 --- examples/plasma2040/plasma2040_rotary.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/plasma2040/plasma2040_rotary.cpp b/examples/plasma2040/plasma2040_rotary.cpp index 4ab222cd..b93acfec 100644 --- a/examples/plasma2040/plasma2040_rotary.cpp +++ b/examples/plasma2040/plasma2040_rotary.cpp @@ -43,10 +43,10 @@ const uint UPDATES = 60; // Pick *one* LED type by uncommenting the relevant line below: // APA102-style LEDs with Data/Clock lines. AKA DotStar -APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); +//APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); // WS28X-style LEDs with a single signal line. AKA NeoPixel -//WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); +WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); Button user_sw(plasma2040::USER_SW, Polarity::ACTIVE_LOW, 0); From cf4b45acc7a22104bd286d7277dd92203aec1608 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 15:21:14 +0100 Subject: [PATCH 11/23] Exposed Get to MP, and added I2C pin numbers --- micropython/modules/plasma/plasma.c | 6 ++++ micropython/modules/plasma/plasma.cpp | 46 +++++++++++++++++++++++++++ micropython/modules/plasma/plasma.h | 2 ++ 3 files changed, 54 insertions(+) diff --git a/micropython/modules/plasma/plasma.c b/micropython/modules/plasma/plasma.c index 795ca5b6..4209bbd8 100644 --- a/micropython/modules/plasma/plasma.c +++ b/micropython/modules/plasma/plasma.c @@ -7,12 +7,14 @@ MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaAPA102_set_rgb_obj, 5, PlasmaAPA102_set_rgb); MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaAPA102_set_hsv_obj, 3, PlasmaAPA102_set_hsv); MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaAPA102_set_brightness_obj, 2, PlasmaAPA102_set_brightness); MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaAPA102_start_obj, 1, PlasmaAPA102_start); +MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaAPA102_get_obj, 2, PlasmaAPA102_get); MP_DEFINE_CONST_FUN_OBJ_1(PlasmaAPA102_clear_obj, PlasmaAPA102_clear); MP_DEFINE_CONST_FUN_OBJ_1(PlasmaWS2812___del___obj, PlasmaWS2812___del__); MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaWS2812_set_rgb_obj, 5, PlasmaWS2812_set_rgb); MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaWS2812_set_hsv_obj, 3, PlasmaWS2812_set_hsv); MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaWS2812_start_obj, 1, PlasmaWS2812_start); +MP_DEFINE_CONST_FUN_OBJ_KW(PlasmaWS2812_get_obj, 2, PlasmaAPA102_get); MP_DEFINE_CONST_FUN_OBJ_1(PlasmaWS2812_clear_obj, PlasmaWS2812_clear); /***** Binding of Methods *****/ @@ -22,6 +24,7 @@ STATIC const mp_rom_map_elem_t PlasmaAPA102_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_hsv), MP_ROM_PTR(&PlasmaAPA102_set_hsv_obj) }, { MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&PlasmaAPA102_set_brightness_obj) }, { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&PlasmaAPA102_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&PlasmaAPA102_get_obj) }, { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&PlasmaAPA102_clear_obj) }, }; STATIC const mp_rom_map_elem_t PlasmaWS2812_locals_dict_table[] = { @@ -29,6 +32,7 @@ STATIC const mp_rom_map_elem_t PlasmaWS2812_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_rgb), MP_ROM_PTR(&PlasmaWS2812_set_rgb_obj) }, { MP_ROM_QSTR(MP_QSTR_set_hsv), MP_ROM_PTR(&PlasmaWS2812_set_hsv_obj) }, { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&PlasmaWS2812_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_get), MP_ROM_PTR(&PlasmaWS2812_get_obj) }, { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&PlasmaWS2812_clear_obj) }, }; @@ -70,6 +74,8 @@ STATIC const mp_map_elem_t plasma2040_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_CLK), MP_ROM_INT(14) }, { MP_ROM_QSTR(MP_QSTR_DAT), MP_ROM_INT(15) }, { MP_ROM_QSTR(MP_QSTR_CURRENT_SENSE), MP_ROM_INT(29) }, + { MP_ROM_QSTR(MP_QSTR_SDA), MP_ROM_INT(20) }, + { MP_ROM_QSTR(MP_QSTR_SCL), MP_ROM_INT(21) }, { MP_ROM_QSTR(MP_QSTR_SHUNT_RESISTOR), MP_ROM_PTR(&shunt_resistor) }, { MP_ROM_QSTR(MP_QSTR_ADC_GAIN), MP_ROM_INT(50) }, diff --git a/micropython/modules/plasma/plasma.cpp b/micropython/modules/plasma/plasma.cpp index 96f11c37..3098efe1 100644 --- a/micropython/modules/plasma/plasma.cpp +++ b/micropython/modules/plasma/plasma.cpp @@ -182,6 +182,29 @@ mp_obj_t PlasmaWS2812_set_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t return mp_const_none; } +mp_obj_t PlasmaWS2812_get(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_index }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_index, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + 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); + + int index = args[ARG_index].u_int; + + _PlasmaWS2812_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _PlasmaWS2812_obj_t); + WS2812::RGB rgb = self->ws2812->get(index); + + mp_obj_t tuple[4]; + tuple[0] = mp_obj_new_int(rgb.r); + tuple[1] = mp_obj_new_float(rgb.g); + tuple[2] = mp_obj_new_float(rgb.b); + tuple[3] = mp_obj_new_float(rgb.w); + return mp_obj_new_tuple(4, tuple); +} + /********** APA102 **********/ /***** Variables Struct *****/ @@ -357,4 +380,27 @@ mp_obj_t PlasmaAPA102_set_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t return mp_const_none; } +mp_obj_t PlasmaAPA102_get(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_index }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_index, MP_ARG_REQUIRED | MP_ARG_INT }, + }; + + 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); + + int index = args[ARG_index].u_int; + + _PlasmaAPA102_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _PlasmaAPA102_obj_t); + APA102::RGB rgb = self->apa102->get(index); + + mp_obj_t tuple[4]; + tuple[0] = mp_obj_new_int(rgb.r); + tuple[1] = mp_obj_new_float(rgb.g); + tuple[2] = mp_obj_new_float(rgb.b); + tuple[3] = mp_obj_new_float(rgb.sof); + return mp_obj_new_tuple(4, tuple); +} + } \ No newline at end of file diff --git a/micropython/modules/plasma/plasma.h b/micropython/modules/plasma/plasma.h index 018757a8..d2f4a925 100644 --- a/micropython/modules/plasma/plasma.h +++ b/micropython/modules/plasma/plasma.h @@ -13,6 +13,7 @@ extern mp_obj_t PlasmaAPA102_start(size_t n_args, const mp_obj_t *pos_args, mp_m extern mp_obj_t PlasmaAPA102_set_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t PlasmaAPA102_set_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t PlasmaAPA102_set_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t PlasmaAPA102_get(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t PlasmaAPA102_clear(mp_obj_t self_in); extern void PlasmaWS2812_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); @@ -21,6 +22,7 @@ extern mp_obj_t PlasmaWS2812___del__(mp_obj_t self_in); extern mp_obj_t PlasmaWS2812_start(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t PlasmaWS2812_set_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t PlasmaWS2812_set_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t PlasmaWS2812_get(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t PlasmaWS2812_clear(mp_obj_t self_in); extern bool Pimoroni_mp_obj_to_i2c(mp_obj_t in, void *out); \ No newline at end of file From 8a4c4d8af8fffe567e1c203cf81471d4217a0201 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 15:35:14 +0100 Subject: [PATCH 12/23] Exposed clear on rotary breakout --- micropython/modules/breakout_encoder/breakout_encoder.c | 2 ++ micropython/modules/breakout_encoder/breakout_encoder.cpp | 7 +++++++ micropython/modules/breakout_encoder/breakout_encoder.h | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/micropython/modules/breakout_encoder/breakout_encoder.c b/micropython/modules/breakout_encoder/breakout_encoder.c index dd3973a5..1d01e181 100644 --- a/micropython/modules/breakout_encoder/breakout_encoder.c +++ b/micropython/modules/breakout_encoder/breakout_encoder.c @@ -14,6 +14,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoder_set_brightness_obj, 2, BreakoutEncode MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoder_set_led_obj, 4, BreakoutEncoder_set_led); MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoder_available_obj, BreakoutEncoder_available); MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoder_read_obj, BreakoutEncoder_read); +MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoder_clear_obj, BreakoutEncoder_clear); /***** Binding of Methods *****/ STATIC const mp_rom_map_elem_t BreakoutEncoder_locals_dict_table[] = { @@ -26,6 +27,7 @@ STATIC const mp_rom_map_elem_t BreakoutEncoder_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_set_led), MP_ROM_PTR(&BreakoutEncoder_set_led_obj) }, { MP_ROM_QSTR(MP_QSTR_available), MP_ROM_PTR(&BreakoutEncoder_available_obj) }, { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&BreakoutEncoder_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&BreakoutEncoder_clear_obj) }, { MP_ROM_QSTR(MP_QSTR_DIRECTION_CW), MP_ROM_INT(1) }, { MP_ROM_QSTR(MP_QSTR_DIRECTION_CCW), MP_ROM_INT(0) }, }; diff --git a/micropython/modules/breakout_encoder/breakout_encoder.cpp b/micropython/modules/breakout_encoder/breakout_encoder.cpp index 314bf86b..c9525501 100644 --- a/micropython/modules/breakout_encoder/breakout_encoder.cpp +++ b/micropython/modules/breakout_encoder/breakout_encoder.cpp @@ -199,4 +199,11 @@ mp_obj_t BreakoutEncoder_read(mp_obj_t self_in) { breakout_encoder_BreakoutEncoder_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_BreakoutEncoder_obj_t); return mp_obj_new_int(self->breakout->read()); } + +mp_obj_t BreakoutEncoder_clear(mp_obj_t self_in) { + breakout_encoder_BreakoutEncoder_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_BreakoutEncoder_obj_t); + self->breakout->clear(); + + return mp_const_none; +} } \ No newline at end of file diff --git a/micropython/modules/breakout_encoder/breakout_encoder.h b/micropython/modules/breakout_encoder/breakout_encoder.h index 50c88e0a..cf7b63f1 100644 --- a/micropython/modules/breakout_encoder/breakout_encoder.h +++ b/micropython/modules/breakout_encoder/breakout_encoder.h @@ -15,4 +15,5 @@ extern mp_obj_t BreakoutEncoder_set_direction(size_t n_args, const mp_obj_t *pos extern mp_obj_t BreakoutEncoder_set_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t BreakoutEncoder_set_led(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); extern mp_obj_t BreakoutEncoder_available(mp_obj_t self_in); -extern mp_obj_t BreakoutEncoder_read(mp_obj_t self_in); \ No newline at end of file +extern mp_obj_t BreakoutEncoder_read(mp_obj_t self_in); +extern mp_obj_t BreakoutEncoder_clear(mp_obj_t self_in); \ No newline at end of file From 51d4901e34884acbf25b3173f3eac7f9f28b86a1 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 16:01:51 +0100 Subject: [PATCH 13/23] Added rotary encoder MP example --- micropython/examples/plasma2040/rotary.py | 189 ++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 micropython/examples/plasma2040/rotary.py diff --git a/micropython/examples/plasma2040/rotary.py b/micropython/examples/plasma2040/rotary.py new file mode 100644 index 00000000..7d0b4202 --- /dev/null +++ b/micropython/examples/plasma2040/rotary.py @@ -0,0 +1,189 @@ +import plasma +from plasma import plasma2040 +import time +import math + +# Import helpers for RGB LEDs and Buttons +from pimoroni import RGBLED, Button + +# Import bme68x and I2C helper +from breakout_encoder import BreakoutEncoder +from pimoroni_i2c import PimoroniI2C + +# Press "B" to enable cycling. +# Press "A" to change the encoder mode. +# Press "Boot" to reset the effects back to default. + +# Set how many LEDs you have +NUM_LEDS = 30 + +# The speed that the LEDs will start cycling at +DEFAULT_SPEED = 20 + +# The hue (in degrees) that the LEDs will start at +DEFAULT_HUE = 0 + +# The angle (in degrees) from the hue, that the LEDs will end at +DEFAULT_ANGLE = 120 + +# The brightness (between 0 and 31) to set the LEDs to +DEFAULT_BRIGHTNESS = 16 + +# How many times the LEDs will be updated per second +UPDATES = 60 + + +# Pick *one* LED type by uncommenting the relevant line below: + +# APA102 / DotStar™ LEDs +led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) + +# WS2812 / NeoPixel™ LEDs +# led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) + +user_sw = Button(plasma2040.USER_SW, repeat_time=0) +button_a = Button(plasma2040.BUTTON_A, repeat_time=0) +button_b = Button(plasma2040.BUTTON_B, repeat_time=0) +led = RGBLED(plasma2040.LED_R, plasma2040.LED_G, plasma2040.LED_B) + +PINS_PLASMA2040 = {"sda": plasma2040.SDA, "scl": plasma2040.SCL} + +i2c = PimoroniI2C(**PINS_PLASMA2040) +enc = BreakoutEncoder(i2c) +enc.set_brightness(1.0) + + + +COLOUR, ANGLE, BRIGHTNESS, SPEED = range(4) + + +def wrap(v, mn, mx): + if v <= mn: + v += mx - mn + + if v > mx: + v -= mx - mn + + return v + + +def colour_cycle(hue, t, angle): + t /= 200.0 + + for i in range(NUM_LEDS): + percent_along = float(i) / NUM_LEDS + offset = math.sin((percent_along + 0.5 + t) * math.pi) * angle + h = wrap((hue + offset) / 360.0, 0.0, 1.0) + led_strip.set_hsv(i, h, 1.0, 1.0) + + +def speed_gauge(v, vmax=100): + light_pixels = NUM_LEDS * v / vmax + + for i in range(NUM_LEDS): + if i < light_pixels: + led_strip.set_rgb(i, 0, 255, 0) + else: + led_strip.set_rgb(i, 255, 0, 0) + + +def brightness_gauge(v, vmax = 100): + light_pixels = NUM_LEDS * v / vmax + + for i in range(NUM_LEDS): + if i < light_pixels: + led_strip.set_rgb(i, 64, 64, 64) + else: + led_strip.set_rgb(i, 0, 0, 0) + + +# Start updating the LED strip +led_strip.start() + +enc.clear_interrupt_flag() + +# Initialise the default values +speed = DEFAULT_SPEED +hue = DEFAULT_HUE +angle = DEFAULT_ANGLE +brightness = DEFAULT_BRIGHTNESS + +cycle = True +mode = COLOUR +start_time = time.ticks_ms() + +while True: + t = time.ticks_ms() - start_time + + if enc.get_interrupt_flag(): + count = enc.read() + enc.clear_interrupt_flag() + enc.clear() + + cycle = False + + if mode == COLOUR: + hue += count + hue = min(359, max(0, hue)) + colour_cycle(hue, 0, angle) + + elif mode == ANGLE: + angle += count + angle = min(359, max(0, angle)) + colour_cycle(hue, 0, angle) + + elif mode == BRIGHTNESS: + brightness += count + brightness = min(31, max(0, brightness)) + led_strip.set_brightness(brightness) + brightness_gauge(brightness, 31) + + elif mode == SPEED: + speed += count + speed = min(100, max(0, speed)) + speed_gauge(brightness, 100) + + + sw_pressed = user_sw.read() + a_pressed = button_a.read() + b_pressed = button_b.read() + + if sw_pressed: + speed = DEFAULT_SPEED + hue = DEFAULT_HUE + angle = DEFAULT_ANGLE + brightness = DEFAULT_BRIGHTNESS + + if b_pressed: + if not cycle: + start_time = time.millis(); + cycle = True + speed = min(255, max(1, speed)) + + if mode == COLOUR: + led.set_rgb(255, 0, 0) + if a_pressed: + mode = ANGLE + + elif mode == ANGLE: + led.set_rgb(255, 255, 0) + if a_pressed: + mode = BRIGHTNESS + + elif mode == BRIGHTNESS: + led.set_rgb(0, 255, 0) + if a_pressed: + mode = SPEED + + elif mode == SPEED: + led.set_rgb(0, 0, 255) + if a_pressed: + mode = COLOUR + + if cycle: + colour_cycle(hue, float(t * speed) / 100.0, angle) + + mid_led = led_strip.get(int(NUM_LEDS / 2)) + enc.set_led(int(mid_led[0]), int(mid_led[1]), int(mid_led[2])) + + time.sleep(1.0 / UPDATES) From d8d0a538c04fcf5a0aac211bb2c05aab042cbc81 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 16:02:29 +0100 Subject: [PATCH 14/23] Renamed Time to Speed to match MP example --- examples/plasma2040/plasma2040_rotary.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/plasma2040/plasma2040_rotary.cpp b/examples/plasma2040/plasma2040_rotary.cpp index b93acfec..ba1999f5 100644 --- a/examples/plasma2040/plasma2040_rotary.cpp +++ b/examples/plasma2040/plasma2040_rotary.cpp @@ -35,11 +35,10 @@ const int16_t DEFAULT_ANGLE = 120; // The brightness (between 0 and 31) to set the LEDs to const int16_t DEFAULT_BRIGHTNESS = 16; - - // How many times the LEDs will be updated per second const uint UPDATES = 60; + // Pick *one* LED type by uncommenting the relevant line below: // APA102-style LEDs with Data/Clock lines. AKA DotStar @@ -62,7 +61,7 @@ enum ENCODER_MODE { COLOUR, ANGLE, BRIGHTNESS, - TIME + SPEED }; float wrap(float v, float min, float max) { @@ -159,7 +158,7 @@ int main() { brightness_gauge(brightness, 31); break; - case ENCODER_MODE::TIME: + case ENCODER_MODE::SPEED: speed += count; speed = std::min((int16_t)100, std::max((int16_t)0, speed)); speed_gauge(speed, 100); @@ -197,10 +196,10 @@ int main() { case ENCODER_MODE::BRIGHTNESS: led.set_rgb(0, 255, 0); - if(a_pressed) mode = ENCODER_MODE::TIME; + if(a_pressed) mode = ENCODER_MODE::SPEED; break; - case ENCODER_MODE::TIME: + case ENCODER_MODE::SPEED: led.set_rgb(0, 0, 255); if(a_pressed) mode = ENCODER_MODE::COLOUR; break; @@ -209,8 +208,8 @@ int main() { if(cycle) colour_cycle(hue, (float)(t * speed) / 100.0f, (float)angle); - auto first_led = led_strip.get(led_strip.num_leds / 2); - enc.set_led(first_led.r, first_led.g, first_led.b); + auto mid_led = led_strip.get(led_strip.num_leds / 2); + enc.set_led(mid_led.r, mid_led.g, mid_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 From f92d35a014c7f593b62f26422a292065dc7d435d Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 16:06:44 +0100 Subject: [PATCH 15/23] Added I2C defines --- micropython/examples/plasma2040/monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/examples/plasma2040/monitor.py b/micropython/examples/plasma2040/monitor.py index 1f6a47d3..690d8ad7 100644 --- a/micropython/examples/plasma2040/monitor.py +++ b/micropython/examples/plasma2040/monitor.py @@ -57,7 +57,7 @@ button_a = Button(plasma2040.BUTTON_A, repeat_time=0) button_b = Button(plasma2040.BUTTON_B, repeat_time=0) led = RGBLED(plasma2040.LED_R, plasma2040.LED_G, plasma2040.LED_B) -PINS_PLASMA2040 = {"sda": 20, "scl": 21} +PINS_PLASMA2040 = {"sda": plasma2040.SDA, "scl": plasma2040.SCL} i2c = PimoroniI2C(**PINS_PLASMA2040) bme = BreakoutBME68X(i2c) From 43933727263db361ddffdde0051b1b113c32ebca Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 24 Aug 2021 16:10:23 +0100 Subject: [PATCH 16/23] Linting Fixes --- micropython/examples/plasma2040/rotary.py | 32 +++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/micropython/examples/plasma2040/rotary.py b/micropython/examples/plasma2040/rotary.py index 7d0b4202..63660b0e 100644 --- a/micropython/examples/plasma2040/rotary.py +++ b/micropython/examples/plasma2040/rotary.py @@ -53,18 +53,17 @@ enc = BreakoutEncoder(i2c) enc.set_brightness(1.0) - COLOUR, ANGLE, BRIGHTNESS, SPEED = range(4) def wrap(v, mn, mx): - if v <= mn: - v += mx - mn + if v <= mn: + v += mx - mn - if v > mx: - v -= mx - mn + if v > mx: + v -= mx - mn - return v + return v def colour_cycle(hue, t, angle): @@ -87,7 +86,7 @@ def speed_gauge(v, vmax=100): led_strip.set_rgb(i, 255, 0, 0) -def brightness_gauge(v, vmax = 100): +def brightness_gauge(v, vmax=100): light_pixels = NUM_LEDS * v / vmax for i in range(NUM_LEDS): @@ -114,14 +113,14 @@ start_time = time.ticks_ms() while True: t = time.ticks_ms() - start_time - + if enc.get_interrupt_flag(): count = enc.read() enc.clear_interrupt_flag() enc.clear() - + cycle = False - + if mode == COLOUR: hue += count hue = min(359, max(0, hue)) @@ -142,12 +141,11 @@ while True: speed += count speed = min(100, max(0, speed)) speed_gauge(brightness, 100) - - + sw_pressed = user_sw.read() a_pressed = button_a.read() b_pressed = button_b.read() - + if sw_pressed: speed = DEFAULT_SPEED hue = DEFAULT_HUE @@ -156,10 +154,10 @@ while True: if b_pressed: if not cycle: - start_time = time.millis(); + start_time = time.ticks_ms() cycle = True speed = min(255, max(1, speed)) - + if mode == COLOUR: led.set_rgb(255, 0, 0) if a_pressed: @@ -179,10 +177,10 @@ while True: led.set_rgb(0, 0, 255) if a_pressed: mode = COLOUR - + if cycle: colour_cycle(hue, float(t * speed) / 100.0, angle) - + mid_led = led_strip.get(int(NUM_LEDS / 2)) enc.set_led(int(mid_led[0]), int(mid_led[1]), int(mid_led[2])) From 9d505f208a29f2ba1b4ed7e0bbdddbd964aa8b95 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 25 Aug 2021 15:06:05 +0100 Subject: [PATCH 17/23] Added MP accelerometer example --- micropython/examples/plasma2040/level.py | 206 +++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 micropython/examples/plasma2040/level.py diff --git a/micropython/examples/plasma2040/level.py b/micropython/examples/plasma2040/level.py new file mode 100644 index 00000000..1ea5c6d0 --- /dev/null +++ b/micropython/examples/plasma2040/level.py @@ -0,0 +1,206 @@ +import plasma +from plasma import plasma2040 +import time +import math +import random + +# Import helpers for RGB LEDs and Buttons +from pimoroni import RGBLED, Button + +# Import msa301 and I2C helper +from breakout_msa301 import BreakoutMSA301 +from pimoroni_i2c import PimoroniI2C + +# A simple balancing game, where you use the MSA301 accelerometer to line up a band with a goal on the strip. +# This can either be done using: +# - Angle mode: Where position on the strip directly matches the accelerometer's angle +# - Velocity mode: Where tilting the accelerometer changes the speed the band moves at +# When the goal position is reached, a new position is randomly selected + +# Press "A" to change the game mode. +# Press "B" to start or stop the game mode. +# Press "Boot" to invert the direction of the accelerometer tilt + +# Set how many LEDs you have +NUM_LEDS = 30 + +# How many times the LEDs will be updated per second +UPDATES = 60 + +# The sensitivity of the accelerometer input +ANGLE_SENSITIVITY = 0.05 +VELOCITY_SENSITIVITY = 0.2 / UPDATES + +# The band colour hues to show in Angle mode +ANGLE_MODE_GOAL_HUE = 0.333 +ANGLE_MODE_EDGE_HUE = 0.0 + +# The band colour hues to show in Velocity mode +VELOCITY_MODE_GOAL_HUE = 0.667 +VELOCITY_MODE_EDGE_HUE = 1.0 + +# The width and colour settings for the band +BAND_PIXEL_WIDTH = 2.0 +BAND_SATURATION = 1.0 +BAND_IN_GOAL_SATURATION = 0.5 +BAND_BRIGHTNESS = 1.0 + +# The width and colour settings for the goal +# Goal should be wider than the band by a small amount +GOAL_PIXEL_WIDTH = BAND_PIXEL_WIDTH + 2.0 +GOAL_BRIGHTNESS = 0.1 + +# The percentage of the new angle (between 0.0 and 1.0) to apply to the last angle +# Has the effect of smoothing out the reading, at the cost of making it slower to react +SMOOTHING_FACTOR = 0.1 + + +# Pick *one* LED type by uncommenting the relevant line below: + +# APA102 / DotStar™ LEDs +led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) + +# WS2812 / NeoPixel™ LEDs +# led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) + +user_sw = Button(plasma2040.USER_SW, repeat_time=0) +button_a = Button(plasma2040.BUTTON_A, repeat_time=0) +button_b = Button(plasma2040.BUTTON_B, repeat_time=0) +led = RGBLED(plasma2040.LED_R, plasma2040.LED_G, plasma2040.LED_B) + +PINS_PLASMA2040 = {"sda": plasma2040.SDA, "scl": plasma2040.SCL} + +i2c = PimoroniI2C(**PINS_PLASMA2040) +msa = BreakoutMSA301(i2c) + + +ANGLE, VELOCITY = range(2) + +# Maps a value from one range to another +def map(x, in_min, in_max, out_min, out_max): + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + +# Shows a band and goal with the given widths at the positions on the strip +def colour_band(centre_position, width, goal_position, goal_width, hue): + if centre_position >= 0.0 and width > 0.0 and goal_width > 0.0: + band_pixels_start = centre_position - (width / 2) + band_pixels_end = centre_position + (width / 2) + + goal_pixels_start = goal_position - (goal_width / 2) + goal_pixels_end = goal_position + (goal_width / 2) + + # Go through each led in the strip + for i in range(NUM_LEDS): + + # Set saturation and brightness values for if the led is inside or outside of the goal + saturation = BAND_SATURATION + brightness = 0.0 + if i >= goal_pixels_start and i < goal_pixels_end: + saturation = BAND_IN_GOAL_SATURATION + brightness = GOAL_BRIGHTNESS + + i2 = i + 1 + if i2 <= band_pixels_end: + if i2 <= band_pixels_start: + # Outside of the band + led_strip.set_hsv(i, hue, 0.0, brightness) + elif i <= band_pixels_start: + # Transition into the band + val = map(band_pixels_start, float(i), float(i2), BAND_BRIGHTNESS, brightness) + sat = map(band_pixels_start, float(i), float(i2), BAND_SATURATION, saturation) + led_strip.set_hsv(i, hue, sat, val) + else: + # Inside the band + led_strip.set_hsv(i, hue, 1.0, 1.0) + + elif i <= band_pixels_end: + # Transition out of the band + val = map(band_pixels_end, float(i), float(i2), brightness, BAND_BRIGHTNESS) + sat = map(band_pixels_end, float(i), float(i2), saturation, BAND_SATURATION) + led_strip.set_hsv(i, hue, sat, val) + else: + # Outside of the band + led_strip.set_hsv(i, hue, 0.0, brightness) + + +mode = ANGLE + +goal_position = 0.0 +measured_angle = 0.0 +invert = False +game_mode = False + +# Start updating the LED strip +led_strip.start() + +while True: + # Read the x and y axes of the accelerometer + x = msa.get_x_axis() + y = msa.get_y_axis() + + # Convert those values to an angle in degrees, and invert if selected + new_measured_angle = (math.atan2(x, -y) * 180.0) / math.pi + if invert: + new_measured_angle = -new_measured_angle + print("Angle:", new_measured_angle, "deg") + + # Smooth out the measured angle + measured_angle = ((new_measured_angle - measured_angle) * SMOOTHING_FACTOR) + measured_angle + + if mode == ANGLE: + # Apply the measured angle directly to the used angle, clamping it between -1 and +1 + band_position = measured_angle * ANGLE_SENSITIVITY + band_position = min(1.0, max(-1.0, band_position)) + + # Convert the difference between the band and goal positions into a colour hue + position_diff = min(abs(band_position - goal_position), 1.0) + hue = map(position_diff, 0.0, 1.0, ANGLE_MODE_GOAL_HUE, ANGLE_MODE_EDGE_HUE) + + elif mode == VELOCITY: + band_position += measured_angle * VELOCITY_SENSITIVITY + band_position = min(1.0, max(-1.0, band_position)) + + # Convert the difference between the band and goal positions into a colour hue + position_diff = min(abs(band_position - goal_position), 1.0) + hue = map(position_diff, 0.0, 1.0, VELOCITY_MODE_GOAL_HUE, VELOCITY_MODE_EDGE_HUE) + + # Convert the band and goal positions to positions on the LED strip + strip_band_position = map(band_position, -1.0, 1.0, 0.0, float(NUM_LEDS)) + strip_goal_position = map(goal_position, -1.0, 1.0, 0.0, float(NUM_LEDS)) + + # Draw the band and goal + colour_band(strip_band_position, BAND_PIXEL_WIDTH, strip_goal_position, GOAL_PIXEL_WIDTH, hue) + + sw_pressed = user_sw.read() + a_pressed = button_a.read() + b_pressed = button_b.read() + + if b_pressed: + game_mode = not game_mode + + if sw_pressed: + invert = not invert + + if mode == ANGLE: + if game_mode: + led.set_rgb(255, 255, 0) + else: + led.set_rgb(0, 255, 0) + if a_pressed: + mode = VELOCITY + + elif mode == VELOCITY: + if game_mode: + led.set_rgb(255, 0, 255) + else: + led.set_rgb(0, 0, 255) + if a_pressed: + mode = ANGLE + + if game_mode: + # Check if the band is within the goal, and if so, set a new goal + if(strip_band_position >= strip_goal_position - (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2 and + strip_band_position <= strip_goal_position + (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2): + goal_position = random.uniform(-1.0, 1.0) + + time.sleep(1.0 / UPDATES) From 08a54a600f4842cf7ea5ea29372c13f11d5a05da Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 25 Aug 2021 15:23:22 +0100 Subject: [PATCH 18/23] Linting fixes --- micropython/examples/plasma2040/level.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/micropython/examples/plasma2040/level.py b/micropython/examples/plasma2040/level.py index 1ea5c6d0..2090d938 100644 --- a/micropython/examples/plasma2040/level.py +++ b/micropython/examples/plasma2040/level.py @@ -58,10 +58,10 @@ SMOOTHING_FACTOR = 0.1 # Pick *one* LED type by uncommenting the relevant line below: # APA102 / DotStar™ LEDs -led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) +# led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) # WS2812 / NeoPixel™ LEDs -# led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) +led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) user_sw = Button(plasma2040.USER_SW, repeat_time=0) button_a = Button(plasma2040.BUTTON_A, repeat_time=0) @@ -76,10 +76,12 @@ msa = BreakoutMSA301(i2c) ANGLE, VELOCITY = range(2) + # Maps a value from one range to another def map(x, in_min, in_max, out_min, out_max): return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + # Shows a band and goal with the given widths at the positions on the strip def colour_band(centre_position, width, goal_position, goal_width, hue): if centre_position >= 0.0 and width > 0.0 and goal_width > 0.0: @@ -91,7 +93,6 @@ def colour_band(centre_position, width, goal_position, goal_width, hue): # Go through each led in the strip for i in range(NUM_LEDS): - # Set saturation and brightness values for if the led is inside or outside of the goal saturation = BAND_SATURATION brightness = 0.0 @@ -143,7 +144,7 @@ while True: if invert: new_measured_angle = -new_measured_angle print("Angle:", new_measured_angle, "deg") - + # Smooth out the measured angle measured_angle = ((new_measured_angle - measured_angle) * SMOOTHING_FACTOR) + measured_angle @@ -199,8 +200,9 @@ while True: if game_mode: # Check if the band is within the goal, and if so, set a new goal - if(strip_band_position >= strip_goal_position - (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2 and - strip_band_position <= strip_goal_position + (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2): + above_lower = strip_band_position >= strip_goal_position - (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2 + below_upper = strip_band_position <= strip_goal_position + (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2 + if above_lower and below_upper: goal_position = random.uniform(-1.0, 1.0) time.sleep(1.0 / UPDATES) From 53429e0c6f4c47066f1e16982cbc33b802c12e6a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Aug 2021 16:02:06 +0100 Subject: [PATCH 19/23] Do not pio_sm_unclaim for MicroPython Adds an ifdef guard around `pio_sm_unclaim` that prevents it being called when MicroPython cleans up/finalizes classes. For some reason this appeared to be causing a hardfault. --- drivers/plasma/apa102.cpp | 6 +++--- drivers/plasma/apa102.hpp | 6 ++++++ drivers/plasma/ws2812.cpp | 6 +++--- drivers/plasma/ws2812.hpp | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/plasma/apa102.cpp b/drivers/plasma/apa102.cpp index 7f1c7e2d..bb60a972 100644 --- a/drivers/plasma/apa102.cpp +++ b/drivers/plasma/apa102.cpp @@ -3,14 +3,14 @@ namespace plasma { APA102::APA102(uint num_leds, PIO pio, uint sm, uint pin_dat, uint pin_clk, uint freq, RGB* buffer) : buffer(buffer), num_leds(num_leds), pio(pio), sm(sm) { - uint offset = pio_add_program(pio, &apa102_program); + pio_program_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); + pio_sm_config c = apa102_program_get_default_config(pio_program_offset); sm_config_set_out_pins(&c, pin_dat, 1); sm_config_set_sideset_pins(&c, pin_clk); @@ -21,7 +21,7 @@ APA102::APA102(uint num_leds, PIO pio, uint sm, uint pin_dat, uint pin_clk, uint 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_init(pio, sm, pio_program_offset, &c); pio_sm_set_enabled(pio, sm, true); dma_channel = dma_claim_unused_channel(true); diff --git a/drivers/plasma/apa102.hpp b/drivers/plasma/apa102.hpp index accdcbcc..094c3e6e 100644 --- a/drivers/plasma/apa102.hpp +++ b/drivers/plasma/apa102.hpp @@ -60,7 +60,12 @@ namespace plasma { clear(); update(true); dma_channel_unclaim(dma_channel); + pio_sm_set_enabled(pio, sm, false); + pio_remove_program(pio, &apa102_program, pio_program_offset); +#ifndef MICROPY_BUILD_TYPE + // pio_sm_unclaim seems to hardfault in MicroPython pio_sm_unclaim(pio, sm); +#endif delete[] buffer; } bool start(uint fps=60); @@ -78,6 +83,7 @@ namespace plasma { uint32_t fps; PIO pio; uint sm; + uint pio_program_offset; int dma_channel; struct repeating_timer timer; }; diff --git a/drivers/plasma/ws2812.cpp b/drivers/plasma/ws2812.cpp index 92a1b0b0..3c49f618 100644 --- a/drivers/plasma/ws2812.cpp +++ b/drivers/plasma/ws2812.cpp @@ -3,12 +3,12 @@ namespace plasma { WS2812::WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq, bool rgbw, COLOR_ORDER color_order, RGB* buffer) : buffer(buffer), num_leds(num_leds), color_order(color_order), pio(pio), sm(sm) { - uint offset = pio_add_program(pio, &ws2812_program); + pio_program_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); + pio_sm_config c = ws2812_program_get_default_config(pio_program_offset); sm_config_set_sideset_pins(&c, pin); sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); // Discard first (APA102 global brightness) byte. TODO support RGBW WS281X LEDs @@ -18,7 +18,7 @@ WS2812::WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq, bool rgbw, 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_init(pio, sm, pio_program_offset, &c); pio_sm_set_enabled(pio, sm, true); dma_channel = dma_claim_unused_channel(true); diff --git a/drivers/plasma/ws2812.hpp b/drivers/plasma/ws2812.hpp index 3d57cabb..4f503c3a 100644 --- a/drivers/plasma/ws2812.hpp +++ b/drivers/plasma/ws2812.hpp @@ -69,7 +69,12 @@ namespace plasma { clear(); update(true); dma_channel_unclaim(dma_channel); + pio_sm_set_enabled(pio, sm, false); + pio_remove_program(pio, &ws2812_program, pio_program_offset); +#ifndef MICROPY_BUILD_TYPE + // pio_sm_unclaim seems to hardfault in MicroPython pio_sm_unclaim(pio, sm); +#endif delete[] buffer; } bool start(uint fps=60); @@ -87,6 +92,7 @@ namespace plasma { uint32_t fps; PIO pio; uint sm; + uint pio_program_offset; int dma_channel; struct repeating_timer timer; }; From 03a7cbee06939ec922226ffe90f222a084b44891 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Aug 2021 16:06:11 +0100 Subject: [PATCH 20/23] Fix speed gauge --- micropython/examples/plasma2040/rotary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/examples/plasma2040/rotary.py b/micropython/examples/plasma2040/rotary.py index 63660b0e..11e36ade 100644 --- a/micropython/examples/plasma2040/rotary.py +++ b/micropython/examples/plasma2040/rotary.py @@ -140,7 +140,7 @@ while True: elif mode == SPEED: speed += count speed = min(100, max(0, speed)) - speed_gauge(brightness, 100) + speed_gauge(speed, 100) sw_pressed = user_sw.read() a_pressed = button_a.read() From bb23ba22db37e1782268a38c3bfe7598e8b2d765 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 25 Aug 2021 18:12:10 +0100 Subject: [PATCH 21/23] Added C++ port of accelerometer example --- examples/plasma2040/CMakeLists.txt | 1 + examples/plasma2040/plasma2040_level.cmake | 17 ++ examples/plasma2040/plasma2040_level.cpp | 245 +++++++++++++++++++++ micropython/examples/plasma2040/level.py | 7 +- 4 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 examples/plasma2040/plasma2040_level.cmake create mode 100644 examples/plasma2040/plasma2040_level.cpp diff --git a/examples/plasma2040/CMakeLists.txt b/examples/plasma2040/CMakeLists.txt index 30abd500..72ec8311 100644 --- a/examples/plasma2040/CMakeLists.txt +++ b/examples/plasma2040/CMakeLists.txt @@ -1,3 +1,4 @@ +include(plasma2040_level.cmake) include(plasma2040_monitor.cmake) include(plasma2040_rainbow.cmake) include(plasma2040_rotary.cmake) diff --git a/examples/plasma2040/plasma2040_level.cmake b/examples/plasma2040/plasma2040_level.cmake new file mode 100644 index 00000000..bbdadd62 --- /dev/null +++ b/examples/plasma2040/plasma2040_level.cmake @@ -0,0 +1,17 @@ +set(OUTPUT_NAME plasma2040_level) +add_executable(${OUTPUT_NAME} plasma2040_level.cpp) + +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + plasma2040 + breakout_msa301 + hardware_i2c + pimoroni_i2c + rgbled + button + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/plasma2040/plasma2040_level.cpp b/examples/plasma2040/plasma2040_level.cpp new file mode 100644 index 00000000..2d3d0756 --- /dev/null +++ b/examples/plasma2040/plasma2040_level.cpp @@ -0,0 +1,245 @@ +#include +#include +#include + +#include "pico/stdlib.h" + +#include "plasma2040.hpp" + +#include "common/pimoroni_common.hpp" +#include "breakout_msa301.hpp" +#include "rgbled.hpp" +#include "button.hpp" + +/* +A simple balancing game, where you use the MSA301 accelerometer to line up a band with a goal on the strip. +This can either be done using: +- Angle mode: Where position on the strip directly matches the accelerometer's angle +- Velocity mode: Where tilting the accelerometer changes the speed the band moves at +When the goal position is reached, a new position is randomly selected + +Press "A" to change the game mode. +Press "B" to start or stop the game mode. +Press "Boot" to invert the direction of the accelerometer tilt +*/ + +using namespace pimoroni; +using namespace plasma; + +// Set how many LEDs you have +const uint N_LEDS = 30; + +// How many times the LEDs will be updated per second +const uint UPDATES = 60; + +// The sensitivity of the accelerometer input +constexpr float ANGLE_SENSITIVITY = 0.05f; +constexpr float VELOCITY_SENSITIVITY = 0.2f / UPDATES; + +// The band colour hues to show in Angle mode +constexpr float ANGLE_MODE_GOAL_HUE = 0.333f; +constexpr float ANGLE_MODE_EDGE_HUE = 0.0f; + +// The band colour hues to show in Velocity mode +constexpr float VELOCITY_MODE_GOAL_HUE = 0.667f; +constexpr float VELOCITY_MODE_EDGE_HUE = 1.0f; + +// The width and colour settings for the band +constexpr float BAND_PIXEL_WIDTH = 2.0f; +constexpr float BAND_SATURATION = 1.0f; +constexpr float BAND_IN_GOAL_SATURATION = 0.5f; +constexpr float BAND_BRIGHTNESS = 1.0f; + +// The width and colour settings for the goal +// Goal should be wider than the band by a small amount +constexpr float GOAL_PIXEL_WIDTH = BAND_PIXEL_WIDTH + 2.0f; +constexpr float GOAL_BRIGHTNESS = 0.1f; + +// The percentage of the new angle (between 0.0 and 1.0) to apply to the last angle +// Has the effect of smoothing out the reading, at the cost of making it slower to react +constexpr float SMOOTHING_FACTOR = 0.1f; + + +// Pick *one* LED type by uncommenting the relevant line below: + +// APA102-style LEDs with Data/Clock lines. AKA DotStar +//APA102 led_strip(N_LEDS, pio0, 0, plasma2040::DAT, plasma2040::CLK); + +// WS28X-style LEDs with a single signal line. AKA NeoPixel +WS2812 led_strip(N_LEDS, pio0, 0, plasma2040::DAT); + + +Button user_sw(plasma2040::USER_SW, Polarity::ACTIVE_LOW, 0); +Button button_a(plasma2040::BUTTON_A, Polarity::ACTIVE_LOW, 0); +Button button_b(plasma2040::BUTTON_B, Polarity::ACTIVE_LOW, 0); + +RGBLED led(plasma2040::LED_R, plasma2040::LED_G, plasma2040::LED_B); + +I2C i2c(BOARD::PICO_EXPLORER); +BreakoutMSA301 msa(&i2c); + +enum LEVEL_MODE { + ANGLE, + VELOCITY +}; + +// Maps a value from one range to another +float map(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// Shows a band and goal with the given widths at the positions on the strip +void colour_band(float centre_position, float width, float goal_position, float goal_width, float hue) { + if((centre_position >= 0.0f) && (width > 0.0) && (goal_width > 0.0)) { + float band_pixels_start = centre_position - (width / 2); + float band_pixels_end = centre_position + (width / 2); + + float goal_pixels_start = goal_position - (goal_width / 2); + float goal_pixels_end = goal_position + (goal_width / 2); + + // Go through each led in the strip + uint i2; + float saturation, brightness, sat, val; + for(uint i = 0; i < led_strip.num_leds; i++) { + // Set saturation and brightness values for if the led is inside or outside of the goal + saturation = BAND_SATURATION; + brightness = 0.0f; + if((i >= goal_pixels_start) && (i < goal_pixels_end)) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + + i2 = i + 1; + if(i2 <= band_pixels_end) { + if(i2 <= band_pixels_start) { + // Outside of the band + led_strip.set_hsv(i, hue, 0.0, brightness); + } + else if(i <= band_pixels_start) { + // Transition into the band + val = map(band_pixels_start, (float)i, (float)i2, BAND_BRIGHTNESS, brightness); + sat = map(band_pixels_start, (float)i, (float)i2, BAND_SATURATION, saturation); + led_strip.set_hsv(i, hue, sat, val); + } + else { + // Inside the band + led_strip.set_hsv(i, hue, 1.0, 1.0); + } + } + else if(i <= band_pixels_end) { + // Transition out of the band + val = map(band_pixels_end, (float)i, (float)i2, brightness, BAND_BRIGHTNESS); + sat = map(band_pixels_end, (float)i, (float)i2, saturation, BAND_SATURATION); + led_strip.set_hsv(i, hue, sat, val); + } + else { + // Outside of the band + led_strip.set_hsv(i, hue, 0.0, brightness); + } + } + } +} + +int main() { + stdio_init_all(); + + led_strip.start(UPDATES); + + bool accel_detected = msa.init(); + + float band_position = 0.0f; + float goal_position = 0.0f; + float measured_angle = 0.0f; + bool invert = false; + bool game_mode = false; + + LEVEL_MODE mode = LEVEL_MODE::ANGLE; + while(true) { + if(accel_detected) { + // Read the x and y axes of the accelerometer + float x = msa.get_x_axis(); + float y = msa.get_y_axis(); + + // Convert those values to an angle in degrees, and invert if selected + float new_measured_angle = (atan2(x, -y) * 180.0f) / M_PI; + if(invert) + new_measured_angle = -new_measured_angle; + printf("Angle: %f deg\n", new_measured_angle); + + // Smooth out the measured angle + measured_angle = ((new_measured_angle - measured_angle) * SMOOTHING_FACTOR) + measured_angle; + + float hue = 0.0; + float position_diff; + switch(mode) { + case LEVEL_MODE::ANGLE: + // Apply the measured angle directly to the band position, clamping it between -1 and +1 + band_position = measured_angle * ANGLE_SENSITIVITY; + band_position = std::min(1.0f, std::max(-1.0f, band_position)); + + // Convert the difference between the band and goal positions into a colour hue + position_diff = std::min(abs(band_position - goal_position), 1.0f); + hue = map(position_diff, 0.0f, 1.0f, ANGLE_MODE_GOAL_HUE, ANGLE_MODE_EDGE_HUE); + break; + + case LEVEL_MODE::VELOCITY: + // Apply the measured angle as a velocity to the band position, clamping it between -1 and +1 + band_position += measured_angle * VELOCITY_SENSITIVITY; + band_position = std::min(1.0f, std::max(-1.0f, band_position)); + + // Convert the difference between the band and goal positions into a colour hue + position_diff = std::min(abs(band_position - goal_position), 1.0f); + hue = map(position_diff, 0.0f, 1.0f, VELOCITY_MODE_GOAL_HUE, VELOCITY_MODE_EDGE_HUE); + break; + } + + // Convert the band and goal positions to positions on the LED strip + float strip_band_position = map(band_position, -1.0f, 1.0f, 0.0f, float(led_strip.num_leds)); + float strip_goal_position = map(goal_position, -1.0f, 1.0f, 0.0f, float(led_strip.num_leds)); + + // Draw the band and goal + colour_band(strip_band_position, BAND_PIXEL_WIDTH, strip_goal_position, GOAL_PIXEL_WIDTH, hue); + + bool sw_pressed = user_sw.read(); + bool a_pressed = button_a.read(); + bool b_pressed = button_b.read(); + + if(b_pressed) + game_mode = !game_mode; + + if(sw_pressed) + invert = !invert; + + switch(mode) { + case ANGLE: + if(game_mode) + led.set_rgb(255, 255, 0); + else + led.set_rgb(0, 255, 0); + if(a_pressed) + mode = VELOCITY; + break; + + case VELOCITY: + if(game_mode) + led.set_rgb(255, 0, 255); + else + led.set_rgb(0, 0, 255); + if(a_pressed) + mode = ANGLE; + break; + } + + if(game_mode) { + // Check if the band is within the goal, and if so, set a new goal + bool above_lower = strip_band_position >= strip_goal_position - (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2; + bool below_upper = strip_band_position <= strip_goal_position + (GOAL_PIXEL_WIDTH - BAND_PIXEL_WIDTH) / 2; + if(above_lower && below_upper) + goal_position = map((float)rand(), 0.0f, (float)RAND_MAX, -1.0f, 1.0f); + } + } + // 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 / UPDATES); + } +} diff --git a/micropython/examples/plasma2040/level.py b/micropython/examples/plasma2040/level.py index 2090d938..da9fcaa4 100644 --- a/micropython/examples/plasma2040/level.py +++ b/micropython/examples/plasma2040/level.py @@ -58,10 +58,10 @@ SMOOTHING_FACTOR = 0.1 # Pick *one* LED type by uncommenting the relevant line below: # APA102 / DotStar™ LEDs -# led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) +led_strip = plasma.APA102(NUM_LEDS, 0, 0, plasma2040.DAT, plasma2040.CLK) # WS2812 / NeoPixel™ LEDs -led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) +# led_strip = plasma.WS2812(NUM_LEDS, 0, 0, plasma2040.DAT) user_sw = Button(plasma2040.USER_SW, repeat_time=0) button_a = Button(plasma2040.BUTTON_A, repeat_time=0) @@ -149,7 +149,7 @@ while True: measured_angle = ((new_measured_angle - measured_angle) * SMOOTHING_FACTOR) + measured_angle if mode == ANGLE: - # Apply the measured angle directly to the used angle, clamping it between -1 and +1 + # Apply the measured angle directly to the band position, clamping it between -1 and +1 band_position = measured_angle * ANGLE_SENSITIVITY band_position = min(1.0, max(-1.0, band_position)) @@ -158,6 +158,7 @@ while True: hue = map(position_diff, 0.0, 1.0, ANGLE_MODE_GOAL_HUE, ANGLE_MODE_EDGE_HUE) elif mode == VELOCITY: + # Apply the measured angle as a velocity to the band position, clamping it between -1 and +1 band_position += measured_angle * VELOCITY_SENSITIVITY band_position = min(1.0, max(-1.0, band_position)) From 5abdad05a8f36e78d6a8a06a99e0ca5061ab2aac Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Aug 2021 21:00:44 +0100 Subject: [PATCH 22/23] Fix MicroPython alloc'd bytearray support in APA102 driver A bytearray allocated in Python would point to uninitialised bytes, missing the SOF byte and brightness for APA102 pixels. Add a blunt loop over the MicroPython buffer, calling "brightness" for each RGB element to ensure the SOF byte and brightness are initialized. --- micropython/modules/plasma/plasma.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/micropython/modules/plasma/plasma.cpp b/micropython/modules/plasma/plasma.cpp index 3098efe1..9d61cc51 100644 --- a/micropython/modules/plasma/plasma.cpp +++ b/micropython/modules/plasma/plasma.cpp @@ -268,22 +268,29 @@ mp_obj_t PlasmaAPA102_make_new(const mp_obj_type_t *type, size_t n_args, size_t int clk = args[ARG_clk].u_int; int freq = args[ARG_freq].u_int; - void *buffer = nullptr; + APA102::RGB *buffer = nullptr; if (args[ARG_buffer].u_obj) { mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_RW); - buffer = bufinfo.buf; + buffer = (APA102::RGB *)bufinfo.buf; if(bufinfo.len < (size_t)(num_leds * 4)) { mp_raise_ValueError("Supplied buffer is too small for LED count!"); } + // If a bytearray is supplied it'll be raw, uninitialized bytes + // iterate through the RGB elements and call "brightness" + // to set up the SOF bytes, otherwise a flickery mess will happen! + // Oh for such niceties as "placement new"... + for(auto i = 0; i < num_leds; i++) { + buffer[i].brightness(15); + } } self = m_new_obj_with_finaliser(_PlasmaAPA102_obj_t); self->base.type = &PlasmaAPA102_type; self->buf = buffer; - self->apa102 = new APA102(num_leds, pio, sm, dat, clk, freq, (APA102::RGB *)buffer); + self->apa102 = new APA102(num_leds, pio, sm, dat, clk, freq, buffer); return MP_OBJ_FROM_PTR(self); } From e283d460d4234415613d60971c49b09fff324d8a Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Wed, 25 Aug 2021 21:57:00 +0100 Subject: [PATCH 23/23] Make ws2812 and apa102 responsibly only for their own buffers This fixes a bug where delete[] would be called on a bytearray buffer allocated by MicroPython on gc_heap. --- drivers/plasma/apa102.cpp | 1 + drivers/plasma/apa102.hpp | 6 +++++- drivers/plasma/ws2812.cpp | 1 + drivers/plasma/ws2812.hpp | 6 +++++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/plasma/apa102.cpp b/drivers/plasma/apa102.cpp index bb60a972..5ba6d4ef 100644 --- a/drivers/plasma/apa102.cpp +++ b/drivers/plasma/apa102.cpp @@ -34,6 +34,7 @@ APA102::APA102(uint num_leds, PIO pio, uint sm, uint pin_dat, uint pin_clk, uint if(this->buffer == nullptr) { this->buffer = new RGB[num_leds]; + managed_buffer = true; } } diff --git a/drivers/plasma/apa102.hpp b/drivers/plasma/apa102.hpp index 094c3e6e..acccd6ff 100644 --- a/drivers/plasma/apa102.hpp +++ b/drivers/plasma/apa102.hpp @@ -66,7 +66,10 @@ namespace plasma { // pio_sm_unclaim seems to hardfault in MicroPython pio_sm_unclaim(pio, sm); #endif - delete[] buffer; + if(managed_buffer) { + // Only delete buffers we have allocated ourselves. + delete[] buffer; + } } bool start(uint fps=60); bool stop(); @@ -86,5 +89,6 @@ namespace plasma { uint pio_program_offset; int dma_channel; struct repeating_timer timer; + bool managed_buffer = false; }; } \ No newline at end of file diff --git a/drivers/plasma/ws2812.cpp b/drivers/plasma/ws2812.cpp index 3c49f618..4ef5b171 100644 --- a/drivers/plasma/ws2812.cpp +++ b/drivers/plasma/ws2812.cpp @@ -33,6 +33,7 @@ WS2812::WS2812(uint num_leds, PIO pio, uint sm, uint pin, uint freq, bool rgbw, if(!this->buffer) { this->buffer = new RGB[num_leds]; + managed_buffer = true; } } diff --git a/drivers/plasma/ws2812.hpp b/drivers/plasma/ws2812.hpp index 4f503c3a..87ec42af 100644 --- a/drivers/plasma/ws2812.hpp +++ b/drivers/plasma/ws2812.hpp @@ -75,7 +75,10 @@ namespace plasma { // pio_sm_unclaim seems to hardfault in MicroPython pio_sm_unclaim(pio, sm); #endif - delete[] buffer; + if(managed_buffer) { + // Only delete buffers we have allocated ourselves. + delete[] buffer; + } } bool start(uint fps=60); bool stop(); @@ -95,5 +98,6 @@ namespace plasma { uint pio_program_offset; int dma_channel; struct repeating_timer timer; + bool managed_buffer = false; }; } \ No newline at end of file