diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1ba0f213..557ae24b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(breakout_dotmatrix) add_subdirectory(breakout_encoder) +add_subdirectory(breakout_encoder_wheel) add_subdirectory(breakout_ioexpander) add_subdirectory(breakout_ltr559) add_subdirectory(breakout_colourlcd160x80) diff --git a/examples/breakout_encoder_wheel/CMakeLists.txt b/examples/breakout_encoder_wheel/CMakeLists.txt new file mode 100644 index 00000000..5d4150ea --- /dev/null +++ b/examples/breakout_encoder_wheel/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(buttons) +add_subdirectory(chase_game) +add_subdirectory(clock) +add_subdirectory(colour_picker) +add_subdirectory(encoder) +#add_subdirectory(gpio_pwm) +add_subdirectory(led_rainbow) +add_subdirectory(stop_watch) \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/buttons/CMakeLists.txt b/examples/breakout_encoder_wheel/buttons/CMakeLists.txt new file mode 100644 index 00000000..b3a477e8 --- /dev/null +++ b/examples/breakout_encoder_wheel/buttons/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_buttons) +add_executable(${OUTPUT_NAME} buttons.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/buttons/buttons.cpp b/examples/breakout_encoder_wheel/buttons/buttons.cpp new file mode 100644 index 00000000..0b7b42c2 --- /dev/null +++ b/examples/breakout_encoder_wheel/buttons/buttons.cpp @@ -0,0 +1,95 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +A demonstration of reading the 5 buttons on Encoder Wheel. +*/ + +// Constants +const std::string BUTTON_NAMES[] = {"Up", "Down", "Left", "Right", "Centre"}; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +bool last_pressed[NUM_BUTTONS] = {false, false, false, false, false}; +bool pressed[NUM_BUTTONS] = {false, false, false, false, false}; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + // Read all of the encoder wheel's buttons + for(int b = 0 ; b < NUM_BUTTONS; b++) { + pressed[b] = wheel.pressed(b); + if(pressed[b] != last_pressed[b]) { + printf("%s %s\n", BUTTON_NAMES[b].c_str(), pressed[b] ? "Pressed" : "Released"); + } + last_pressed[b] = pressed[b]; + } + + // Clear the LED ring + wheel.clear(); + + for(int i = 0; i < NUM_LEDS; i++) { + if(i % 6 == 3) { + wheel.set_rgb(i, 64, 64, 64); + } + } + + // If up is pressed, set the top LEDs to yellow + if(pressed[UP]) { + int mid = NUM_LEDS; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 255, 255, 0); + } + } + + // If right is pressed, set the right LEDs to red + if(pressed[RIGHT]) { + int mid = NUM_LEDS / 4; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 255, 0, 0); + } + } + + // If down is pressed, set the bottom LEDs to green + if(pressed[DOWN]) { + int mid = NUM_LEDS / 2; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 0, 255, 0); + } + } + + // If left is pressed, set the left LEDs to blue + if(pressed[LEFT]) { + int mid = (NUM_LEDS * 3) / 4; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 0, 0, 255); + } + } + + // If centre is pressed, set the diagonal LEDs to half white + if(pressed[CENTRE]) { + for(int i = 0; i < NUM_LEDS; i++) { + if(i % 6 >= 2 && i % 6 <= 4) { + wheel.set_rgb(i, 128, 128, 128); + } + } + } + wheel.show(); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/chase_game/CMakeLists.txt b/examples/breakout_encoder_wheel/chase_game/CMakeLists.txt new file mode 100644 index 00000000..367d26ec --- /dev/null +++ b/examples/breakout_encoder_wheel/chase_game/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_chase_game) +add_executable(${OUTPUT_NAME} chase_game.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/chase_game/chase_game.cpp b/examples/breakout_encoder_wheel/chase_game/chase_game.cpp new file mode 100644 index 00000000..d1658a4c --- /dev/null +++ b/examples/breakout_encoder_wheel/chase_game/chase_game.cpp @@ -0,0 +1,147 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +A simple alignment game. Use Encoder Wheel's rotary dial to align the coloured band +to the white goal. The closer to the goal, the greener your coloured band will be. +When you reach the goal, the goal will move to a new random position. +*/ + +// The band colour hues to show in Angle mode +constexpr float GOAL_HUE = 0.333f; +constexpr float FAR_HUE = 0.0f; + +// The width and colour settings for the band +constexpr float BAND_WIDTH = 5.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_MARGIN = 1.0f; +constexpr float GOAL_WIDTH = BAND_WIDTH + (2.0f * GOAL_MARGIN); +constexpr float GOAL_BRIGHTNESS = 0.4f; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float goal_position = 0.0f; +int16_t band_position = 0; + + +// 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.0f && goal_width > 0.0) { + float band_start = centre_position - (width / 2); + float band_end = centre_position + (width / 2); + float band_centre = centre_position; + + float goal_start = goal_position - (goal_width / 2); + float goal_end = goal_position + (goal_width / 2); + + // Go through each led in the strip + for(int i = 0; i < NUM_LEDS; i++) { + // Set saturation and brightness values for if the led is inside or outside of the goal + float saturation = BAND_SATURATION; + float brightness = 0.0f; + + if(i >= goal_start && i < goal_end) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + if(goal_end >= NUM_LEDS && i + NUM_LEDS < goal_end) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + if(goal_start < 0 && i - NUM_LEDS >= goal_start) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + + float val = brightness; + float sat = 0.0f; + if(i >= band_start && i < band_end) { + // Inside the band + if(i < band_centre) { + // Transition into the band + val = map(i, band_centre, band_start, BAND_BRIGHTNESS, brightness); + sat = map(i, band_centre, band_start, BAND_SATURATION, saturation); + } + else { + val = map(i, band_centre, band_end, BAND_BRIGHTNESS, brightness); + sat = map(i, band_centre, band_end, BAND_SATURATION, saturation); + } + } + else if(band_end >= NUM_LEDS && i + NUM_LEDS < band_end && i < band_centre) { + val = map(i + NUM_LEDS, band_centre, band_end, BAND_BRIGHTNESS, brightness); + sat = map(i + NUM_LEDS, band_centre, band_end, BAND_SATURATION, saturation); + } + else if(band_start < 0 && i - NUM_LEDS >= band_start && i >= band_centre) { + val = map(i - NUM_LEDS, band_centre, band_start, BAND_BRIGHTNESS, brightness); + sat = map(i - NUM_LEDS, band_centre, band_start, BAND_SATURATION, saturation); + } + //else { + // Outside of the band + //} + wheel.set_hsv(i, hue, sat, val); + } + wheel.show(); + } +} + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + band_position = wheel.step(); + + // Convert the difference between the band and goal positions into a colour hue + float diff1, diff2; + if(band_position > goal_position) { + diff1 = band_position - goal_position; + diff2 = (goal_position + NUM_LEDS) - band_position; + } + else { + diff1 = goal_position - band_position; + diff2 = (band_position + NUM_LEDS) - goal_position; + } + + float position_diff = MIN(diff1, diff2); + float hue = map(position_diff, 0, NUM_LEDS / 2.0f, GOAL_HUE, FAR_HUE); + + // Convert the band and goal positions to positions on the LED strip + float strip_band_position = map(band_position, 0, NUM_LEDS, 0.0f, (float)NUM_LEDS); + float strip_goal_position = map(goal_position, 0, NUM_LEDS, 0.0f, (float)NUM_LEDS); + + // Draw the band and goal + colour_band(strip_band_position, BAND_WIDTH, strip_goal_position, GOAL_WIDTH, hue); + + // Check if the band is within the goal, and if so, set a new goal + if(band_position >= goal_position - GOAL_MARGIN && band_position <= goal_position + GOAL_MARGIN) + goal_position = rand() % NUM_LEDS; + if(band_position >= NUM_LEDS && band_position + NUM_LEDS < goal_position + GOAL_MARGIN) + goal_position = rand() % NUM_LEDS; + if(goal_position - GOAL_MARGIN < 0 && band_position - NUM_LEDS >= goal_position + GOAL_MARGIN) + goal_position = rand() % NUM_LEDS; + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/clock/CMakeLists.txt b/examples/breakout_encoder_wheel/clock/CMakeLists.txt new file mode 100644 index 00000000..09e3d221 --- /dev/null +++ b/examples/breakout_encoder_wheel/clock/CMakeLists.txt @@ -0,0 +1,14 @@ +set(OUTPUT_NAME encoderwheel_clock) +add_executable(${OUTPUT_NAME} clock.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + hardware_rtc + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/clock/clock.cpp b/examples/breakout_encoder_wheel/clock/clock.cpp new file mode 100644 index 00000000..2f8e3712 --- /dev/null +++ b/examples/breakout_encoder_wheel/clock/clock.cpp @@ -0,0 +1,113 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" +#include "hardware/rtc.h" +#include "pico/util/datetime.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Displays a 12 hour clock on Encoder Wheel's LED ring, getting time from the system. +*/ + +// Datetime Indices +const uint HOUR = 4; +const uint MINUTE = 5; +const uint SECOND = 6; + +// Constants +constexpr float BRIGHTNESS = 1.0f; // The brightness of the LEDs +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +// Handy values for the number of milliseconds +constexpr float MILLIS_PER_SECOND = 1000; +constexpr float MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60; +constexpr float MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; +constexpr float MILLIS_PER_HALF_DAY = MILLIS_PER_HOUR * 12; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + + +// Calculates the brightness of an LED based on its index and a position along the LED ring +int led_brightness_at(int led, float position, float half_width = 1.0f, float span = 1.0f) { + float brightness = 0.0f; + float upper = position + half_width; + float lower = position - half_width; + if(led > position) + brightness = CLAMP((upper - led) / span, 0.0f, 1.0f); + else + brightness = CLAMP((led - lower) / span, 0.0f, 1.0f); + + // Handle the LEDs being in a circle + if(upper >= NUM_LEDS) + brightness = CLAMP(((upper - NUM_LEDS) - led) / span, brightness, 1.0f); + else if(lower < 0.0f) + brightness = CLAMP((led - (lower + NUM_LEDS)) / span, brightness, 1.0f); + + return (int)(brightness * BRIGHTNESS * 255); +} + + +int main() { + stdio_init_all(); + + // Start on Thursday 4th of May 2023 14:20:00 + datetime_t now = { + .year = 2023, + .month = 05, + .day = 04, + .dotw = 4, // 0 is Sunday, so 4 is Thursday + .hour = 14, + .min = 20, + .sec = 00 + }; + + // Start the RTC + rtc_init(); + rtc_set_datetime(&now); + + // clk_sys is >2000x faster than clk_rtc, so datetime is not updated immediately when rtc_get_datetime() is called. + // tbe delay is up to 3 RTC clock cycles (which is 64us with the default clock settings) + sleep_us(64); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + // Get the current system time + rtc_get_datetime(&now); + + // Convert the seconds, minutes, and hours into milliseconds (this is done to give a smoother animation, particularly for the seconds hand) + uint sec_as_millis = (now.sec * MILLIS_PER_SECOND); + uint min_as_millis = (now.min * MILLIS_PER_MINUTE) + sec_as_millis; + uint hour_as_millis = ((now.hour % 12) * MILLIS_PER_HOUR) + min_as_millis; + + // Calculate the position on the LED ring that the, second, minute, and hour hands should be + float sec_pos = MIN(sec_as_millis / MILLIS_PER_MINUTE, 1.0f) * NUM_LEDS; + float min_pos = MIN(min_as_millis / MILLIS_PER_HOUR, 1.0f) * NUM_LEDS; + float hour_pos = MIN(hour_as_millis / MILLIS_PER_HALF_DAY, 1.0f) * NUM_LEDS; + + for(int i = 0; i < NUM_LEDS; i++) { + // Turn on the LEDs close to the position of the current second, minute, and hour + int r = led_brightness_at(i, sec_pos); + int g = led_brightness_at(i, min_pos); + int b = led_brightness_at(i, hour_pos); + wheel.set_rgb(i, r, g, b); + } + wheel.show(); + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt b/examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt new file mode 100644 index 00000000..05266590 --- /dev/null +++ b/examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_colour_picker) +add_executable(${OUTPUT_NAME} colour_picker.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp b/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp new file mode 100644 index 00000000..bf0f300e --- /dev/null +++ b/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp @@ -0,0 +1,167 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Create a colour wheel on the Encoder Wheel's LED ring, and use all functions of the wheel to interact with it. + +Rotate the wheel to select a Hue +Press the up direction to increase Brightness +Press the down direction to decrease Brightness +Press the left direction to decrease Saturation +Press the right direction to increase Saturation +Press the centre to hide the selection marker +*/ + +// Constants +constexpr float BRIGHTNESS_STEP = 0.02f; // How much to increase or decrease the brightness each update +constexpr float SATURATION_STEP = 0.02f; // How much to increase or decrease the saturation each update +const uint UPDATES = 50; // How many times to update the LEDs per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float brightness = 1.0f; +float saturation = 1.0f; +int position = 0; +bool changed = true; +bool last_centre_pressed = false; + +// Struct for storing RGB values +struct Pixel { + uint8_t r; + uint8_t g; + uint8_t b; + Pixel() : r(0), g(0), b(0) {}; + Pixel(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}; +}; + +// Basic function to convert Hue, Saturation and Value to an RGB colour +Pixel hsv_to_rgb(float h, float s, float v) { + if(h < 0.0f) { + h = 1.0f + fmodf(h, 1.0f); + } + + int i = int(h * 6); + float f = h * 6 - i; + + v = v * 255.0f; + + float sv = s * v; + float fsv = f * sv; + + auto p = uint8_t(-sv + v); + auto q = uint8_t(-fsv + v); + auto t = uint8_t(fsv - sv + v); + + uint8_t bv = uint8_t(v); + + switch (i % 6) { + default: + case 0: return Pixel(bv, t, p); + case 1: return Pixel(q, bv, p); + case 2: return Pixel(p, bv, t); + case 3: return Pixel(p, q, bv); + case 4: return Pixel(t, p, bv); + case 5: return Pixel(bv, p, q); + } +} + +// Simple function to clamp a value between 0.0 and 1.0 +float clamp01(float value) { + return MAX(MIN(value, 1.0f), 0.0f); +} + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + // If up is pressed, increase the brightness + if(wheel.pressed(UP)) { + brightness += BRIGHTNESS_STEP; + changed = true; // Trigger a change + } + + // If down is pressed, decrease the brightness + if(wheel.pressed(DOWN)) { + brightness -= BRIGHTNESS_STEP; + changed = true; // Trigger a change + } + + // If right is pressed, increase the saturation + if(wheel.pressed(RIGHT)) { + saturation += SATURATION_STEP; + changed = true; // Trigger a change + } + + // If left is pressed, decrease the saturation + if(wheel.pressed(LEFT)) { + saturation -= SATURATION_STEP; + changed = true; // Trigger a change + } + + // Limit the brightness and saturation between 0.0 and 1.0 + brightness = clamp01(brightness); + saturation = clamp01(saturation); + + // Check if the encoder has been turned + if(wheel.delta() != 0) { + // Update the position based on the count change + position = wheel.step(); + changed = true; // Trigger a change + } + + // If centre is pressed, trigger a change + bool centre_pressed = wheel.pressed(CENTRE); + if(centre_pressed != last_centre_pressed) { + changed = true; + } + last_centre_pressed = centre_pressed; + + // Was a change triggered? + if(changed) { + // Print the colour at the current hue, saturation, and brightness + Pixel pixel = hsv_to_rgb((float)position / NUM_LEDS, saturation, brightness); + printf("Colour Code = #%02x%02x%02x\n", pixel.r, pixel.g, pixel.b); + + // Set the LED at the current position to either the actual colour, + // or an inverted version to show a "selection marker" + if(centre_pressed) + wheel.set_rgb(position, pixel.r, pixel.g, pixel.b); + else + wheel.set_rgb(position, 255 - pixel.r, 255 - pixel.g, 255 - pixel.b); + + // Set the LEDs below the current position + for(int i = 0; i < position; i++) { + wheel.set_hsv(i, (float)i / NUM_LEDS, saturation, brightness); + } + + // Set the LEDs after the current position + for(int i = position + 1; i < NUM_LEDS; i++) { + wheel.set_hsv(i, (float)i / NUM_LEDS, saturation, brightness); + } + wheel.show(); + changed = false; + } + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/encoder/CMakeLists.txt b/examples/breakout_encoder_wheel/encoder/CMakeLists.txt new file mode 100644 index 00000000..4da72ee5 --- /dev/null +++ b/examples/breakout_encoder_wheel/encoder/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_encoder) +add_executable(${OUTPUT_NAME} encoder.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/encoder/encoder.cpp b/examples/breakout_encoder_wheel/encoder/encoder.cpp new file mode 100644 index 00000000..5784519d --- /dev/null +++ b/examples/breakout_encoder_wheel/encoder/encoder.cpp @@ -0,0 +1,59 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +A demonstration of reading the rotary dial of the Encoder Wheel breakout. +*/ + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +int position = 0; +float hue = 0.0f; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + + // Set the first LED + wheel.clear(); + wheel.set_hsv(position, hue, 1.0f, 1.0f); + wheel.show(); + + // Loop forever + while(true) { + // Has the dial been turned since the last time we checked? + int16_t change = wheel.delta(); + if(change != 0) { + // Print out the direction the dial was turned, and the count + if(change > 0) + printf("Clockwise, Count = %d\n", wheel.count()); + else + printf("Counter Clockwise, Count = %d\n", wheel.count()); + + // Record the new position (from 0 to 23) + position = wheel.step(); + + // Record a colour hue from 0.0 to 1.0 + hue = fmodf(wheel.revolutions(), 1.0f); + + // Set the LED at the new position to the new hue + wheel.clear(); + wheel.set_hsv(position, hue, 1.0f, 1.0f); + wheel.show(); + } + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt b/examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt new file mode 100644 index 00000000..87151a4d --- /dev/null +++ b/examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_led_rainbow) +add_executable(${OUTPUT_NAME} led_rainbow.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp b/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp new file mode 100644 index 00000000..429f56fa --- /dev/null +++ b/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Displays a rotating rainbow pattern on Encoder Wheel's LED ring. +*/ + +// Constants +constexpr float SPEED = 5.0f; // The speed that the LEDs will cycle at +constexpr float BRIGHTNESS = 1.0f; // The brightness of the LEDs +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float offset = 0.0; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + offset += SPEED / 1000.0f; + + // Update all the LEDs + for(int i = 0; i < NUM_LEDS; i++) { + float hue = (float)i / NUM_LEDS; + wheel.set_hsv(i, hue + offset, 1.0, BRIGHTNESS); + } + wheel.show(); + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt b/examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt new file mode 100644 index 00000000..54eb41d1 --- /dev/null +++ b/examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_stop_watch) +add_executable(${OUTPUT_NAME} stop_watch.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp b/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp new file mode 100644 index 00000000..f39e5b47 --- /dev/null +++ b/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp @@ -0,0 +1,150 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Display a circular stop-watch on the Encoder Wheel's LED ring. + +Press the centre button to start the stopwatch, then again to pause and resume. +*/ + +// Constants +constexpr float BRIGHTNESS = 1.0f; // The brightness of the LEDs +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint MINUTE_UPDATES = UPDATES * 60; // How many times the LEDs will be updated per minute +const uint HOUR_UPDATES = MINUTE_UPDATES * 60; // How many times the LEDs will be updated per hour +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +constexpr float IDLE_PULSE_MIN = 0.2f; // The brightness (between 0.0 and 1.0) that the idle pulse will go down to +constexpr float IDLE_PULSE_MAX = 0.5f; // The brightness (between 0.0 and 1.0) that the idle pulse will go up to +constexpr float IDLE_PULSE_TIME = 2.0f; // The time (in seconds) to perform a complete idle pulse +constexpr uint UPDATES_PER_PULSE = IDLE_PULSE_TIME * UPDATES; + +// The state constants used for program flow +enum State { + IDLE = 0, + COUNTING, + PAUSED +}; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +State state = IDLE; +uint idle_update = 0; +uint second_update = 0; +uint minute_update = 0; +uint hour_update = 0; +bool last_centre_pressed = false; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Record the current time + absolute_time_t current_time = get_absolute_time(); + + // Run the update loop forever + while(true) { + + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + // Read whether or not the wheen centre has been pressed + bool centre_pressed = wheel.pressed(CENTRE); + if(centre_pressed && centre_pressed != last_centre_pressed) { + switch(state) { + case IDLE: // If we're currently idle, switch to counting + second_update = 0; + minute_update = 0; + hour_update = 0; + state = COUNTING; + break; + case COUNTING: // If we're counting, switch to paused + state = PAUSED; + break; + case PAUSED: // If we're paused, switch back to counting + state = COUNTING; + } + } + last_centre_pressed = centre_pressed; + + switch(state) { + // If we're idle, perform a pulsing animation to show the stopwatch is ready to go + case IDLE: + { + float percent_along = MIN((float)idle_update / (float)UPDATES_PER_PULSE, 1.0f); + float brightness = ((cosf(percent_along * M_PI * 2.0f) + 1.0f) / 2.0f) * ((IDLE_PULSE_MAX - IDLE_PULSE_MIN)) + IDLE_PULSE_MIN; + // Update all the LEDs + for(int i = 0; i < NUM_LEDS; i++) { + wheel.set_hsv(i, 0.0, 0.0, brightness); + } + wheel.show(); + + // Advance to the next update, wrapping around to zero if at the end + idle_update += 1; + if(idle_update >= UPDATES_PER_PULSE) { + idle_update = 0; + } + } + break; + + // If we're counting, perform the stopwatch animation + case COUNTING: + { + // Calculate how many LED channels should light, as a proportion of a second, minute, and hour + float r_to_light = MIN((float)second_update / UPDATES, 1.0f) * 24.0f; + float g_to_light = MIN((float)minute_update / MINUTE_UPDATES, 1.0f) * 24.0f; + float b_to_light = MIN((float)hour_update / HOUR_UPDATES, 1.0f) * 24.0f; + + // Set each LED, such that ones below the current time are fully lit, ones after + // are off, and the one at the transition is at a percentage of the brightness + for(int i = 0; i < NUM_LEDS; i++) { + int r = (int)(CLAMP(r_to_light - i, 0.0f, 1.0f) * BRIGHTNESS * 255.0f); + int g = (int)(CLAMP(g_to_light - i, 0.0f, 1.0f) * BRIGHTNESS * 255.0f); + int b = (int)(CLAMP(b_to_light - i, 0.0f, 1.0f) * BRIGHTNESS * 255.0f); + wheel.set_rgb(i, r, g, b); + } + wheel.show(); + + // Advance the second updates count, wrapping around to zero if at the end + second_update += 1; + if(second_update >= UPDATES) { + second_update = 0; + } + + // Advance the minute updates count, wrapping around to zero if at the end + minute_update += 1; + if(minute_update >= MINUTE_UPDATES) { + minute_update = 0; + } + + // Advance the hour updates count, wrapping around to zero if at the end + hour_update += 1; + if(hour_update >= HOUR_UPDATES) { + hour_update = 0; + } + } + break; + + case PAUSED: + // Do nothing + break; + } + + // Sleep until the next update, accounting for how long the above operations took to perform + current_time = delayed_by_us(start_time, UPDATE_RATE_US); + sleep_until(current_time); + } + } + + return 0; +} \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index a1e684eb..81f86635 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -3,6 +3,7 @@ #include namespace pimoroni { +namespace encoderwheel { bool BreakoutEncoderWheel::init(bool skip_chip_id_check) { bool success = false; @@ -151,7 +152,7 @@ namespace pimoroni { void BreakoutEncoderWheel::set_hsv(int index, float h, float s, float v) { int r, g, b; if(h < 0.0f) { - h = 1.0f + fmod(h, 1.0f); + h = 1.0f + fmodf(h, 1.0f); } int i = int(h * 6); @@ -244,4 +245,5 @@ namespace pimoroni { } } } +} } \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index ecd229c9..81263a09 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -5,6 +5,21 @@ #include "common/pimoroni_common.hpp" namespace pimoroni { +namespace encoderwheel { + static const uint8_t NUM_LEDS = 24; + static const uint8_t NUM_BUTTONS = 5; + static const uint8_t NUM_GPIOS = 3; + + static const uint8_t UP = 0; + static const uint8_t DOWN = 1; + static const uint8_t LEFT = 2; + static const uint8_t RIGHT = 3; + static const uint8_t CENTRE = 4; + + static const uint8_t GP7 = 7; + static const uint8_t GP8 = 8; + static const uint8_t GP9 = 9; + static const uint8_t GPIOS[] = {GP7, GP8, GP9}; class BreakoutEncoderWheel { struct RGBLookup { @@ -142,5 +157,5 @@ namespace pimoroni { private: void take_encoder_reading(); }; - +} } \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/buttons.py b/micropython/examples/breakout_encoder_wheel/buttons.py index 266ddc4c..65d70d69 100644 --- a/micropython/examples/breakout_encoder_wheel/buttons.py +++ b/micropython/examples/breakout_encoder_wheel/buttons.py @@ -12,8 +12,6 @@ PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} # Constants BUTTON_NAMES = ["Up", "Down", "Left", "Right", "Centre"] -UPDATES = 50 # How many times the buttons will be checked and LEDs updated, per second -UPDATE_RATE = 1 / UPDATES # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)