Merge pull request #774 from pimoroni/breakout_encoder_wheel

Support for RGB Encoder Wheel Breakout
This commit is contained in:
Philip Howard 2023-05-12 11:58:15 +01:00 committed by GitHub
commit 8648196cc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 3727 additions and 18 deletions

View File

@ -116,6 +116,7 @@ We also maintain a C++/CMake boilerplate with GitHub workflows configured for te
* MICS6814 - Gas Sensor - https://shop.pimoroni.com/products/mics6814-gas-sensor-breakout
* RGB Potentiometer - https://shop.pimoroni.com/products/rgb-potentiometer-breakout
* RGB Encoder - https://shop.pimoroni.com/products/rgb-encoder-breakout
* RGB Encoder Wheel - https://shop.pimoroni.com/products/rgb-encoder-wheel-breakout
* IO Expander - https://shop.pimoroni.com/products/io-expander
* RV3028 - Real-Time Clock (RTC) - https://shop.pimoroni.com/products/rv3028-real-time-clock-rtc-breakout
* ST7735 - 0.96" LCD - https://shop.pimoroni.com/products/0-96-spi-colour-lcd-160x80-breakout

View File

@ -320,8 +320,21 @@ namespace pimoroni {
Pin::adc(1, 7, 0)}
{}
bool IOExpander::init(bool skipChipIdCheck) {
bool succeeded = true;
bool IOExpander::init(bool skipChipIdCheck, bool perform_reset) {
if(!skipChipIdCheck) {
uint16_t chip_id = get_chip_id();
if(chip_id != CHIP_ID) {
if(debug) {
printf("Chip ID invalid: %04x expected: %04x\n", chip_id, CHIP_ID);
}
return false;
}
}
// Reset the chip if requested, to put it into a known state
if(perform_reset && !reset()) {
return false;
}
if(interrupt != PIN_UNUSED) {
gpio_set_function(interrupt, GPIO_FUNC_SIO);
@ -331,17 +344,36 @@ namespace pimoroni {
enable_interrupt_out(true);
}
if(!skipChipIdCheck) {
uint16_t chip_id = get_chip_id();
if(chip_id != CHIP_ID) {
if(debug) {
printf("Chip ID invalid: %04x expected: %04x\n", chip_id, CHIP_ID);
}
succeeded = false;
return true;
}
uint8_t IOExpander::check_reset() {
uint8_t user_flash_reg = reg::USER_FLASH;
uint8_t value;
if(i2c_write_blocking(i2c->get_i2c(), address, &user_flash_reg, 1, false) == PICO_ERROR_GENERIC) {
return 0x00;
}
if(i2c_read_blocking(i2c->get_i2c(), address, (uint8_t *)&value, sizeof(uint8_t), false) == PICO_ERROR_GENERIC) {
return 0x00;
}
return value;
}
bool IOExpander::reset() {
uint32_t start_time = millis();
set_bits(reg::CTRL, ctrl_mask::RESET);
// Wait for a register to read its initialised value
while(check_reset() != 0x78) {
sleep_ms(1);
if(millis() - start_time >= RESET_TIMEOUT_MS) {
if(debug)
printf("Timed out waiting for Reset!");
return false;
}
}
return succeeded;
return true;
}
i2c_inst_t* IOExpander::get_i2c() const {
@ -370,7 +402,7 @@ namespace pimoroni {
void IOExpander::set_address(uint8_t address) {
set_bit(reg::CTRL, 4);
i2c->reg_write_uint8(address, reg::ADDR, address);
i2c->reg_write_uint8(this->address, reg::ADDR, address);
this->address = address;
sleep_ms(250); //TODO Handle addr change IOError better
//wait_for_flash()
@ -491,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) {
@ -669,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;
@ -685,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) {

View File

@ -26,6 +26,8 @@ namespace pimoroni {
static const uint8_t PIN_MODE_PWM = 0b00101; // PWM, Output, Push-Pull mode
static const uint8_t PIN_MODE_ADC = 0b01010; // ADC, Input-only (high-impedance)
static const uint32_t RESET_TIMEOUT_MS = 1000;
public:
static const uint8_t DEFAULT_I2C_ADDRESS = 0x18;
@ -45,6 +47,10 @@ namespace pimoroni {
static const uint16_t LOW = 0;
static const uint16_t HIGH = 1;
static const uint32_t CLOCK_FREQ = 24000000;
static const uint32_t MAX_PERIOD = (1 << 16) - 1;
static const uint32_t MAX_DIVIDER = (1 << 7);
//--------------------------------------------------
// Subclasses
@ -171,7 +177,12 @@ namespace pimoroni {
// Methods
//--------------------------------------------------
public:
bool init(bool skip_chip_id_check = false);
bool init(bool skip_chip_id_check = false, bool perform_reset = false);
private:
uint8_t check_reset();
public:
bool reset();
// For print access in micropython
i2c_inst_t* get_i2c() const;
@ -198,7 +209,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);
@ -206,7 +218,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);

View File

@ -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)

View File

@ -0,0 +1,9 @@
add_subdirectory(buttons)
add_subdirectory(chase_game)
add_subdirectory(clock)
add_subdirectory(colour_picker)
add_subdirectory(encoder)
add_subdirectory(gpio_pwm)
add_subdirectory(interrupt)
add_subdirectory(led_rainbow)
add_subdirectory(stop_watch)

View File

@ -0,0 +1,77 @@
# RGB Encoder Wheel Breakout Examples (C++) <!-- omit in toc -->
- [Function Examples](#function-examples)
- [Buttons](#buttons)
- [Encoder](#encoder)
- [Interrupt](#interrupt)
- [LED Examples](#led-examples)
- [LED Rainbow](#led-rainbow)
- [Clock](#clock)
- [Interactive Examples](#interactive-examples)
- [Colour Picker](#colour-picker)
- [Stop Watch](#stop-watch)
- [Chase Game](#chase-game)
- [GPIO Examples](#gpio-examples)
- [GPIO PWM](#gpio-pwm)
## Function Examples
### Buttons
[buttons/buttons.cpp](buttons/buttons.cpp)
A demonstration of reading the 5 buttons on Encoder Wheel.
### Encoder
[encoder/encoder.cpp](encoder/encoder.cpp)
A demonstration of reading the rotary dial of the Encoder Wheel breakout.
### Interrupt
[interrupt/interrupt.cpp](interrupt/interrupt.cpp)
How to read the buttons and rotary dial of the Encoder Wheel breakout, only when an interrupt occurs.
## LED Examples
### LED Rainbow
[led_rainbow/led_rainbow.cpp](led_rainbow/led_rainbow.cpp)
Displays a rotating rainbow pattern on Encoder Wheel's LED ring.
### Clock
[clock/clock.cpp](clock/clock.cpp)
Displays a 12 hour clock on Encoder Wheel's LED ring, getting time from the system.
## Interactive Examples
### Colour Picker
[colour_picker/colour_picker.cpp](colour_picker/colour_picker.cpp)
Create a colour wheel on the Encoder Wheel's LED ring, and use all functions of the wheel to interact with it.
### Stop Watch
[stop_watch/stop_watch.cpp](stop_watch/stop_watch.cpp)
Display a circular stop-watch on the Encoder Wheel's LED ring.
### Chase Game
[chase_game/chase_game.cpp](chase_game/chase_game.cpp)
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.
## GPIO Examples
### GPIO PWM
[gpio_pwm/gpio_pwm.cpp](gpio_pwm/gpio_pwm.cpp)
Output a sine wave PWM sequence on the Encoder Wheel's side GPIO pins.

View File

@ -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})

View File

@ -0,0 +1,96 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -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})

View File

@ -0,0 +1,148 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -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})

View File

@ -0,0 +1,114 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -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})

View File

@ -0,0 +1,168 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -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})

View File

@ -0,0 +1,60 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -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})

View File

@ -0,0 +1,74 @@
#include <math.h>
#include <string>
#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;
}

View File

@ -0,0 +1,13 @@
set(OUTPUT_NAME encoderwheel_interrupt)
add_executable(${OUTPUT_NAME} interrupt.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})

View File

@ -0,0 +1,84 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#include "breakout_encoder_wheel.hpp"
#include "time.h"
using namespace pimoroni;
using namespace encoderwheel;
/*
How to read the buttons and rotary dial of the Encoder Wheel breakout, only when an interrupt occurs.
*/
// Constants
const std::string BUTTON_NAMES[] = {"Up", "Down", "Left", "Right", "Centre"};
// Create a new BreakoutEncoderWheel
I2C i2c(BOARD::BREAKOUT_GARDEN);
BreakoutEncoderWheel wheel(&i2c,
BreakoutEncoderWheel::DEFAULT_IOE_I2C_ADDRESS,
BreakoutEncoderWheel::DEFAULT_LED_I2C_ADDRESS,
3); // 3 for BG_BASE, 22 for EXPLORER_BASE, or 19 for some RP2040 boards
// If wiring the breakout via the qw/st connector, use the below line instead
// BreakoutEncoderWheel wheel(&i2c);
// Variables
bool last_pressed[NUM_BUTTONS] = {false, false, false, false, false};
bool pressed[NUM_BUTTONS] = {false, false, false, false, false};
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();
// Clear any left over interrupt from previous code
wheel.clear_interrupt_flag();
// Loop forever
while(true) {
// Check if the interrupt has fired
if(wheel.get_interrupt_flag()) {
wheel.clear_interrupt_flag();
// 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];
}
// The interrupt may have come from several sources,
// so check if it was a position change
int new_position = wheel.step();
if(new_position != position) {
// Record the new position (from 0 to 23)
position = new_position;
printf("Position = %d\n", position);
// 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;
}

View File

@ -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})

View File

@ -0,0 +1,54 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -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})

View File

@ -0,0 +1,151 @@
#include <math.h>
#include <string>
#include "pimoroni_i2c.hpp"
#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;
}

View File

@ -2,6 +2,7 @@ add_subdirectory(hershey_fonts)
add_subdirectory(bitmap_fonts)
add_subdirectory(breakout_dotmatrix)
add_subdirectory(breakout_encoder)
add_subdirectory(breakout_encoder_wheel)
add_subdirectory(breakout_ioexpander)
add_subdirectory(breakout_ltr559)
add_subdirectory(breakout_rgbmatrix5x5)

View File

@ -0,0 +1 @@
include(breakout_encoder_wheel.cmake)

View File

@ -0,0 +1,334 @@
# RGB Encoder Wheel Breakout (C++) <!-- omit in toc -->
This is the C++ library reference for the [Pimoroni RGB Encoder Wheel Breakout](https://shop.pimoroni.com/products/rgb-encoder-wheel-breakout).
## Table of Content <!-- omit in toc -->
- [Getting Started](#getting-started)
- [Reading the Buttons](#reading-the-buttons)
- [Reading the Encoder](#reading-the-encoder)
- [Count and Angle](#count-and-angle)
- [Count Delta](#count-delta)
- [Step and Turn](#step-and-turn)
- [Changing the Direction](#changing-the-direction)
- [Resetting to Zero](#resetting-to-zero)
- [LEDs](#leds)
- [Setting an LED](#setting-an-led)
- [RGB](#rgb)
- [HSV](#hsv)
- [Clear all LEDs](#clear-all-leds)
- [Showing](#showing)
- [GPIOs](#gpios)
- [Setup](#setup)
- [Mode](#mode)
- [As Input or ADC](#as-input-or-adc)
- [As Output](#as-output)
- [As PWM](#as-pwm)
- [Delayed Loading](#delayed-loading)
- [Limitations](#limitations)
- [Function Reference](#function-reference)
- [Constants Reference](#constants-reference)
- [Address Constants](#address-constants)
- [Value Constants](#value-constants)
- [Button Constants](#button-constants)
- [GPIO Constants](#gpio-constants)
- [Count Constants](#count-constants)
## Getting Started
To start coding for your Encoder Wheel breakout, you will first need to create an object for accessing the I2C bus that the breakout is connected to. The easiest way to do this is via the `PimoroniI2C` class, with one of the handy pin constants from `pimoroni`, like so:
```c++
#include "pimoroni_i2c.hpp"
using namespace pimoroni;
I2C i2c(BOARD::BREAKOUT_GARDEN);
```
This creates a `i2c` object that can be passed into the Encoder Wheel's class as part of its creation:
```c++
#include "breakout_encoder_wheel.hpp"
using namespace encoderwheel;
BreakoutEncoderWheel wheel(&i2c);
```
The above lines of code import the `BreakoutEncoderWheel` class and create an instance of it, called `wheel`. This will be used in the rest of the examples going forward.
## Reading the Buttons
EncoderWheel has five buttons, covering up, down, left, right, and centre. These can be read using the `.pressed(button)` function, which accepts a button number between `0` and `4`. For convenience, each button can be referred to using these constants:
* `UP` = `0`
* `DOWN` = `1`
* `LEFT` = `2`
* `RIGHT` = `3`
* `CENTRE` = `4`
For example, to read the centre button you would write:
```c++
bool centre_state = wheel.pressed(CENTRE);
```
You can also get the number of buttons using the `NUM_BUTTONS` constant.
## Reading the Encoder
Within the directional buttons of the Encoder Wheel breakout is a rotary encoder with 24 counts per revolution.
### Count and Angle
The current count can be read by calling `.count()`. It can also be read back as either the number of `.revolutions()` of the encoder, or the angle in `.degrees()` or `.radians()`.
Be aware that the count is stored as an integer, if it is continually increased or decreased it will eventually wrap at `+32767` and `-32768`. This will cause a jump in the returned by `.revolutions()`, `degrees()` and `.radians()`, that will need handling by your code.
### Count Delta
Often you are not interested in the exact count that the encoder is at, but rather if the count has changed since the last time you checked. This change can be read by calling `.delta()` at regular intervals. The returned value can then be used with a check in code, for the value being non-zero.
### Step and Turn
Sometimes it can be useful to know the encoder's position in the form of which step it is at and how many turns have occurred. The current step can be read by calling `.step()`, which returns a value from `0` to `23`, and the number of turns can be read by calling `.turn()`.
These functions differ from reading the `.count()` or `.revolutions()` by using separate counters, and so avoid the wrapping issue that these functions experience.
### Changing the Direction
The counting direction of an encoder can be changed by calling `.direction(REVERSED_DIR)` at any time in code. The `REVERSED_DIR` constant comes from `pimoroni_common.hpp`. There is also a `NORMAL_DIR` constant, though this is the default.
### Resetting to Zero
There are times where an encoder's count (and related values) need to be reset back to zero. This can be done by calling `.zero()`.
## LEDs
The Encoder Wheel breakout features 24 RGB LEDs arranged in a ring around the wheel. This is the same number as there are steps on the wheel, letting you use the LEDs to show the current step of the wheel.
### Setting an LED
You can set the colour of a LED on the ring in either the RGB colourspace, or HSV (Hue, Saturation, Value). HSV is useful for creating rainbow patterns.
#### RGB
Set the first LED - `0` - to Purple `255, 0, 255`:
```c++
wheel.set_rgb(0, 255, 0, 255);
```
#### HSV
Set the first LED - `0` - to Red `0.0`:
```c++
wheel.set_hsv(0, 0.0f, 1.0f, 1.0f);
```
### Clear all LEDs
To turn off all the LEDs, the function `.clear()` can be called. This simply goes through each LED and sets its RGB colour to black, making them emit no light.
This function is useful to have at the end of your code to turn the lights off, otherwise they will continue to show the last colours they were given.
### Showing
Changes to the LEDs do not get applied immediately, due to the amount of I2C communication involved. As such, to have the LEDs show what they have been set to after calling the `.set_rgb()`, `.set_hsv()`, and `.clear()` functions, a specific call to `.show()` needs to be made.
## GPIOs
There are three spare GPIO pins on the edge of Encoder Wheel. These can be used as digital outputs, pwm outputs, digital inputs, and analog inputs.
### Setup
To start using a GPIO pin, one of the handy constants from the `encoderwheel` namespace can be used to reference them (see [GPIO Constants](#gpio-constants)).
Then you need to import the constants for the pin mode to use. These are on the `IOExpander` class that Encoder Wheel is based on.
```c++
#import "breakout_ioexpander.hpp"
using namespace pimoroni;
// For input
IOExpander::PIN_IN; // or PIN_IN_PU of a pull-up is wanted
// For output
IOExpander::PIN_OUT;
// For PWM
IOExpander::PIN_PWM;
// For ADC
IOExpander::PIN_ADC;
```
### Mode
With the intended constants imported, the mode of a GPIO pin can be set by calling `.gpio_pin_mode(gpio, mode)`:
```c++
wheel.gpio_pin_mode(GP7, IOExpander::PIN_<IN or IN_PU or OUT or PWM or ADC>);
```
It is also possible to read the current mode of a GPIO pin by calling `.gpio_pin_mode(gpio)`:
```c++
mode = wheel.gpio_pin_mode(GP7);
```
### As Input or ADC
The current value of an GPIO pin in input or ADC mode can be read by calling `.gpio_pin_value(gpio)`:
```c++
value = wheel.gpio_pin_value(GP7);
```
If the mode is digital, the value will either be `0` or `1`.
If the mode is analog, the value will be a voltage from `0.0` to `3.3`.
### As Output
The current value of a GPIO pin in output mode can be set by calling `.gpio_pin_value(gpio, value)`:
```c++
wheel.gpio_pin_value(GP7, value);
```
The expected value is either `0` or `1`, or `True` or `False`.
### As PWM
The GPIO pins can also be set as PWM outputs. The `PIN_PWM` constant can be accessed from the `IOExpander` class, and passed into the `.gpio_pin_mode()` function.
The frequency of the PWM signal can then be configured by calling `.gpio_pwm_frequency()`, which accepts a frequency (in Hz). It returns the cycle period, which should be used to set duty cycles.
Finally, the duty cycle of the PWM signal can be set by calling `.gpio_pin_value()` and providing it with a value between `0` and the cycle period.
Below is an example of setting a gpio pin to output a 25KHz signal with a 50% duty cycle:
```c++
#include "pimoroni_i2c.hpp"
#include "breakout_encoder_wheel.hpp"
using namespace pimoroni;
using namespace encoderwheel;
// Initialise EncoderWheel
I2C i2c(BOARD::BREAKOUT_GARDEN);
BreakoutEncoderWheel wheel(&i2c);
// Setup the gpio pin as a PWM output
wheel.gpio_pin_mode(GP7, IOExpander::PIN_PWM);
// Set the gpio pin's frequency to 25KHz, and record the cycle period
uint16_t period = wheel.gpio_pwm_frequency(25000.0f);
// Output a 50% duty cycle square wave
wheel.gpio_pin_value(GP7, (int)(period * 0.5f));
```
#### Delayed Loading
By default, changes to a gpio pin's frequency or value are applied immediately. However, sometimes this may not be wanted, and instead you want all pins to receive updated parameters at the same time, regardless of how long the code ran that calculated the update.
For this purpose, `.gpio_pwm_frequency()` and `.gpio_pin_value()` include an optional parameter `load`, which by default is `true`. To avoid this "loading" set the parameter to `false`. Then either the last call can include a `true`, or a specific call to `.gpio_pwm_load()` can be made.
In addition, any function that performs a load, including the `.gpio_pwm_load()` function, can be made to wait until the new PWM value has been sent out of the pins. By default this is disabled, but can be enabled by including setting the `wait_for_load` parameter to `true` in the relevant function calls.
#### Limitations
All of Encoder Wheel's PWM outputs share the same timing parameters. This means that GP7, GP8, and GP9 share the same frequency. Keep this in mind if changing the frequency of one, as the others will not automatically know about the change, resulting in unexpected duty cycle outputs.
## Function Reference
Here is the complete list of functions available on the `BreakoutEncoderWheel` class:
```c++
BreakoutEncoderWheel(uint8_t ioe_address = DEFAULT_IOE_I2C_ADDRESS, uint8_t led_address = DEFAULT_LED_I2C_ADDRESS);
BreakoutEncoderWheel(I2C *i2c, uint8_t ioe_address = 0x13, uint8_t led_address = 0x77, uint interrupt = PIN_UNUSED, uint32_t timeout = 1, bool debug = false);
bool init(bool skip_chip_id_check = false);
void set_ioe_address(uint8_t address);
bool get_interrupt_flag();
void clear_interrupt_flag();
bool pressed(uint button);
int16_t count();
int16_t delta();
void zero();
int16_t step();
int16_t turn();
float revolutions();
float degrees();
float radians();
Direction direction();
void direction(Direction direction);
void set_rgb(int index, int r, int g, int b);
void set_hsv(int index, float h, float s = 1.0f, float v = 1.0f);
void clear();
void show();
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);
uint16_t gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false);
```
## Constants Reference
Here is the complete list of public constants on the `BreakoutEncoderWheel` class:
### Address Constants
* `DEFAULT_IOE_I2C_ADDR` = `0x13`
* `DEFAULT_LED_I2C_ADDR` = `0x77`
* `ALTERNATE_LED_I2C_ADDR` = `0x74`
### Value Constants
* `DEFAULT_DIRECTION` = `NORMAL_DIR`
* `DEFAULT_TIMEOUT` = `1`
Here is the complete list of public constants in the `encoderwheel` namespace:
### Button Constants
* `UP` = `0`
* `DOWN` = `1`
* `LEFT` = `2`
* `RIGHT` = `3`
* `CENTRE` = `4`
### GPIO Constants
* `GP7` = `7`
* `GP8` = `8`
* `GP9` = `9`
* `GPIOS` = (`7`, `8`, `9`)
### Count Constants
* `NUM_LEDS` = `24`
* `NUM_BUTTONS` = `5`
* `NUM_GPIOS` = `5`

View File

@ -0,0 +1,11 @@
set(LIB_NAME breakout_encoder_wheel)
add_library(${LIB_NAME} INTERFACE)
target_sources(${LIB_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp
)
target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib ioexpander is31fl3731)

View File

@ -0,0 +1,260 @@
#include "breakout_encoder_wheel.hpp"
#include <algorithm>
#include <cmath>
namespace pimoroni {
namespace encoderwheel {
bool BreakoutEncoderWheel::init(bool skip_chip_id_check) {
bool success = false;
if(ioe.init(skip_chip_id_check, true) && led_ring.init()) {
ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 0, true); // count microsteps
ioe.set_mode(SW_UP, IOExpander::PIN_IN_PU);
ioe.set_mode(SW_DOWN, IOExpander::PIN_IN_PU);
ioe.set_mode(SW_LEFT, IOExpander::PIN_IN_PU);
ioe.set_mode(SW_RIGHT, IOExpander::PIN_IN_PU);
ioe.set_mode(SW_CENTRE, IOExpander::PIN_IN_PU);
ioe.set_pin_interrupt(SW_UP, true);
ioe.set_pin_interrupt(SW_DOWN, true);
ioe.set_pin_interrupt(SW_LEFT, true);
ioe.set_pin_interrupt(SW_RIGHT, true);
ioe.set_pin_interrupt(SW_CENTRE, true);
led_ring.enable({
0b00000000, 0b10111111,
0b00111110, 0b00111110,
0b00111111, 0b10111110,
0b00000111, 0b10000110,
0b00110000, 0b00110000,
0b00111111, 0b10111110,
0b00111111, 0b10111110,
0b01111111, 0b11111110,
0b01111111, 0b00000000
}, 0);
success = true;
}
return success;
}
i2c_inst_t* BreakoutEncoderWheel::get_i2c() const {
return ioe.get_i2c();
}
int BreakoutEncoderWheel::get_ioe_address() const {
return ioe.get_address();
}
int BreakoutEncoderWheel::get_led_address() const {
return led_ring.get_address();
}
int BreakoutEncoderWheel::get_sda() const {
return ioe.get_sda();
}
int BreakoutEncoderWheel::get_scl() const {
return ioe.get_scl();
}
int BreakoutEncoderWheel::get_int() const {
return ioe.get_int();
}
void BreakoutEncoderWheel::set_ioe_address(uint8_t address) {
ioe.set_address(address);
}
bool BreakoutEncoderWheel::get_interrupt_flag() {
return ioe.get_interrupt_flag();
}
void BreakoutEncoderWheel::clear_interrupt_flag() {
ioe.clear_interrupt_flag();
}
bool BreakoutEncoderWheel::pressed(uint button) {
switch(button) {
case 0:
return ioe.input(SW_UP) == 0;
case 1:
return ioe.input(SW_DOWN) == 0;
case 2:
return ioe.input(SW_LEFT) == 0;
case 3:
return ioe.input(SW_RIGHT) == 0;
case 4:
return ioe.input(SW_CENTRE) == 0;
default:
return false;
}
}
int16_t BreakoutEncoderWheel::count() {
take_encoder_reading();
return enc_count;
}
int16_t BreakoutEncoderWheel::delta() {
take_encoder_reading();
// Determine the change in counts since the last time this function was performed
int16_t change = enc_count - last_delta_count;
last_delta_count = enc_count;
return change;
}
void BreakoutEncoderWheel::zero() {
ioe.clear_rotary_encoder(ENC_CHANNEL);
enc_count = 0;
enc_step = 0;
enc_turn = 0;
last_raw_count = 0;
last_delta_count = 0;
}
int16_t BreakoutEncoderWheel::step() {
take_encoder_reading();
return enc_step;
}
int16_t BreakoutEncoderWheel::turn() {
take_encoder_reading();
return enc_turn;
}
float BreakoutEncoderWheel::revolutions() {
return (float)count() / (float)ENC_COUNTS_PER_REV;
}
float BreakoutEncoderWheel::degrees() {
return revolutions() * 360.0f;
}
float BreakoutEncoderWheel::radians() {
return revolutions() * M_PI * 2.0f;
}
Direction BreakoutEncoderWheel::direction() {
return enc_direction;
}
void BreakoutEncoderWheel::direction(Direction direction) {
enc_direction = direction;
}
void BreakoutEncoderWheel::set_rgb(int index, int r, int g, int b) {
RGBLookup rgb = lookup_table[index];
led_ring.set(rgb.r, r);
led_ring.set(rgb.g, g);
led_ring.set(rgb.b, b);
}
void BreakoutEncoderWheel::set_hsv(int index, float h, float s, float v) {
int r, g, b;
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: r = bv; g = t; b = p; break;
case 1: r = q; g = bv; b = p; break;
case 2: r = p; g = bv; b = t; break;
case 3: r = p; g = q; b = bv; break;
case 4: r = t; g = p; b = bv; break;
case 5: r = bv; g = p; b = q; break;
}
set_rgb(index, r, g, b);
}
void BreakoutEncoderWheel::clear() {
led_ring.clear();
}
void BreakoutEncoderWheel::show() {
led_ring.update();
}
uint8_t BreakoutEncoderWheel::gpio_pin_mode(uint8_t gpio) {
assert(gpio < GP7 || gpio > GP9);
return ioe.get_mode(gpio);
}
void BreakoutEncoderWheel::gpio_pin_mode(uint8_t gpio, uint8_t mode) {
assert(gpio < GP7 || gpio > GP9);
ioe.set_mode(gpio, mode);
}
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(uint8_t gpio) {
assert(gpio < GP7 || gpio > GP9);
return ioe.input_as_voltage(gpio);
}
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);
}
uint16_t BreakoutEncoderWheel::gpio_pwm_frequency(float frequency, bool load, bool wait_for_load) {
return ioe.set_pwm_frequency(frequency, load, wait_for_load);
}
void BreakoutEncoderWheel::take_encoder_reading() {
// Read the current count
int16_t raw_count = ioe.read_rotary_encoder(ENC_CHANNEL) / ENC_COUNT_DIVIDER;
int16_t raw_change = raw_count - last_raw_count;
last_raw_count = raw_count;
// Invert the change
if(enc_direction == REVERSED_DIR) {
raw_change = 0 - raw_change;
}
enc_count += raw_change;
enc_step += raw_change;
if(raw_change > 0) {
while(enc_step >= ENC_COUNTS_PER_REV) {
enc_step -= ENC_COUNTS_PER_REV;
enc_turn += 1;
}
}
else if(raw_change < 0) {
while(enc_step < 0) {
enc_step += ENC_COUNTS_PER_REV;
enc_turn -= 1;
}
}
}
}
}

View File

@ -0,0 +1,161 @@
#pragma once
#include "drivers/ioexpander/ioexpander.hpp"
#include "drivers/is31fl3731/is31fl3731.hpp"
#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 {
uint8_t r;
uint8_t g;
uint8_t b;
};
//--------------------------------------------------
// Constants
//--------------------------------------------------
public:
static const uint8_t DEFAULT_IOE_I2C_ADDRESS = 0x13;
static const uint8_t DEFAULT_LED_I2C_ADDRESS = 0x77;
static const uint8_t ALTERNATE_LED_I2C_ADDRESS = 0x74;
static const Direction DEFAULT_DIRECTION = NORMAL_DIR;
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 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
// their specific location in the 144 pixel buffer.
static constexpr RGBLookup lookup_table[24] = {
{128, 32, 48},
{129, 33, 49},
{130, 17, 50},
{131, 18, 34},
{132, 19, 35},
{133, 20, 36},
{134, 21, 37},
{112, 80, 96},
{113, 81, 97},
{114, 82, 98},
{115, 83, 99},
{116, 84, 100},
{117, 68, 101},
{118, 69, 85},
{127, 47, 63},
{121, 41, 57},
{122, 25, 58},
{123, 26, 42},
{124, 27, 43},
{125, 28, 44},
{126, 29, 45},
{15, 95, 111},
{8, 89, 105},
{9, 90, 106},
};
//--------------------------------------------------
// Variables
//--------------------------------------------------
private:
IOExpander ioe;
IS31FL3731 led_ring;
Direction enc_direction = DEFAULT_DIRECTION;
int16_t enc_count = 0;
int16_t enc_step = 0;
int16_t enc_turn = 0;
int16_t last_raw_count = 0;
int16_t last_delta_count = 0;
//--------------------------------------------------
// Constructors/Destructor
//--------------------------------------------------
public:
BreakoutEncoderWheel(uint8_t ioe_address = DEFAULT_IOE_I2C_ADDRESS, uint8_t led_address = DEFAULT_LED_I2C_ADDRESS)
: BreakoutEncoderWheel(new I2C(), ioe_address, led_address) {}
BreakoutEncoderWheel(I2C *i2c, uint8_t ioe_address = DEFAULT_IOE_I2C_ADDRESS, uint8_t led_address = DEFAULT_LED_I2C_ADDRESS, uint interrupt = PIN_UNUSED, uint32_t timeout = DEFAULT_TIMEOUT, bool debug = false)
: ioe(i2c, ioe_address, interrupt, timeout, debug), led_ring(i2c, led_address) {}
//--------------------------------------------------
// Methods
//--------------------------------------------------
public:
bool init(bool skip_chip_id_check = false);
// For print access in micropython
i2c_inst_t* get_i2c() const;
int get_ioe_address() const;
int get_led_address() const;
int get_sda() const;
int get_scl() const;
int get_int() const;
// Calls through to IOExpander class
void set_ioe_address(uint8_t address);
bool get_interrupt_flag();
void clear_interrupt_flag();
// Encoder breakout specific
bool pressed(uint button);
int16_t count();
int16_t delta();
void zero();
int16_t step();
int16_t turn();
float revolutions();
float degrees();
float radians();
Direction direction();
void direction(Direction direction);
void set_rgb(int index, int r, int g, int b);
void set_hsv(int index, float h, float s = 1.0f, float v = 1.0f);
void clear();
void show();
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);
uint16_t gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false);
private:
void take_encoder_reading();
};
}
}

View File

@ -0,0 +1,77 @@
# RGB Encoder Wheel Breakout Examples (Micropython) <!-- omit in toc -->
- [Function Examples](#function-examples)
- [Buttons](#buttons)
- [Encoder](#encoder)
- [Interrupt](#interrupt)
- [LED Examples](#led-examples)
- [LED Rainbow](#led-rainbow)
- [Clock](#clock)
- [Interactive Examples](#interactive-examples)
- [Colour Picker](#colour-picker)
- [Stop Watch](#stop-watch)
- [Chase Game](#chase-game)
- [GPIO Examples](#gpio-examples)
- [GPIO PWM](#gpio-pwm)
## Function Examples
### Buttons
[buttons.py](buttons.py)
A demonstration of reading the 5 buttons on Encoder Wheel.
### Encoder
[encoder.py](encoder.py)
A demonstration of reading the rotary dial of the Encoder Wheel breakout.
### Interrupt
[interrupt.py](interrupt.py)
How to read the buttons and rotary dial of the Encoder Wheel breakout, only when an interrupt occurs.
## LED Examples
### LED Rainbow
[led_rainbow.py](led_rainbow.py)
Displays a rotating rainbow pattern on Encoder Wheel's LED ring.
### Clock
[clock.py](clock.py)
Displays a 12 hour clock on Encoder Wheel's LED ring, getting time from the system.
## Interactive Examples
### Colour Picker
[colour_picker.py](colour_picker.py)
Create a colour wheel on the Encoder Wheel's LED ring, and use all functions of the wheel to interact with it.
### Stop Watch
[stop_watch.py](stop_watch.py)
Display a circular stop-watch on the Encoder Wheel's LED ring.
### Chase Game
[chase_game.py](chase_game.py)
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.
## GPIO Examples
### GPIO PWM
[gpio_pwm.py](gpio_pwm.py)
Output a sine wave PWM sequence on the Encoder Wheel's side GPIO pins.

View File

@ -0,0 +1,68 @@
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, UP, DOWN, LEFT, RIGHT, CENTRE, NUM_BUTTONS, NUM_LEDS
"""
A demonstration of reading the 5 buttons on Encoder Wheel.
Press Ctrl+C to stop the program.
"""
# Constants
BUTTON_NAMES = ["Up", "Down", "Left", "Right", "Centre"]
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Variables
last_pressed = [False] * NUM_BUTTONS
pressed = [False] * NUM_BUTTONS
# Loop forever
while True:
# Read all of the encoder wheel's buttons
for b in range(NUM_BUTTONS):
pressed[b] = wheel.pressed(b)
if pressed[b] != last_pressed[b]:
print(BUTTON_NAMES[b], "Pressed" if pressed[b] else "Released")
last_pressed[b] = pressed[b]
# Clear the LED ring
wheel.clear()
for i in range(NUM_LEDS):
if i % 6 == 3:
wheel.set_rgb(i, 64, 64, 64)
# If up is pressed, set the top LEDs to yellow
if pressed[UP]:
mid = NUM_LEDS
for i in range(mid - 2, mid + 3):
wheel.set_rgb(i % NUM_LEDS, 255, 255, 0)
# If right is pressed, set the right LEDs to red
if pressed[RIGHT]:
mid = NUM_LEDS // 4
for i in range(mid - 2, mid + 3):
wheel.set_rgb(i % NUM_LEDS, 255, 0, 0)
# If down is pressed, set the bottom LEDs to green
if pressed[DOWN]:
mid = NUM_LEDS // 2
for i in range(mid - 2, mid + 3):
wheel.set_rgb(i % NUM_LEDS, 0, 255, 0)
# If left is pressed, set the left LEDs to blue
if pressed[LEFT]:
mid = (NUM_LEDS * 3) // 4
for i in range(mid - 2, mid + 3):
wheel.set_rgb(i % NUM_LEDS, 0, 0, 255)
# If centre is pressed, set the diagonal LEDs to half white
if pressed[CENTRE]:
for i in range(NUM_LEDS):
if i % 6 >= 2 and i % 6 <= 4:
wheel.set_rgb(i, 128, 128, 128)
wheel.show()

View File

@ -0,0 +1,122 @@
import random
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, NUM_LEDS
"""
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.
Press Ctrl+C to stop the program.
"""
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# The band colour hues to show in Angle mode
GOAL_HUE = 0.333
FAR_HUE = 0.0
# The width and colour settings for the band
BAND_WIDTH = 5.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_MARGIN = 1
GOAL_WIDTH = BAND_WIDTH + (2 * GOAL_MARGIN)
GOAL_BRIGHTNESS = 0.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
# 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_start = centre_position - (width / 2)
band_end = centre_position + (width / 2)
band_centre = centre_position
goal_start = goal_position - (goal_width / 2)
goal_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_start and i < goal_end:
saturation = BAND_IN_GOAL_SATURATION
brightness = GOAL_BRIGHTNESS
if goal_end >= NUM_LEDS and i + NUM_LEDS < goal_end:
saturation = BAND_IN_GOAL_SATURATION
brightness = GOAL_BRIGHTNESS
if goal_start < 0 and i - NUM_LEDS >= goal_start:
saturation = BAND_IN_GOAL_SATURATION
brightness = GOAL_BRIGHTNESS
if i >= band_start and 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)
wheel.set_hsv(i, hue, sat, val)
elif band_end >= NUM_LEDS and i + NUM_LEDS < band_end and 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)
wheel.set_hsv(i, hue, sat, val)
elif band_start < 0 and i - NUM_LEDS >= band_start and 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)
wheel.set_hsv(i, hue, sat, val)
else:
# Outside of the band
wheel.set_hsv(i, hue, 0.0, brightness)
wheel.show()
goal_position = 0.0
band_position = 0
while True:
band_position = wheel.step()
# Convert the difference between the band and goal positions into a colour hue
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
position_diff = min(diff1, diff2)
hue = map(position_diff, 0, NUM_LEDS // 2, GOAL_HUE, FAR_HUE)
# Convert the band and goal positions to positions on the LED strip
strip_band_position = map(band_position, 0, NUM_LEDS, 0.0, float(NUM_LEDS))
strip_goal_position = map(goal_position, 0, NUM_LEDS, 0.0, 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 and band_position <= goal_position + GOAL_MARGIN:
goal_position = random.randint(0, NUM_LEDS - 1)
if band_position >= NUM_LEDS and band_position + NUM_LEDS < goal_position + GOAL_MARGIN:
goal_position = random.randint(0, NUM_LEDS - 1)
if goal_position - GOAL_MARGIN < 0 and band_position - NUM_LEDS >= goal_position + GOAL_MARGIN:
goal_position = random.randint(0, NUM_LEDS - 1)

View File

@ -0,0 +1,97 @@
import time
from machine import RTC
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, NUM_LEDS
"""
Displays a 12 hour clock on Encoder Wheel's LED ring, getting time from the system.
Press Ctrl+C to stop the program.
"""
# Datetime Indices
HOUR = 4
MINUTE = 5
SECOND = 6
# Constants
BRIGHTNESS = 1.0 # The brightness of the LEDs
UPDATES = 50 # How many times the LEDs will be updated per second
UPDATE_RATE_US = 1000000 // UPDATES
# Handy values for the number of milliseconds
MILLIS_PER_SECOND = 1000
MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60
MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60
MILLIS_PER_HALF_DAY = MILLIS_PER_HOUR * 12
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Access the built-in RTC
rtc = RTC()
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
# inconsistent timings when dealing with complex operations or external communication
def sleep_until(end_time):
time_to_sleep = time.ticks_diff(end_time, time.ticks_us())
if time_to_sleep > 0:
time.sleep_us(time_to_sleep)
# Simple function to clamp a value between a minimum and maximum
def clamp(n, smallest, largest):
return max(smallest, min(n, largest))
# Calculates the brightness of an LED based on its index and a position along the LED ring
def led_brightness_at(led, position, half_width=1, span=1):
brightness = 0.0
upper = position + half_width
lower = position - half_width
if led > position:
brightness = clamp((upper - led) / span, 0.0, 1.0)
else:
brightness = clamp((led - lower) / span, 0.0, 1.0)
# Handle the LEDs being in a circle
if upper >= NUM_LEDS:
brightness = clamp(((upper - NUM_LEDS) - led) / span, brightness, 1.0)
elif lower < 0.0:
brightness = clamp((led - (lower + NUM_LEDS)) / span, brightness, 1.0)
return int(brightness * BRIGHTNESS * 255)
# Make rainbows
while True:
# Record the start time of this loop
start_time = time.ticks_us()
# Get the current system time
now = rtc.datetime()
# Convert the seconds, minutes, and hours into milliseconds (this is done to give a smoother animation, particularly for the seconds hand)
sec_as_millis = (now[SECOND] * MILLIS_PER_SECOND)
min_as_millis = (now[MINUTE] * MILLIS_PER_MINUTE) + sec_as_millis
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
sec_pos = min(sec_as_millis / MILLIS_PER_MINUTE, 1.0) * NUM_LEDS
min_pos = min(min_as_millis / MILLIS_PER_HOUR, 1.0) * NUM_LEDS
hour_pos = min(hour_as_millis / MILLIS_PER_HALF_DAY, 1.0) * NUM_LEDS
for i in range(NUM_LEDS):
# Turn on the LEDs close to the position of the current second, minute, and hour
r = led_brightness_at(i, sec_pos)
g = led_brightness_at(i, min_pos)
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(time.ticks_add(start_time, UPDATE_RATE_US))

View File

@ -0,0 +1,138 @@
import time
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, UP, DOWN, LEFT, RIGHT, CENTRE, NUM_LEDS
"""
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
Press Ctrl+C to stop the program.
"""
# Constants
BRIGHTNESS_STEP = 0.02 # How much to increase or decrease the brightness each update
SATURATION_STEP = 0.02 # How much to increase or decrease the saturation each update
UPDATES = 50 # How many times to update the LEDs per second
UPDATE_RATE_US = 1000000 // UPDATES
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Variables
brightness = 1.0
saturation = 1.0
position = 0
changed = True
last_centre_pressed = False
# From CPython Lib/colorsys.py
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h * 6.0)
f = (h * 6.0) - i
p = v * (1.0 - s)
q = v * (1.0 - s * f)
t = v * (1.0 - s * (1.0 - f))
i = i % 6
if i == 0:
return v, t, p
if i == 1:
return q, v, p
if i == 2:
return p, v, t
if i == 3:
return p, q, v
if i == 4:
return t, p, v
if i == 5:
return v, p, q
# Simple function to clamp a value between 0.0 and 1.0
def clamp01(value):
return max(min(value, 1.0), 0.0)
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
# inconsistent timings when dealing with complex operations or external communication
def sleep_until(end_time):
time_to_sleep = time.ticks_diff(end_time, time.ticks_us())
if time_to_sleep > 0:
time.sleep_us(time_to_sleep)
while True:
# Record the start time of this loop
start_time = time.ticks_us()
# 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
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
r, g, b = [int(c * 255) for c in hsv_to_rgb(position / NUM_LEDS, saturation, brightness)]
print("Colour Code = #", '{:02x}'.format(r), '{:02x}'.format(g), '{:02x}'.format(b), sep="")
# 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, r, g, b)
else:
wheel.set_rgb(position, 255 - r, 255 - g, 255 - b)
# Set the LEDs below the current position
for i in range(0, position):
wheel.set_hsv(i, i / NUM_LEDS, saturation, brightness)
# Set the LEDs after the current position
for i in range(position + 1, NUM_LEDS):
wheel.set_hsv(i, 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(time.ticks_add(start_time, UPDATE_RATE_US))

View File

@ -0,0 +1,45 @@
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel
"""
A demonstration of reading the rotary dial of the Encoder Wheel breakout.
Press Ctrl+C to stop the program.
"""
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Variables
position = 0
hue = 0.0
# Set the first LED
wheel.clear()
wheel.set_hsv(position, hue, 1.0, 1.0)
wheel.show()
# Loop forever
while True:
# Has the dial been turned since the last time we checked?
change = wheel.delta()
if change != 0:
# Print out the direction the dial was turned, and the count
if change > 0:
print("Clockwise, Count =", wheel.count())
else:
print("Counter Clockwise, Count =", wheel.count())
# Record the new position (from 0 to 23)
position = wheel.step()
# Record a colour hue from 0.0 to 1.0
hue = wheel.revolutions() % 1.0
# Set the LED at the new position to the new hue
wheel.clear()
wheel.set_hsv(position, hue, 1.0, 1.0)
wheel.show()

View File

@ -0,0 +1,67 @@
import math
import time
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, CENTRE, GPIOS, NUM_GPIOS
from breakout_ioexpander import PWM
"""
Output a sine wave PWM sequence on the Encoder Wheel's side GPIO pins.
Press the centre button or Ctrl+C to stop the program.
"""
# Constants
SPEED = 5 # The speed that the PWM will cycle at
UPDATES = 50 # How many times to update LEDs and Servos per second
UPDATE_RATE_US = 1000000 // UPDATES
FREQUENCY = 1000 # The frequency to run the PWM at
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Set the PWM frequency for the GPIOs
period = wheel.gpio_pwm_frequency(FREQUENCY)
# Set the GPIO pins to PWM outputs
for g in GPIOS:
wheel.gpio_pin_mode(g, PWM)
# Variables
offset = 0.0
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
# inconsistent timings when dealing with complex operations or external communication
def sleep_until(end_time):
time_to_sleep = time.ticks_diff(end_time, time.ticks_us())
if time_to_sleep > 0:
time.sleep_us(time_to_sleep)
# Make PWM waves until the centre button is pressed
while not wheel.pressed(CENTRE):
# Record the start time of this loop
start_time = time.ticks_us()
offset += SPEED / 1000.0
# Update all the PWMs
for i in range(NUM_GPIOS):
angle = ((i / NUM_GPIOS) + offset) * math.pi
duty = int(((math.sin(angle) / 2) + 0.5) * period)
# Set the GPIO pin to the new duty cycle, but do not load it yet
wheel.gpio_pin_value(GPIOS[i], duty, load=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(time.ticks_add(start_time, UPDATE_RATE_US))
# Turn off the PWM outputs
for g in GPIOS:
wheel.gpio_pin_value(g, 0)

View File

@ -0,0 +1,62 @@
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, NUM_BUTTONS
"""
How to read the buttons and rotary dial of the Encoder Wheel breakout, only when an interrupt occurs.
Press Ctrl+C to stop the program.
"""
# Constants
BUTTON_NAMES = ["Up", "Down", "Left", "Right", "Centre"]
# Create a new BreakoutEncoderWheel with a pin on the Pico specified as an interrupt
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c, interrupt=3) # 3 for BG_BASE, 22 for EXPLORER_BASE, or 19 for some RP2040 boards
# If wiring the breakout via the qw/st connector, use the below line instead
# wheel = BreakoutEncoderWheel(i2c)
# Variables
last_pressed = [False] * NUM_BUTTONS
pressed = [False] * NUM_BUTTONS
position = 0
hue = 0.0
# Set the first LED
wheel.clear()
wheel.set_hsv(position, hue, 1.0, 1.0)
wheel.show()
# Clear any left over interrupt from previous code
wheel.clear_interrupt_flag()
# Loop forever
while True:
# Check if the interrupt has fired
if wheel.get_interrupt_flag():
wheel.clear_interrupt_flag()
# Read all of the encoder wheel's buttons
for b in range(NUM_BUTTONS):
pressed[b] = wheel.pressed(b)
if pressed[b] != last_pressed[b]:
print(BUTTON_NAMES[b], "Pressed" if pressed[b] else "Released")
last_pressed[b] = pressed[b]
# The interrupt may have come from several sources,
# so check if it was a position change
new_position = wheel.step()
if new_position != position:
# Record the new position (from 0 to 23)
position = new_position
print("Position = ", position)
# Record a colour hue from 0.0 to 1.0
hue = wheel.revolutions() % 1.0
# Set the LED at the new position to the new hue
wheel.clear()
wheel.set_hsv(position, hue, 1.0, 1.0)
wheel.show()

View File

@ -0,0 +1,49 @@
import time
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, NUM_LEDS
"""
Displays a rotating rainbow pattern on Encoder Wheel's LED ring.
Press Ctrl+C to stop the program.
"""
# Constants
SPEED = 5 # The speed that the LEDs will cycle at
BRIGHTNESS = 1.0 # The brightness of the LEDs
UPDATES = 50 # How many times the LEDs will be updated per second
UPDATE_RATE_US = 1000000 // UPDATES
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Variables
offset = 0.0
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
# inconsistent timings when dealing with complex operations or external communication
def sleep_until(end_time):
time_to_sleep = time.ticks_diff(end_time, time.ticks_us())
if time_to_sleep > 0:
time.sleep_us(time_to_sleep)
# Make rainbows
while True:
# Record the start time of this loop
start_time = time.ticks_us()
offset += SPEED / 1000.0
# Update all the LEDs
for i in range(NUM_LEDS):
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(time.ticks_add(start_time, UPDATE_RATE_US))

View File

@ -0,0 +1,124 @@
import math
import time
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_encoder_wheel import BreakoutEncoderWheel, CENTRE, NUM_LEDS
"""
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.
Press Ctrl+C to stop the program.
"""
# Constants
BRIGHTNESS = 1.0 # The brightness of the LEDs when the stopwatch is running
UPDATES = 50 # How many times the LEDs will be updated per second
MINUTE_UPDATES = UPDATES * 60 # How many times the LEDs will be updated per minute
HOUR_UPDATES = MINUTE_UPDATES * 60 # How many times the LEDs will be updated per hour
UPDATE_RATE_US = 1000000 // UPDATES
IDLE_PULSE_MIN = 0.2 # The brightness (between 0.0 and 1.0) that the idle pulse will go down to
IDLE_PULSE_MAX = 0.5 # The brightness (between 0.0 and 1.0) that the idle pulse will go up to
IDLE_PULSE_TIME = 2 # The time (in seconds) to perform a complete idle pulse
UPDATES_PER_PULSE = IDLE_PULSE_TIME * UPDATES
IDLE, COUNTING, PAUSED = range(3) # The state constants used for program flow
# Create a new BreakoutEncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Variables
state = IDLE
idle_update = 0
second_update = 0
minute_update = 0
hour_update = 0
last_centre_pressed = False
# Simple function to clamp a value between a minimum and maximum
def clamp(n, smallest, largest):
return max(smallest, min(n, largest))
# Sleep until a specific time in the future. Use this instead of time.sleep() to correct for
# inconsistent timings when dealing with complex operations or external communication
def sleep_until(end_time):
time_to_sleep = time.ticks_diff(end_time, time.ticks_us())
if time_to_sleep > 0:
time.sleep_us(time_to_sleep)
# Record the current time
current_time = time.ticks_us()
# Run the update loop forever
while True:
# Read whether or not the wheen centre has been pressed
centre_pressed = wheel.pressed(CENTRE)
if centre_pressed and centre_pressed != last_centre_pressed:
if state == IDLE: # If we're currently idle, switch to counting
second_update = 0
minute_update = 0
hour_update = 0
state = COUNTING
elif state == COUNTING: # If we're counting, switch to paused
state = PAUSED
elif state == PAUSED: # If we're paused, switch back to counting
state = COUNTING
last_centre_pressed = centre_pressed
# If we're idle, perform a pulsing animation to show the stopwatch is ready to go
if state == IDLE:
percent_along = min(idle_update / UPDATES_PER_PULSE, 1.0)
brightness = ((math.cos(percent_along * math.pi * 2) + 1.0) / 2.0) * ((IDLE_PULSE_MAX - IDLE_PULSE_MIN)) + IDLE_PULSE_MIN
# Update all the LEDs
for i in range(NUM_LEDS):
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
# If we're counting, perform the stopwatch animation
elif state == COUNTING:
# Calculate how many LED channels should light, as a proportion of a second, minute, and hour
r_to_light = min(second_update / UPDATES, 1.0) * 24
g_to_light = min(minute_update / MINUTE_UPDATES, 1.0) * 24
b_to_light = min(hour_update / HOUR_UPDATES, 1.0) * 24
# 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 i in range(NUM_LEDS):
r = int(clamp(r_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255)
g = int(clamp(g_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255)
b = int(clamp(b_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255)
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
# Sleep until the next update, accounting for how long the above operations took to perform
current_time = time.ticks_add(current_time, UPDATE_RATE_US)
sleep_until(current_time)

View File

@ -0,0 +1,322 @@
# RGB Encoder Wheel Breakout (Micropython) <!-- omit in toc -->
This is the Micropython library reference for the [Pimoroni RGB Encoder Wheel Breakout](https://shop.pimoroni.com/products/rgb-encoder-wheel-breakout).
## Table of Content <!-- omit in toc -->
- [Getting Started](#getting-started)
- [Reading the Buttons](#reading-the-buttons)
- [Reading the Encoder](#reading-the-encoder)
- [Count and Angle](#count-and-angle)
- [Count Delta](#count-delta)
- [Step and Turn](#step-and-turn)
- [Changing the Direction](#changing-the-direction)
- [Resetting to Zero](#resetting-to-zero)
- [LEDs](#leds)
- [Setting an LED](#setting-an-led)
- [RGB](#rgb)
- [HSV](#hsv)
- [Clear all LEDs](#clear-all-leds)
- [Showing](#showing)
- [GPIOs](#gpios)
- [Setup](#setup)
- [Mode](#mode)
- [As Input or ADC](#as-input-or-adc)
- [As Output](#as-output)
- [As PWM](#as-pwm)
- [Delayed Loading](#delayed-loading)
- [Limitations](#limitations)
- [Function Reference](#function-reference)
- [Constants Reference](#constants-reference)
- [Address Constants](#address-constants)
- [Button Constants](#button-constants)
- [GPIO Constants](#gpio-constants)
- [Count Constants](#count-constants)
## Getting Started
To start coding for your Encoder Wheel breakout, you will first need to create an object for accessing the I2C bus that the breakout is connected to. The easiest way to do this is via the `PimoroniI2C` class, with one of the handy pin constants from `pimoroni`, like so:
```python
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
```
This creates a `i2c` variable that can be passed into the Encoder Wheel's class as part of its creation:
```python
from breakout_encoder_wheel import BreakoutEncoderWheel
wheel = BreakoutEncoderWheel(i2c)
```
The above lines of code import the `BreakoutEncoderWheel` class and create an instance of it, called `wheel`. This will be used in the rest of the examples going forward.
## Reading the Buttons
EncoderWheel has five buttons, covering up, down, left, right, and centre. These can be read using the `.pressed(button)` function, which accepts a button number between `0` and `4`. For convenience, each button can be referred to using these constants:
* `UP` = `0`
* `DOWN` = `1`
* `LEFT` = `2`
* `RIGHT` = `3`
* `CENTRE` = `4`
For example, to read the centre button you would write:
```python
centre_state = wheel.pressed(CENTRE)
```
You can also get the number of buttons using the `NUM_BUTTONS` constant.
## Reading the Encoder
Within the directional buttons of the Encoder Wheel breakout is a rotary encoder with 24 counts per revolution.
### Count and Angle
The current count can be read by calling `.count()`. It can also be read back as either the number of `.revolutions()` of the encoder, or the angle in `.degrees()` or `.radians()`.
Be aware that the count is stored as an integer, if it is continually increased or decreased it will eventually wrap at `+32767` and `-32768`. This will cause a jump in the returned by `.revolutions()`, `degrees()` and `.radians()`, that will need handling by your code.
### Count Delta
Often you are not interested in the exact count that the encoder is at, but rather if the count has changed since the last time you checked. This change can be read by calling `.delta()` at regular intervals. The returned value can then be used with a check in code, for the value being non-zero.
### Step and Turn
Sometimes it can be useful to know the encoder's position in the form of which step it is at and how many turns have occurred. The current step can be read by calling `.step()`, which returns a value from `0` to `23`, and the number of turns can be read by calling `.turn()`.
These functions differ from reading the `.count()` or `.revolutions()` by using separate counters, and so avoid the wrapping issue that these functions experience.
### Changing the Direction
The counting direction of an encoder can be changed by calling `.direction(REVERSED_DIR)` at any time in code. The `REVERSED_DIR` constant comes from the `pimoroni` module. There is also a `NORMAL_DIR` constant, though this is the default.
### Resetting to Zero
There are times where an encoder's count (and related values) need to be reset back to zero. This can be done by calling `.zero()`.
## LEDs
The Encoder Wheel breakout features 24 RGB LEDs arranged in a ring around the wheel. This is the same number as there are steps on the wheel, letting you use the LEDs to show the current step of the wheel.
### Setting an LED
You can set the colour of a LED on the ring in either the RGB colourspace, or HSV (Hue, Saturation, Value). HSV is useful for creating rainbow patterns.
#### RGB
Set the first LED - `0` - to Purple `255, 0, 255`:
```python
wheel.set_rgb(0, 255, 0, 255)
```
#### HSV
Set the first LED - `0` - to Red `0.0`:
```python
wheel.set_hsv(0, 0.0, 1.0, 1.0)
```
### Clear all LEDs
To turn off all the LEDs, the function `.clear()` can be called. This simply goes through each LED and sets its RGB colour to black, making them emit no light.
This function is useful to have at the end of your code to turn the lights off, otherwise they will continue to show the last colours they were given.
### Showing
Changes to the LEDs do not get applied immediately, due to the amount of I2C communication involved. As such, to have the LEDs show what they have been set to after calling the `.set_rgb()`, `.set_hsv()`, and `.clear()` functions, a specific call to `.show()` needs to be made.
## GPIOs
There are three spare GPIO pins on the edge of Encoder Wheel. These can be used as digital outputs, pwm outputs, digital inputs, and analog inputs.
### Setup
To start using a GPIO pin, first import one of the handy constants used to reference them (see [GPIO Constants](#gpio-constants)). For example, to use the first GPIO pin:
```python
from breakout_encoder_wheel import GP7
```
Then you need to import the constants for the pin mode to use. These are on the `breakout_ioexpander` module that Encoder Wheel is based on.
```python
# For input
from breakout_ioexpander import IN # or IN_PU of a pull-up is wanted
# For output
from breakout_ioexpander import OUT
# For PWM
from breakout_ioexpander import PWM
# For ADC
from breakout_ioexpander import ADC
```
### Mode
With the intended constants imported, the mode of a GPIO pin can be set by calling `.gpio_pin_mode(gpio, mode)`:
```python
wheel.gpio_pin_mode(GP7, <IN or IN_PU or OUT or PWM or ADC>)
```
It is also possible to read the current mode of a GPIO pin by calling `.gpio_pin_mode(gpio)`:
```python
mode = wheel.gpio_pin_mode(GP7)
```
### As Input or ADC
The current value of an GPIO pin in input or ADC mode can be read by calling `.gpio_pin_value(gpio)`:
```python
value = wheel.gpio_pin_value(GP7)
```
If the mode is digital, the value will either be `0` or `1`.
If the mode is analog, the value will be a voltage from `0.0` to `3.3`.
### As Output
The current value of a GPIO pin in output mode can be set by calling `.gpio_pin_value(gpio, value)`:
```python
wheel.gpio_pin_value(GP7, value)
```
The expected value is either `0` or `1`, or `True` or `False`.
### As PWM
The GPIO pins can also be set as PWM outputs. The `PWM` constant can be imported from the `breakout_ioexpander` module, and passed into the `.gpio_pin_mode()` function.
The frequency of the PWM signal can then be configured by calling `.gpio_pwm_frequency()`, which accepts a frequency (in Hz). It returns the cycle period, which should be used to set duty cycles.
Finally, the duty cycle of the PWM signal can be set by calling `.gpio_pin_value()` and providing it with a value between `0` and the cycle period.
Below is an example of setting a gpio pin to output a 25KHz signal with a 50% duty cycle:
```python
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS # or PICO_EXPLORER_I2C_PINS or HEADER_I2C_PINS
from breakout_ioexpander import PWM
from breakout_encoder_wheel import BreakoutEncoderWheel, GP7
# Initialise EncoderWheel
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
wheel = BreakoutEncoderWheel(i2c)
# Setup the gpio pin as a PWM output
wheel.gpio_pin_mode(GP7, PWM)
# Set the gpio pin's frequency to 25KHz, and record the cycle period
period = wheel.gpio_pwm_frequency(25000)
# Output a 50% duty cycle square wave
wheel.gpio_pin_value(GP7, int(period * 0.5))
```
#### Delayed Loading
By default, changes to a gpio pin's frequency or value are applied immediately. However, sometimes this may not be wanted, and instead you want all pins to receive updated parameters at the same time, regardless of how long the code ran that calculated the update.
For this purpose, `.gpio_pwm_frequency()` and `.gpio_pin_value()` include an optional parameter `load`, which by default is `True`. To avoid this "loading" include `load=False` in the relevant function calls. Then either the last call can include `load=True`, or a specific call to `.gpio_pwm_load()` can be made.
In addition, any function that performs a load, including the `.gpio_pwm_load()` function, can be made to wait until the new PWM value has been sent out of the pins. By default this is disabled, but can be enabled by including `wait_for_load=True` in the relevant function calls.
#### Limitations
All of Encoder Wheel's PWM outputs share the same timing parameters. This means that GP7, GP8, and GP9 share the same frequency. Keep this in mind if changing the frequency of one, as the others will not automatically know about the change, resulting in unexpected duty cycle outputs.
## Function Reference
Here is the complete list of functions available on the `BreakoutEncoderWheel` class:
```python
BreakoutEncoderWheel(ioe_address=0x13, led_address=0x77, interrupt=PIN_UNUSED)
set_ioe_address(address)
get_interrupt_flag()
clear_interrupt_flag()
pressed(button)
count()
delta()
step()
turn()
zero()
revolutions()
degrees()
radians()
direction()
direction(direction)
set_rgb(index, r, g, b)
set_hsv(index, h, s=1.0, v=1.0)
clear()
show()
gpio_pin_mode(gpio)
gpio_pin_mode(gpio, mode)
gpio_pin_value(gpio)
gpio_pin_value(gpio, value, load=True, wait_for_load=False)
gpio_pwm_load(wait_for_load=True)
gpio_pwm_frequency(frequency, load=True, wait_for_load=True)
```
## Constants Reference
Here is the complete list of constants on the `breakoutencoderwheel` module:
### Address Constants
* `DEFAULT_IOE_I2C_ADDR` = `0x13`
* `DEFAULT_LED_I2C_ADDR` = `0x77`
* `ALTERNATE_LED_I2C_ADDR` = `0x74`
### Button Constants
* `UP` = `0`
* `DOWN` = `1`
* `LEFT` = `2`
* `RIGHT` = `3`
* `CENTRE` = `4`
### GPIO Constants
* `GP7` = `7`
* `GP8` = `8`
* `GP9` = `9`
* `GPIOS` = (`7`, `8`, `9`)
### Count Constants
* `NUM_LEDS` = `24`
* `NUM_BUTTONS` = `5`
* `NUM_GPIOS` = `5`

View File

@ -0,0 +1,129 @@
#include "breakout_encoder_wheel.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// BreakoutEncoderWheel Class
////////////////////////////////////////////////////////////////////////////////////////////////////
/***** Methods *****/
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_ioe_address_obj, 2, BreakoutEncoderWheel_set_ioe_address);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_get_interrupt_flag_obj, BreakoutEncoderWheel_get_interrupt_flag);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_clear_interrupt_flag_obj, BreakoutEncoderWheel_clear_interrupt_flag);
MP_DEFINE_CONST_FUN_OBJ_2(BreakoutEncoderWheel_pressed_obj, BreakoutEncoderWheel_pressed);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_count_obj, BreakoutEncoderWheel_count);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_delta_obj, BreakoutEncoderWheel_delta);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_step_obj, BreakoutEncoderWheel_step);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_turn_obj, BreakoutEncoderWheel_turn);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_zero_obj, BreakoutEncoderWheel_zero);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_revolutions_obj, BreakoutEncoderWheel_revolutions);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_degrees_obj, BreakoutEncoderWheel_degrees);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_radians_obj, BreakoutEncoderWheel_radians);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_direction_obj, 1, BreakoutEncoderWheel_direction);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_rgb_obj, 2, BreakoutEncoderWheel_set_rgb);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_hsv_obj, 2, BreakoutEncoderWheel_set_hsv);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_clear_obj, BreakoutEncoderWheel_clear);
MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_show_obj, BreakoutEncoderWheel_show);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_gpio_pin_mode_obj, 2, BreakoutEncoderWheel_gpio_pin_mode);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_gpio_pin_value_obj, 2, BreakoutEncoderWheel_gpio_pin_value);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_gpio_pwm_load_obj, 1, BreakoutEncoderWheel_gpio_pwm_load);
MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_gpio_pwm_frequency_obj, 2, BreakoutEncoderWheel_gpio_pwm_frequency);
/***** Binding of Methods *****/
STATIC const mp_rom_map_elem_t BreakoutEncoderWheel_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_set_ioe_address), MP_ROM_PTR(&BreakoutEncoderWheel_set_ioe_address_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_interrupt_flag), MP_ROM_PTR(&BreakoutEncoderWheel_get_interrupt_flag_obj) },
{ MP_ROM_QSTR(MP_QSTR_clear_interrupt_flag), MP_ROM_PTR(&BreakoutEncoderWheel_clear_interrupt_flag_obj) },
{ MP_ROM_QSTR(MP_QSTR_pressed), MP_ROM_PTR(&BreakoutEncoderWheel_pressed_obj) },
{ MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&BreakoutEncoderWheel_count_obj) },
{ MP_ROM_QSTR(MP_QSTR_delta), MP_ROM_PTR(&BreakoutEncoderWheel_delta_obj) },
{ MP_ROM_QSTR(MP_QSTR_step), MP_ROM_PTR(&BreakoutEncoderWheel_step_obj) },
{ MP_ROM_QSTR(MP_QSTR_turn), MP_ROM_PTR(&BreakoutEncoderWheel_turn_obj) },
{ MP_ROM_QSTR(MP_QSTR_zero), MP_ROM_PTR(&BreakoutEncoderWheel_zero_obj) },
{ MP_ROM_QSTR(MP_QSTR_revolutions), MP_ROM_PTR(&BreakoutEncoderWheel_revolutions_obj) },
{ MP_ROM_QSTR(MP_QSTR_degrees), MP_ROM_PTR(&BreakoutEncoderWheel_degrees_obj) },
{ MP_ROM_QSTR(MP_QSTR_radians), MP_ROM_PTR(&BreakoutEncoderWheel_radians_obj) },
{ MP_ROM_QSTR(MP_QSTR_direction), MP_ROM_PTR(&BreakoutEncoderWheel_direction_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_rgb), MP_ROM_PTR(&BreakoutEncoderWheel_set_rgb_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_hsv), MP_ROM_PTR(&BreakoutEncoderWheel_set_hsv_obj) },
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&BreakoutEncoderWheel_clear_obj) },
{ MP_ROM_QSTR(MP_QSTR_show), MP_ROM_PTR(&BreakoutEncoderWheel_show_obj) },
{ MP_ROM_QSTR(MP_QSTR_gpio_pin_mode), MP_ROM_PTR(&BreakoutEncoderWheel_gpio_pin_mode_obj) },
{ MP_ROM_QSTR(MP_QSTR_gpio_pin_value), MP_ROM_PTR(&BreakoutEncoderWheel_gpio_pin_value_obj) },
{ MP_ROM_QSTR(MP_QSTR_gpio_pwm_load), MP_ROM_PTR(&BreakoutEncoderWheel_gpio_pwm_load_obj) },
{ MP_ROM_QSTR(MP_QSTR_gpio_pwm_frequency), MP_ROM_PTR(&BreakoutEncoderWheel_gpio_pwm_frequency_obj) },
};
STATIC MP_DEFINE_CONST_DICT(BreakoutEncoderWheel_locals_dict, BreakoutEncoderWheel_locals_dict_table);
/***** Class Definition *****/
#ifdef MP_DEFINE_CONST_OBJ_TYPE
MP_DEFINE_CONST_OBJ_TYPE(
breakout_encoder_wheel_BreakoutEncoderWheel_type,
MP_QSTR_BreakoutEncoderWheel,
MP_TYPE_FLAG_NONE,
make_new, BreakoutEncoderWheel_make_new,
locals_dict, (mp_obj_dict_t*)&BreakoutEncoderWheel_locals_dict
);
#else
const mp_obj_type_t breakout_encoder_wheel_BreakoutEncoderWheel_type = {
{ &mp_type_type },
.name = MP_QSTR_BreakoutEncoderWheel,
.make_new = BreakoutEncoderWheel_make_new,
.locals_dict = (mp_obj_dict_t*)&BreakoutEncoderWheel_locals_dict,
};
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
// breakout_encoder_wheel Module
////////////////////////////////////////////////////////////////////////////////////////////////////
/***** Module Constants *****/
const mp_rom_obj_tuple_t breakout_encoder_wheel_gpio_pins = {
{&mp_type_tuple}, 3, { MP_ROM_INT(7), MP_ROM_INT(8), MP_ROM_INT(9) },
};
/***** Globals Table *****/
STATIC const mp_rom_map_elem_t breakout_encoder_wheel_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_breakout_encoder_wheel) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_BreakoutEncoderWheel), (mp_obj_t)&breakout_encoder_wheel_BreakoutEncoderWheel_type },
{ MP_ROM_QSTR(MP_QSTR_DEFAULT_IOE_I2C_ADDR), MP_ROM_INT(0x13) },
{ MP_ROM_QSTR(MP_QSTR_DEFAULT_LED_I2C_ADDR), MP_ROM_INT(0x77) },
{ MP_ROM_QSTR(MP_QSTR_ALTERNATE_LED_I2C_ADDR), MP_ROM_INT(0x74) },
{ MP_ROM_QSTR(MP_QSTR_NUM_LEDS), MP_ROM_INT(24) },
{ MP_ROM_QSTR(MP_QSTR_NUM_BUTTONS), MP_ROM_INT(5) },
{ MP_ROM_QSTR(MP_QSTR_NUM_GPIOS), MP_ROM_INT(3) },
{ MP_ROM_QSTR(MP_QSTR_UP), MP_ROM_INT(0) },
{ MP_ROM_QSTR(MP_QSTR_DOWN), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_LEFT), MP_ROM_INT(2) },
{ MP_ROM_QSTR(MP_QSTR_RIGHT), MP_ROM_INT(3) },
{ MP_ROM_QSTR(MP_QSTR_CENTRE), MP_ROM_INT(4) },
{ MP_ROM_QSTR(MP_QSTR_GP7), MP_ROM_INT(7) },
{ MP_ROM_QSTR(MP_QSTR_GP8), MP_ROM_INT(8) },
{ MP_ROM_QSTR(MP_QSTR_GP9), MP_ROM_INT(9) },
{ MP_ROM_QSTR(MP_QSTR_GPIOS), MP_ROM_PTR(&breakout_encoder_wheel_gpio_pins) },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_breakout_encoder_wheel_globals, breakout_encoder_wheel_globals_table);
/***** Module Definition *****/
const mp_obj_module_t breakout_encoder_wheel_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_breakout_encoder_wheel_globals,
};
////////////////////////////////////////////////////////////////////////////////////////////////////
#if MICROPY_VERSION <= 70144
MP_REGISTER_MODULE(MP_QSTR_breakout_encoder_wheel, breakout_encoder_wheel_user_cmodule, MODULE_BREAKOUT_ENCODER_WHEEL_ENABLED);
#else
MP_REGISTER_MODULE(MP_QSTR_breakout_encoder_wheel, breakout_encoder_wheel_user_cmodule);
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,361 @@
#include "libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp"
#include "micropython/modules/util.hpp"
#include <cstdio>
using namespace pimoroni;
using namespace encoderwheel;
extern "C" {
#include "breakout_encoder_wheel.h"
#include "pimoroni_i2c.h"
/***** Variables Struct *****/
typedef struct _breakout_encoder_wheel_BreakoutEncoderWheel_obj_t {
mp_obj_base_t base;
BreakoutEncoderWheel *breakout;
_PimoroniI2C_obj_t *i2c;
} breakout_encoder_wheel_BreakoutEncoderWheel_obj_t;
/***** Constructor *****/
mp_obj_t BreakoutEncoderWheel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = nullptr;
enum { ARG_i2c, ARG_ioe_address, ARG_led_address, ARG_interrupt };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_i2c, MP_ARG_OBJ, {.u_obj = nullptr} },
{ MP_QSTR_ioe_address, MP_ARG_INT, {.u_int = BreakoutEncoderWheel::DEFAULT_IOE_I2C_ADDRESS} },
{ MP_QSTR_led_address, MP_ARG_INT, {.u_int = BreakoutEncoderWheel::DEFAULT_LED_I2C_ADDRESS} },
{ MP_QSTR_interrupt, MP_ARG_INT, {.u_int = PIN_UNUSED} },
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
self = m_new_obj(breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
self->base.type = &breakout_encoder_wheel_BreakoutEncoderWheel_type;
self->i2c = PimoroniI2C_from_machine_i2c_or_native(args[ARG_i2c].u_obj);
self->breakout = m_new_class(BreakoutEncoderWheel, (pimoroni::I2C *)(self->i2c->i2c), args[ARG_ioe_address].u_int, args[ARG_led_address].u_int, args[ARG_interrupt].u_int);
if(!self->breakout->init()) {
mp_raise_msg(&mp_type_RuntimeError, "BreakoutEncoderWheel: breakout not found when initialising");
}
return MP_OBJ_FROM_PTR(self);
}
/***** Methods *****/
mp_obj_t BreakoutEncoderWheel_set_ioe_address(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_address };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_address, 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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
self->breakout->set_ioe_address(args[ARG_address].u_int);
return mp_const_none;
}
mp_obj_t BreakoutEncoderWheel_get_interrupt_flag(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_bool(self->breakout->get_interrupt_flag());
}
mp_obj_t BreakoutEncoderWheel_clear_interrupt_flag(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
self->breakout->clear_interrupt_flag();
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_pressed(mp_obj_t self_in, mp_obj_t button_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
int button = mp_obj_get_int(button_in);
if(button < 0 || button >= 5) {
mp_raise_ValueError("button out of range. Expected 0 to 4");
}
return mp_obj_new_bool(self->breakout->pressed(button));
}
extern mp_obj_t BreakoutEncoderWheel_count(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_int(self->breakout->count());
}
extern mp_obj_t BreakoutEncoderWheel_delta(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_int(self->breakout->delta());
}
extern mp_obj_t BreakoutEncoderWheel_step(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_int(self->breakout->step());
}
extern mp_obj_t BreakoutEncoderWheel_turn(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_int(self->breakout->turn());
}
extern mp_obj_t BreakoutEncoderWheel_zero(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
self->breakout->zero();
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_revolutions(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_float(self->breakout->revolutions());
}
extern mp_obj_t BreakoutEncoderWheel_degrees(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_float(self->breakout->degrees());
}
extern mp_obj_t BreakoutEncoderWheel_radians(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
return mp_obj_new_float(self->breakout->radians());
}
extern mp_obj_t BreakoutEncoderWheel_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_direction };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_direction, MP_ARG_OBJ, { .u_obj = mp_const_none }},
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
if(n_args <= 1) {
return mp_obj_new_int(self->breakout->direction());
}
else {
int direction = mp_obj_get_int(args[ARG_direction].u_obj);
if(direction < 0 || direction > 1) {
mp_raise_ValueError("direction out of range. Expected NORMAL_DIR (0) or REVERSED_DIR (1)");
}
self->breakout->direction((Direction)direction);
return mp_const_none;
}
}
extern mp_obj_t BreakoutEncoderWheel_set_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_index, ARG_r, ARG_g, ARG_b };
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_QSTR_r, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_g, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_b, MP_ARG_REQUIRED | MP_ARG_INT },
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
int index = args[ARG_index].u_int;
int r = args[ARG_r].u_int;
int g = args[ARG_g].u_int;
int b = args[ARG_b].u_int;
if(index < 0 || index >= 24)
mp_raise_ValueError("index out of range. Expected 0 to 23");
else if(r < 0 || r > 255)
mp_raise_ValueError("r out of range. Expected 0 to 255");
else if(g < 0 || g > 255)
mp_raise_ValueError("g out of range. Expected 0 to 255");
else if(b < 0 || b > 255)
mp_raise_ValueError("b out of range. Expected 0 to 255");
else
self->breakout->set_rgb(index, r, g, b);
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_set_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_index, ARG_h, ARG_s, ARG_v };
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_QSTR_h, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_s, MP_ARG_OBJ, { .u_obj = mp_const_none }},
{ MP_QSTR_v, MP_ARG_OBJ, { .u_obj = mp_const_none }},
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
int index = args[ARG_index].u_int;
float h = mp_obj_get_float(args[ARG_h].u_obj);
float s = 1.0f;
if (args[ARG_s].u_obj != mp_const_none) {
s = mp_obj_get_float(args[ARG_s].u_obj);
}
float v = 1.0f;
if (args[ARG_v].u_obj != mp_const_none) {
v = mp_obj_get_float(args[ARG_v].u_obj);
}
if(index < 0 || index >= 24)
mp_raise_ValueError("index out of range. Expected 0 to 23");
else
self->breakout->set_hsv(index, h, s, v);
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_clear(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
self->breakout->clear();
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_show(mp_obj_t self_in) {
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
self->breakout->show();
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_gpio_pin_mode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_gpio, ARG_mode };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_gpio, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_mode, MP_ARG_OBJ, { .u_obj = mp_const_none }},
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
int gpio = args[ARG_gpio].u_int;
if(gpio < 7 || gpio > 9) {
mp_raise_ValueError("gpio out of range. Expected GP7 (7), GP8 (8), or GP9 (9)");
}
if(args[ARG_mode].u_obj == mp_const_none) {
return mp_obj_new_int(self->breakout->gpio_pin_mode(gpio));
}
else {
int mode = mp_obj_get_int(args[ARG_mode].u_obj);
self->breakout->gpio_pin_mode(gpio, mode);
return mp_const_none;
}
}
extern mp_obj_t BreakoutEncoderWheel_gpio_pin_value(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_gpio, ARG_value, ARG_load, ARG_wait_for_load };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_gpio, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_value, MP_ARG_OBJ, { .u_obj = mp_const_none }},
{ MP_QSTR_load, MP_ARG_BOOL, { .u_bool = true }},
{ MP_QSTR_wait_for_load, MP_ARG_BOOL, { .u_bool = false }},
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
int gpio = args[ARG_gpio].u_int;
if(gpio < 7 || gpio > 9) {
mp_raise_ValueError("gpio out of range. Expected GP7 (7), GP8 (8), or GP9 (9)");
}
if(args[ARG_value].u_obj == mp_const_none) {
if(self->breakout->gpio_pin_mode(gpio) == IOExpander::PIN_ADC) {
return mp_obj_new_float(self->breakout->gpio_pin_value_as_voltage(gpio));
}
else {
return mp_obj_new_int(self->breakout->gpio_pin_value(gpio));
}
}
else {
int value = mp_obj_get_int(args[ARG_value].u_obj);
bool load = args[ARG_load].u_bool;
bool wait_for_load = args[ARG_wait_for_load].u_bool;
self->breakout->gpio_pin_value(gpio, value, load, wait_for_load);
return mp_const_none;
}
}
extern mp_obj_t BreakoutEncoderWheel_gpio_pwm_load(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_wait_for_load };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_wait_for_load, MP_ARG_BOOL, { .u_bool = true }},
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
bool wait_for_load = args[ARG_wait_for_load].u_bool;
self->breakout->gpio_pwm_load(wait_for_load);
return mp_const_none;
}
extern mp_obj_t BreakoutEncoderWheel_gpio_pwm_frequency(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_frequency, ARG_load, ARG_wait_for_load };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_frequency, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_load, MP_ARG_BOOL, { .u_bool = true }},
{ MP_QSTR_wait_for_load, MP_ARG_BOOL, { .u_bool = true }},
};
// Parse args.
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);
breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t);
float frequency = mp_obj_get_float(args[ARG_frequency].u_obj);
uint32_t period = (uint32_t)(IOExpander::CLOCK_FREQ / frequency);
if (period / 128 > IOExpander::MAX_PERIOD) {
mp_raise_ValueError("The provided frequency is too low");
}
if (period < 2) {
mp_raise_ValueError("The provided frequency is too high");
}
bool load = args[ARG_load].u_bool;
bool wait_for_load = args[ARG_wait_for_load].u_bool;
period = self->breakout->gpio_pwm_frequency(frequency, load, wait_for_load);
return mp_obj_new_int(period);
}
}

View File

@ -0,0 +1,32 @@
// Include MicroPython API.
#include "py/runtime.h"
/***** Extern of Class Definition *****/
extern const mp_obj_type_t breakout_encoder_wheel_BreakoutEncoderWheel_type;
/***** Extern of Class Methods *****/
extern mp_obj_t BreakoutEncoderWheel_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t BreakoutEncoderWheel_set_ioe_address(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_get_interrupt_flag(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_clear_interrupt_flag(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_pressed(mp_obj_t self_in, mp_obj_t button_in);
extern mp_obj_t BreakoutEncoderWheel_count(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_delta(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_step(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_turn(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_zero(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_revolutions(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_degrees(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_radians(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_set_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_set_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_clear(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_show(mp_obj_t self_in);
extern mp_obj_t BreakoutEncoderWheel_gpio_pin_mode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_gpio_pin_value(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_gpio_pwm_load(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t BreakoutEncoderWheel_gpio_pwm_frequency(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);

View File

@ -0,0 +1,21 @@
set(MOD_NAME breakout_encoder_wheel)
string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER)
add_library(usermod_${MOD_NAME} INTERFACE)
target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/${MOD_NAME}/${MOD_NAME}.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/ioexpander/ioexpander.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/is31fl3731/is31fl3731.cpp
)
target_include_directories(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
-DMODULE_${MOD_NAME_UPPER}_ENABLED=1
)
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME})

View File

@ -88,6 +88,11 @@ const mp_obj_type_t breakout_ioexpander_BreakoutIOExpander_type = {
STATIC const mp_map_elem_t breakout_ioexpander_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_breakout_ioexpander) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_BreakoutIOExpander), (mp_obj_t)&breakout_ioexpander_BreakoutIOExpander_type },
{ MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(IOE_PIN_IN) },
{ MP_ROM_QSTR(MP_QSTR_IN_PU), MP_ROM_INT(IOE_PIN_IN_PU) },
{ MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(IOE_PIN_OUT) },
{ MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_INT(IOE_PIN_PWM) },
{ MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_INT(IOE_PIN_ADC) },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_breakout_ioexpander_globals, breakout_ioexpander_globals_table);

View File

@ -1,5 +1,6 @@
include(breakout_dotmatrix/micropython)
include(breakout_encoder/micropython)
include(breakout_encoder_wheel/micropython)
include(breakout_ioexpander/micropython)
include(breakout_ltr559/micropython)
include(breakout_as7262/micropython)