From bb23ba22db37e1782268a38c3bfe7598e8b2d765 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 25 Aug 2021 18:12:10 +0100 Subject: [PATCH] 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))