From 59d57a193bf41f87f9782a8f7501aeaebee66bb4 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Mon, 12 Sep 2022 15:47:07 +0100 Subject: [PATCH 01/25] Initial support for Encoder wheel breakout --- libraries/CMakeLists.txt | 1 + .../breakout_encoder_wheel/CMakeLists.txt | 1 + .../breakout_encoder_wheel.cmake | 11 + .../breakout_encoder_wheel.cpp | 89 +++++++++ .../breakout_encoder_wheel.hpp | 134 +++++++++++++ .../breakout_encoder_wheel/button_test.py | 188 ++++++++++++++++++ .../breakout_encoder_wheel.c | 69 +++++++ .../breakout_encoder_wheel.cpp | 168 ++++++++++++++++ .../breakout_encoder_wheel.h | 18 ++ .../breakout_encoder_wheel/micropython.cmake | 21 ++ .../micropython-common-breakouts.cmake | 1 + 11 files changed, 701 insertions(+) create mode 100644 libraries/breakout_encoder_wheel/CMakeLists.txt create mode 100644 libraries/breakout_encoder_wheel/breakout_encoder_wheel.cmake create mode 100644 libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp create mode 100644 libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp create mode 100644 micropython/examples/breakout_encoder_wheel/button_test.py create mode 100644 micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c create mode 100644 micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp create mode 100644 micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h create mode 100644 micropython/modules/breakout_encoder_wheel/micropython.cmake diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 317ba021..e5bca9d3 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -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) diff --git a/libraries/breakout_encoder_wheel/CMakeLists.txt b/libraries/breakout_encoder_wheel/CMakeLists.txt new file mode 100644 index 00000000..8373bfa4 --- /dev/null +++ b/libraries/breakout_encoder_wheel/CMakeLists.txt @@ -0,0 +1 @@ +include(breakout_encoder_wheel.cmake) diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cmake b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cmake new file mode 100644 index 00000000..73d9e8ae --- /dev/null +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cmake @@ -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) diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp new file mode 100644 index 00000000..44bef6f3 --- /dev/null +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -0,0 +1,89 @@ +#include "breakout_encoder_wheel.hpp" +#include + +namespace pimoroni { + + bool BreakoutEncoderWheel::init(bool skip_chip_id_check) { + bool success = false; + if(ioe.init(skip_chip_id_check)) { + + if(interrupt_pin != PIN_UNUSED) { + ioe.enable_interrupt_out(true); + } + + ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B); + + 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(); + } + + BreakoutEncoderWheel::Direction BreakoutEncoderWheel::get_encoder_direction() { + return direction; + } + + void BreakoutEncoderWheel::set_encoder_direction(Direction direction) { + this->direction = direction; + } + + void BreakoutEncoderWheel::set_pixel(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + RGBLookup rgb = lookup_table[index]; + led_ring.set(rgb.r, r); + led_ring.set(rgb.g, g); + led_ring.set(rgb.b, b); + } + + bool BreakoutEncoderWheel::wheel_available() { + return (ioe.get_interrupt_flag() > 0); + } + + int16_t BreakoutEncoderWheel::read_wheel() { + int16_t count = ioe.read_rotary_encoder(ENC_CHANNEL); + if(direction != DIRECTION_CW) + count = 0 - count; + + ioe.clear_interrupt_flag(); + return count; + } + + void BreakoutEncoderWheel::clear_wheel() { + ioe.clear_rotary_encoder(ENC_CHANNEL); + } +} \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp new file mode 100644 index 00000000..ec17cb78 --- /dev/null +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "drivers/ioexpander/ioexpander.hpp" +#include "drivers/is31fl3731/is31fl3731.hpp" +#include "common/pimoroni_common.hpp" + +namespace pimoroni { + + class BreakoutEncoderWheel { + struct RGBLookup { + uint8_t r; + uint8_t g; + uint8_t b; + }; + + //-------------------------------------------------- + // Enums + //-------------------------------------------------- + public: + enum Direction : bool { + DIRECTION_CW = true, + DIRECTION_CCW = false + }; + + + //-------------------------------------------------- + // Constants + //-------------------------------------------------- + public: + static const uint8_t DEFAULT_IOE_I2C_ADDRESS = 0x13; + static const uint8_t DEFAULT_LED_I2C_ADDRESS = 0x77; + static const uint8_t LED_I2C_ADDRESS_ALTERNATE = 0x74; + + static const Direction DEFAULT_DIRECTION = DIRECTION_CW; + static const uint32_t DEFAULT_TIMEOUT = 1; + + private: + static const uint8_t SWITCH_CENTRE = 1; + static const uint8_t SWITCH_UP = 13; + static const uint8_t SWITCH_LEFT = 11; + static const uint8_t SWITCH_DOWN = 4; + static const uint8_t SWITCH_RIGHT = 2; + + static const uint8_t ENC_TERM_A = 12; + static const uint8_t ENC_TERM_B = 3; + + static const uint8_t ENC_CHANNEL = 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 direction = DEFAULT_DIRECTION; + uint interrupt_pin = PIN_UNUSED; // A local copy of the value passed to the IOExpander, used in initialisation + + + //-------------------------------------------------- + // 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 + Direction get_encoder_direction(); + void set_encoder_direction(Direction direction); + + void set_pixel(uint8_t index, uint8_t r, uint8_t g, uint8_t b); + //void update(uint8_t frame = 0); + //void clear(); + + bool wheel_available(); + int16_t read_wheel(); + void clear_wheel(); + bool read_switch(); + }; + +} \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/button_test.py b/micropython/examples/breakout_encoder_wheel/button_test.py new file mode 100644 index 00000000..4da930ee --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/button_test.py @@ -0,0 +1,188 @@ +import time +from pimoroni_i2c import PimoroniI2C +from breakout_ioexpander import BreakoutIOExpander +from adafruit_is31fl3731 import IS31FL3731 +import sys + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +s1_pin = 1 +s2_pin = 13 +s3_pin = 11 +s4_pin = 4 +s5_pin = 2 + +ENC_TERM_A = 3 +ENC_TERM_B = 12 + +ENC_CHANNEL = 1 + +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +ioe = BreakoutIOExpander(i2c, address=0x18) + +ioe.set_mode(s1_pin, BreakoutIOExpander.PIN_IN_PU) +ioe.set_mode(s2_pin, BreakoutIOExpander.PIN_IN_PU) +ioe.set_mode(s3_pin, BreakoutIOExpander.PIN_IN_PU) +ioe.set_mode(s4_pin, BreakoutIOExpander.PIN_IN_PU) +ioe.set_mode(s5_pin, BreakoutIOExpander.PIN_IN_PU) + +ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 6, count_microsteps=True) +#ioe.set_mode(ENC_TERM_A, BreakoutIOExpander.PIN_IN_PU) +#ioe.set_mode(ENC_TERM_B, BreakoutIOExpander.PIN_IN_PU) + +display = IS31FL3731(i2c, address=0x77) +#display.fill(10) + +mapping = ((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)) + + +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 + +''' +while True: + for x in range(0, 24): + single = mapping[x] + display.pixel(single[0], 0, 255) + display.pixel(single[1], 0, 255) + display.pixel(single[2], 0, 255) + print(x) + time.sleep(0.05) + display.pixel(single[0], 0, 0) + display.pixel(single[1], 0, 0) + display.pixel(single[2], 0, 0) +''' + +last_s1 = True +last_s2 = True +last_s3 = True +last_s4 = True +last_s5 = True +last_count = -1 + +last_enc_a = False +last_enc_b = False + + +while True: + s1 = bool(ioe.input(s1_pin)) + s2 = bool(ioe.input(s2_pin)) + s3 = bool(ioe.input(s3_pin)) + s4 = bool(ioe.input(s4_pin)) + s5 = bool(ioe.input(s5_pin)) + if s1 is not last_s1: + if s1: + print("Centre (S1) has been released") + else: + print("Centre (S1) has been pressed") + last_s1 = s1 + + if s2 is not last_s2: + if s2: + print("Up (S2) has been released") + else: + print("Up (S2) has been pressed") + last_s2 = s2 + + if s3 is not last_s3: + if s3: + print("Left (S3) has been released") + else: + print("Left (S3) has been pressed") + last_s3 = s3 + + if s4 is not last_s4: + if s4: + print("Down (S4) has been released") + else: + print("Down (S4) has been pressed") + last_s4 = s4 + + if s5 is not last_s5: + if s5: + print("Right (S5) has been released") + else: + print("Right (S5) has been pressed") + last_s5 = s5 + + count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 + if count != last_count: + if count - last_count > 0: + print("Clockwise, Count = ", count) + else: + print("Counter Clockwise, Count = ", count) + + last_single = mapping[last_count % 24] + display.pixel(last_single[0], 0, 0) + display.pixel(last_single[1], 0, 0) + display.pixel(last_single[2], 0, 0) + single = mapping[count % 24] + r, g, b = hsv_to_rgb(count / 24, 1.0, 1.0) + display.pixel(single[0], 0, int(255 * r)) + display.pixel(single[1], 0, int(255 * g)) + display.pixel(single[2], 0, int(255 * b)) + last_count = count + + ''' + enc_a = bool(ioe.input(ENC_TERM_A)) + enc_b = bool(ioe.input(ENC_TERM_B)) + + if enc_a is not last_enc_a: + if enc_a: + print("ENC A high") + else: + print("ENC A low") + last_enc_a = enc_a + + if enc_b is not last_enc_b: + if enc_b: + print("ENC B high") + else: + print("ENC B low") + last_enc_b = enc_b + ''' + + time.sleep(0.005) \ No newline at end of file diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c new file mode 100644 index 00000000..468d83f4 --- /dev/null +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -0,0 +1,69 @@ +#include "breakout_encoder_wheel.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// BreakoutEncoderWheel Class +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Methods *****/ +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_address_obj, 2, BreakoutEncoderWheel_set_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_1(BreakoutEncoderWheel_get_direction_obj, BreakoutEncoderWheel_get_direction); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_direction_obj, 2, BreakoutEncoderWheel_set_direction); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_brightness_obj, 2, BreakoutEncoderWheel_set_brightness); +MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_led_obj, 4, BreakoutEncoderWheel_set_led); +MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_available_obj, BreakoutEncoderWheel_available); +MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_read_obj, BreakoutEncoderWheel_read); +MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_clear_obj, BreakoutEncoderWheel_clear); + +/***** Binding of Methods *****/ +STATIC const mp_rom_map_elem_t BreakoutEncoderWheel_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_set_address), MP_ROM_PTR(&BreakoutEncoderWheel_set_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_get_direction), MP_ROM_PTR(&BreakoutEncoderWheel_get_direction_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_direction), MP_ROM_PTR(&BreakoutEncoderWheel_set_direction_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&BreakoutEncoderWheel_set_brightness_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_led), MP_ROM_PTR(&BreakoutEncoderWheel_set_led_obj) }, + { MP_ROM_QSTR(MP_QSTR_available), MP_ROM_PTR(&BreakoutEncoderWheel_available_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&BreakoutEncoderWheel_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&BreakoutEncoderWheel_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_DIRECTION_CW), MP_ROM_INT(1) }, + { MP_ROM_QSTR(MP_QSTR_DIRECTION_CCW), MP_ROM_INT(0) }, +}; +STATIC MP_DEFINE_CONST_DICT(BreakoutEncoderWheel_locals_dict, BreakoutEncoderWheel_locals_dict_table); + +/***** Class Definition *****/ +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, +}; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// breakout_encoder_wheel Module +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Globals Table *****/ +STATIC const mp_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 }, +}; +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_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_breakout_encoder_wheel, breakout_encoder_wheel_user_cmodule); +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp new file mode 100644 index 00000000..89d67ed4 --- /dev/null +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -0,0 +1,168 @@ +#include "libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp" +#include "micropython/modules/util.hpp" +#include + + +using namespace pimoroni; + +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_address, ARG_interrupt }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_i2c, MP_ARG_OBJ, {.u_obj = nullptr} }, + { MP_QSTR_address, MP_ARG_INT, {.u_int = BreakoutEncoderWheel::DEFAULT_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_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_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_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; +} + +mp_obj_t BreakoutEncoderWheel_get_direction(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_direction()); +} + +mp_obj_t BreakoutEncoderWheel_set_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_clockwise }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_clockwise, MP_ARG_REQUIRED | MP_ARG_BOOL }, + }; + + 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_direction(args[ARG_clockwise].u_bool ? BreakoutEncoderWheel::DIRECTION_CW : BreakoutEncoderWheel::DIRECTION_CCW); + + return mp_const_none; +} + +mp_obj_t BreakoutEncoderWheel_set_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_brightness }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_brightness, MP_ARG_REQUIRED | MP_ARG_OBJ }, + }; + + 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 brightness = mp_obj_get_float(args[ARG_brightness].u_obj); + if(brightness < 0 || brightness > 1.0f) + mp_raise_ValueError("brightness out of range. Expected 0.0 to 1.0"); + else + self->breakout->set_brightness(brightness); + + return mp_const_none; +} + +mp_obj_t BreakoutEncoderWheel_set_led(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_r, ARG_g, ARG_b, ARG_w }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { 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 }, + }; + + 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 r = args[ARG_r].u_int; + int g = args[ARG_g].u_int; + int b = args[ARG_b].u_int; + + 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_led(r, g, b); + + return mp_const_none; +} + +mp_obj_t BreakoutEncoderWheel_available(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->available()); +} + +mp_obj_t BreakoutEncoderWheel_read(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->read()); +} + +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; +} +} \ No newline at end of file diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h new file mode 100644 index 00000000..c85739e4 --- /dev/null +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h @@ -0,0 +1,18 @@ +// 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_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_get_direction(mp_obj_t self_in); +extern mp_obj_t BreakoutEncoderWheel_set_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutEncoderWheel_set_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutEncoderWheel_set_led(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t BreakoutEncoderWheel_available(mp_obj_t self_in); +extern mp_obj_t BreakoutEncoderWheel_read(mp_obj_t self_in); +extern mp_obj_t BreakoutEncoderWheel_clear(mp_obj_t self_in); \ No newline at end of file diff --git a/micropython/modules/breakout_encoder_wheel/micropython.cmake b/micropython/modules/breakout_encoder_wheel/micropython.cmake new file mode 100644 index 00000000..de763c17 --- /dev/null +++ b/micropython/modules/breakout_encoder_wheel/micropython.cmake @@ -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}) \ No newline at end of file diff --git a/micropython/modules/micropython-common-breakouts.cmake b/micropython/modules/micropython-common-breakouts.cmake index 17958fa4..f18c3fb0 100644 --- a/micropython/modules/micropython-common-breakouts.cmake +++ b/micropython/modules/micropython-common-breakouts.cmake @@ -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) From 15978e5ddc744ed029e20e1fcc921685ed27631d Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 27 Sep 2022 12:57:40 +0100 Subject: [PATCH 02/25] Temporary fix for compiler issues --- .../breakout_encoder_wheel.cpp | 14 ++++--- .../breakout_encoder_wheel.hpp | 2 +- .../breakout_encoder_wheel.cpp | 39 ++++++++++--------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 44bef6f3..affda7e2 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -56,11 +56,12 @@ namespace pimoroni { } BreakoutEncoderWheel::Direction BreakoutEncoderWheel::get_encoder_direction() { - return direction; + //return direction; + return DEFAULT_DIRECTION; } void BreakoutEncoderWheel::set_encoder_direction(Direction direction) { - this->direction = direction; + //this->direction = direction; } void BreakoutEncoderWheel::set_pixel(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { @@ -75,12 +76,13 @@ namespace pimoroni { } int16_t BreakoutEncoderWheel::read_wheel() { - int16_t count = ioe.read_rotary_encoder(ENC_CHANNEL); - if(direction != DIRECTION_CW) - count = 0 - count; + //int16_t count = ioe.read_rotary_encoder(ENC_CHANNEL); + //if(direction != DIRECTION_CW) + // count = 0 - count; ioe.clear_interrupt_flag(); - return count; + //return count; + return 0; } void BreakoutEncoderWheel::clear_wheel() { diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index ec17cb78..26c82054 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -83,7 +83,7 @@ namespace pimoroni { private: IOExpander ioe; IS31FL3731 led_ring; - Direction direction = DEFAULT_DIRECTION; + //Direction direction = DEFAULT_DIRECTION; uint interrupt_pin = PIN_UNUSED; // A local copy of the value passed to the IOExpander, used in initialisation diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 89d67ed4..87a1153e 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -24,7 +24,7 @@ mp_obj_t BreakoutEncoderWheel_make_new(const mp_obj_type_t *type, size_t n_args, enum { ARG_i2c, ARG_address, ARG_interrupt }; static const mp_arg_t allowed_args[] = { { MP_QSTR_i2c, MP_ARG_OBJ, {.u_obj = nullptr} }, - { MP_QSTR_address, MP_ARG_INT, {.u_int = BreakoutEncoderWheel::DEFAULT_I2C_ADDRESS} }, + { MP_QSTR_address, MP_ARG_INT, {.u_int = BreakoutEncoderWheel::DEFAULT_IOE_I2C_ADDRESS} }, { MP_QSTR_interrupt, MP_ARG_INT, {.u_int = PIN_UNUSED} }, }; @@ -59,7 +59,7 @@ mp_obj_t BreakoutEncoderWheel_set_address(size_t n_args, const mp_obj_t *pos_arg 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_address(args[ARG_address].u_int); + self->breakout->set_ioe_address(args[ARG_address].u_int); return mp_const_none; } @@ -77,8 +77,9 @@ mp_obj_t BreakoutEncoderWheel_clear_interrupt_flag(mp_obj_t self_in) { } mp_obj_t BreakoutEncoderWheel_get_direction(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_direction()); + //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_direction()); + return mp_const_none; } mp_obj_t BreakoutEncoderWheel_set_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -91,9 +92,9 @@ mp_obj_t BreakoutEncoderWheel_set_direction(size_t n_args, const mp_obj_t *pos_a 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); + //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_direction(args[ARG_clockwise].u_bool ? BreakoutEncoderWheel::DIRECTION_CW : BreakoutEncoderWheel::DIRECTION_CCW); + //self->breakout->set_direction(args[ARG_clockwise].u_bool ? BreakoutEncoderWheel::DIRECTION_CW : BreakoutEncoderWheel::DIRECTION_CCW); return mp_const_none; } @@ -108,13 +109,13 @@ mp_obj_t BreakoutEncoderWheel_set_brightness(size_t n_args, const mp_obj_t *pos_ 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); + //breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t); float brightness = mp_obj_get_float(args[ARG_brightness].u_obj); if(brightness < 0 || brightness > 1.0f) mp_raise_ValueError("brightness out of range. Expected 0.0 to 1.0"); - else - self->breakout->set_brightness(brightness); + //else + //self->breakout->set_brightness(brightness); return mp_const_none; } @@ -131,7 +132,7 @@ mp_obj_t BreakoutEncoderWheel_set_led(size_t n_args, const mp_obj_t *pos_args, m 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); + //breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t); int r = args[ARG_r].u_int; int g = args[ARG_g].u_int; @@ -143,25 +144,27 @@ mp_obj_t BreakoutEncoderWheel_set_led(size_t n_args, const mp_obj_t *pos_args, m 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_led(r, g, b); + //else + //self->breakout->set_led(r, g, b); return mp_const_none; } mp_obj_t BreakoutEncoderWheel_available(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->available()); + //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->available()); + return mp_const_none; } mp_obj_t BreakoutEncoderWheel_read(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->read()); + //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->read()); + return mp_const_none; } 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(); + //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; } From e3c3692e31bef4ccd5f346ec1d8807c26b1ee3fa Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 27 Sep 2022 12:57:59 +0100 Subject: [PATCH 03/25] Test python for encoder --- .../breakout_encoder_wheel/button_test.py | 56 ++- .../encoder_only_test.py | 69 +++ .../super_io_encoder_test.py | 456 ++++++++++++++++++ 3 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 micropython/examples/breakout_encoder_wheel/encoder_only_test.py create mode 100644 micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py diff --git a/micropython/examples/breakout_encoder_wheel/button_test.py b/micropython/examples/breakout_encoder_wheel/button_test.py index 4da930ee..ffd743b5 100644 --- a/micropython/examples/breakout_encoder_wheel/button_test.py +++ b/micropython/examples/breakout_encoder_wheel/button_test.py @@ -3,6 +3,7 @@ from pimoroni_i2c import PimoroniI2C from breakout_ioexpander import BreakoutIOExpander from adafruit_is31fl3731 import IS31FL3731 import sys +from machine import I2C, Pin PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} @@ -18,8 +19,15 @@ ENC_TERM_B = 12 ENC_CHANNEL = 1 -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) -ioe = BreakoutIOExpander(i2c, address=0x18) +#i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = I2C(0, sda=Pin(4), scl=Pin(5)) +ioe = BreakoutIOExpander(i2c, address=0x18)#, interrupt=3) +ioe.enable_interrupt_out(pin_swap=True) +#ioe.set_address(0x13) + +#2c.writeto(0x18, bytearray([0xF9, 0x06])) +#i2c.writeto(0x18, bytearray([0x00, 0b11000000])) +#i2c.writeto(0x18, bytearray([0x01, 0b00110001])) ioe.set_mode(s1_pin, BreakoutIOExpander.PIN_IN_PU) ioe.set_mode(s2_pin, BreakoutIOExpander.PIN_IN_PU) @@ -30,9 +38,18 @@ ioe.set_mode(s5_pin, BreakoutIOExpander.PIN_IN_PU) ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 6, count_microsteps=True) #ioe.set_mode(ENC_TERM_A, BreakoutIOExpander.PIN_IN_PU) #ioe.set_mode(ENC_TERM_B, BreakoutIOExpander.PIN_IN_PU) +ioe.set_pin_interrupt(s1_pin, True) +ioe.set_pin_interrupt(s2_pin, True) +ioe.set_pin_interrupt(s3_pin, True) +ioe.set_pin_interrupt(s4_pin, True) +ioe.set_pin_interrupt(s5_pin, True) display = IS31FL3731(i2c, address=0x77) -#display.fill(10) +display2 = IS31FL3731(i2c, address=0x77-3) +display.fill(10) +display2.fill(10) + +time.sleep(1) mapping = ((128, 32, 48), (129, 33, 49), @@ -106,6 +123,14 @@ last_count = -1 last_enc_a = False last_enc_b = False +toggler = False + +import sys +from machine import Pin +p15 = Pin(15, Pin.OUT) +p15.value(True) + +last_sp = 0 while True: s1 = bool(ioe.input(s1_pin)) @@ -148,24 +173,45 @@ while True: print("Right (S5) has been pressed") last_s5 = s5 - count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 + if ioe.get_interrupt_flag(): + ioe.clear_interrupt_flag() + + count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 if count != last_count: if count - last_count > 0: print("Clockwise, Count = ", count) else: print("Counter Clockwise, Count = ", count) + if count == 0 and (last_count > 1 or last_count < -1): + p15.value(False) + #sys.exit() + last_single = mapping[last_count % 24] display.pixel(last_single[0], 0, 0) display.pixel(last_single[1], 0, 0) display.pixel(last_single[2], 0, 0) + display2.pixel(last_single[0], 0, 0) + display2.pixel(last_single[1], 0, 0) + display2.pixel(last_single[2], 0, 0) single = mapping[count % 24] r, g, b = hsv_to_rgb(count / 24, 1.0, 1.0) display.pixel(single[0], 0, int(255 * r)) display.pixel(single[1], 0, int(255 * g)) display.pixel(single[2], 0, int(255 * b)) + display2.pixel(single[0], 0, int(255 * r)) + display2.pixel(single[1], 0, int(255 * g)) + display2.pixel(single[2], 0, int(255 * b)) last_count = count + + + i2c.writeto(0x13, bytearray([0x41])) + sp = i2c.readfrom(0x13, 1) + if sp != last_sp: + print("SP =", sp) + last_sp = sp + ''' enc_a = bool(ioe.input(ENC_TERM_A)) enc_b = bool(ioe.input(ENC_TERM_B)) @@ -185,4 +231,4 @@ while True: last_enc_b = enc_b ''' - time.sleep(0.005) \ No newline at end of file + #time.sleep(0.001) \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/encoder_only_test.py b/micropython/examples/breakout_encoder_wheel/encoder_only_test.py new file mode 100644 index 00000000..9a61d4c2 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/encoder_only_test.py @@ -0,0 +1,69 @@ +import time +from pimoroni_i2c import PimoroniI2C +from breakout_ioexpander import BreakoutIOExpander +from adafruit_is31fl3731 import IS31FL3731 +import sys +from machine import I2C, Pin + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +ENC_TERM_A = 3 +ENC_TERM_B = 12 + +ENC_CHANNEL = 1 + +#i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = I2C(0, sda=Pin(4), scl=Pin(5)) +ioe = BreakoutIOExpander(i2c, address=0x18) + +ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 6, count_microsteps=True) +#ioe.set_mode(ENC_TERM_A, BreakoutIOExpander.PIN_IN_PU) +#ioe.set_mode(ENC_TERM_B, BreakoutIOExpander.PIN_IN_PU) + +last_count = -1 + +last_enc_a = False +last_enc_b = False + +import sys +from machine import Pin +p15 = Pin(15, Pin.OUT) +p15.value(True) + +last_sp = 0 + +while True: + count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 + if count != last_count: + if count - last_count > 0: + print("Clockwise, Count = ", count) + else: + print("Counter Clockwise, Count = ", count) + + if count == 0 and (last_count > 1 or last_count < -1): + p15.value(False) + #sys.exit() + + last_count = count + + ''' + enc_a = bool(ioe.input(ENC_TERM_A)) + enc_b = bool(ioe.input(ENC_TERM_B)) + + if enc_a is not last_enc_a: + if enc_a: + print("ENC A high") + else: + print("ENC A low") + last_enc_a = enc_a + + if enc_b is not last_enc_b: + if enc_b: + print("ENC B high") + else: + print("ENC B low") + last_enc_b = enc_b + ''' + + #time.sleep(0.001) \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py b/micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py new file mode 100644 index 00000000..9e99d33d --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py @@ -0,0 +1,456 @@ +import time +from pimoroni_i2c import PimoroniI2C +from breakout_ioexpander import BreakoutIOExpander +from adafruit_is31fl3731 import IS31FL3731 +import sys +from machine import I2C, Pin + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +ENC_TERM_A = 1 +ENC_TERM_B = 3 + +ENC_CHANNEL = 1 + +#i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = I2C(0, sda=Pin(4), scl=Pin(5)) +#ioe = BreakoutIOExpander(i2c, address=0x18) + +NUM_PINS=20 + +I2C_ADDR = 0x16 +CHIP_ID = 0x510E +CHIP_VERSION = 2 + +REG_CHIP_ID_L = 0xfa +REG_CHIP_ID_H = 0xfb +REG_VERSION = 0xfc + +# Rotary encoder +REG_ENC_EN = 0x04 +BIT_ENC_EN_1 = 0 +BIT_ENC_MICROSTEP_1 = 1 +BIT_ENC_EN_2 = 2 +BIT_ENC_MICROSTEP_2 = 3 +BIT_ENC_EN_3 = 4 +BIT_ENC_MICROSTEP_3 = 5 +BIT_ENC_EN_4 = 6 +BIT_ENC_MICROSTEP_4 = 7 + +REG_ENC_1_CFG = 0x05 +REG_ENC_1_COUNT = 0x06 +REG_ENC_2_CFG = 0x07 +REG_ENC_2_COUNT = 0x08 +REG_ENC_3_CFG = 0x09 +REG_ENC_3_COUNT = 0x0A +REG_ENC_4_CFG = 0x0B +REG_ENC_4_COUNT = 0x0C + +# Cap touch +REG_CAPTOUCH_EN = 0x0D +REG_CAPTOUCH_CFG = 0x0E +REG_CAPTOUCH_0 = 0x0F # First of 8 bytes from 15-22 + +# Switch counters +REG_SWITCH_EN_P0 = 0x17 +REG_SWITCH_EN_P1 = 0x18 +REG_SWITCH_P00 = 0x19 # First of 8 bytes from 25-40 +REG_SWITCH_P10 = 0x21 # First of 8 bytes from 33-49 + +REG_USER_FLASH = 0xD0 +REG_FLASH_PAGE = 0xF0 +REG_DEBUG = 0xF8 + +REG_P0 = 0x40 # Bit addressing + + + + + +REG_RWKL = 0x46 +REG_TCON = 0x48 +REG_TMOD = 0x49 +REG_TL0 = 0x4a +REG_TL1 = 0x4b +REG_TH0 = 0x4c +REG_TH1 = 0x4d +REG_CKCON = 0x4e +REG_WKCON = 0x4f +REG_P1 = 0x50 # Bit addressing + +REG_CAPCON0 = 0x52 +REG_CAPCON1 = 0x53 +REG_CAPCON2 = 0x54 +REG_CKDIV = 0x55 +REG_CKSWT = 0x56 # TA protected +REG_CKEN = 0x57 # TA protected + + + + + + + +REG_P2 = 0x60 # Bit addressing +REG_AUXR1 = 0x62 + + + + + + + +REG_WDCON = 0x6a # TA protected + +REG_P3M1 = 0x6c +REG_P3M2 = 0x6d +REG_P3 = 0x70 # Bit addressing +REG_P0M1 = 0x71 +REG_P0M2 = 0x72 +REG_P1M1 = 0x73 +REG_P1M2 = 0x74 +REG_ADCRL = 0x82 +REG_ADCRH = 0x83 +REG_T3CON = 0x84 +REG_RL3 = 0x85 +REG_RH3 = 0x86 +REG_T2CON = 0x88 +REG_T2MOD = 0x89 +REG_RCMP2L = 0x8a +REG_RCMP2H = 0x8b +REG_TL2 = 0x8c +REG_TH2 = 0x8d +REG_ADCMPL = 0x8e +REG_ADCMPH = 0x8f +REG_PWM0PH = 0x91 +REG_PWM0C0H = 0x92 +REG_PWM0C1H = 0x93 +REG_PWM0C2H = 0x94 +REG_PWM0C3H = 0x95 +REG_PNP = 0x96 +REG_PWM0FBD = 0x97 +REG_PWM0CON0 = 0x98 +REG_PWM0PL = 0x99 +REG_PWM0C0L = 0x9a +REG_PWM0C1L = 0x9b +REG_PWM0C2L = 0x9c +REG_PWM0C3L = 0x9d +REG_PIOCON0 = 0x9e +REG_PWM0CON1 = 0x9f +REG_ADCCON1 = 0xa1 +REG_ADCCON2 = 0xa2 +REG_ADCDLY = 0xa3 +REG_C0L = 0xa4 +REG_C0H = 0xa5 +REG_C1L = 0xa6 +REG_C1H = 0xa7 +REG_ADCCON0 = 0xa8 +REG_C2L = 0xad +REG_C2H = 0xae +REG_CAPCON3 = 0xb1 +REG_CAPCON4 = 0xb2 +REG_SPCR = 0xb3 +REG_SPSR = 0xb4 +REG_SPDR = 0xb5 +REG_AINDIDS0 = 0xb6 +REG_PWM0DTEN = 0xb9 # TA protected +REG_PWM0DTCNT = 0xba # TA protected +REG_PWM0MEN = 0xbb +REG_PWM0MD = 0xbc +REG_P3S = 0x41 +REG_P3SR = 0x42 +REG_P0S = 0x43 +REG_P0SR = 0x44 +REG_P1S = 0x45 +REG_P1SR = 0x47 +REG_PWM0C4H = 0x51 +REG_PWM0C5H = 0x58 +REG_PIOCON1 = 0x59 +REG_PWM0C4L = 0x5a +REG_PWM0C5L = 0x5b +REG_SPCR2 = 0x5c +REG_ADCBAL = 0x5d +REG_ADCBAH = 0x5e +REG_ADCCON3 = 0x5f +REG_P2M1 = 0x61 +REG_P2M2 = 0x63 +REG_P2SR = 0x64 +REG_P2S = 0x65 +REG_ADCSN = 0x66 +REG_ADCCN = 0x67 +REG_ADCSR = 0x68 +REG_P0UP = 0x69 +REG_P1UP = 0x6b +REG_P2UP = 0x6e +REG_P3UP = 0x6f +REG_RWKH = 0x75 +REG_AINDIDS1 = 0x76 +REG_P0DW = 0x77 +REG_P1DW = 0x78 +REG_P2DW = 0x79 +REG_P3DW = 0x7a +REG_AUXR4 = 0x7b +REG_AUXR5 = 0x7c +REG_AUXR7 = 0x7d +REG_AUXR8 = 0x7e +REG_PWM1PH = 0x7f +REG_PWM1C0H = 0x80 +REG_PWM1C1H = 0x81 +REG_PWM1MD = 0x87 +REG_PWM1MEN = 0x90 +REG_PWM1PL = 0xa0 +REG_PWM1C0L = 0xa9 +REG_PWM1C1L = 0xaa +REG_PWM1CON0 = 0xab +REG_PWM1CON1 = 0xac +REG_PIOCON2 = 0xaf +REG_PWM2PH = 0xb0 +REG_PWM2C0H = 0xb7 +REG_PWM2C1H = 0xb8 +REG_PWM2MD = 0xbd +REG_PWM2MEN = 0xbe +REG_PWM2PL = 0xbf +REG_PWM2C0L = 0xc0 +REG_PWM2C1L = 0xc1 +REG_PWM2CON0 = 0xc2 +REG_PWM2CON1 = 0xc3 +REG_PWM3PH = 0xc4 +REG_PWM3C0H = 0xc5 +REG_PWM3C1H = 0xc6 +REG_PWM3MD = 0xc7 +REG_PWM3MEN = 0xc8 +REG_PWM3PL = 0xc9 +REG_PWM3C0L = 0xca +REG_PWM3C1L = 0xcb +REG_PWM3CON0 = 0xcc +REG_PWM3CON1 = 0xcd + +REG_INT = 0xf9 +MASK_INT_TRIG = 0x1 +MASK_INT_OUT = 0x2 +BIT_INT_TRIGD = 0 +BIT_INT_OUT_EN = 1 +BIT_INT_PIN_SWAP = 2 # 0 = P1.3, 1 = P0.0 + +REG_INT_MASK_P0 = 0x00 +REG_INT_MASK_P1 = 0x01 +REG_INT_MASK_P2 = 0x02 +REG_INT_MASK_P3 = 0x03 + + +REG_VERSION = 0xfc +REG_ADDR = 0xfd + +REG_CTRL = 0xfe # 0 = Sleep, 1 = Reset, 2 = Read Flash, 3 = Write Flash, 4 = Addr Unlock +MASK_CTRL_SLEEP = 0x1 +MASK_CTRL_RESET = 0x2 +MASK_CTRL_FREAD = 0x4 +MASK_CTRL_FWRITE = 0x8 +MASK_CTRL_ADDRWR = 0x10 + +# Special mode registers, use a bit-addressing scheme to avoid +# writing the *whole* port and smashing the i2c pins +BIT_ADDRESSED_REGS = [REG_P0, REG_P1, REG_P2, REG_P3] + +reg_m1 = (REG_P0M1, REG_P1M1, -1, REG_P3M1) +reg_m2 = (REG_P0M2, REG_P1M2, -1, REG_P3M2) +reg_p = (REG_P0, REG_P1, -1, REG_P3) +reg_ps = (REG_P0S, REG_P1S, REG_P2S, REG_P3S) + +adc_ports = ((0, 4), (0, 5), (0, 6)) + +PIN_MODE_IO = 0b00000 # General IO mode, IE: not ADC or PWM +PIN_MODE_QB = 0b00000 # Output, Quasi-Bidirectional mode +PIN_MODE_PP = 0b00001 # Output, Push-Pull mode +PIN_MODE_IN = 0b00010 # Input-only (high-impedance) +PIN_MODE_PU = 0b10000 # Input (with pull-up) +PIN_MODE_OD = 0b00011 # Output, Open-Drain mode +PIN_MODE_PWM = 0b00101 # PWM, Output, Push-Pull mode +PIN_MODE_ADC = 0b01010 # ADC, Input-only (high-impedance) + +def read_reg(reg): + i2c.writeto(0x16, bytearray([reg])) + return i2c.readfrom(0x16, 1)[0] + +def write_reg(reg, val): + i2c.writeto(0x16, bytearray([reg, val])) + +def set_bits(reg, bits): + """Set the specified bits (using a mask) in a register.""" + if reg in BIT_ADDRESSED_REGS: + for bit in range(8): + if bits & (1 << bit): + write_reg(reg, 0b1000 | (bit & 0b111)) + else: + value = read_reg(reg) + time.sleep(0.001) + write_reg(reg, value | bits) + +def set_bit(reg, bit): + """Set the specified bit (nth position from right) in a register.""" + set_bits(reg, (1 << bit)) + +def clr_bits(reg, bits): + """Clear the specified bits (using a mask) in a register.""" + if reg in BIT_ADDRESSED_REGS: + for bit in range(8): + if bits & (1 << bit): + write_reg(reg, 0b0000 | (bit & 0b111)) + else: + value = read_reg(reg) + time.sleep(0.001) + write_reg(reg, value & ~bits) + +def clr_bit(reg, bit): + """Clear the specified bit (nth position from right) in a register.""" + clr_bits(reg, (1 << bit)) + +def change_bit(reg, bit, state): + """Toggle one register bit on/off.""" + if state: + set_bit(reg, bit) + else: + clr_bit(reg, bit) + +def set_mode(pin, mode, schmitt_trigger, invert=False): + if pin < 1 or pin > NUM_PINS: + printf("ValueError: Pin should be in range 1-14.\n"); + return; + + io_pin = adc_ports[pin - 1] + + gpio_mode = mode & 0b11; + io_type = (mode >> 2) & 0b11; + initial_state = mode >> 4; + + pm1 = read_reg(reg_m1[io_pin[0]]); + pm2 = read_reg(reg_m2[io_pin[0]]); + + # Clear the pm1 and pm2 bits + pm1 &= 255 - (1 << io_pin[1]); + pm2 &= 255 - (1 << io_pin[1]); + + # Set the new pm1 and pm2 bits according to our gpio_mode + pm1 |= (gpio_mode >> 1) << io_pin[1]; + pm2 |= (gpio_mode & 0b1) << io_pin[1]; + + write_reg(reg_m1[io_pin[0]], pm1) + write_reg(reg_m2[io_pin[0]], pm2) + + # Set up Schmitt trigger mode on inputs + if mode == PIN_MODE_PU: + change_bit(reg_ps[io_pin[0]], io_pin[1], schmitt_trigger) + + # 5th bit of mode encodes default output pin state + #write_reg(reg_p[io_pin[0]], (initial_state << 3) | io_pin[1]) + +def output(pin, value): + if pin < 1 or pin > NUM_PINS: + raise ValueError("Pin should be in range 1-14.") + + io_pin = adc_ports[pin - 1] + + if value == False: + clr_bit(reg_p[io_pin[0]], io_pin[1]) + elif value == True: + set_bit(reg_p[io_pin[0]], io_pin[1]) + +def setup_rotary_encoder(channel, pin_a, pin_b, pin_c, count_microsteps): + channel -= 1; + set_mode(pin_a, PIN_MODE_PU, True); + set_mode(pin_b, PIN_MODE_PU, True); + + if pin_c != 0: + set_mode(pin_c, PIN_MODE_OD, False) + output(pin_c, 0) + + write_reg(0x06, pin_a | (pin_b << 4)) + change_bit(REG_ENC_EN, (channel * 2) + 1, count_microsteps) + set_bit(REG_ENC_EN, channel * 2) + + # Reset internal encoder count to zero + write_reg(0x06, 0x00); + +""" +read_reg(0xFB) +read_reg(0xFA) + +read_reg(0x9E) +write_reg(0x9E, 0x00) +read_reg(0x73) +read_reg(0x74) +write_reg(0x73, 0x88) +write_reg(0x74, 0x02) +read_reg(0xC4) +write_reg(0xC4, 0x04) +write_reg(0x50, 0x0A) +read_reg(0xC9) +write_reg(0xC9, 0x00) +read_reg(0x71) +read_reg(0x72) +write_reg(0x71, 0x03) +write_reg(0x72, 0x1A) +read_reg(0xC2) +write_reg(0xC2, 0x20) +#write_reg(0x40, 0x0D) +time.sleep(0.01) + +read_reg(0x9E) +write_reg(0x9E, 0x00) + +read_reg(0x71) +read_reg(0x72) +write_reg(0x71, 0x03) +write_reg(0x72, 0x1A) +#write_reg(0x40, 0x01) +#write_reg(0x40, 0x01) + +write_reg(0x05, 0xC3) +read_reg(0x04) +#write_reg(0x04, 0x03) +read_reg(0x04) +#write_reg(0x04, 0x03) +write_reg(0x06, 0x00) +""" + +setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 0, count_microsteps=True) + + +last_count = -1 + +last_enc_a = False +last_enc_b = False + +toggler = False + +import sys +from machine import Pin +p15 = Pin(15, Pin.OUT) +p15.value(True) + +last_sp = 0 + +while True: + count = read_reg(0x06) // 2 + #count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 + if count != last_count: + if count - last_count > 0: + print("Clockwise, Count = ", count) + else: + print("Counter Clockwise, Count = ", count) + + if count == 0 and (last_count > 1 or last_count < -1): + p15.value(False) + #sys.exit() + + last_count = count + + #i2c.writeto(0x18, bytearray([0x41])) + #sp = i2c.readfrom(0x18, 1) + + #if sp != last_sp: + # print("SP =", sp) + #last_sp = sp + + #time.sleep(0.001) \ No newline at end of file From 7c11593f7cf9e007cfbc1928aadf802f9af10da9 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 27 Sep 2022 12:58:18 +0100 Subject: [PATCH 04/25] Fix for IOExpander address not getting changed --- drivers/ioexpander/ioexpander.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ioexpander/ioexpander.cpp b/drivers/ioexpander/ioexpander.cpp index 7a7450c3..30d10574 100644 --- a/drivers/ioexpander/ioexpander.cpp +++ b/drivers/ioexpander/ioexpander.cpp @@ -370,7 +370,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() From f35352509019fb586cd7f0ad7835b5b735c23bbe Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 27 Apr 2023 20:51:44 +0100 Subject: [PATCH 05/25] First porting of python examples --- .../examples/breakout_encoder_wheel/README.md | 70 +++ .../breakout_encoder_wheel/button_test.py | 234 --------- .../breakout_encoder_wheel/buttons.py | 72 +++ .../breakout_encoder_wheel/chase_game.py | 124 +++++ .../examples/breakout_encoder_wheel/clock.py | 92 ++++ .../breakout_encoder_wheel/colour_picker.py | 118 +++++ .../breakout_encoder_wheel/encoder.py | 47 ++ .../encoder_only_test.py | 69 --- .../breakout_encoder_wheel/gpio_pwm.py | 68 +++ .../breakout_encoder_wheel/led_rainbow.py | 52 ++ .../breakout_encoder_wheel/stop_watch.py | 126 +++++ .../super_io_encoder_test.py | 456 ------------------ 12 files changed, 769 insertions(+), 759 deletions(-) create mode 100644 micropython/examples/breakout_encoder_wheel/README.md delete mode 100644 micropython/examples/breakout_encoder_wheel/button_test.py create mode 100644 micropython/examples/breakout_encoder_wheel/buttons.py create mode 100644 micropython/examples/breakout_encoder_wheel/chase_game.py create mode 100644 micropython/examples/breakout_encoder_wheel/clock.py create mode 100644 micropython/examples/breakout_encoder_wheel/colour_picker.py create mode 100644 micropython/examples/breakout_encoder_wheel/encoder.py delete mode 100644 micropython/examples/breakout_encoder_wheel/encoder_only_test.py create mode 100644 micropython/examples/breakout_encoder_wheel/gpio_pwm.py create mode 100644 micropython/examples/breakout_encoder_wheel/led_rainbow.py create mode 100644 micropython/examples/breakout_encoder_wheel/stop_watch.py delete mode 100644 micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py diff --git a/micropython/examples/breakout_encoder_wheel/README.md b/micropython/examples/breakout_encoder_wheel/README.md new file mode 100644 index 00000000..9f901812 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/README.md @@ -0,0 +1,70 @@ +# Encoder Wheel Breakout Micropython Examples + +- [Function Examples](#function-examples) + - [Buttons](#buttons) + - [Encoder](#encoder) +- [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. + + +## 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. diff --git a/micropython/examples/breakout_encoder_wheel/button_test.py b/micropython/examples/breakout_encoder_wheel/button_test.py deleted file mode 100644 index ffd743b5..00000000 --- a/micropython/examples/breakout_encoder_wheel/button_test.py +++ /dev/null @@ -1,234 +0,0 @@ -import time -from pimoroni_i2c import PimoroniI2C -from breakout_ioexpander import BreakoutIOExpander -from adafruit_is31fl3731 import IS31FL3731 -import sys -from machine import I2C, Pin - -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - -s1_pin = 1 -s2_pin = 13 -s3_pin = 11 -s4_pin = 4 -s5_pin = 2 - -ENC_TERM_A = 3 -ENC_TERM_B = 12 - -ENC_CHANNEL = 1 - -#i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) -i2c = I2C(0, sda=Pin(4), scl=Pin(5)) -ioe = BreakoutIOExpander(i2c, address=0x18)#, interrupt=3) -ioe.enable_interrupt_out(pin_swap=True) -#ioe.set_address(0x13) - -#2c.writeto(0x18, bytearray([0xF9, 0x06])) -#i2c.writeto(0x18, bytearray([0x00, 0b11000000])) -#i2c.writeto(0x18, bytearray([0x01, 0b00110001])) - -ioe.set_mode(s1_pin, BreakoutIOExpander.PIN_IN_PU) -ioe.set_mode(s2_pin, BreakoutIOExpander.PIN_IN_PU) -ioe.set_mode(s3_pin, BreakoutIOExpander.PIN_IN_PU) -ioe.set_mode(s4_pin, BreakoutIOExpander.PIN_IN_PU) -ioe.set_mode(s5_pin, BreakoutIOExpander.PIN_IN_PU) - -ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 6, count_microsteps=True) -#ioe.set_mode(ENC_TERM_A, BreakoutIOExpander.PIN_IN_PU) -#ioe.set_mode(ENC_TERM_B, BreakoutIOExpander.PIN_IN_PU) -ioe.set_pin_interrupt(s1_pin, True) -ioe.set_pin_interrupt(s2_pin, True) -ioe.set_pin_interrupt(s3_pin, True) -ioe.set_pin_interrupt(s4_pin, True) -ioe.set_pin_interrupt(s5_pin, True) - -display = IS31FL3731(i2c, address=0x77) -display2 = IS31FL3731(i2c, address=0x77-3) -display.fill(10) -display2.fill(10) - -time.sleep(1) - -mapping = ((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)) - - -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 - -''' -while True: - for x in range(0, 24): - single = mapping[x] - display.pixel(single[0], 0, 255) - display.pixel(single[1], 0, 255) - display.pixel(single[2], 0, 255) - print(x) - time.sleep(0.05) - display.pixel(single[0], 0, 0) - display.pixel(single[1], 0, 0) - display.pixel(single[2], 0, 0) -''' - -last_s1 = True -last_s2 = True -last_s3 = True -last_s4 = True -last_s5 = True -last_count = -1 - -last_enc_a = False -last_enc_b = False - -toggler = False - -import sys -from machine import Pin -p15 = Pin(15, Pin.OUT) -p15.value(True) - -last_sp = 0 - -while True: - s1 = bool(ioe.input(s1_pin)) - s2 = bool(ioe.input(s2_pin)) - s3 = bool(ioe.input(s3_pin)) - s4 = bool(ioe.input(s4_pin)) - s5 = bool(ioe.input(s5_pin)) - if s1 is not last_s1: - if s1: - print("Centre (S1) has been released") - else: - print("Centre (S1) has been pressed") - last_s1 = s1 - - if s2 is not last_s2: - if s2: - print("Up (S2) has been released") - else: - print("Up (S2) has been pressed") - last_s2 = s2 - - if s3 is not last_s3: - if s3: - print("Left (S3) has been released") - else: - print("Left (S3) has been pressed") - last_s3 = s3 - - if s4 is not last_s4: - if s4: - print("Down (S4) has been released") - else: - print("Down (S4) has been pressed") - last_s4 = s4 - - if s5 is not last_s5: - if s5: - print("Right (S5) has been released") - else: - print("Right (S5) has been pressed") - last_s5 = s5 - - if ioe.get_interrupt_flag(): - ioe.clear_interrupt_flag() - - count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 - if count != last_count: - if count - last_count > 0: - print("Clockwise, Count = ", count) - else: - print("Counter Clockwise, Count = ", count) - - if count == 0 and (last_count > 1 or last_count < -1): - p15.value(False) - #sys.exit() - - last_single = mapping[last_count % 24] - display.pixel(last_single[0], 0, 0) - display.pixel(last_single[1], 0, 0) - display.pixel(last_single[2], 0, 0) - display2.pixel(last_single[0], 0, 0) - display2.pixel(last_single[1], 0, 0) - display2.pixel(last_single[2], 0, 0) - single = mapping[count % 24] - r, g, b = hsv_to_rgb(count / 24, 1.0, 1.0) - display.pixel(single[0], 0, int(255 * r)) - display.pixel(single[1], 0, int(255 * g)) - display.pixel(single[2], 0, int(255 * b)) - display2.pixel(single[0], 0, int(255 * r)) - display2.pixel(single[1], 0, int(255 * g)) - display2.pixel(single[2], 0, int(255 * b)) - last_count = count - - - i2c.writeto(0x13, bytearray([0x41])) - sp = i2c.readfrom(0x13, 1) - if sp != last_sp: - print("SP =", sp) - last_sp = sp - - - ''' - enc_a = bool(ioe.input(ENC_TERM_A)) - enc_b = bool(ioe.input(ENC_TERM_B)) - - if enc_a is not last_enc_a: - if enc_a: - print("ENC A high") - else: - print("ENC A low") - last_enc_a = enc_a - - if enc_b is not last_enc_b: - if enc_b: - print("ENC B high") - else: - print("ENC B low") - last_enc_b = enc_b - ''' - - #time.sleep(0.001) \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/buttons.py b/micropython/examples/breakout_encoder_wheel/buttons.py new file mode 100644 index 00000000..266ddc4c --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/buttons.py @@ -0,0 +1,72 @@ +from pimoroni_i2c import PimoroniI2C +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. +""" + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +# Constants +BUTTON_NAMES = ["Up", "Down", "Left", "Right", "Centre"] +UPDATES = 50 # How many times the buttons will be checked and LEDs updated, per second +UPDATE_RATE = 1 / UPDATES + +# Create a new BreakoutEncoderWheel +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +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() diff --git a/micropython/examples/breakout_encoder_wheel/chase_game.py b/micropython/examples/breakout_encoder_wheel/chase_game.py new file mode 100644 index 00000000..e621c9b7 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/chase_game.py @@ -0,0 +1,124 @@ +import random +from pimoroni_i2c import PimoroniI2C +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. +""" + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +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) diff --git a/micropython/examples/breakout_encoder_wheel/clock.py b/micropython/examples/breakout_encoder_wheel/clock.py new file mode 100644 index 00000000..fa67b5b0 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/clock.py @@ -0,0 +1,92 @@ +import time +from datetime import datetime + +from pimoroni_i2c import PimoroniI2C +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. +""" + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +# Constants +BRIGHTNESS = 1.0 # The brightness of the LEDs +UPDATES = 50 # How many times the LEDs will be updated per second +UPDATE_RATE = 1 / 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(**PINS_BREAKOUT_GARDEN) +wheel = BreakoutEncoderWheel(i2c) + + +# 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 = end_time - time.monotonic() + if time_to_sleep > 0.0: + time.sleep(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 brightness * BRIGHTNESS * 255 + + +# Make rainbows +while True: + + # Record the start time of this loop + start_time = time.monotonic() + + # Get the current system time + now = datetime.now() + + # 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) + (now.microsecond // 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(start_time + UPDATE_RATE) diff --git a/micropython/examples/breakout_encoder_wheel/colour_picker.py b/micropython/examples/breakout_encoder_wheel/colour_picker.py new file mode 100644 index 00000000..24c1836a --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/colour_picker.py @@ -0,0 +1,118 @@ +import time +from colorsys import hsv_to_rgb + +from pimoroni_i2c import PimoroniI2C +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. +""" + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +# 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 = 1 / UPDATES + +# Create a new BreakoutEncoderWheel +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +wheel = BreakoutEncoderWheel(i2c) + +# Variables +brightness = 1.0 +saturation = 1.0 +position = 0 +changed = True +last_centre_pressed = False + + +# 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 = end_time - time.monotonic() + if time_to_sleep > 0.0: + time.sleep(time_to_sleep) + + +while True: + # Record the start time of this loop + start_time = time.monotonic() + + # 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 = #", hex(r)[2:].zfill(2), hex(g)[2:].zfill(2), hex(b)[2:].zfill(2), 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(start_time + UPDATE_RATE) diff --git a/micropython/examples/breakout_encoder_wheel/encoder.py b/micropython/examples/breakout_encoder_wheel/encoder.py new file mode 100644 index 00000000..4422f88f --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/encoder.py @@ -0,0 +1,47 @@ +from pimoroni_i2c import PimoroniI2C +from breakout_encoder_wheel import BreakoutEncoderWheel + +print(""" +A demonstration of reading the rotary dial of the Encoder Wheel breakout. + +Press Ctrl+C to stop the program. +""") + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +# Create a new BreakoutEncoderWheel +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +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() diff --git a/micropython/examples/breakout_encoder_wheel/encoder_only_test.py b/micropython/examples/breakout_encoder_wheel/encoder_only_test.py deleted file mode 100644 index 9a61d4c2..00000000 --- a/micropython/examples/breakout_encoder_wheel/encoder_only_test.py +++ /dev/null @@ -1,69 +0,0 @@ -import time -from pimoroni_i2c import PimoroniI2C -from breakout_ioexpander import BreakoutIOExpander -from adafruit_is31fl3731 import IS31FL3731 -import sys -from machine import I2C, Pin - -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - -ENC_TERM_A = 3 -ENC_TERM_B = 12 - -ENC_CHANNEL = 1 - -#i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) -i2c = I2C(0, sda=Pin(4), scl=Pin(5)) -ioe = BreakoutIOExpander(i2c, address=0x18) - -ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 6, count_microsteps=True) -#ioe.set_mode(ENC_TERM_A, BreakoutIOExpander.PIN_IN_PU) -#ioe.set_mode(ENC_TERM_B, BreakoutIOExpander.PIN_IN_PU) - -last_count = -1 - -last_enc_a = False -last_enc_b = False - -import sys -from machine import Pin -p15 = Pin(15, Pin.OUT) -p15.value(True) - -last_sp = 0 - -while True: - count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 - if count != last_count: - if count - last_count > 0: - print("Clockwise, Count = ", count) - else: - print("Counter Clockwise, Count = ", count) - - if count == 0 and (last_count > 1 or last_count < -1): - p15.value(False) - #sys.exit() - - last_count = count - - ''' - enc_a = bool(ioe.input(ENC_TERM_A)) - enc_b = bool(ioe.input(ENC_TERM_B)) - - if enc_a is not last_enc_a: - if enc_a: - print("ENC A high") - else: - print("ENC A low") - last_enc_a = enc_a - - if enc_b is not last_enc_b: - if enc_b: - print("ENC B high") - else: - print("ENC B low") - last_enc_b = enc_b - ''' - - #time.sleep(0.001) \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py new file mode 100644 index 00000000..3c238e50 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py @@ -0,0 +1,68 @@ +import math +import time + +from ioexpander import PWM +from pimoroni_i2c import PimoroniI2C +from breakout_encoder_wheel import BreakoutEncoderWheel, CENTRE, GPIOS, NUM_GPIOS + +print(""" +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. +""") + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +SPEED = 5 # The speed that the PWM will cycle at +UPDATES = 50 # How many times to update LEDs and Servos per second +UPDATE_RATE = 1 / UPDATES +FREQUENCY = 1000 # The frequency to run the PWM at + +# Create a new BreakoutEncoderWheel +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +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) + +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 = end_time - time.monotonic() + if time_to_sleep > 0.0: + time.sleep(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.monotonic() + + 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(start_time + UPDATE_RATE) + +# Turn off the PWM outputs +for g in GPIOS: + wheel.gpio_pin_value(g, 0) diff --git a/micropython/examples/breakout_encoder_wheel/led_rainbow.py b/micropython/examples/breakout_encoder_wheel/led_rainbow.py new file mode 100644 index 00000000..76f780ba --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/led_rainbow.py @@ -0,0 +1,52 @@ +import time + +from pimoroni_i2c import PimoroniI2C +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. +""" + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +# 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 = 1 / UPDATES + +# Create a new BreakoutEncoderWheel +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +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 = end_time - time.monotonic() + if time_to_sleep > 0.0: + time.sleep(time_to_sleep) + + +# Make rainbows +while True: + + # Record the start time of this loop + start_time = time.monotonic() + + 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(start_time + UPDATE_RATE) diff --git a/micropython/examples/breakout_encoder_wheel/stop_watch.py b/micropython/examples/breakout_encoder_wheel/stop_watch.py new file mode 100644 index 00000000..240def09 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/stop_watch.py @@ -0,0 +1,126 @@ +import math +import time + +from pimoroni_i2c import PimoroniI2C +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. +""" + +PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} +PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} + +# 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 = 1 / 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 + +i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +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 = end_time - time.monotonic() + if time_to_sleep > 0.0: + time.sleep(time_to_sleep) + + +# Record the current time +current_time = time.monotonic() + +# 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 = clamp(r_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255 + g = clamp(g_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255 + b = 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 += UPDATE_RATE + sleep_until(current_time) diff --git a/micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py b/micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py deleted file mode 100644 index 9e99d33d..00000000 --- a/micropython/examples/breakout_encoder_wheel/super_io_encoder_test.py +++ /dev/null @@ -1,456 +0,0 @@ -import time -from pimoroni_i2c import PimoroniI2C -from breakout_ioexpander import BreakoutIOExpander -from adafruit_is31fl3731 import IS31FL3731 -import sys -from machine import I2C, Pin - -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - -ENC_TERM_A = 1 -ENC_TERM_B = 3 - -ENC_CHANNEL = 1 - -#i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) -i2c = I2C(0, sda=Pin(4), scl=Pin(5)) -#ioe = BreakoutIOExpander(i2c, address=0x18) - -NUM_PINS=20 - -I2C_ADDR = 0x16 -CHIP_ID = 0x510E -CHIP_VERSION = 2 - -REG_CHIP_ID_L = 0xfa -REG_CHIP_ID_H = 0xfb -REG_VERSION = 0xfc - -# Rotary encoder -REG_ENC_EN = 0x04 -BIT_ENC_EN_1 = 0 -BIT_ENC_MICROSTEP_1 = 1 -BIT_ENC_EN_2 = 2 -BIT_ENC_MICROSTEP_2 = 3 -BIT_ENC_EN_3 = 4 -BIT_ENC_MICROSTEP_3 = 5 -BIT_ENC_EN_4 = 6 -BIT_ENC_MICROSTEP_4 = 7 - -REG_ENC_1_CFG = 0x05 -REG_ENC_1_COUNT = 0x06 -REG_ENC_2_CFG = 0x07 -REG_ENC_2_COUNT = 0x08 -REG_ENC_3_CFG = 0x09 -REG_ENC_3_COUNT = 0x0A -REG_ENC_4_CFG = 0x0B -REG_ENC_4_COUNT = 0x0C - -# Cap touch -REG_CAPTOUCH_EN = 0x0D -REG_CAPTOUCH_CFG = 0x0E -REG_CAPTOUCH_0 = 0x0F # First of 8 bytes from 15-22 - -# Switch counters -REG_SWITCH_EN_P0 = 0x17 -REG_SWITCH_EN_P1 = 0x18 -REG_SWITCH_P00 = 0x19 # First of 8 bytes from 25-40 -REG_SWITCH_P10 = 0x21 # First of 8 bytes from 33-49 - -REG_USER_FLASH = 0xD0 -REG_FLASH_PAGE = 0xF0 -REG_DEBUG = 0xF8 - -REG_P0 = 0x40 # Bit addressing - - - - - -REG_RWKL = 0x46 -REG_TCON = 0x48 -REG_TMOD = 0x49 -REG_TL0 = 0x4a -REG_TL1 = 0x4b -REG_TH0 = 0x4c -REG_TH1 = 0x4d -REG_CKCON = 0x4e -REG_WKCON = 0x4f -REG_P1 = 0x50 # Bit addressing - -REG_CAPCON0 = 0x52 -REG_CAPCON1 = 0x53 -REG_CAPCON2 = 0x54 -REG_CKDIV = 0x55 -REG_CKSWT = 0x56 # TA protected -REG_CKEN = 0x57 # TA protected - - - - - - - -REG_P2 = 0x60 # Bit addressing -REG_AUXR1 = 0x62 - - - - - - - -REG_WDCON = 0x6a # TA protected - -REG_P3M1 = 0x6c -REG_P3M2 = 0x6d -REG_P3 = 0x70 # Bit addressing -REG_P0M1 = 0x71 -REG_P0M2 = 0x72 -REG_P1M1 = 0x73 -REG_P1M2 = 0x74 -REG_ADCRL = 0x82 -REG_ADCRH = 0x83 -REG_T3CON = 0x84 -REG_RL3 = 0x85 -REG_RH3 = 0x86 -REG_T2CON = 0x88 -REG_T2MOD = 0x89 -REG_RCMP2L = 0x8a -REG_RCMP2H = 0x8b -REG_TL2 = 0x8c -REG_TH2 = 0x8d -REG_ADCMPL = 0x8e -REG_ADCMPH = 0x8f -REG_PWM0PH = 0x91 -REG_PWM0C0H = 0x92 -REG_PWM0C1H = 0x93 -REG_PWM0C2H = 0x94 -REG_PWM0C3H = 0x95 -REG_PNP = 0x96 -REG_PWM0FBD = 0x97 -REG_PWM0CON0 = 0x98 -REG_PWM0PL = 0x99 -REG_PWM0C0L = 0x9a -REG_PWM0C1L = 0x9b -REG_PWM0C2L = 0x9c -REG_PWM0C3L = 0x9d -REG_PIOCON0 = 0x9e -REG_PWM0CON1 = 0x9f -REG_ADCCON1 = 0xa1 -REG_ADCCON2 = 0xa2 -REG_ADCDLY = 0xa3 -REG_C0L = 0xa4 -REG_C0H = 0xa5 -REG_C1L = 0xa6 -REG_C1H = 0xa7 -REG_ADCCON0 = 0xa8 -REG_C2L = 0xad -REG_C2H = 0xae -REG_CAPCON3 = 0xb1 -REG_CAPCON4 = 0xb2 -REG_SPCR = 0xb3 -REG_SPSR = 0xb4 -REG_SPDR = 0xb5 -REG_AINDIDS0 = 0xb6 -REG_PWM0DTEN = 0xb9 # TA protected -REG_PWM0DTCNT = 0xba # TA protected -REG_PWM0MEN = 0xbb -REG_PWM0MD = 0xbc -REG_P3S = 0x41 -REG_P3SR = 0x42 -REG_P0S = 0x43 -REG_P0SR = 0x44 -REG_P1S = 0x45 -REG_P1SR = 0x47 -REG_PWM0C4H = 0x51 -REG_PWM0C5H = 0x58 -REG_PIOCON1 = 0x59 -REG_PWM0C4L = 0x5a -REG_PWM0C5L = 0x5b -REG_SPCR2 = 0x5c -REG_ADCBAL = 0x5d -REG_ADCBAH = 0x5e -REG_ADCCON3 = 0x5f -REG_P2M1 = 0x61 -REG_P2M2 = 0x63 -REG_P2SR = 0x64 -REG_P2S = 0x65 -REG_ADCSN = 0x66 -REG_ADCCN = 0x67 -REG_ADCSR = 0x68 -REG_P0UP = 0x69 -REG_P1UP = 0x6b -REG_P2UP = 0x6e -REG_P3UP = 0x6f -REG_RWKH = 0x75 -REG_AINDIDS1 = 0x76 -REG_P0DW = 0x77 -REG_P1DW = 0x78 -REG_P2DW = 0x79 -REG_P3DW = 0x7a -REG_AUXR4 = 0x7b -REG_AUXR5 = 0x7c -REG_AUXR7 = 0x7d -REG_AUXR8 = 0x7e -REG_PWM1PH = 0x7f -REG_PWM1C0H = 0x80 -REG_PWM1C1H = 0x81 -REG_PWM1MD = 0x87 -REG_PWM1MEN = 0x90 -REG_PWM1PL = 0xa0 -REG_PWM1C0L = 0xa9 -REG_PWM1C1L = 0xaa -REG_PWM1CON0 = 0xab -REG_PWM1CON1 = 0xac -REG_PIOCON2 = 0xaf -REG_PWM2PH = 0xb0 -REG_PWM2C0H = 0xb7 -REG_PWM2C1H = 0xb8 -REG_PWM2MD = 0xbd -REG_PWM2MEN = 0xbe -REG_PWM2PL = 0xbf -REG_PWM2C0L = 0xc0 -REG_PWM2C1L = 0xc1 -REG_PWM2CON0 = 0xc2 -REG_PWM2CON1 = 0xc3 -REG_PWM3PH = 0xc4 -REG_PWM3C0H = 0xc5 -REG_PWM3C1H = 0xc6 -REG_PWM3MD = 0xc7 -REG_PWM3MEN = 0xc8 -REG_PWM3PL = 0xc9 -REG_PWM3C0L = 0xca -REG_PWM3C1L = 0xcb -REG_PWM3CON0 = 0xcc -REG_PWM3CON1 = 0xcd - -REG_INT = 0xf9 -MASK_INT_TRIG = 0x1 -MASK_INT_OUT = 0x2 -BIT_INT_TRIGD = 0 -BIT_INT_OUT_EN = 1 -BIT_INT_PIN_SWAP = 2 # 0 = P1.3, 1 = P0.0 - -REG_INT_MASK_P0 = 0x00 -REG_INT_MASK_P1 = 0x01 -REG_INT_MASK_P2 = 0x02 -REG_INT_MASK_P3 = 0x03 - - -REG_VERSION = 0xfc -REG_ADDR = 0xfd - -REG_CTRL = 0xfe # 0 = Sleep, 1 = Reset, 2 = Read Flash, 3 = Write Flash, 4 = Addr Unlock -MASK_CTRL_SLEEP = 0x1 -MASK_CTRL_RESET = 0x2 -MASK_CTRL_FREAD = 0x4 -MASK_CTRL_FWRITE = 0x8 -MASK_CTRL_ADDRWR = 0x10 - -# Special mode registers, use a bit-addressing scheme to avoid -# writing the *whole* port and smashing the i2c pins -BIT_ADDRESSED_REGS = [REG_P0, REG_P1, REG_P2, REG_P3] - -reg_m1 = (REG_P0M1, REG_P1M1, -1, REG_P3M1) -reg_m2 = (REG_P0M2, REG_P1M2, -1, REG_P3M2) -reg_p = (REG_P0, REG_P1, -1, REG_P3) -reg_ps = (REG_P0S, REG_P1S, REG_P2S, REG_P3S) - -adc_ports = ((0, 4), (0, 5), (0, 6)) - -PIN_MODE_IO = 0b00000 # General IO mode, IE: not ADC or PWM -PIN_MODE_QB = 0b00000 # Output, Quasi-Bidirectional mode -PIN_MODE_PP = 0b00001 # Output, Push-Pull mode -PIN_MODE_IN = 0b00010 # Input-only (high-impedance) -PIN_MODE_PU = 0b10000 # Input (with pull-up) -PIN_MODE_OD = 0b00011 # Output, Open-Drain mode -PIN_MODE_PWM = 0b00101 # PWM, Output, Push-Pull mode -PIN_MODE_ADC = 0b01010 # ADC, Input-only (high-impedance) - -def read_reg(reg): - i2c.writeto(0x16, bytearray([reg])) - return i2c.readfrom(0x16, 1)[0] - -def write_reg(reg, val): - i2c.writeto(0x16, bytearray([reg, val])) - -def set_bits(reg, bits): - """Set the specified bits (using a mask) in a register.""" - if reg in BIT_ADDRESSED_REGS: - for bit in range(8): - if bits & (1 << bit): - write_reg(reg, 0b1000 | (bit & 0b111)) - else: - value = read_reg(reg) - time.sleep(0.001) - write_reg(reg, value | bits) - -def set_bit(reg, bit): - """Set the specified bit (nth position from right) in a register.""" - set_bits(reg, (1 << bit)) - -def clr_bits(reg, bits): - """Clear the specified bits (using a mask) in a register.""" - if reg in BIT_ADDRESSED_REGS: - for bit in range(8): - if bits & (1 << bit): - write_reg(reg, 0b0000 | (bit & 0b111)) - else: - value = read_reg(reg) - time.sleep(0.001) - write_reg(reg, value & ~bits) - -def clr_bit(reg, bit): - """Clear the specified bit (nth position from right) in a register.""" - clr_bits(reg, (1 << bit)) - -def change_bit(reg, bit, state): - """Toggle one register bit on/off.""" - if state: - set_bit(reg, bit) - else: - clr_bit(reg, bit) - -def set_mode(pin, mode, schmitt_trigger, invert=False): - if pin < 1 or pin > NUM_PINS: - printf("ValueError: Pin should be in range 1-14.\n"); - return; - - io_pin = adc_ports[pin - 1] - - gpio_mode = mode & 0b11; - io_type = (mode >> 2) & 0b11; - initial_state = mode >> 4; - - pm1 = read_reg(reg_m1[io_pin[0]]); - pm2 = read_reg(reg_m2[io_pin[0]]); - - # Clear the pm1 and pm2 bits - pm1 &= 255 - (1 << io_pin[1]); - pm2 &= 255 - (1 << io_pin[1]); - - # Set the new pm1 and pm2 bits according to our gpio_mode - pm1 |= (gpio_mode >> 1) << io_pin[1]; - pm2 |= (gpio_mode & 0b1) << io_pin[1]; - - write_reg(reg_m1[io_pin[0]], pm1) - write_reg(reg_m2[io_pin[0]], pm2) - - # Set up Schmitt trigger mode on inputs - if mode == PIN_MODE_PU: - change_bit(reg_ps[io_pin[0]], io_pin[1], schmitt_trigger) - - # 5th bit of mode encodes default output pin state - #write_reg(reg_p[io_pin[0]], (initial_state << 3) | io_pin[1]) - -def output(pin, value): - if pin < 1 or pin > NUM_PINS: - raise ValueError("Pin should be in range 1-14.") - - io_pin = adc_ports[pin - 1] - - if value == False: - clr_bit(reg_p[io_pin[0]], io_pin[1]) - elif value == True: - set_bit(reg_p[io_pin[0]], io_pin[1]) - -def setup_rotary_encoder(channel, pin_a, pin_b, pin_c, count_microsteps): - channel -= 1; - set_mode(pin_a, PIN_MODE_PU, True); - set_mode(pin_b, PIN_MODE_PU, True); - - if pin_c != 0: - set_mode(pin_c, PIN_MODE_OD, False) - output(pin_c, 0) - - write_reg(0x06, pin_a | (pin_b << 4)) - change_bit(REG_ENC_EN, (channel * 2) + 1, count_microsteps) - set_bit(REG_ENC_EN, channel * 2) - - # Reset internal encoder count to zero - write_reg(0x06, 0x00); - -""" -read_reg(0xFB) -read_reg(0xFA) - -read_reg(0x9E) -write_reg(0x9E, 0x00) -read_reg(0x73) -read_reg(0x74) -write_reg(0x73, 0x88) -write_reg(0x74, 0x02) -read_reg(0xC4) -write_reg(0xC4, 0x04) -write_reg(0x50, 0x0A) -read_reg(0xC9) -write_reg(0xC9, 0x00) -read_reg(0x71) -read_reg(0x72) -write_reg(0x71, 0x03) -write_reg(0x72, 0x1A) -read_reg(0xC2) -write_reg(0xC2, 0x20) -#write_reg(0x40, 0x0D) -time.sleep(0.01) - -read_reg(0x9E) -write_reg(0x9E, 0x00) - -read_reg(0x71) -read_reg(0x72) -write_reg(0x71, 0x03) -write_reg(0x72, 0x1A) -#write_reg(0x40, 0x01) -#write_reg(0x40, 0x01) - -write_reg(0x05, 0xC3) -read_reg(0x04) -#write_reg(0x04, 0x03) -read_reg(0x04) -#write_reg(0x04, 0x03) -write_reg(0x06, 0x00) -""" - -setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B, 0, count_microsteps=True) - - -last_count = -1 - -last_enc_a = False -last_enc_b = False - -toggler = False - -import sys -from machine import Pin -p15 = Pin(15, Pin.OUT) -p15.value(True) - -last_sp = 0 - -while True: - count = read_reg(0x06) // 2 - #count = ioe.read_rotary_encoder(ENC_CHANNEL) // 2 - if count != last_count: - if count - last_count > 0: - print("Clockwise, Count = ", count) - else: - print("Counter Clockwise, Count = ", count) - - if count == 0 and (last_count > 1 or last_count < -1): - p15.value(False) - #sys.exit() - - last_count = count - - #i2c.writeto(0x18, bytearray([0x41])) - #sp = i2c.readfrom(0x18, 1) - - #if sp != last_sp: - # print("SP =", sp) - #last_sp = sp - - #time.sleep(0.001) \ No newline at end of file From cd83a51e8aacd320bc7ac2ff49162c6f71105df1 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 27 Apr 2023 21:02:50 +0100 Subject: [PATCH 06/25] Fix micropython config --- .../breakout_encoder_wheel/breakout_encoder_wheel.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c index 468d83f4..87e14b05 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -34,12 +34,22 @@ STATIC const mp_rom_map_elem_t BreakoutEncoderWheel_locals_dict_table[] = { 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_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 //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -61,7 +71,7 @@ const mp_obj_module_t breakout_encoder_wheel_user_cmodule = { //////////////////////////////////////////////////////////////////////////////////////////////////// #if MICROPY_VERSION <= 70144 -MP_REGISTER_MODULE(MP_QSTR_breakout_encoder_wheel, breakout_encoder_wheel_user_cmodule, MODULE_BREAKOUT_ENCODER_ENABLED); +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 From 6464f44437ecf93fbdbf026b248f770a82a52ee2 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 27 Apr 2023 21:23:35 +0100 Subject: [PATCH 07/25] Fix micropython config --- .../modules/breakout_encoder_wheel/breakout_encoder_wheel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c index 87e14b05..4af83f03 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -36,7 +36,7 @@ STATIC MP_DEFINE_CONST_DICT(BreakoutEncoderWheel_locals_dict, BreakoutEncoderWhe /***** Class Definition *****/ #ifdef MP_DEFINE_CONST_OBJ_TYPE MP_DEFINE_CONST_OBJ_TYPE( - breakout_encoder_BreakoutEncoderWheel_type, + breakout_encoder_wheel_BreakoutEncoderWheel_type, MP_QSTR_BreakoutEncoderWheel, MP_TYPE_FLAG_NONE, make_new, BreakoutEncoderWheel_make_new, From 178afd1469c650d4acc62463ff6cf135c1026514 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 27 Apr 2023 21:39:08 +0100 Subject: [PATCH 08/25] remove description prints --- micropython/examples/breakout_encoder_wheel/encoder.py | 4 ++-- micropython/examples/breakout_encoder_wheel/gpio_pwm.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/micropython/examples/breakout_encoder_wheel/encoder.py b/micropython/examples/breakout_encoder_wheel/encoder.py index 4422f88f..699f9e8e 100644 --- a/micropython/examples/breakout_encoder_wheel/encoder.py +++ b/micropython/examples/breakout_encoder_wheel/encoder.py @@ -1,11 +1,11 @@ from pimoroni_i2c import PimoroniI2C from breakout_encoder_wheel import BreakoutEncoderWheel -print(""" +""" A demonstration of reading the rotary dial of the Encoder Wheel breakout. Press Ctrl+C to stop the program. -""") +""" PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} diff --git a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py index 3c238e50..e877a9b5 100644 --- a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py +++ b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py @@ -5,11 +5,11 @@ from ioexpander import PWM from pimoroni_i2c import PimoroniI2C from breakout_encoder_wheel import BreakoutEncoderWheel, CENTRE, GPIOS, NUM_GPIOS -print(""" +""" 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. -""") +""" PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} From 2d45ed6ace38f0c61408b85e97a49914aead866e Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 2 May 2023 12:51:05 +0100 Subject: [PATCH 09/25] Updated mp function list --- .../breakout_encoder_wheel.c | 78 +++++-- .../breakout_encoder_wheel.cpp | 218 ++++++++++++++---- .../breakout_encoder_wheel.h | 28 ++- 3 files changed, 257 insertions(+), 67 deletions(-) diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c index 4af83f03..2ba0e26a 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -8,28 +8,54 @@ MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_address_obj, 2, BreakoutEncoderWheel_set_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_1(BreakoutEncoderWheel_get_direction_obj, BreakoutEncoderWheel_get_direction); -MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_direction_obj, 2, BreakoutEncoderWheel_set_direction); -MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_brightness_obj, 2, BreakoutEncoderWheel_set_brightness); -MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_led_obj, 4, BreakoutEncoderWheel_set_led); -MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_available_obj, BreakoutEncoderWheel_available); -MP_DEFINE_CONST_FUN_OBJ_1(BreakoutEncoderWheel_read_obj, BreakoutEncoderWheel_read); + +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_address), MP_ROM_PTR(&BreakoutEncoderWheel_set_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_get_direction), MP_ROM_PTR(&BreakoutEncoderWheel_get_direction_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_direction), MP_ROM_PTR(&BreakoutEncoderWheel_set_direction_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_brightness), MP_ROM_PTR(&BreakoutEncoderWheel_set_brightness_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_led), MP_ROM_PTR(&BreakoutEncoderWheel_set_led_obj) }, - { MP_ROM_QSTR(MP_QSTR_available), MP_ROM_PTR(&BreakoutEncoderWheel_available_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&BreakoutEncoderWheel_read_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_DIRECTION_CW), MP_ROM_INT(1) }, - { MP_ROM_QSTR(MP_QSTR_DIRECTION_CCW), MP_ROM_INT(0) }, + { 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); @@ -56,10 +82,34 @@ const mp_obj_type_t breakout_encoder_wheel_BreakoutEncoderWheel_type = { // 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_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_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(5) }, + + { 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); diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 87a1153e..4d6c78f6 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -76,96 +76,222 @@ mp_obj_t BreakoutEncoderWheel_clear_interrupt_flag(mp_obj_t self_in) { return mp_const_none; } -mp_obj_t BreakoutEncoderWheel_get_direction(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_direction()); +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; } -mp_obj_t BreakoutEncoderWheel_set_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_clockwise }; +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_clockwise, MP_ARG_REQUIRED | MP_ARG_BOOL }, + { 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); + 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_direction(args[ARG_clockwise].u_bool ? BreakoutEncoderWheel::DIRECTION_CW : BreakoutEncoderWheel::DIRECTION_CCW); - - return mp_const_none; + 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; + } } -mp_obj_t BreakoutEncoderWheel_set_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_brightness }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_brightness, MP_ARG_REQUIRED | MP_ARG_OBJ }, - }; - - 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 brightness = mp_obj_get_float(args[ARG_brightness].u_obj); - if(brightness < 0 || brightness > 1.0f) - mp_raise_ValueError("brightness out of range. Expected 0.0 to 1.0"); - //else - //self->breakout->set_brightness(brightness); - - return mp_const_none; -} - -mp_obj_t BreakoutEncoderWheel_set_led(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_self, ARG_r, ARG_g, ARG_b, ARG_w }; +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); + 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(r < 0 || r > 255) + 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_led(r, g, b); + else + self->breakout->set_rgb(index, r, g, b); return mp_const_none; } -mp_obj_t BreakoutEncoderWheel_available(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->available()); +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_INT }, + { MP_QSTR_s, MP_ARG_INT, { .u_obj = mp_const_none }}, + { MP_QSTR_v, MP_ARG_INT, { .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; } -mp_obj_t BreakoutEncoderWheel_read(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->read()); +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; } -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(); +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) { + //breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t); + + 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) { + //breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t); + + 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 = 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); + + 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 = false }}, + { 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); + float frequency = mp_obj_get_float(args[ARG_frequency].u_obj); + bool load = args[ARG_load].u_bool; + bool wait_for_load = args[ARG_wait_for_load].u_bool; + + int period = self->breakout->gpio_pwm_frequency(frequency, load, wait_for_load); + + return mp_obj_new_int(period); +} } \ No newline at end of file diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h index c85739e4..c03926f2 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h @@ -9,10 +9,24 @@ extern mp_obj_t BreakoutEncoderWheel_make_new(const mp_obj_type_t *type, size_t extern mp_obj_t BreakoutEncoderWheel_set_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_get_direction(mp_obj_t self_in); -extern mp_obj_t BreakoutEncoderWheel_set_direction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -extern mp_obj_t BreakoutEncoderWheel_set_brightness(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -extern mp_obj_t BreakoutEncoderWheel_set_led(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -extern mp_obj_t BreakoutEncoderWheel_available(mp_obj_t self_in); -extern mp_obj_t BreakoutEncoderWheel_read(mp_obj_t self_in); -extern mp_obj_t BreakoutEncoderWheel_clear(mp_obj_t self_in); \ No newline at end of file + +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); From 928c28b6774284b01f53ecad87372cd2ba50763c Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 2 May 2023 13:28:49 +0100 Subject: [PATCH 10/25] Switch to rom_map --- .../modules/breakout_encoder_wheel/breakout_encoder_wheel.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c index 2ba0e26a..ee813643 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -84,11 +84,11 @@ const mp_obj_type_t breakout_encoder_wheel_BreakoutEncoderWheel_type = { /***** 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), }, + {&mp_type_tuple}, 3, { MP_ROM_INT(7), MP_ROM_INT(8), MP_ROM_INT(9) }, }; /***** Globals Table *****/ -STATIC const mp_map_elem_t breakout_encoder_wheel_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 }, From d4d6cd1936e38a6af8d72e7bf0c3ae60971f111f Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 2 May 2023 16:19:22 +0100 Subject: [PATCH 11/25] Progress on encoder wheel C++ and MP --- .../breakout_encoder_wheel.cpp | 139 ++++++++++++++++-- .../breakout_encoder_wheel.hpp | 60 ++++---- .../examples/breakout_encoder_wheel/clock.py | 23 ++- .../breakout_encoder_wheel/colour_picker.py | 31 +++- .../breakout_encoder_wheel/gpio_pwm.py | 8 +- .../breakout_encoder_wheel/led_rainbow.py | 4 +- .../breakout_encoder_wheel/stop_watch.py | 4 +- .../breakout_encoder_wheel.c | 2 +- .../breakout_encoder_wheel.cpp | 8 +- 9 files changed, 214 insertions(+), 65 deletions(-) diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index affda7e2..d70c16d4 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -1,5 +1,6 @@ #include "breakout_encoder_wheel.hpp" #include +#include namespace pimoroni { @@ -13,7 +14,21 @@ namespace pimoroni { ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B); - success = true; + if(led_ring.init()) { + 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; @@ -55,22 +70,127 @@ namespace pimoroni { ioe.clear_interrupt_flag(); } - BreakoutEncoderWheel::Direction BreakoutEncoderWheel::get_encoder_direction() { - //return direction; - return DEFAULT_DIRECTION; + bool BreakoutEncoderWheel::pressed(uint button) { + return 0; // TODO } - void BreakoutEncoderWheel::set_encoder_direction(Direction direction) { - //this->direction = direction; + int BreakoutEncoderWheel::count() { + return 0; // TODO } - void BreakoutEncoderWheel::set_pixel(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + int BreakoutEncoderWheel::delta() { + return 0; // TODO + } + + int BreakoutEncoderWheel::step() { + return 0; // TODO + } + + int BreakoutEncoderWheel::turn() { + return 0; // TODO + } + + void BreakoutEncoderWheel::zero() { + + } + + float BreakoutEncoderWheel::revolutions() { + return 0; // TODO + } + + float BreakoutEncoderWheel::degrees() { + return 0; // TODO + } + + float BreakoutEncoderWheel::radians() { + return 0; // TODO + } + + 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 + fmod(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(); + } + + int BreakoutEncoderWheel::gpio_pin_mode(int gpio) { + return 0; // TODO + } + + void BreakoutEncoderWheel::gpio_pin_mode(int gpio, int mode) { + + } + + int BreakoutEncoderWheel::gpio_pin_value(int gpio) { + return 0; // TODO + } + + float BreakoutEncoderWheel::gpio_pin_value_as_voltage(int gpio) { + return 0; // TODO + } + + void BreakoutEncoderWheel::gpio_pin_value(int gpio, int value, bool load, bool wait_for_load) { + + } + + void BreakoutEncoderWheel::gpio_pwm_load(bool wait_for_load) { + + } + + int BreakoutEncoderWheel::gpio_pwm_frequency(float frequency, bool load, bool wait_for_load) { + return 0; // TODO + } + + /* bool BreakoutEncoderWheel::wheel_available() { return (ioe.get_interrupt_flag() > 0); } @@ -84,8 +204,5 @@ namespace pimoroni { //return count; return 0; } - - void BreakoutEncoderWheel::clear_wheel() { - ioe.clear_rotary_encoder(ENC_CHANNEL); - } + */ } \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index 26c82054..df8f95b9 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -13,15 +13,6 @@ namespace pimoroni { uint8_t b; }; - //-------------------------------------------------- - // Enums - //-------------------------------------------------- - public: - enum Direction : bool { - DIRECTION_CW = true, - DIRECTION_CCW = false - }; - //-------------------------------------------------- // Constants @@ -29,22 +20,21 @@ namespace pimoroni { public: static const uint8_t DEFAULT_IOE_I2C_ADDRESS = 0x13; static const uint8_t DEFAULT_LED_I2C_ADDRESS = 0x77; - static const uint8_t LED_I2C_ADDRESS_ALTERNATE = 0x74; + static const uint8_t ALTERNATE_LED_I2C_ADDRESS = 0x74; - static const Direction DEFAULT_DIRECTION = DIRECTION_CW; + static const Direction DEFAULT_DIRECTION = NORMAL_DIR; static const uint32_t DEFAULT_TIMEOUT = 1; private: - static const uint8_t SWITCH_CENTRE = 1; - static const uint8_t SWITCH_UP = 13; - static const uint8_t SWITCH_LEFT = 11; - static const uint8_t SWITCH_DOWN = 4; - static const uint8_t SWITCH_RIGHT = 2; - + static const uint8_t ENC_CHANNEL = 1; static const uint8_t ENC_TERM_A = 12; static const uint8_t ENC_TERM_B = 3; - static const uint8_t ENC_CHANNEL = 1; + static const uint8_t SW_UP = 13; + static const uint8_t SW_DOWN = 4; + static const uint8_t SW_LEFT = 11; + static const uint8_t SW_RIGHT = 2; + static const uint8_t SW_CENTRE = 1; // This wonderful lookup table maps the LEDs on the encoder wheel // from their 3x24 (remember, they're RGB) configuration to @@ -85,7 +75,7 @@ namespace pimoroni { IS31FL3731 led_ring; //Direction direction = DEFAULT_DIRECTION; uint interrupt_pin = PIN_UNUSED; // A local copy of the value passed to the IOExpander, used in initialisation - + Direction enc_direction = DEFAULT_DIRECTION; //-------------------------------------------------- // Constructors/Destructor @@ -118,17 +108,29 @@ namespace pimoroni { void clear_interrupt_flag(); // Encoder breakout specific - Direction get_encoder_direction(); - void set_encoder_direction(Direction direction); + bool pressed(uint button); + int count(); + int delta(); + int step(); + int turn(); + void zero(); + 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(); - void set_pixel(uint8_t index, uint8_t r, uint8_t g, uint8_t b); - //void update(uint8_t frame = 0); - //void clear(); - - bool wheel_available(); - int16_t read_wheel(); - void clear_wheel(); - bool read_switch(); + int gpio_pin_mode(int gpio); + void gpio_pin_mode(int gpio, int mode); + int gpio_pin_value(int gpio); + float gpio_pin_value_as_voltage(int gpio); + void gpio_pin_value(int gpio, int value, bool load = true, bool wait_for_load = false); + void gpio_pwm_load(bool wait_for_load = false); + int gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false); }; } \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/clock.py b/micropython/examples/breakout_encoder_wheel/clock.py index fa67b5b0..a2fbdccb 100644 --- a/micropython/examples/breakout_encoder_wheel/clock.py +++ b/micropython/examples/breakout_encoder_wheel/clock.py @@ -1,5 +1,5 @@ import time -from datetime import datetime +from machine import RTC from pimoroni_i2c import PimoroniI2C from breakout_encoder_wheel import BreakoutEncoderWheel, NUM_LEDS @@ -13,6 +13,12 @@ Press Ctrl+C to stop the program. PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} +# Datetime Indices +HOUR = 4 +MINUTE = 5 +SECOND = 6 +MICROSECOND = 7 + # Constants BRIGHTNESS = 1.0 # The brightness of the LEDs UPDATES = 50 # How many times the LEDs will be updated per second @@ -27,12 +33,13 @@ MILLIS_PER_HALF_DAY = MILLIS_PER_HOUR * 12 # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) wheel = BreakoutEncoderWheel(i2c) +rtc = machine.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 = end_time - time.monotonic() + time_to_sleep = end_time - (time.ticks_ms() / 1000) if time_to_sleep > 0.0: time.sleep(time_to_sleep) @@ -58,22 +65,22 @@ def led_brightness_at(led, position, half_width=1, span=1): elif lower < 0.0: brightness = clamp((led - (lower + NUM_LEDS)) / span, brightness, 1.0) - return brightness * BRIGHTNESS * 255 + return int(brightness * BRIGHTNESS * 255) # Make rainbows while True: # Record the start time of this loop - start_time = time.monotonic() + start_time = time.ticks_ms() / 1000 # Get the current system time - now = datetime.now() + 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) + (now.microsecond // 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 + sec_as_millis = (now[SECOND] * MILLIS_PER_SECOND) + (now[MICROSECOND] // 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 diff --git a/micropython/examples/breakout_encoder_wheel/colour_picker.py b/micropython/examples/breakout_encoder_wheel/colour_picker.py index 24c1836a..583e6668 100644 --- a/micropython/examples/breakout_encoder_wheel/colour_picker.py +++ b/micropython/examples/breakout_encoder_wheel/colour_picker.py @@ -1,5 +1,4 @@ import time -from colorsys import hsv_to_rgb from pimoroni_i2c import PimoroniI2C from breakout_encoder_wheel import BreakoutEncoderWheel, UP, DOWN, LEFT, RIGHT, CENTRE, NUM_LEDS @@ -38,6 +37,30 @@ 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) @@ -46,14 +69,14 @@ def clamp01(value): # 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 = end_time - time.monotonic() + time_to_sleep = end_time - (time.ticks_ms() / 1000) if time_to_sleep > 0.0: time.sleep(time_to_sleep) while True: # Record the start time of this loop - start_time = time.monotonic() + start_time = time.ticks_ms() / 1000 # If up is pressed, increase the brightness if wheel.pressed(UP): @@ -95,7 +118,7 @@ while True: 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 = #", hex(r)[2:].zfill(2), hex(g)[2:].zfill(2), hex(b)[2:].zfill(2), sep="") + 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" diff --git a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py index e877a9b5..b8bb0d46 100644 --- a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py +++ b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py @@ -1,7 +1,7 @@ import math import time -from ioexpander import PWM +from breakout_ioexpander import BreakoutIOExpander from pimoroni_i2c import PimoroniI2C from breakout_encoder_wheel import BreakoutEncoderWheel, CENTRE, GPIOS, NUM_GPIOS @@ -28,7 +28,7 @@ period = wheel.gpio_pwm_frequency(FREQUENCY) # Set the GPIO pins to PWM outputs for g in GPIOS: - wheel.gpio_pin_mode(g, PWM) + wheel.gpio_pin_mode(g, BreakoutIOExpander.PIN_PWM) offset = 0.0 @@ -36,7 +36,7 @@ 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 = end_time - time.monotonic() + time_to_sleep = end_time - (time.ticks_ms() / 1000) if time_to_sleep > 0.0: time.sleep(time_to_sleep) @@ -45,7 +45,7 @@ def sleep_until(end_time): while not wheel.pressed(CENTRE): # Record the start time of this loop - start_time = time.monotonic() + start_time = time.ticks_ms() / 1000 offset += SPEED / 1000.0 diff --git a/micropython/examples/breakout_encoder_wheel/led_rainbow.py b/micropython/examples/breakout_encoder_wheel/led_rainbow.py index 76f780ba..eef15328 100644 --- a/micropython/examples/breakout_encoder_wheel/led_rainbow.py +++ b/micropython/examples/breakout_encoder_wheel/led_rainbow.py @@ -29,7 +29,7 @@ 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 = end_time - time.monotonic() + time_to_sleep = end_time - (time.ticks_ms() / 1000) if time_to_sleep > 0.0: time.sleep(time_to_sleep) @@ -38,7 +38,7 @@ def sleep_until(end_time): while True: # Record the start time of this loop - start_time = time.monotonic() + start_time = time.ticks_ms() / 1000 offset += SPEED / 1000.0 diff --git a/micropython/examples/breakout_encoder_wheel/stop_watch.py b/micropython/examples/breakout_encoder_wheel/stop_watch.py index 240def09..e7fac17b 100644 --- a/micropython/examples/breakout_encoder_wheel/stop_watch.py +++ b/micropython/examples/breakout_encoder_wheel/stop_watch.py @@ -49,13 +49,13 @@ def clamp(n, smallest, 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 = end_time - time.monotonic() + time_to_sleep = end_time - (time.ticks_ms() / 1000) if time_to_sleep > 0.0: time.sleep(time_to_sleep) # Record the current time -current_time = time.monotonic() +current_time = (time.ticks_ms() / 1000) # Run the update loop forever while True: diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c index ee813643..dabb5301 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -104,7 +104,7 @@ STATIC const mp_rom_map_elem_t breakout_encoder_wheel_globals_table[] = { { 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(5) }, + { 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) }, diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 4d6c78f6..41361e03 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -81,7 +81,7 @@ extern mp_obj_t BreakoutEncoderWheel_pressed(mp_obj_t self_in, mp_obj_t button_i int button = mp_obj_get_int(button_in); if(button < 0 || button >= 5) { - mp_raise_ValueError("button out of range. Expected 0 to 4") + mp_raise_ValueError("button out of range. Expected 0 to 4"); } return mp_obj_new_bool(self->breakout->pressed(button)); @@ -195,9 +195,9 @@ extern mp_obj_t BreakoutEncoderWheel_set_hsv(size_t n_args, const mp_obj_t *pos_ 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_INT }, - { MP_QSTR_s, MP_ARG_INT, { .u_obj = mp_const_none }}, - { MP_QSTR_v, MP_ARG_INT, { .u_obj = mp_const_none }}, + { 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. From 387df3bd12e52c7821ac69ab7eb9563f2ad01236 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 2 May 2023 17:31:11 +0100 Subject: [PATCH 12/25] Progress on encoder wheel C++ and MP --- .../breakout_encoder_wheel.cpp | 21 ++++++++++++++++++- .../examples/breakout_encoder_wheel/clock.py | 17 +++++++-------- .../breakout_encoder_wheel/colour_picker.py | 12 +++++------ .../breakout_encoder_wheel/gpio_pwm.py | 12 +++++------ .../breakout_encoder_wheel/led_rainbow.py | 14 ++++++------- .../breakout_encoder_wheel/stop_watch.py | 18 ++++++++-------- .../breakout_encoder_wheel.cpp | 7 ++++--- 7 files changed, 60 insertions(+), 41 deletions(-) diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index d70c16d4..29d1464e 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -29,6 +29,12 @@ namespace pimoroni { success = true; } + + 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); } return success; @@ -71,7 +77,20 @@ namespace pimoroni { } bool BreakoutEncoderWheel::pressed(uint button) { - return 0; // TODO + 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; + } } int BreakoutEncoderWheel::count() { diff --git a/micropython/examples/breakout_encoder_wheel/clock.py b/micropython/examples/breakout_encoder_wheel/clock.py index a2fbdccb..6016763a 100644 --- a/micropython/examples/breakout_encoder_wheel/clock.py +++ b/micropython/examples/breakout_encoder_wheel/clock.py @@ -17,12 +17,11 @@ PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} HOUR = 4 MINUTE = 5 SECOND = 6 -MICROSECOND = 7 # Constants BRIGHTNESS = 1.0 # The brightness of the LEDs UPDATES = 50 # How many times the LEDs will be updated per second -UPDATE_RATE = 1 / UPDATES +UPDATE_RATE_US = 1000000 // UPDATES # Handy values for the number of milliseconds MILLIS_PER_SECOND = 1000 @@ -33,15 +32,15 @@ MILLIS_PER_HALF_DAY = MILLIS_PER_HOUR * 12 # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) wheel = BreakoutEncoderWheel(i2c) -rtc = machine.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 = end_time - (time.ticks_ms() / 1000) - if time_to_sleep > 0.0: - time.sleep(time_to_sleep) + 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 @@ -72,13 +71,13 @@ def led_brightness_at(led, position, half_width=1, span=1): while True: # Record the start time of this loop - start_time = time.ticks_ms() / 1000 + 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) + (now[MICROSECOND] // MILLIS_PER_SECOND) + 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 @@ -96,4 +95,4 @@ while True: wheel.show() # Sleep until the next update, accounting for how long the above operations took to perform - sleep_until(start_time + UPDATE_RATE) + sleep_until(time.ticks_add(start_time, UPDATE_RATE_US)) diff --git a/micropython/examples/breakout_encoder_wheel/colour_picker.py b/micropython/examples/breakout_encoder_wheel/colour_picker.py index 583e6668..dcaef39a 100644 --- a/micropython/examples/breakout_encoder_wheel/colour_picker.py +++ b/micropython/examples/breakout_encoder_wheel/colour_picker.py @@ -23,7 +23,7 @@ PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} 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 = 1 / UPDATES +UPDATE_RATE_US = 1000000 // UPDATES # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) @@ -69,14 +69,14 @@ def clamp01(value): # 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 = end_time - (time.ticks_ms() / 1000) - if time_to_sleep > 0.0: - time.sleep(time_to_sleep) + 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_ms() / 1000 + start_time = time.ticks_us() # If up is pressed, increase the brightness if wheel.pressed(UP): @@ -138,4 +138,4 @@ while True: changed = False # Sleep until the next update, accounting for how long the above operations took to perform - sleep_until(start_time + UPDATE_RATE) + sleep_until(time.ticks_add(start_time, UPDATE_RATE_US)) diff --git a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py index b8bb0d46..c7904f5f 100644 --- a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py +++ b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py @@ -16,7 +16,7 @@ PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} SPEED = 5 # The speed that the PWM will cycle at UPDATES = 50 # How many times to update LEDs and Servos per second -UPDATE_RATE = 1 / UPDATES +UPDATE_RATE_US = 1000000 // UPDATES FREQUENCY = 1000 # The frequency to run the PWM at # Create a new BreakoutEncoderWheel @@ -36,16 +36,16 @@ 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 = end_time - (time.ticks_ms() / 1000) - if time_to_sleep > 0.0: - time.sleep(time_to_sleep) + 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_ms() / 1000 + start_time = time.ticks_us() offset += SPEED / 1000.0 @@ -61,7 +61,7 @@ while not wheel.pressed(CENTRE): wheel.gpio_pwm_load() # Sleep until the next update, accounting for how long the above operations took to perform - sleep_until(start_time + UPDATE_RATE) + sleep_until(time.ticks_add(start_time, UPDATE_RATE_US)) # Turn off the PWM outputs for g in GPIOS: diff --git a/micropython/examples/breakout_encoder_wheel/led_rainbow.py b/micropython/examples/breakout_encoder_wheel/led_rainbow.py index eef15328..239f03f5 100644 --- a/micropython/examples/breakout_encoder_wheel/led_rainbow.py +++ b/micropython/examples/breakout_encoder_wheel/led_rainbow.py @@ -16,7 +16,7 @@ PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} 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 = 1 / UPDATES +UPDATE_RATE_US = 1000000 // UPDATES # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) @@ -29,16 +29,16 @@ 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 = end_time - (time.ticks_ms() / 1000) - if time_to_sleep > 0.0: - time.sleep(time_to_sleep) - + 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_ms() / 1000 + start_time = time.ticks_us() offset += SPEED / 1000.0 @@ -49,4 +49,4 @@ while True: wheel.show() # Sleep until the next update, accounting for how long the above operations took to perform - sleep_until(start_time + UPDATE_RATE) + sleep_until(time.ticks_add(start_time, UPDATE_RATE_US)) diff --git a/micropython/examples/breakout_encoder_wheel/stop_watch.py b/micropython/examples/breakout_encoder_wheel/stop_watch.py index e7fac17b..3581c91e 100644 --- a/micropython/examples/breakout_encoder_wheel/stop_watch.py +++ b/micropython/examples/breakout_encoder_wheel/stop_watch.py @@ -20,7 +20,7 @@ BRIGHTNESS = 1.0 # The brightness of the LEDs when the stopwa 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 = 1 / UPDATES +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 @@ -49,13 +49,13 @@ def clamp(n, smallest, 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 = end_time - (time.ticks_ms() / 1000) - if time_to_sleep > 0.0: - time.sleep(time_to_sleep) + 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_ms() / 1000) +current_time = time.ticks_us() # Run the update loop forever while True: @@ -100,9 +100,9 @@ while True: # 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 = clamp(r_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255 - g = clamp(g_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255 - b = clamp(b_to_light - i, 0.0, 1.0) * BRIGHTNESS * 255 + 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() @@ -122,5 +122,5 @@ while True: hour_update = 0 # Sleep until the next update, accounting for how long the above operations took to perform - current_time += UPDATE_RATE + current_time = time.ticks_add(current_time, UPDATE_RATE_US) sleep_until(current_time) diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 41361e03..809848ed 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -21,10 +21,11 @@ typedef struct _breakout_encoder_wheel_BreakoutEncoderWheel_obj_t { 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_address, ARG_interrupt }; + 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_address, MP_ARG_INT, {.u_int = BreakoutEncoderWheel::DEFAULT_IOE_I2C_ADDRESS} }, + { 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} }, }; @@ -37,7 +38,7 @@ mp_obj_t BreakoutEncoderWheel_make_new(const mp_obj_type_t *type, size_t n_args, 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_address].u_int, args[ARG_interrupt].u_int); + 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"); From 1cfae8b5f8c26fcf47d2e731ed01e0a669e65f13 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 2 May 2023 22:55:19 +0100 Subject: [PATCH 13/25] More work on encoder wheel C++, adding reset to ioe --- drivers/ioexpander/ioexpander.cpp | 52 ++++++-- drivers/ioexpander/ioexpander.hpp | 7 +- .../breakout_encoder_wheel.cpp | 116 ++++++++++-------- .../breakout_encoder_wheel.hpp | 22 +++- .../breakout_encoder_wheel.c | 6 +- .../breakout_encoder_wheel.cpp | 2 +- .../breakout_encoder_wheel.h | 2 +- 7 files changed, 137 insertions(+), 70 deletions(-) diff --git a/drivers/ioexpander/ioexpander.cpp b/drivers/ioexpander/ioexpander.cpp index 30d10574..dd98a1ea 100644 --- a/drivers/ioexpander/ioexpander.cpp +++ b/drivers/ioexpander/ioexpander.cpp @@ -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 >= timeout) { + if(debug) + printf("Timed out waiting for Reset!"); + return false; } } - return succeeded; + return true; } i2c_inst_t* IOExpander::get_i2c() const { diff --git a/drivers/ioexpander/ioexpander.hpp b/drivers/ioexpander/ioexpander.hpp index eb4809d1..3143ab38 100644 --- a/drivers/ioexpander/ioexpander.hpp +++ b/drivers/ioexpander/ioexpander.hpp @@ -171,7 +171,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; diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 29d1464e..a1e684eb 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -6,35 +6,29 @@ namespace pimoroni { bool BreakoutEncoderWheel::init(bool skip_chip_id_check) { bool success = false; - if(ioe.init(skip_chip_id_check)) { + if(ioe.init(skip_chip_id_check, true) && led_ring.init()) { - if(interrupt_pin != PIN_UNUSED) { - ioe.enable_interrupt_out(true); - } - - ioe.setup_rotary_encoder(ENC_CHANNEL, ENC_TERM_A, ENC_TERM_B); - - if(led_ring.init()) { - led_ring.enable({ - 0b00000000, 0b10111111, - 0b00111110, 0b00111110, - 0b00111111, 0b10111110, - 0b00000111, 0b10000110, - 0b00110000, 0b00110000, - 0b00111111, 0b10111110, - 0b00111111, 0b10111110, - 0b01111111, 0b11111110, - 0b01111111, 0b00000000 - }, 0); - - success = true; - } + 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); + + 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; @@ -67,7 +61,7 @@ namespace pimoroni { void BreakoutEncoderWheel::set_ioe_address(uint8_t address) { ioe.set_address(address); } - + bool BreakoutEncoderWheel::get_interrupt_flag() { return ioe.get_interrupt_flag(); } @@ -93,36 +87,50 @@ namespace pimoroni { } } - int BreakoutEncoderWheel::count() { - return 0; // TODO + int16_t BreakoutEncoderWheel::count() { + take_encoder_reading(); + return enc_count; } - int BreakoutEncoderWheel::delta() { - return 0; // TODO - } + int16_t BreakoutEncoderWheel::delta() { + take_encoder_reading(); - int BreakoutEncoderWheel::step() { - return 0; // TODO - } + // 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; - int BreakoutEncoderWheel::turn() { - return 0; // TODO + 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 0; // TODO + return (float)count() / (float)ENC_COUNTS_PER_REV; } float BreakoutEncoderWheel::degrees() { - return 0; // TODO + return revolutions() * 360.0f; } float BreakoutEncoderWheel::radians() { - return 0; // TODO + return revolutions() * M_PI * 2.0f; } Direction BreakoutEncoderWheel::direction() { @@ -209,19 +217,31 @@ namespace pimoroni { return 0; // TODO } - /* - bool BreakoutEncoderWheel::wheel_available() { - return (ioe.get_interrupt_flag() > 0); - } + 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; - int16_t BreakoutEncoderWheel::read_wheel() { - //int16_t count = ioe.read_rotary_encoder(ENC_CHANNEL); - //if(direction != DIRECTION_CW) - // count = 0 - count; + // Invert the change + if(enc_direction == REVERSED_DIR) { + raw_change = 0 - raw_change; + } - ioe.clear_interrupt_flag(); - //return count; - return 0; + 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; + } + } } - */ } \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index df8f95b9..378813ea 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -29,6 +29,8 @@ namespace pimoroni { static const uint8_t ENC_CHANNEL = 1; static const uint8_t ENC_TERM_A = 12; static const uint8_t ENC_TERM_B = 3; + 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; @@ -73,9 +75,14 @@ namespace pimoroni { private: IOExpander ioe; IS31FL3731 led_ring; - //Direction direction = DEFAULT_DIRECTION; - uint interrupt_pin = PIN_UNUSED; // A local copy of the value passed to the IOExpander, used in initialisation + 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 @@ -109,11 +116,11 @@ namespace pimoroni { // Encoder breakout specific bool pressed(uint button); - int count(); - int delta(); - int step(); - int turn(); + int16_t count(); + int16_t delta(); void zero(); + int16_t step(); + int16_t turn(); float revolutions(); float degrees(); float radians(); @@ -131,6 +138,9 @@ namespace pimoroni { void gpio_pin_value(int gpio, int value, bool load = true, bool wait_for_load = false); void gpio_pwm_load(bool wait_for_load = false); int gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false); + + private: + void take_encoder_reading(); }; } \ No newline at end of file diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c index dabb5301..06e1de3c 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.c @@ -5,7 +5,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// /***** Methods *****/ -MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_set_address_obj, 2, BreakoutEncoderWheel_set_address); +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); @@ -32,7 +32,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(BreakoutEncoderWheel_gpio_pwm_frequency_obj, 2, Break /***** Binding of Methods *****/ STATIC const mp_rom_map_elem_t BreakoutEncoderWheel_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_set_address), MP_ROM_PTR(&BreakoutEncoderWheel_set_address_obj) }, + { 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) }, @@ -92,7 +92,7 @@ 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_I2C_ADDR), MP_ROM_INT(0x13) }, + { 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) }, diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 809848ed..c3b678b5 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -48,7 +48,7 @@ mp_obj_t BreakoutEncoderWheel_make_new(const mp_obj_type_t *type, size_t n_args, } /***** Methods *****/ -mp_obj_t BreakoutEncoderWheel_set_address(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +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 }, diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h index c03926f2..069e5126 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.h @@ -6,7 +6,7 @@ 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_address(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_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); From 226e7507dd50370fa21246a04d36d1cb0e3a936d Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Tue, 2 May 2023 22:58:38 +0100 Subject: [PATCH 14/25] Fix --- micropython/examples/breakout_encoder_wheel/led_rainbow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/examples/breakout_encoder_wheel/led_rainbow.py b/micropython/examples/breakout_encoder_wheel/led_rainbow.py index 239f03f5..8010c10c 100644 --- a/micropython/examples/breakout_encoder_wheel/led_rainbow.py +++ b/micropython/examples/breakout_encoder_wheel/led_rainbow.py @@ -33,7 +33,7 @@ def sleep_until(end_time): if time_to_sleep > 0: time.sleep_us(time_to_sleep) - + # Make rainbows while True: From e3f9f14dcf9ed03480ecb9dc31b16a9569592773 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 3 May 2023 12:34:59 +0100 Subject: [PATCH 15/25] Fix ioe reset timing out too early, and encoder reversed --- drivers/ioexpander/ioexpander.cpp | 2 +- drivers/ioexpander/ioexpander.hpp | 2 ++ libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/ioexpander/ioexpander.cpp b/drivers/ioexpander/ioexpander.cpp index dd98a1ea..ddf20c1c 100644 --- a/drivers/ioexpander/ioexpander.cpp +++ b/drivers/ioexpander/ioexpander.cpp @@ -366,7 +366,7 @@ namespace pimoroni { // Wait for a register to read its initialised value while(check_reset() != 0x78) { sleep_ms(1); - if(millis() - start_time >= timeout) { + if(millis() - start_time >= RESET_TIMEOUT_MS) { if(debug) printf("Timed out waiting for Reset!"); return false; diff --git a/drivers/ioexpander/ioexpander.hpp b/drivers/ioexpander/ioexpander.hpp index 3143ab38..88cb9e04 100644 --- a/drivers/ioexpander/ioexpander.hpp +++ b/drivers/ioexpander/ioexpander.hpp @@ -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; diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index 378813ea..ecd229c9 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -27,8 +27,8 @@ namespace pimoroni { private: static const uint8_t ENC_CHANNEL = 1; - static const uint8_t ENC_TERM_A = 12; - static const uint8_t ENC_TERM_B = 3; + 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; From 9f925b52598ccb8f55d6e36706cb9a76d0fbe83e Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 3 May 2023 17:22:58 +0100 Subject: [PATCH 16/25] Ported most encoder wheel examples to C++ --- examples/CMakeLists.txt | 1 + .../breakout_encoder_wheel/CMakeLists.txt | 8 + .../buttons/CMakeLists.txt | 13 ++ .../buttons/buttons.cpp | 95 ++++++++++ .../chase_game/CMakeLists.txt | 13 ++ .../chase_game/chase_game.cpp | 147 +++++++++++++++ .../clock/CMakeLists.txt | 14 ++ .../breakout_encoder_wheel/clock/clock.cpp | 113 ++++++++++++ .../colour_picker/CMakeLists.txt | 13 ++ .../colour_picker/colour_picker.cpp | 167 ++++++++++++++++++ .../encoder/CMakeLists.txt | 13 ++ .../encoder/encoder.cpp | 59 +++++++ .../led_rainbow/CMakeLists.txt | 13 ++ .../led_rainbow/led_rainbow.cpp | 53 ++++++ .../stop_watch/CMakeLists.txt | 13 ++ .../stop_watch/stop_watch.cpp | 150 ++++++++++++++++ .../breakout_encoder_wheel.cpp | 4 +- .../breakout_encoder_wheel.hpp | 17 +- .../breakout_encoder_wheel/buttons.py | 2 - 19 files changed, 904 insertions(+), 4 deletions(-) create mode 100644 examples/breakout_encoder_wheel/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/buttons/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/buttons/buttons.cpp create mode 100644 examples/breakout_encoder_wheel/chase_game/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/chase_game/chase_game.cpp create mode 100644 examples/breakout_encoder_wheel/clock/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/clock/clock.cpp create mode 100644 examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp create mode 100644 examples/breakout_encoder_wheel/encoder/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/encoder/encoder.cpp create mode 100644 examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp create mode 100644 examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1ba0f213..557ae24b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(breakout_dotmatrix) add_subdirectory(breakout_encoder) +add_subdirectory(breakout_encoder_wheel) add_subdirectory(breakout_ioexpander) add_subdirectory(breakout_ltr559) add_subdirectory(breakout_colourlcd160x80) diff --git a/examples/breakout_encoder_wheel/CMakeLists.txt b/examples/breakout_encoder_wheel/CMakeLists.txt new file mode 100644 index 00000000..5d4150ea --- /dev/null +++ b/examples/breakout_encoder_wheel/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(buttons) +add_subdirectory(chase_game) +add_subdirectory(clock) +add_subdirectory(colour_picker) +add_subdirectory(encoder) +#add_subdirectory(gpio_pwm) +add_subdirectory(led_rainbow) +add_subdirectory(stop_watch) \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/buttons/CMakeLists.txt b/examples/breakout_encoder_wheel/buttons/CMakeLists.txt new file mode 100644 index 00000000..b3a477e8 --- /dev/null +++ b/examples/breakout_encoder_wheel/buttons/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_buttons) +add_executable(${OUTPUT_NAME} buttons.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/buttons/buttons.cpp b/examples/breakout_encoder_wheel/buttons/buttons.cpp new file mode 100644 index 00000000..0b7b42c2 --- /dev/null +++ b/examples/breakout_encoder_wheel/buttons/buttons.cpp @@ -0,0 +1,95 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +A demonstration of reading the 5 buttons on Encoder Wheel. +*/ + +// Constants +const std::string BUTTON_NAMES[] = {"Up", "Down", "Left", "Right", "Centre"}; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +bool last_pressed[NUM_BUTTONS] = {false, false, false, false, false}; +bool pressed[NUM_BUTTONS] = {false, false, false, false, false}; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + // Read all of the encoder wheel's buttons + for(int b = 0 ; b < NUM_BUTTONS; b++) { + pressed[b] = wheel.pressed(b); + if(pressed[b] != last_pressed[b]) { + printf("%s %s\n", BUTTON_NAMES[b].c_str(), pressed[b] ? "Pressed" : "Released"); + } + last_pressed[b] = pressed[b]; + } + + // Clear the LED ring + wheel.clear(); + + for(int i = 0; i < NUM_LEDS; i++) { + if(i % 6 == 3) { + wheel.set_rgb(i, 64, 64, 64); + } + } + + // If up is pressed, set the top LEDs to yellow + if(pressed[UP]) { + int mid = NUM_LEDS; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 255, 255, 0); + } + } + + // If right is pressed, set the right LEDs to red + if(pressed[RIGHT]) { + int mid = NUM_LEDS / 4; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 255, 0, 0); + } + } + + // If down is pressed, set the bottom LEDs to green + if(pressed[DOWN]) { + int mid = NUM_LEDS / 2; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 0, 255, 0); + } + } + + // If left is pressed, set the left LEDs to blue + if(pressed[LEFT]) { + int mid = (NUM_LEDS * 3) / 4; + for(int i = mid - 2; i < mid + 3; i++) { + wheel.set_rgb(i % NUM_LEDS, 0, 0, 255); + } + } + + // If centre is pressed, set the diagonal LEDs to half white + if(pressed[CENTRE]) { + for(int i = 0; i < NUM_LEDS; i++) { + if(i % 6 >= 2 && i % 6 <= 4) { + wheel.set_rgb(i, 128, 128, 128); + } + } + } + wheel.show(); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/chase_game/CMakeLists.txt b/examples/breakout_encoder_wheel/chase_game/CMakeLists.txt new file mode 100644 index 00000000..367d26ec --- /dev/null +++ b/examples/breakout_encoder_wheel/chase_game/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_chase_game) +add_executable(${OUTPUT_NAME} chase_game.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/chase_game/chase_game.cpp b/examples/breakout_encoder_wheel/chase_game/chase_game.cpp new file mode 100644 index 00000000..d1658a4c --- /dev/null +++ b/examples/breakout_encoder_wheel/chase_game/chase_game.cpp @@ -0,0 +1,147 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +A simple alignment game. Use Encoder Wheel's rotary dial to align the coloured band +to the white goal. The closer to the goal, the greener your coloured band will be. +When you reach the goal, the goal will move to a new random position. +*/ + +// The band colour hues to show in Angle mode +constexpr float GOAL_HUE = 0.333f; +constexpr float FAR_HUE = 0.0f; + +// The width and colour settings for the band +constexpr float BAND_WIDTH = 5.0f; +constexpr float BAND_SATURATION = 1.0f; +constexpr float BAND_IN_GOAL_SATURATION = 0.5f; +constexpr float BAND_BRIGHTNESS = 1.0f; + +// The width and colour settings for the goal +// Goal should be wider than the band by a small amount +constexpr float GOAL_MARGIN = 1.0f; +constexpr float GOAL_WIDTH = BAND_WIDTH + (2.0f * GOAL_MARGIN); +constexpr float GOAL_BRIGHTNESS = 0.4f; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float goal_position = 0.0f; +int16_t band_position = 0; + + +// Maps a value from one range to another +float map(float x, float in_min, float in_max, float out_min, float out_max) { + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// Shows a band and goal with the given widths at the positions on the strip +void colour_band(float centre_position, float width, float goal_position, float goal_width, float hue) { + if(centre_position >= 0.0f && width > 0.0f && goal_width > 0.0) { + float band_start = centre_position - (width / 2); + float band_end = centre_position + (width / 2); + float band_centre = centre_position; + + float goal_start = goal_position - (goal_width / 2); + float goal_end = goal_position + (goal_width / 2); + + // Go through each led in the strip + for(int i = 0; i < NUM_LEDS; i++) { + // Set saturation and brightness values for if the led is inside or outside of the goal + float saturation = BAND_SATURATION; + float brightness = 0.0f; + + if(i >= goal_start && i < goal_end) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + if(goal_end >= NUM_LEDS && i + NUM_LEDS < goal_end) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + if(goal_start < 0 && i - NUM_LEDS >= goal_start) { + saturation = BAND_IN_GOAL_SATURATION; + brightness = GOAL_BRIGHTNESS; + } + + float val = brightness; + float sat = 0.0f; + if(i >= band_start && i < band_end) { + // Inside the band + if(i < band_centre) { + // Transition into the band + val = map(i, band_centre, band_start, BAND_BRIGHTNESS, brightness); + sat = map(i, band_centre, band_start, BAND_SATURATION, saturation); + } + else { + val = map(i, band_centre, band_end, BAND_BRIGHTNESS, brightness); + sat = map(i, band_centre, band_end, BAND_SATURATION, saturation); + } + } + else if(band_end >= NUM_LEDS && i + NUM_LEDS < band_end && i < band_centre) { + val = map(i + NUM_LEDS, band_centre, band_end, BAND_BRIGHTNESS, brightness); + sat = map(i + NUM_LEDS, band_centre, band_end, BAND_SATURATION, saturation); + } + else if(band_start < 0 && i - NUM_LEDS >= band_start && i >= band_centre) { + val = map(i - NUM_LEDS, band_centre, band_start, BAND_BRIGHTNESS, brightness); + sat = map(i - NUM_LEDS, band_centre, band_start, BAND_SATURATION, saturation); + } + //else { + // Outside of the band + //} + wheel.set_hsv(i, hue, sat, val); + } + wheel.show(); + } +} + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + band_position = wheel.step(); + + // Convert the difference between the band and goal positions into a colour hue + float diff1, diff2; + if(band_position > goal_position) { + diff1 = band_position - goal_position; + diff2 = (goal_position + NUM_LEDS) - band_position; + } + else { + diff1 = goal_position - band_position; + diff2 = (band_position + NUM_LEDS) - goal_position; + } + + float position_diff = MIN(diff1, diff2); + float hue = map(position_diff, 0, NUM_LEDS / 2.0f, GOAL_HUE, FAR_HUE); + + // Convert the band and goal positions to positions on the LED strip + float strip_band_position = map(band_position, 0, NUM_LEDS, 0.0f, (float)NUM_LEDS); + float strip_goal_position = map(goal_position, 0, NUM_LEDS, 0.0f, (float)NUM_LEDS); + + // Draw the band and goal + colour_band(strip_band_position, BAND_WIDTH, strip_goal_position, GOAL_WIDTH, hue); + + // Check if the band is within the goal, and if so, set a new goal + if(band_position >= goal_position - GOAL_MARGIN && band_position <= goal_position + GOAL_MARGIN) + goal_position = rand() % NUM_LEDS; + if(band_position >= NUM_LEDS && band_position + NUM_LEDS < goal_position + GOAL_MARGIN) + goal_position = rand() % NUM_LEDS; + if(goal_position - GOAL_MARGIN < 0 && band_position - NUM_LEDS >= goal_position + GOAL_MARGIN) + goal_position = rand() % NUM_LEDS; + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/clock/CMakeLists.txt b/examples/breakout_encoder_wheel/clock/CMakeLists.txt new file mode 100644 index 00000000..09e3d221 --- /dev/null +++ b/examples/breakout_encoder_wheel/clock/CMakeLists.txt @@ -0,0 +1,14 @@ +set(OUTPUT_NAME encoderwheel_clock) +add_executable(${OUTPUT_NAME} clock.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + hardware_rtc + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/clock/clock.cpp b/examples/breakout_encoder_wheel/clock/clock.cpp new file mode 100644 index 00000000..2f8e3712 --- /dev/null +++ b/examples/breakout_encoder_wheel/clock/clock.cpp @@ -0,0 +1,113 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" +#include "hardware/rtc.h" +#include "pico/util/datetime.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Displays a 12 hour clock on Encoder Wheel's LED ring, getting time from the system. +*/ + +// Datetime Indices +const uint HOUR = 4; +const uint MINUTE = 5; +const uint SECOND = 6; + +// Constants +constexpr float BRIGHTNESS = 1.0f; // The brightness of the LEDs +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +// Handy values for the number of milliseconds +constexpr float MILLIS_PER_SECOND = 1000; +constexpr float MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60; +constexpr float MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; +constexpr float MILLIS_PER_HALF_DAY = MILLIS_PER_HOUR * 12; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + + +// Calculates the brightness of an LED based on its index and a position along the LED ring +int led_brightness_at(int led, float position, float half_width = 1.0f, float span = 1.0f) { + float brightness = 0.0f; + float upper = position + half_width; + float lower = position - half_width; + if(led > position) + brightness = CLAMP((upper - led) / span, 0.0f, 1.0f); + else + brightness = CLAMP((led - lower) / span, 0.0f, 1.0f); + + // Handle the LEDs being in a circle + if(upper >= NUM_LEDS) + brightness = CLAMP(((upper - NUM_LEDS) - led) / span, brightness, 1.0f); + else if(lower < 0.0f) + brightness = CLAMP((led - (lower + NUM_LEDS)) / span, brightness, 1.0f); + + return (int)(brightness * BRIGHTNESS * 255); +} + + +int main() { + stdio_init_all(); + + // Start on Thursday 4th of May 2023 14:20:00 + datetime_t now = { + .year = 2023, + .month = 05, + .day = 04, + .dotw = 4, // 0 is Sunday, so 4 is Thursday + .hour = 14, + .min = 20, + .sec = 00 + }; + + // Start the RTC + rtc_init(); + rtc_set_datetime(&now); + + // clk_sys is >2000x faster than clk_rtc, so datetime is not updated immediately when rtc_get_datetime() is called. + // tbe delay is up to 3 RTC clock cycles (which is 64us with the default clock settings) + sleep_us(64); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + // Get the current system time + rtc_get_datetime(&now); + + // Convert the seconds, minutes, and hours into milliseconds (this is done to give a smoother animation, particularly for the seconds hand) + uint sec_as_millis = (now.sec * MILLIS_PER_SECOND); + uint min_as_millis = (now.min * MILLIS_PER_MINUTE) + sec_as_millis; + uint hour_as_millis = ((now.hour % 12) * MILLIS_PER_HOUR) + min_as_millis; + + // Calculate the position on the LED ring that the, second, minute, and hour hands should be + float sec_pos = MIN(sec_as_millis / MILLIS_PER_MINUTE, 1.0f) * NUM_LEDS; + float min_pos = MIN(min_as_millis / MILLIS_PER_HOUR, 1.0f) * NUM_LEDS; + float hour_pos = MIN(hour_as_millis / MILLIS_PER_HALF_DAY, 1.0f) * NUM_LEDS; + + for(int i = 0; i < NUM_LEDS; i++) { + // Turn on the LEDs close to the position of the current second, minute, and hour + int r = led_brightness_at(i, sec_pos); + int g = led_brightness_at(i, min_pos); + int b = led_brightness_at(i, hour_pos); + wheel.set_rgb(i, r, g, b); + } + wheel.show(); + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt b/examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt new file mode 100644 index 00000000..05266590 --- /dev/null +++ b/examples/breakout_encoder_wheel/colour_picker/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_colour_picker) +add_executable(${OUTPUT_NAME} colour_picker.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp b/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp new file mode 100644 index 00000000..bf0f300e --- /dev/null +++ b/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp @@ -0,0 +1,167 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Create a colour wheel on the Encoder Wheel's LED ring, and use all functions of the wheel to interact with it. + +Rotate the wheel to select a Hue +Press the up direction to increase Brightness +Press the down direction to decrease Brightness +Press the left direction to decrease Saturation +Press the right direction to increase Saturation +Press the centre to hide the selection marker +*/ + +// Constants +constexpr float BRIGHTNESS_STEP = 0.02f; // How much to increase or decrease the brightness each update +constexpr float SATURATION_STEP = 0.02f; // How much to increase or decrease the saturation each update +const uint UPDATES = 50; // How many times to update the LEDs per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float brightness = 1.0f; +float saturation = 1.0f; +int position = 0; +bool changed = true; +bool last_centre_pressed = false; + +// Struct for storing RGB values +struct Pixel { + uint8_t r; + uint8_t g; + uint8_t b; + Pixel() : r(0), g(0), b(0) {}; + Pixel(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}; +}; + +// Basic function to convert Hue, Saturation and Value to an RGB colour +Pixel hsv_to_rgb(float h, float s, float v) { + if(h < 0.0f) { + h = 1.0f + fmodf(h, 1.0f); + } + + int i = int(h * 6); + float f = h * 6 - i; + + v = v * 255.0f; + + float sv = s * v; + float fsv = f * sv; + + auto p = uint8_t(-sv + v); + auto q = uint8_t(-fsv + v); + auto t = uint8_t(fsv - sv + v); + + uint8_t bv = uint8_t(v); + + switch (i % 6) { + default: + case 0: return Pixel(bv, t, p); + case 1: return Pixel(q, bv, p); + case 2: return Pixel(p, bv, t); + case 3: return Pixel(p, q, bv); + case 4: return Pixel(t, p, bv); + case 5: return Pixel(bv, p, q); + } +} + +// Simple function to clamp a value between 0.0 and 1.0 +float clamp01(float value) { + return MAX(MIN(value, 1.0f), 0.0f); +} + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + // If up is pressed, increase the brightness + if(wheel.pressed(UP)) { + brightness += BRIGHTNESS_STEP; + changed = true; // Trigger a change + } + + // If down is pressed, decrease the brightness + if(wheel.pressed(DOWN)) { + brightness -= BRIGHTNESS_STEP; + changed = true; // Trigger a change + } + + // If right is pressed, increase the saturation + if(wheel.pressed(RIGHT)) { + saturation += SATURATION_STEP; + changed = true; // Trigger a change + } + + // If left is pressed, decrease the saturation + if(wheel.pressed(LEFT)) { + saturation -= SATURATION_STEP; + changed = true; // Trigger a change + } + + // Limit the brightness and saturation between 0.0 and 1.0 + brightness = clamp01(brightness); + saturation = clamp01(saturation); + + // Check if the encoder has been turned + if(wheel.delta() != 0) { + // Update the position based on the count change + position = wheel.step(); + changed = true; // Trigger a change + } + + // If centre is pressed, trigger a change + bool centre_pressed = wheel.pressed(CENTRE); + if(centre_pressed != last_centre_pressed) { + changed = true; + } + last_centre_pressed = centre_pressed; + + // Was a change triggered? + if(changed) { + // Print the colour at the current hue, saturation, and brightness + Pixel pixel = hsv_to_rgb((float)position / NUM_LEDS, saturation, brightness); + printf("Colour Code = #%02x%02x%02x\n", pixel.r, pixel.g, pixel.b); + + // Set the LED at the current position to either the actual colour, + // or an inverted version to show a "selection marker" + if(centre_pressed) + wheel.set_rgb(position, pixel.r, pixel.g, pixel.b); + else + wheel.set_rgb(position, 255 - pixel.r, 255 - pixel.g, 255 - pixel.b); + + // Set the LEDs below the current position + for(int i = 0; i < position; i++) { + wheel.set_hsv(i, (float)i / NUM_LEDS, saturation, brightness); + } + + // Set the LEDs after the current position + for(int i = position + 1; i < NUM_LEDS; i++) { + wheel.set_hsv(i, (float)i / NUM_LEDS, saturation, brightness); + } + wheel.show(); + changed = false; + } + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/encoder/CMakeLists.txt b/examples/breakout_encoder_wheel/encoder/CMakeLists.txt new file mode 100644 index 00000000..4da72ee5 --- /dev/null +++ b/examples/breakout_encoder_wheel/encoder/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_encoder) +add_executable(${OUTPUT_NAME} encoder.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/encoder/encoder.cpp b/examples/breakout_encoder_wheel/encoder/encoder.cpp new file mode 100644 index 00000000..5784519d --- /dev/null +++ b/examples/breakout_encoder_wheel/encoder/encoder.cpp @@ -0,0 +1,59 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +A demonstration of reading the rotary dial of the Encoder Wheel breakout. +*/ + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +int position = 0; +float hue = 0.0f; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + + // Set the first LED + wheel.clear(); + wheel.set_hsv(position, hue, 1.0f, 1.0f); + wheel.show(); + + // Loop forever + while(true) { + // Has the dial been turned since the last time we checked? + int16_t change = wheel.delta(); + if(change != 0) { + // Print out the direction the dial was turned, and the count + if(change > 0) + printf("Clockwise, Count = %d\n", wheel.count()); + else + printf("Counter Clockwise, Count = %d\n", wheel.count()); + + // Record the new position (from 0 to 23) + position = wheel.step(); + + // Record a colour hue from 0.0 to 1.0 + hue = fmodf(wheel.revolutions(), 1.0f); + + // Set the LED at the new position to the new hue + wheel.clear(); + wheel.set_hsv(position, hue, 1.0f, 1.0f); + wheel.show(); + } + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt b/examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt new file mode 100644 index 00000000..87151a4d --- /dev/null +++ b/examples/breakout_encoder_wheel/led_rainbow/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_led_rainbow) +add_executable(${OUTPUT_NAME} led_rainbow.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp b/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp new file mode 100644 index 00000000..429f56fa --- /dev/null +++ b/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Displays a rotating rainbow pattern on Encoder Wheel's LED ring. +*/ + +// Constants +constexpr float SPEED = 5.0f; // The speed that the LEDs will cycle at +constexpr float BRIGHTNESS = 1.0f; // The brightness of the LEDs +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float offset = 0.0; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Loop forever + while(true) { + + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + offset += SPEED / 1000.0f; + + // Update all the LEDs + for(int i = 0; i < NUM_LEDS; i++) { + float hue = (float)i / NUM_LEDS; + wheel.set_hsv(i, hue + offset, 1.0, BRIGHTNESS); + } + wheel.show(); + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + } + + return 0; +} \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt b/examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt new file mode 100644 index 00000000..54eb41d1 --- /dev/null +++ b/examples/breakout_encoder_wheel/stop_watch/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_stop_watch) +add_executable(${OUTPUT_NAME} stop_watch.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp b/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp new file mode 100644 index 00000000..f39e5b47 --- /dev/null +++ b/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp @@ -0,0 +1,150 @@ +#include +#include +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Display a circular stop-watch on the Encoder Wheel's LED ring. + +Press the centre button to start the stopwatch, then again to pause and resume. +*/ + +// Constants +constexpr float BRIGHTNESS = 1.0f; // The brightness of the LEDs +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint MINUTE_UPDATES = UPDATES * 60; // How many times the LEDs will be updated per minute +const uint HOUR_UPDATES = MINUTE_UPDATES * 60; // How many times the LEDs will be updated per hour +const uint UPDATE_RATE_US = 1000000 / UPDATES; + +constexpr float IDLE_PULSE_MIN = 0.2f; // The brightness (between 0.0 and 1.0) that the idle pulse will go down to +constexpr float IDLE_PULSE_MAX = 0.5f; // The brightness (between 0.0 and 1.0) that the idle pulse will go up to +constexpr float IDLE_PULSE_TIME = 2.0f; // The time (in seconds) to perform a complete idle pulse +constexpr uint UPDATES_PER_PULSE = IDLE_PULSE_TIME * UPDATES; + +// The state constants used for program flow +enum State { + IDLE = 0, + COUNTING, + PAUSED +}; + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +State state = IDLE; +uint idle_update = 0; +uint second_update = 0; +uint minute_update = 0; +uint hour_update = 0; +bool last_centre_pressed = false; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + // Record the current time + absolute_time_t current_time = get_absolute_time(); + + // Run the update loop forever + while(true) { + + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + // Read whether or not the wheen centre has been pressed + bool centre_pressed = wheel.pressed(CENTRE); + if(centre_pressed && centre_pressed != last_centre_pressed) { + switch(state) { + case IDLE: // If we're currently idle, switch to counting + second_update = 0; + minute_update = 0; + hour_update = 0; + state = COUNTING; + break; + case COUNTING: // If we're counting, switch to paused + state = PAUSED; + break; + case PAUSED: // If we're paused, switch back to counting + state = COUNTING; + } + } + last_centre_pressed = centre_pressed; + + switch(state) { + // If we're idle, perform a pulsing animation to show the stopwatch is ready to go + case IDLE: + { + float percent_along = MIN((float)idle_update / (float)UPDATES_PER_PULSE, 1.0f); + float brightness = ((cosf(percent_along * M_PI * 2.0f) + 1.0f) / 2.0f) * ((IDLE_PULSE_MAX - IDLE_PULSE_MIN)) + IDLE_PULSE_MIN; + // Update all the LEDs + for(int i = 0; i < NUM_LEDS; i++) { + wheel.set_hsv(i, 0.0, 0.0, brightness); + } + wheel.show(); + + // Advance to the next update, wrapping around to zero if at the end + idle_update += 1; + if(idle_update >= UPDATES_PER_PULSE) { + idle_update = 0; + } + } + break; + + // If we're counting, perform the stopwatch animation + case COUNTING: + { + // Calculate how many LED channels should light, as a proportion of a second, minute, and hour + float r_to_light = MIN((float)second_update / UPDATES, 1.0f) * 24.0f; + float g_to_light = MIN((float)minute_update / MINUTE_UPDATES, 1.0f) * 24.0f; + float b_to_light = MIN((float)hour_update / HOUR_UPDATES, 1.0f) * 24.0f; + + // Set each LED, such that ones below the current time are fully lit, ones after + // are off, and the one at the transition is at a percentage of the brightness + for(int i = 0; i < NUM_LEDS; i++) { + int r = (int)(CLAMP(r_to_light - i, 0.0f, 1.0f) * BRIGHTNESS * 255.0f); + int g = (int)(CLAMP(g_to_light - i, 0.0f, 1.0f) * BRIGHTNESS * 255.0f); + int b = (int)(CLAMP(b_to_light - i, 0.0f, 1.0f) * BRIGHTNESS * 255.0f); + wheel.set_rgb(i, r, g, b); + } + wheel.show(); + + // Advance the second updates count, wrapping around to zero if at the end + second_update += 1; + if(second_update >= UPDATES) { + second_update = 0; + } + + // Advance the minute updates count, wrapping around to zero if at the end + minute_update += 1; + if(minute_update >= MINUTE_UPDATES) { + minute_update = 0; + } + + // Advance the hour updates count, wrapping around to zero if at the end + hour_update += 1; + if(hour_update >= HOUR_UPDATES) { + hour_update = 0; + } + } + break; + + case PAUSED: + // Do nothing + break; + } + + // Sleep until the next update, accounting for how long the above operations took to perform + current_time = delayed_by_us(start_time, UPDATE_RATE_US); + sleep_until(current_time); + } + } + + return 0; +} \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index a1e684eb..81f86635 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -3,6 +3,7 @@ #include namespace pimoroni { +namespace encoderwheel { bool BreakoutEncoderWheel::init(bool skip_chip_id_check) { bool success = false; @@ -151,7 +152,7 @@ namespace pimoroni { void BreakoutEncoderWheel::set_hsv(int index, float h, float s, float v) { int r, g, b; if(h < 0.0f) { - h = 1.0f + fmod(h, 1.0f); + h = 1.0f + fmodf(h, 1.0f); } int i = int(h * 6); @@ -244,4 +245,5 @@ namespace pimoroni { } } } +} } \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index ecd229c9..81263a09 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -5,6 +5,21 @@ #include "common/pimoroni_common.hpp" namespace pimoroni { +namespace encoderwheel { + static const uint8_t NUM_LEDS = 24; + static const uint8_t NUM_BUTTONS = 5; + static const uint8_t NUM_GPIOS = 3; + + static const uint8_t UP = 0; + static const uint8_t DOWN = 1; + static const uint8_t LEFT = 2; + static const uint8_t RIGHT = 3; + static const uint8_t CENTRE = 4; + + static const uint8_t GP7 = 7; + static const uint8_t GP8 = 8; + static const uint8_t GP9 = 9; + static const uint8_t GPIOS[] = {GP7, GP8, GP9}; class BreakoutEncoderWheel { struct RGBLookup { @@ -142,5 +157,5 @@ namespace pimoroni { private: void take_encoder_reading(); }; - +} } \ No newline at end of file diff --git a/micropython/examples/breakout_encoder_wheel/buttons.py b/micropython/examples/breakout_encoder_wheel/buttons.py index 266ddc4c..65d70d69 100644 --- a/micropython/examples/breakout_encoder_wheel/buttons.py +++ b/micropython/examples/breakout_encoder_wheel/buttons.py @@ -12,8 +12,6 @@ PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} # Constants BUTTON_NAMES = ["Up", "Down", "Left", "Right", "Centre"] -UPDATES = 50 # How many times the buttons will be checked and LEDs updated, per second -UPDATE_RATE = 1 / UPDATES # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) From e0a405739c1e66f72741e77e7031d188cbbe65d2 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 3 May 2023 17:41:18 +0100 Subject: [PATCH 17/25] Fix namespace issue --- .../modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index c3b678b5..1c7f40b7 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -4,6 +4,7 @@ using namespace pimoroni; +using namespace encoderwheel; extern "C" { #include "breakout_encoder_wheel.h" From 4dadeb0d4dd87df27bb2a36418986577dc1935a3 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 4 May 2023 11:16:55 +0100 Subject: [PATCH 18/25] Add c++ examples readme --- examples/breakout_encoder_wheel/README.md | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 examples/breakout_encoder_wheel/README.md diff --git a/examples/breakout_encoder_wheel/README.md b/examples/breakout_encoder_wheel/README.md new file mode 100644 index 00000000..283a5d9c --- /dev/null +++ b/examples/breakout_encoder_wheel/README.md @@ -0,0 +1,70 @@ +# Encoder Wheel Breakout C++ Examples + +- [Function Examples](#function-examples) + - [Buttons](#buttons) + - [Encoder](#encoder) +- [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. + + +## 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. From 862806f357cd35dca7b00fc10e977699fba70bf7 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 4 May 2023 14:10:12 +0100 Subject: [PATCH 19/25] Simplified example I2C setup --- .../examples/breakout_encoder_wheel/buttons.py | 6 ++---- .../examples/breakout_encoder_wheel/chase_game.py | 6 ++---- .../examples/breakout_encoder_wheel/clock.py | 9 ++++----- .../breakout_encoder_wheel/colour_picker.py | 7 ++----- .../examples/breakout_encoder_wheel/encoder.py | 3 ++- .../examples/breakout_encoder_wheel/gpio_pwm.py | 13 ++++++------- .../examples/breakout_encoder_wheel/led_rainbow.py | 7 ++----- .../examples/breakout_encoder_wheel/stop_watch.py | 8 +++----- 8 files changed, 23 insertions(+), 36 deletions(-) diff --git a/micropython/examples/breakout_encoder_wheel/buttons.py b/micropython/examples/breakout_encoder_wheel/buttons.py index 65d70d69..48a63c5f 100644 --- a/micropython/examples/breakout_encoder_wheel/buttons.py +++ b/micropython/examples/breakout_encoder_wheel/buttons.py @@ -1,4 +1,5 @@ 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 """ @@ -7,14 +8,11 @@ A demonstration of reading the 5 buttons on Encoder Wheel. Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - # Constants BUTTON_NAMES = ["Up", "Down", "Left", "Right", "Centre"] # Create a new BreakoutEncoderWheel -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # Variables diff --git a/micropython/examples/breakout_encoder_wheel/chase_game.py b/micropython/examples/breakout_encoder_wheel/chase_game.py index e621c9b7..9c0bf839 100644 --- a/micropython/examples/breakout_encoder_wheel/chase_game.py +++ b/micropython/examples/breakout_encoder_wheel/chase_game.py @@ -1,5 +1,6 @@ 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 """ @@ -10,10 +11,7 @@ When you reach the goal, the goal will move to a new random position. Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # The band colour hues to show in Angle mode diff --git a/micropython/examples/breakout_encoder_wheel/clock.py b/micropython/examples/breakout_encoder_wheel/clock.py index 6016763a..547fbac2 100644 --- a/micropython/examples/breakout_encoder_wheel/clock.py +++ b/micropython/examples/breakout_encoder_wheel/clock.py @@ -1,7 +1,7 @@ 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 """ @@ -10,9 +10,6 @@ Displays a 12 hour clock on Encoder Wheel's LED ring, getting time from the syst Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - # Datetime Indices HOUR = 4 MINUTE = 5 @@ -30,8 +27,10 @@ MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60 MILLIS_PER_HALF_DAY = MILLIS_PER_HOUR * 12 # Create a new BreakoutEncoderWheel -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) + +# Access the built-in RTC rtc = RTC() diff --git a/micropython/examples/breakout_encoder_wheel/colour_picker.py b/micropython/examples/breakout_encoder_wheel/colour_picker.py index dcaef39a..c6e11cf3 100644 --- a/micropython/examples/breakout_encoder_wheel/colour_picker.py +++ b/micropython/examples/breakout_encoder_wheel/colour_picker.py @@ -1,6 +1,6 @@ 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 """ @@ -16,9 +16,6 @@ Press the centre to hide the selection marker Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - # 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 @@ -26,7 +23,7 @@ UPDATES = 50 # How many times to update the LEDs per second UPDATE_RATE_US = 1000000 // UPDATES # Create a new BreakoutEncoderWheel -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # Variables diff --git a/micropython/examples/breakout_encoder_wheel/encoder.py b/micropython/examples/breakout_encoder_wheel/encoder.py index 699f9e8e..b35b59ab 100644 --- a/micropython/examples/breakout_encoder_wheel/encoder.py +++ b/micropython/examples/breakout_encoder_wheel/encoder.py @@ -1,4 +1,5 @@ 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 """ @@ -11,7 +12,7 @@ PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} # Create a new BreakoutEncoderWheel -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # Variables diff --git a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py index c7904f5f..832fd1c3 100644 --- a/micropython/examples/breakout_encoder_wheel/gpio_pwm.py +++ b/micropython/examples/breakout_encoder_wheel/gpio_pwm.py @@ -1,9 +1,9 @@ import math import time - -from breakout_ioexpander import BreakoutIOExpander 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. @@ -11,16 +11,14 @@ 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. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - +# 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(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # Set the PWM frequency for the GPIOs @@ -28,8 +26,9 @@ period = wheel.gpio_pwm_frequency(FREQUENCY) # Set the GPIO pins to PWM outputs for g in GPIOS: - wheel.gpio_pin_mode(g, BreakoutIOExpander.PIN_PWM) + wheel.gpio_pin_mode(g, PWM) +# Variables offset = 0.0 diff --git a/micropython/examples/breakout_encoder_wheel/led_rainbow.py b/micropython/examples/breakout_encoder_wheel/led_rainbow.py index 8010c10c..d6ceea69 100644 --- a/micropython/examples/breakout_encoder_wheel/led_rainbow.py +++ b/micropython/examples/breakout_encoder_wheel/led_rainbow.py @@ -1,6 +1,6 @@ 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 """ @@ -9,9 +9,6 @@ Displays a rotating rainbow pattern on Encoder Wheel's LED ring. Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - # Constants SPEED = 5 # The speed that the LEDs will cycle at BRIGHTNESS = 1.0 # The brightness of the LEDs @@ -19,7 +16,7 @@ UPDATES = 50 # How many times the LEDs will be updated per second UPDATE_RATE_US = 1000000 // UPDATES # Create a new BreakoutEncoderWheel -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # Variables diff --git a/micropython/examples/breakout_encoder_wheel/stop_watch.py b/micropython/examples/breakout_encoder_wheel/stop_watch.py index 3581c91e..19e47356 100644 --- a/micropython/examples/breakout_encoder_wheel/stop_watch.py +++ b/micropython/examples/breakout_encoder_wheel/stop_watch.py @@ -1,7 +1,7 @@ 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 """ @@ -12,9 +12,6 @@ Press the centre button to start the stopwatch, then again to pause and resume. Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - # 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 @@ -29,7 +26,8 @@ UPDATES_PER_PULSE = IDLE_PULSE_TIME * UPDATES IDLE, COUNTING, PAUSED = range(3) # The state constants used for program flow -i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN) +# Create a new BreakoutEncoderWheel +i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) # Variables From 5619274d3d11a3f40f7e77c542db6cf218905780 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 4 May 2023 14:10:39 +0100 Subject: [PATCH 20/25] Added MP readme --- .../modules/breakout_encoder_wheel/README.md | 320 ++++++++++++++++++ .../breakout_ioexpander/breakout_ioexpander.c | 5 + 2 files changed, 325 insertions(+) create mode 100644 micropython/modules/breakout_encoder_wheel/README.md diff --git a/micropython/modules/breakout_encoder_wheel/README.md b/micropython/modules/breakout_encoder_wheel/README.md new file mode 100644 index 00000000..cf7e2387 --- /dev/null +++ b/micropython/modules/breakout_encoder_wheel/README.md @@ -0,0 +1,320 @@ +# Encoder Wheel Breakout (Micropython) + +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 +- [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, ) +``` + +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_encoderwheel 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) +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) +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` diff --git a/micropython/modules/breakout_ioexpander/breakout_ioexpander.c b/micropython/modules/breakout_ioexpander/breakout_ioexpander.c index eece8446..f43032fe 100644 --- a/micropython/modules/breakout_ioexpander/breakout_ioexpander.c +++ b/micropython/modules/breakout_ioexpander/breakout_ioexpander.c @@ -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); From 8966cbf34810aa719e353adaf83704016a69a245 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Thu, 4 May 2023 14:59:01 +0100 Subject: [PATCH 21/25] Added C++ readme --- examples/breakout_encoder_wheel/README.md | 2 +- .../buttons/buttons.cpp | 1 + .../chase_game/chase_game.cpp | 1 + .../breakout_encoder_wheel/clock/clock.cpp | 1 + .../colour_picker/colour_picker.cpp | 1 + .../encoder/encoder.cpp | 1 + .../led_rainbow/led_rainbow.cpp | 1 + .../stop_watch/stop_watch.cpp | 1 + libraries/breakout_encoder_wheel/README.md | 331 ++++++++++++++++++ .../examples/breakout_encoder_wheel/README.md | 2 +- .../modules/breakout_encoder_wheel/README.md | 2 +- 11 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 libraries/breakout_encoder_wheel/README.md diff --git a/examples/breakout_encoder_wheel/README.md b/examples/breakout_encoder_wheel/README.md index 283a5d9c..18c5be99 100644 --- a/examples/breakout_encoder_wheel/README.md +++ b/examples/breakout_encoder_wheel/README.md @@ -1,4 +1,4 @@ -# Encoder Wheel Breakout C++ Examples +# RGB Encoder Wheel Breakout Examples (C++) - [Function Examples](#function-examples) - [Buttons](#buttons) diff --git a/examples/breakout_encoder_wheel/buttons/buttons.cpp b/examples/breakout_encoder_wheel/buttons/buttons.cpp index 0b7b42c2..8f90edff 100644 --- a/examples/breakout_encoder_wheel/buttons/buttons.cpp +++ b/examples/breakout_encoder_wheel/buttons/buttons.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" diff --git a/examples/breakout_encoder_wheel/chase_game/chase_game.cpp b/examples/breakout_encoder_wheel/chase_game/chase_game.cpp index d1658a4c..191ed0a1 100644 --- a/examples/breakout_encoder_wheel/chase_game/chase_game.cpp +++ b/examples/breakout_encoder_wheel/chase_game/chase_game.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" diff --git a/examples/breakout_encoder_wheel/clock/clock.cpp b/examples/breakout_encoder_wheel/clock/clock.cpp index 2f8e3712..0b666e23 100644 --- a/examples/breakout_encoder_wheel/clock/clock.cpp +++ b/examples/breakout_encoder_wheel/clock/clock.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" #include "hardware/rtc.h" diff --git a/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp b/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp index bf0f300e..39f20028 100644 --- a/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp +++ b/examples/breakout_encoder_wheel/colour_picker/colour_picker.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" diff --git a/examples/breakout_encoder_wheel/encoder/encoder.cpp b/examples/breakout_encoder_wheel/encoder/encoder.cpp index 5784519d..a1065faf 100644 --- a/examples/breakout_encoder_wheel/encoder/encoder.cpp +++ b/examples/breakout_encoder_wheel/encoder/encoder.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" diff --git a/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp b/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp index 429f56fa..c269c9fc 100644 --- a/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp +++ b/examples/breakout_encoder_wheel/led_rainbow/led_rainbow.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" diff --git a/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp b/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp index f39e5b47..f5100898 100644 --- a/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp +++ b/examples/breakout_encoder_wheel/stop_watch/stop_watch.cpp @@ -1,5 +1,6 @@ #include #include +#include "pimoroni_i2c.hpp" #include "breakout_encoder_wheel.hpp" #include "time.h" diff --git a/libraries/breakout_encoder_wheel/README.md b/libraries/breakout_encoder_wheel/README.md new file mode 100644 index 00000000..9a18544e --- /dev/null +++ b/libraries/breakout_encoder_wheel/README.md @@ -0,0 +1,331 @@ +# RGB Encoder Wheel Breakout (C++) + +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 +- [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.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, 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_); +``` + +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); + +// 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(ioe_address=0x13, led_address=0x77, interrupt=PIN_UNUSED) +set_ioe_address(address) +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) +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 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` diff --git a/micropython/examples/breakout_encoder_wheel/README.md b/micropython/examples/breakout_encoder_wheel/README.md index 9f901812..c633ec2a 100644 --- a/micropython/examples/breakout_encoder_wheel/README.md +++ b/micropython/examples/breakout_encoder_wheel/README.md @@ -1,4 +1,4 @@ -# Encoder Wheel Breakout Micropython Examples +# RGB Encoder Wheel Breakout Examples (Micropython) - [Function Examples](#function-examples) - [Buttons](#buttons) diff --git a/micropython/modules/breakout_encoder_wheel/README.md b/micropython/modules/breakout_encoder_wheel/README.md index cf7e2387..c6d511f8 100644 --- a/micropython/modules/breakout_encoder_wheel/README.md +++ b/micropython/modules/breakout_encoder_wheel/README.md @@ -1,4 +1,4 @@ -# Encoder Wheel Breakout (Micropython) +# RGB Encoder Wheel Breakout (Micropython) This is the Micropython library reference for the [Pimoroni RGB Encoder Wheel Breakout](https://shop.pimoroni.com/products/rgb-encoder-wheel-breakout). From 653090c89ee4ff23cb5af70d88b41caa22d7c2d6 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 10 May 2023 12:46:00 +0100 Subject: [PATCH 22/25] Exposed support for GPIO pins on encoder wheel --- drivers/ioexpander/ioexpander.cpp | 30 +++++++- drivers/ioexpander/ioexpander.hpp | 8 +- .../breakout_encoder_wheel/CMakeLists.txt | 2 +- .../gpio_pwm/CMakeLists.txt | 13 ++++ .../gpio_pwm/gpio_pwm.cpp | 74 +++++++++++++++++++ .../breakout_encoder_wheel.cpp | 29 +++++--- .../breakout_encoder_wheel.hpp | 32 ++++---- 7 files changed, 153 insertions(+), 35 deletions(-) create mode 100644 examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp diff --git a/drivers/ioexpander/ioexpander.cpp b/drivers/ioexpander/ioexpander.cpp index ddf20c1c..4dfec84c 100644 --- a/drivers/ioexpander/ioexpander.cpp +++ b/drivers/ioexpander/ioexpander.cpp @@ -523,13 +523,35 @@ namespace pimoroni { return divider_good; } - void IOExpander::set_pwm_period(uint16_t value, bool load) { + void IOExpander::set_pwm_period(uint16_t value, bool load, bool wait_for_load) { value &= 0xffff; i2c->reg_write_uint8(address, reg::PWMPL, (uint8_t)(value & 0xff)); i2c->reg_write_uint8(address, reg::PWMPH, (uint8_t)(value >> 8)); if(load) - pwm_load(); + pwm_load(wait_for_load); + } + + uint16_t IOExpander::set_pwm_frequency(float frequency, bool load, bool wait_for_load) { + uint32_t period = (uint32_t)(CLOCK_FREQ / frequency); + if (period / 128 > MAX_PERIOD) { + return MAX_PERIOD; + } + if (period < 2) { + return 2; + } + + uint8_t divider = 1; + while ((period > MAX_PERIOD) && (divider < MAX_DIVIDER)) { + period >>= 1; + divider <<= 1; + } + + period = MIN(period, MAX_PERIOD); // Should be unnecessary because of earlier raised errors, but kept in case + set_pwm_control(divider); + set_pwm_period((uint16_t)(period - 1), load, wait_for_load); + + return (uint16_t)period; } uint8_t IOExpander::get_mode(uint8_t pin) { @@ -701,7 +723,7 @@ namespace pimoroni { } } - void IOExpander::output(uint8_t pin, uint16_t value, bool load) { + void IOExpander::output(uint8_t pin, uint16_t value, bool load, bool wait_for_load) { if(pin < 1 || pin > NUM_PINS) { printf("Pin should be in range 1-14."); return; @@ -717,7 +739,7 @@ namespace pimoroni { i2c->reg_write_uint8(address, io_pin.reg_pwml, (uint8_t)(value & 0xff)); i2c->reg_write_uint8(address, io_pin.reg_pwmh, (uint8_t)(value >> 8)); if(load) - pwm_load(); + pwm_load(wait_for_load); } else { if(value == LOW) { diff --git a/drivers/ioexpander/ioexpander.hpp b/drivers/ioexpander/ioexpander.hpp index 88cb9e04..2a3fb63e 100644 --- a/drivers/ioexpander/ioexpander.hpp +++ b/drivers/ioexpander/ioexpander.hpp @@ -27,6 +27,9 @@ namespace pimoroni { static const uint8_t PIN_MODE_ADC = 0b01010; // ADC, Input-only (high-impedance) static const uint32_t RESET_TIMEOUT_MS = 1000; + static const uint32_t CLOCK_FREQ = 24000000; + static const uint32_t MAX_PERIOD = (1 << 16) - 1; + static const uint32_t MAX_DIVIDER = (1 << 7); public: static const uint8_t DEFAULT_I2C_ADDRESS = 0x18; @@ -205,7 +208,8 @@ namespace pimoroni { void pwm_clear(bool wait_for_clear = true); bool pwm_clearing(); bool set_pwm_control(uint8_t divider); - void set_pwm_period(uint16_t value, bool load = true); + void set_pwm_period(uint16_t value, bool load = true, bool wait_for_load = true); + uint16_t set_pwm_frequency(float frequency, bool load = true, bool wait_for_load = true); uint8_t get_mode(uint8_t pin); void set_mode(uint8_t pin, uint8_t mode, bool schmitt_trigger = false, bool invert = false); @@ -213,7 +217,7 @@ namespace pimoroni { int16_t input(uint8_t pin, uint32_t adc_timeout = 1); float input_as_voltage(uint8_t pin, uint32_t adc_timeout = 1); - void output(uint8_t pin, uint16_t value, bool load = true); + void output(uint8_t pin, uint16_t value, bool load = true, bool wait_for_load = true); void setup_rotary_encoder(uint8_t channel, uint8_t pin_a, uint8_t pin_b, uint8_t pin_c = 0, bool count_microsteps = false); int16_t read_rotary_encoder(uint8_t channel); diff --git a/examples/breakout_encoder_wheel/CMakeLists.txt b/examples/breakout_encoder_wheel/CMakeLists.txt index 5d4150ea..680046f7 100644 --- a/examples/breakout_encoder_wheel/CMakeLists.txt +++ b/examples/breakout_encoder_wheel/CMakeLists.txt @@ -3,6 +3,6 @@ add_subdirectory(chase_game) add_subdirectory(clock) add_subdirectory(colour_picker) add_subdirectory(encoder) -#add_subdirectory(gpio_pwm) +add_subdirectory(gpio_pwm) add_subdirectory(led_rainbow) add_subdirectory(stop_watch) \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt b/examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt new file mode 100644 index 00000000..2f4a07d1 --- /dev/null +++ b/examples/breakout_encoder_wheel/gpio_pwm/CMakeLists.txt @@ -0,0 +1,13 @@ +set(OUTPUT_NAME encoderwheel_gpio_pwm) +add_executable(${OUTPUT_NAME} gpio_pwm.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + breakout_encoder_wheel +) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp b/examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp new file mode 100644 index 00000000..29f77cca --- /dev/null +++ b/examples/breakout_encoder_wheel/gpio_pwm/gpio_pwm.cpp @@ -0,0 +1,74 @@ +#include +#include +#include "pimoroni_i2c.hpp" +#include "breakout_encoder_wheel.hpp" +#include "time.h" + +using namespace pimoroni; +using namespace encoderwheel; + +/* +Output a sine wave PWM sequence on the Encoder Wheel's side GPIO pins. + +Press the centre button to stop the program. +*/ + +// Constants +constexpr float SPEED = 5.0f; // The speed that the LEDs will cycle at +const uint UPDATES = 50; // How many times the LEDs will be updated per second +const uint UPDATE_RATE_US = 1000000 / UPDATES; +constexpr float FREQUENCY = 1000.0f; // The frequency to run the PWM at + +// Create a new BreakoutEncoderWheel +I2C i2c(BOARD::BREAKOUT_GARDEN); +BreakoutEncoderWheel wheel(&i2c); + +// Variables +float offset = 0.0f; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the encoder wheel + if(wheel.init()) { + + // Set the PWM frequency for the GPIOs + uint16_t period = wheel.gpio_pwm_frequency(FREQUENCY); + + // Set the GPIO pins to PWM outputs + for(int i = 0; i < NUM_GPIOS; i++) { + wheel.gpio_pin_mode(GPIOS[i], IOExpander::PIN_PWM); + } + + // Loop forever + while(!wheel.pressed(CENTRE)) { + // Record the start time of this loop + absolute_time_t start_time = get_absolute_time(); + + offset += SPEED / 1000.0f; + + // Update all the PWMs + for(int i = 0; i < NUM_GPIOS; i++) { + float angle = (((float)i / NUM_GPIOS) + offset) * M_PI; + uint16_t duty = (uint16_t)(((sinf(angle) / 2.0f) + 0.5f) * period); + + // Set the GPIO pin to the new duty cycle, but do not load it yet + wheel.gpio_pin_value(GPIOS[i], duty, false); + } + + // Have all the PWMs load at once + wheel.gpio_pwm_load(); + + // Sleep until the next update, accounting for how long the above operations took to perform + sleep_until(delayed_by_us(start_time, UPDATE_RATE_US)); + } + + // Turn off the PWM outputs + for(int i = 0; i < NUM_GPIOS; i++) { + wheel.gpio_pin_value(GPIOS[i], 0); + } + } + + return 0; +} \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 81f86635..5611a852 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -190,32 +190,37 @@ namespace encoderwheel { led_ring.update(); } - int BreakoutEncoderWheel::gpio_pin_mode(int gpio) { - return 0; // TODO + uint8_t BreakoutEncoderWheel::gpio_pin_mode(uint8_t gpio) { + assert(gpio < GP7 || gpio > GP9); + return ioe.get_mode(gpio); } - void BreakoutEncoderWheel::gpio_pin_mode(int gpio, int mode) { - + void BreakoutEncoderWheel::gpio_pin_mode(uint8_t gpio, uint8_t mode) { + assert(gpio < GP7 || gpio > GP9); + ioe.set_mode(gpio, mode); } - int BreakoutEncoderWheel::gpio_pin_value(int gpio) { - return 0; // TODO + int16_t BreakoutEncoderWheel::gpio_pin_value(uint8_t gpio) { + assert(gpio < GP7 || gpio > GP9); + return ioe.input(gpio); } - float BreakoutEncoderWheel::gpio_pin_value_as_voltage(int gpio) { - return 0; // TODO + float BreakoutEncoderWheel::gpio_pin_value_as_voltage(uint8_t gpio) { + assert(gpio < GP7 || gpio > GP9); + return ioe.input_as_voltage(gpio); } - void BreakoutEncoderWheel::gpio_pin_value(int gpio, int value, bool load, bool wait_for_load) { - + void BreakoutEncoderWheel::gpio_pin_value(uint8_t gpio, uint16_t value, bool load, bool wait_for_load) { + assert(gpio < GP7 || gpio > GP9); + ioe.output(gpio, value, load, wait_for_load); } void BreakoutEncoderWheel::gpio_pwm_load(bool wait_for_load) { - + ioe.pwm_load(wait_for_load); } int BreakoutEncoderWheel::gpio_pwm_frequency(float frequency, bool load, bool wait_for_load) { - return 0; // TODO + return ioe.set_pwm_frequency(frequency, load, wait_for_load); } void BreakoutEncoderWheel::take_encoder_reading() { diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index 81263a09..ead5eb33 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -41,17 +41,17 @@ namespace encoderwheel { static const uint32_t DEFAULT_TIMEOUT = 1; private: - static const uint8_t ENC_CHANNEL = 1; - static const uint8_t ENC_TERM_A = 3; - static const uint8_t ENC_TERM_B = 12; - static const uint8_t ENC_COUNTS_PER_REV = 24; - static const uint8_t ENC_COUNT_DIVIDER = 2; + static const uint8_t ENC_CHANNEL = 1; + static const uint8_t ENC_TERM_A = 3; + static const uint8_t ENC_TERM_B = 12; + static const uint8_t ENC_COUNTS_PER_REV = 24; + static const uint8_t ENC_COUNT_DIVIDER = 2; - static const uint8_t SW_UP = 13; - static const uint8_t SW_DOWN = 4; - static const uint8_t SW_LEFT = 11; - static const uint8_t SW_RIGHT = 2; - static const uint8_t SW_CENTRE = 1; + static const uint8_t SW_UP = 13; + static const uint8_t SW_DOWN = 4; + static const uint8_t SW_LEFT = 11; + static const uint8_t SW_RIGHT = 2; + static const uint8_t SW_CENTRE = 1; // This wonderful lookup table maps the LEDs on the encoder wheel // from their 3x24 (remember, they're RGB) configuration to @@ -146,12 +146,12 @@ namespace encoderwheel { void clear(); void show(); - int gpio_pin_mode(int gpio); - void gpio_pin_mode(int gpio, int mode); - int gpio_pin_value(int gpio); - float gpio_pin_value_as_voltage(int gpio); - void gpio_pin_value(int gpio, int value, bool load = true, bool wait_for_load = false); - void gpio_pwm_load(bool wait_for_load = false); + uint8_t gpio_pin_mode(uint8_t gpio); + void gpio_pin_mode(uint8_t gpio, uint8_t mode); + int16_t gpio_pin_value(uint8_t gpio); + float gpio_pin_value_as_voltage(uint8_t gpio); + void gpio_pin_value(uint8_t gpio, uint16_t value, bool load = true, bool wait_for_load = false); + void gpio_pwm_load(bool wait_for_load = true); int gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false); private: From 12e38c115781948106088632cc91a5e3c269f513 Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 10 May 2023 14:54:32 +0100 Subject: [PATCH 23/25] Implemented GPIO MP support for Encoder wheel --- drivers/ioexpander/ioexpander.hpp | 7 +- .../breakout_encoder_wheel.cpp | 2 +- .../breakout_encoder_wheel.hpp | 2 +- .../breakout_encoder_wheel.cpp | 80 ++++++++++++++++--- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/drivers/ioexpander/ioexpander.hpp b/drivers/ioexpander/ioexpander.hpp index 2a3fb63e..c06b5adf 100644 --- a/drivers/ioexpander/ioexpander.hpp +++ b/drivers/ioexpander/ioexpander.hpp @@ -27,9 +27,6 @@ namespace pimoroni { static const uint8_t PIN_MODE_ADC = 0b01010; // ADC, Input-only (high-impedance) static const uint32_t RESET_TIMEOUT_MS = 1000; - static const uint32_t CLOCK_FREQ = 24000000; - static const uint32_t MAX_PERIOD = (1 << 16) - 1; - static const uint32_t MAX_DIVIDER = (1 << 7); public: static const uint8_t DEFAULT_I2C_ADDRESS = 0x18; @@ -50,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 diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 5611a852..2fe9a1c4 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -219,7 +219,7 @@ namespace encoderwheel { ioe.pwm_load(wait_for_load); } - int BreakoutEncoderWheel::gpio_pwm_frequency(float frequency, bool load, bool 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); } diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp index ead5eb33..076b6003 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.hpp @@ -152,7 +152,7 @@ namespace encoderwheel { float gpio_pin_value_as_voltage(uint8_t gpio); void gpio_pin_value(uint8_t gpio, uint16_t value, bool load = true, bool wait_for_load = false); void gpio_pwm_load(bool wait_for_load = true); - int gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false); + uint16_t gpio_pwm_frequency(float frequency, bool load = true, bool wait_for_load = false); private: void take_encoder_reading(); diff --git a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 1c7f40b7..2765a06e 100644 --- a/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/micropython/modules/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -244,22 +244,77 @@ 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) { - //breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t); +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 }}, + }; - return 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) { - //breakout_encoder_wheel_BreakoutEncoderWheel_obj_t *self = MP_OBJ_TO_PTR2(self_in, breakout_encoder_wheel_BreakoutEncoderWheel_obj_t); + 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 }}, + }; - return 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_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 = false }}, + { MP_QSTR_wait_for_load, MP_ARG_BOOL, { .u_bool = true }}, }; // Parse args. @@ -279,8 +334,8 @@ extern mp_obj_t BreakoutEncoderWheel_gpio_pwm_frequency(size_t n_args, const mp_ 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 = false }}, - { MP_QSTR_wait_for_load, MP_ARG_BOOL, { .u_bool = false }}, + { MP_QSTR_load, MP_ARG_BOOL, { .u_bool = true }}, + { MP_QSTR_wait_for_load, MP_ARG_BOOL, { .u_bool = true }}, }; // Parse args. @@ -289,10 +344,17 @@ extern mp_obj_t BreakoutEncoderWheel_gpio_pwm_frequency(size_t n_args, const mp_ 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; - - int period = self->breakout->gpio_pwm_frequency(frequency, load, wait_for_load); + period = self->breakout->gpio_pwm_frequency(frequency, load, wait_for_load); return mp_obj_new_int(period); } From 0120975b3cf303f2bb0feba5b7aa32b4781f89fa Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 10 May 2023 16:08:51 +0100 Subject: [PATCH 24/25] Readme improvements --- README.md | 1 + libraries/breakout_encoder_wheel/README.md | 57 ++++++++++--------- .../modules/breakout_encoder_wheel/README.md | 2 +- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 71ac73b7..bc6139d9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/libraries/breakout_encoder_wheel/README.md b/libraries/breakout_encoder_wheel/README.md index 9a18544e..f592e5e2 100644 --- a/libraries/breakout_encoder_wheel/README.md +++ b/libraries/breakout_encoder_wheel/README.md @@ -131,7 +131,7 @@ wheel.set_rgb(0, 255, 0, 255); Set the first LED - `0` - to Red `0.0`: ```c++ -wheel.set_hsv(0, 0.0, 1.0, 1.0); +wheel.set_hsv(0, 0.0f, 1.0f, 1.0f); ``` @@ -239,7 +239,7 @@ BreakoutEncoderWheel wheel(&i2c); 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); +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)); @@ -264,29 +264,34 @@ All of Encoder Wheel's PWM outputs share the same timing parameters. This means Here is the complete list of functions available on the `BreakoutEncoderWheel` class: ```c++ -BreakoutEncoderWheel(ioe_address=0x13, led_address=0x77, interrupt=PIN_UNUSED) -set_ioe_address(address) -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) -gpio_pwm_load(wait_for_load=True) -gpio_pwm_frequency(frequency, load=True, wait_for_load=True) +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 @@ -315,7 +320,6 @@ Here is the complete list of public constants in the `encoderwheel` namespace: * `RIGHT` = `3` * `CENTRE` = `4` - ### GPIO Constants * `GP7` = `7` @@ -323,7 +327,6 @@ Here is the complete list of public constants in the `encoderwheel` namespace: * `GP9` = `9` * `GPIOS` = (`7`, `8`, `9`) - ### Count Constants * `NUM_LEDS` = `24` diff --git a/micropython/modules/breakout_encoder_wheel/README.md b/micropython/modules/breakout_encoder_wheel/README.md index c6d511f8..20440459 100644 --- a/micropython/modules/breakout_encoder_wheel/README.md +++ b/micropython/modules/breakout_encoder_wheel/README.md @@ -225,7 +225,7 @@ Below is an example of setting a gpio pin to output a 25KHz signal with a 50% du 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_encoderwheel import BreakoutEncoderWheel, GP7 +from breakout_encoder_wheel import BreakoutEncoderWheel, GP7 # Initialise EncoderWheel i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) From d00185d83181c321a6dcb1601c02cddf687b611e Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Wed, 10 May 2023 16:57:38 +0100 Subject: [PATCH 25/25] Added C & MP interrupt example --- .../breakout_encoder_wheel/CMakeLists.txt | 1 + examples/breakout_encoder_wheel/README.md | 7 ++ .../interrupt/CMakeLists.txt | 13 +++ .../interrupt/interrupt.cpp | 84 +++++++++++++++++++ .../breakout_encoder_wheel.cpp | 6 ++ .../examples/breakout_encoder_wheel/README.md | 7 ++ .../breakout_encoder_wheel/encoder.py | 3 - .../breakout_encoder_wheel/interrupt.py | 62 ++++++++++++++ .../modules/breakout_encoder_wheel/README.md | 4 +- 9 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 examples/breakout_encoder_wheel/interrupt/CMakeLists.txt create mode 100644 examples/breakout_encoder_wheel/interrupt/interrupt.cpp create mode 100644 micropython/examples/breakout_encoder_wheel/interrupt.py diff --git a/examples/breakout_encoder_wheel/CMakeLists.txt b/examples/breakout_encoder_wheel/CMakeLists.txt index 680046f7..d258674e 100644 --- a/examples/breakout_encoder_wheel/CMakeLists.txt +++ b/examples/breakout_encoder_wheel/CMakeLists.txt @@ -4,5 +4,6 @@ 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) \ No newline at end of file diff --git a/examples/breakout_encoder_wheel/README.md b/examples/breakout_encoder_wheel/README.md index 18c5be99..d8f30a85 100644 --- a/examples/breakout_encoder_wheel/README.md +++ b/examples/breakout_encoder_wheel/README.md @@ -3,6 +3,7 @@ - [Function Examples](#function-examples) - [Buttons](#buttons) - [Encoder](#encoder) + - [Interrupt](#interrupt) - [LED Examples](#led-examples) - [LED Rainbow](#led-rainbow) - [Clock](#clock) @@ -28,6 +29,12 @@ A demonstration of reading the 5 buttons on Encoder Wheel. 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 diff --git a/examples/breakout_encoder_wheel/interrupt/CMakeLists.txt b/examples/breakout_encoder_wheel/interrupt/CMakeLists.txt new file mode 100644 index 00000000..a10f054c --- /dev/null +++ b/examples/breakout_encoder_wheel/interrupt/CMakeLists.txt @@ -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}) diff --git a/examples/breakout_encoder_wheel/interrupt/interrupt.cpp b/examples/breakout_encoder_wheel/interrupt/interrupt.cpp new file mode 100644 index 00000000..6d822ffd --- /dev/null +++ b/examples/breakout_encoder_wheel/interrupt/interrupt.cpp @@ -0,0 +1,84 @@ +#include +#include +#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; +} \ No newline at end of file diff --git a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp index 2fe9a1c4..61d6431d 100644 --- a/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp +++ b/libraries/breakout_encoder_wheel/breakout_encoder_wheel.cpp @@ -17,6 +17,12 @@ namespace encoderwheel { 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, diff --git a/micropython/examples/breakout_encoder_wheel/README.md b/micropython/examples/breakout_encoder_wheel/README.md index c633ec2a..7adda3f7 100644 --- a/micropython/examples/breakout_encoder_wheel/README.md +++ b/micropython/examples/breakout_encoder_wheel/README.md @@ -3,6 +3,7 @@ - [Function Examples](#function-examples) - [Buttons](#buttons) - [Encoder](#encoder) + - [Interrupt](#interrupt) - [LED Examples](#led-examples) - [LED Rainbow](#led-rainbow) - [Clock](#clock) @@ -28,6 +29,12 @@ A demonstration of reading the 5 buttons on Encoder Wheel. 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 diff --git a/micropython/examples/breakout_encoder_wheel/encoder.py b/micropython/examples/breakout_encoder_wheel/encoder.py index b35b59ab..8d3783cc 100644 --- a/micropython/examples/breakout_encoder_wheel/encoder.py +++ b/micropython/examples/breakout_encoder_wheel/encoder.py @@ -8,9 +8,6 @@ A demonstration of reading the rotary dial of the Encoder Wheel breakout. Press Ctrl+C to stop the program. """ -PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5} -PINS_PICO_EXPLORER = {"sda": 20, "scl": 21} - # Create a new BreakoutEncoderWheel i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS) wheel = BreakoutEncoderWheel(i2c) diff --git a/micropython/examples/breakout_encoder_wheel/interrupt.py b/micropython/examples/breakout_encoder_wheel/interrupt.py new file mode 100644 index 00000000..f0b839a0 --- /dev/null +++ b/micropython/examples/breakout_encoder_wheel/interrupt.py @@ -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() diff --git a/micropython/modules/breakout_encoder_wheel/README.md b/micropython/modules/breakout_encoder_wheel/README.md index 20440459..365fa566 100644 --- a/micropython/modules/breakout_encoder_wheel/README.md +++ b/micropython/modules/breakout_encoder_wheel/README.md @@ -262,6 +262,8 @@ Here is the complete list of functions available on the `BreakoutEncoderWheel` c ```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() @@ -280,7 +282,7 @@ show() gpio_pin_mode(gpio) gpio_pin_mode(gpio, mode) gpio_pin_value(gpio) -gpio_pin_value(gpio, value) +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) ```