From 653090c89ee4ff23cb5af70d88b41caa22d7c2d6 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 10 May 2023 12:46:00 +0100 Subject: [PATCH] Exposed support for GPIO pins on encoder wheel --- drivers/ioexpander/ioexpander.cpp | 30 +++++++- drivers/ioexpander/ioexpander.hpp | 8 +- .../breakout_encoder_wheel/CMakeLists.txt | 2 +- .../gpio_pwm/CMakeLists.txt | 13 ++++ .../gpio_pwm/gpio_pwm.cpp | 74 +++++++++++++++++++ .../breakout_encoder_wheel.cpp | 29 +++++--- .../breakout_encoder_wheel.hpp | 32 ++++---- 7 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp diff --git a/drivers/ioexpander/ioexpander.cpp b/drivers/ioexpander/ioexpander.cpp index ddf20c1c..4dfec84c 100644 --- a/drivers/ioexpander/ioexpander.cpp +++ b/drivers/ioexpander/ioexpander.cpp @@ -523,13 +523,35 @@ namespace pimoroni { return divider_good; } - void IOExpander::set_pwm_period(uint16_t value, bool load) { + void IOExpander::set_pwm_period(uint16_t value, bool load, bool wait_for_load) { value &= 0xffff; i2c->reg_write_uint8(address, reg::PWMPL, (uint8_t)(value & 0xff)); i2c->reg_write_uint8(address, reg::PWMPH, (uint8_t)(value >> 8)); if(load) - pwm_load(); + pwm_load(wait_for_load); + } + + uint16_t IOExpander::set_pwm_frequency(float frequency, bool load, bool wait_for_load) { + uint32_t period = (uint32_t)(CLOCK_FREQ / frequency); + if (period / 128 > MAX_PERIOD) { + return MAX_PERIOD; + } + if (period < 2) { + return 2; + } + + uint8_t divider = 1; + while ((period > MAX_PERIOD) && (divider < MAX_DIVIDER)) { + period >>= 1; + divider <<= 1; + } + + period = MIN(period, MAX_PERIOD); // Should be unnecessary because of earlier raised errors, but kept in case + set_pwm_control(divider); + set_pwm_period((uint16_t)(period - 1), load, wait_for_load); + + return (uint16_t)period; } uint8_t IOExpander::get_mode(uint8_t pin) { @@ -701,7 +723,7 @@ namespace pimoroni { } } - void IOExpander::output(uint8_t pin, uint16_t value, bool load) { + void IOExpander::output(uint8_t pin, uint16_t value, bool load, bool wait_for_load) { if(pin < 1 || pin > NUM_PINS) { printf("Pin should be in range 1-14."); return; @@ -717,7 +739,7 @@ namespace pimoroni { i2c->reg_write_uint8(address, io_pin.reg_pwml, (uint8_t)(value & 0xff)); i2c->reg_write_uint8(address, io_pin.reg_pwmh, (uint8_t)(value >> 8)); if(load) - pwm_load(); + pwm_load(wait_for_load); } else { if(value == LOW) { diff --git a/drivers/ioexpander/ioexpander.hpp b/drivers/ioexpander/ioexpander.hpp index 88cb9e04..2a3fb63e 100644 --- a/drivers/ioexpander/ioexpander.hpp +++ b/drivers/ioexpander/ioexpander.hpp @@ -27,6 +27,9 @@ namespace pimoroni { static const uint8_t PIN_MODE_ADC = 0b01010; // ADC, Input-only (high-impedance) static const uint32_t RESET_TIMEOUT_MS = 1000; + static const uint32_t CLOCK_FREQ = 24000000; + static const uint32_t MAX_PERIOD = (1 << 16) - 1; + static const uint32_t MAX_DIVIDER = (1 << 7); public: static const uint8_t DEFAULT_I2C_ADDRESS = 0x18; @@ -205,7 +208,8 @@ namespace pimoroni { void pwm_clear(bool wait_for_clear = true); bool pwm_clearing(); bool set_pwm_control(uint8_t divider); - void set_pwm_period(uint16_t value, bool load = true); + void set_pwm_period(uint16_t value, bool load = true, bool wait_for_load = true); + uint16_t set_pwm_frequency(float frequency, bool load = true, bool wait_for_load = true); uint8_t get_mode(uint8_t pin); void set_mode(uint8_t pin, uint8_t mode, bool schmitt_trigger = false, bool invert = false); @@ -213,7 +217,7 @@ namespace pimoroni { int16_t input(uint8_t pin, uint32_t adc_timeout = 1); float input_as_voltage(uint8_t pin, uint32_t adc_timeout = 1); - void output(uint8_t pin, uint16_t value, bool load = true); + void output(uint8_t pin, uint16_t value, bool load = true, bool wait_for_load = true); void setup_rotary_encoder(uint8_t channel, uint8_t pin_a, uint8_t pin_b, uint8_t pin_c = 0, bool count_microsteps = false); int16_t read_rotary_encoder(uint8_t channel); diff --git a/examples/breakout_encoder_wheel/CMakeLists.txt b/examples/breakout_encoder_wheel/CMakeLists.txt index 5d4150ea..680046f7 100644 --- a/examples/breakout_encoder_wheel/CMakeLists.txt +++ b/examples/breakout_encoder_wheel/CMakeLists.txt @@ -3,6 +3,6 @@ add_subdirectory(chase_game) add_subdirectory(clock) add_subdirectory(colour_picker) add_subdirectory(encoder) -#add_subdirectory(gpio_pwm) +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/gpio_pwm/CMakeLists.txt b/examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt new file mode 100644 index 00000000..2f4a07d1 --- /dev/null +++ b/examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_gpio_pwm) +add_executable(${OUTPUT_NAME} gpio_pwm.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/gpio_pwm/gpio_pwm.cpp b/examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp new file mode 100644 index 00000000..29f77cca --- /dev/null +++ b/examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp @@ -0,0 +1,74 @@ +#include +#include +#include "pimoroni_i2c.hpp" +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Output a sine wave PWM sequence on the Encoder Wheel's side GPIO pins. + +Press the centre button to stop the program. +*/ + +// Constants +constexpr float SPEED = 5.0f; // The speed that the LEDs will cycle at +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; +constexpr float FREQUENCY = 1000.0f; // The frequency to run the PWM at + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float offset = 0.0f; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + + // Set the PWM frequency for the GPIOs + uint16_t period = wheel.gpio_pwm_frequency(FREQUENCY); + + // Set the GPIO pins to PWM outputs + for(int i = 0; i < NUM_GPIOS; i++) { + wheel.gpio_pin_mode(GPIOS[i], IOExpander::PIN_PWM); + } + + // Loop forever + while(!wheel.pressed(CENTRE)) { + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + offset += SPEED / 1000.0f; + + // Update all the PWMs + for(int i = 0; i < NUM_GPIOS; i++) { + float angle = (((float)i / NUM_GPIOS) + offset) * M_PI; + uint16_t duty = (uint16_t)(((sinf(angle) / 2.0f) + 0.5f) * period); + + // Set the GPIO pin to the new duty cycle, but do not load it yet + wheel.gpio_pin_value(GPIOS[i], duty, false); + } + + // Have all the PWMs load at once + wheel.gpio_pwm_load(); + + // 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)); + } + + // Turn off the PWM outputs + for(int i = 0; i < NUM_GPIOS; i++) { + wheel.gpio_pin_value(GPIOS[i], 0); + } + } + + 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 81f86635..5611a852 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -190,32 +190,37 @@ namespace encoderwheel { led_ring.update(); } - int BreakoutEncoderWheel::gpio_pin_mode(int gpio) { - return 0; // TODO + uint8_t BreakoutEncoderWheel::gpio_pin_mode(uint8_t gpio) { + assert(gpio < GP7 || gpio > GP9); + return ioe.get_mode(gpio); } - void BreakoutEncoderWheel::gpio_pin_mode(int gpio, int mode) { - + void BreakoutEncoderWheel::gpio_pin_mode(uint8_t gpio, uint8_t mode) { + assert(gpio < GP7 || gpio > GP9); + ioe.set_mode(gpio, mode); } - int BreakoutEncoderWheel::gpio_pin_value(int gpio) { - return 0; // TODO + int16_t BreakoutEncoderWheel::gpio_pin_value(uint8_t gpio) { + assert(gpio < GP7 || gpio > GP9); + return ioe.input(gpio); } - float BreakoutEncoderWheel::gpio_pin_value_as_voltage(int gpio) { - return 0; // TODO + float BreakoutEncoderWheel::gpio_pin_value_as_voltage(uint8_t gpio) { + assert(gpio < GP7 || gpio > GP9); + return ioe.input_as_voltage(gpio); } - void BreakoutEncoderWheel::gpio_pin_value(int gpio, int value, bool load, bool wait_for_load) { - + void BreakoutEncoderWheel::gpio_pin_value(uint8_t gpio, uint16_t value, bool load, bool wait_for_load) { + assert(gpio < GP7 || gpio > GP9); + ioe.output(gpio, value, load, wait_for_load); } void BreakoutEncoderWheel::gpio_pwm_load(bool wait_for_load) { - + ioe.pwm_load(wait_for_load); } int BreakoutEncoderWheel::gpio_pwm_frequency(float frequency, bool load, bool wait_for_load) { - return 0; // TODO + return ioe.set_pwm_frequency(frequency, load, wait_for_load); } void BreakoutEncoderWheel::take_encoder_reading() { diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index 81263a09..ead5eb33 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -41,17 +41,17 @@ namespace encoderwheel { static const uint32_t DEFAULT_TIMEOUT = 1; private: - static const uint8_t ENC_CHANNEL = 1; - static const uint8_t ENC_TERM_A = 3; - static const uint8_t ENC_TERM_B = 12; - static const uint8_t ENC_COUNTS_PER_REV = 24; - static const uint8_t ENC_COUNT_DIVIDER = 2; + static const uint8_t ENC_CHANNEL = 1; + static const uint8_t ENC_TERM_A = 3; + static const uint8_t ENC_TERM_B = 12; + static const uint8_t ENC_COUNTS_PER_REV = 24; + static const uint8_t ENC_COUNT_DIVIDER = 2; - static const uint8_t SW_UP = 13; - static const uint8_t SW_DOWN = 4; - static const uint8_t SW_LEFT = 11; - static const uint8_t SW_RIGHT = 2; - static const uint8_t SW_CENTRE = 1; + static const uint8_t SW_UP = 13; + static const uint8_t SW_DOWN = 4; + static const uint8_t SW_LEFT = 11; + static const uint8_t SW_RIGHT = 2; + static const uint8_t SW_CENTRE = 1; // This wonderful lookup table maps the LEDs on the encoder wheel // from their 3x24 (remember, they're RGB) configuration to @@ -146,12 +146,12 @@ namespace encoderwheel { void clear(); void show(); - int gpio_pin_mode(int gpio); - void gpio_pin_mode(int gpio, int mode); - int gpio_pin_value(int gpio); - float gpio_pin_value_as_voltage(int gpio); - void gpio_pin_value(int gpio, int value, bool load = true, bool wait_for_load = false); - void gpio_pwm_load(bool wait_for_load = false); + uint8_t gpio_pin_mode(uint8_t gpio); + void gpio_pin_mode(uint8_t gpio, uint8_t mode); + int16_t gpio_pin_value(uint8_t gpio); + float gpio_pin_value_as_voltage(uint8_t gpio); + void gpio_pin_value(uint8_t gpio, uint16_t value, bool load = true, bool wait_for_load = false); + void gpio_pwm_load(bool wait_for_load = true); int gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false); private: