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/drivers/plasma/apa102.cpp b/drivers/plasma/apa102.cpp index 7f1c7e2d..5ba6d4ef 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); @@ -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 accdcbcc..acccd6ff 100644 --- a/drivers/plasma/apa102.hpp +++ b/drivers/plasma/apa102.hpp @@ -60,8 +60,16 @@ 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); - delete[] buffer; +#endif + if(managed_buffer) { + // Only delete buffers we have allocated ourselves. + delete[] buffer; + } } bool start(uint fps=60); bool stop(); @@ -78,7 +86,9 @@ namespace plasma { uint32_t fps; PIO pio; uint sm; + 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 92a1b0b0..4ef5b171 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); @@ -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 3da8722a..87ec42af 100644 --- a/drivers/plasma/ws2812.hpp +++ b/drivers/plasma/ws2812.hpp @@ -63,14 +63,22 @@ 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(); 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); - delete[] buffer; +#endif + if(managed_buffer) { + // Only delete buffers we have allocated ourselves. + delete[] buffer; + } } bool start(uint fps=60); bool stop(); @@ -87,7 +95,9 @@ namespace plasma { uint32_t fps; PIO pio; uint sm; + uint pio_program_offset; int dma_channel; struct repeating_timer timer; + bool managed_buffer = false; }; } \ No newline at end of file diff --git a/examples/plasma2040/CMakeLists.txt b/examples/plasma2040/CMakeLists.txt index af288df9..72ec8311 100644 --- a/examples/plasma2040/CMakeLists.txt +++ b/examples/plasma2040/CMakeLists.txt @@ -1,3 +1,5 @@ -include(plasma2040_rotary.cmake) +include(plasma2040_level.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_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/examples/plasma2040/plasma2040_monitor.cmake b/examples/plasma2040/plasma2040_monitor.cmake new file mode 100644 index 00000000..e3291ff4 --- /dev/null +++ b/examples/plasma2040/plasma2040_monitor.cmake @@ -0,0 +1,17 @@ +set(OUTPUT_NAME plasma2040_monitor) +add_executable(${OUTPUT_NAME} plasma2040_monitor.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_monitor.cpp b/examples/plasma2040/plasma2040_monitor.cpp new file mode 100644 index 00000000..362299fc --- /dev/null +++ b/examples/plasma2040/plasma2040_monitor.cpp @@ -0,0 +1,188 @@ +#include +#include +#include + +#include "pico/stdlib.h" + +#include "plasma2040.hpp" + +#include "common/pimoroni_common.hpp" +#include "bme68x.hpp" +#include "rgbled.hpp" +#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. +*/ + +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 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 +//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, ACTIVE_LOW, 0); +Button button_b(plasma2040::BUTTON_B, ACTIVE_LOW, 0); + +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 +}; + +// 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; + 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("%.2fc, %.2fPa, %.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, 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, 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, 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, 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, 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, HUMIDITY_HUE_START, HUMIDITY_HUE_END); + 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; + } + } +} 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); + } } diff --git a/examples/plasma2040/plasma2040_rotary.cpp b/examples/plasma2040/plasma2040_rotary.cpp index 7ee59c42..ba1999f5 100644 --- a/examples/plasma2040/plasma2040_rotary.cpp +++ b/examples/plasma2040/plasma2040_rotary.cpp @@ -11,15 +11,34 @@ #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 @@ -29,9 +48,9 @@ const uint UPDATES = 60; 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 +58,161 @@ I2C i2c(BOARD::PICO_EXPLORER); BreakoutEncoder enc(&i2c); enum ENCODER_MODE { - COLOUR, - ANGLE, - BRIGHTNESS, - TIME + COLOUR, + ANGLE, + BRIGHTNESS, + SPEED }; +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::SPEED: + 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::SPEED; + break; + + case ENCODER_MODE::SPEED: + 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 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 + sleep_ms(1000 / UPDATES); + } } diff --git a/micropython/examples/plasma2040/level.py b/micropython/examples/plasma2040/level.py new file mode 100644 index 00000000..da9fcaa4 --- /dev/null +++ b/micropython/examples/plasma2040/level.py @@ -0,0 +1,209 @@ +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 band position, 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: + # 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)) + + # 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 + 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) diff --git a/micropython/examples/plasma2040/monitor.py b/micropython/examples/plasma2040/monitor.py new file mode 100644 index 00000000..690d8ad7 --- /dev/null +++ b/micropython/examples/plasma2040/monitor.py @@ -0,0 +1,157 @@ +import plasma +from plasma import plasma2040 + +# Import helpers for RGB LEDs and Buttons +from pimoroni import RGBLED, Button + +# Import bme68x and I2C helper +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. + +# 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": plasma2040.SDA, "scl": plasma2040.SCL} + +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 diff --git a/micropython/examples/plasma2040/rotary.py b/micropython/examples/plasma2040/rotary.py new file mode 100644 index 00000000..11e36ade --- /dev/null +++ b/micropython/examples/plasma2040/rotary.py @@ -0,0 +1,187 @@ +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(speed, 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.ticks_ms() + 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) 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 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 d3d319c0..9d61cc51 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. @@ -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 *****/ @@ -245,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); } @@ -357,4 +387,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