From 7e7d932c6526ce0b7fdcead62071fd9a08e6b719 Mon Sep 17 00:00:00 2001 From: Tomas Bubela Date: Mon, 7 Apr 2025 09:20:38 +0200 Subject: [PATCH] Added option for more physical buttons via I2C expander PCF8574 for m5dial * currently there are 8 buttons --- platformio.ini | 21 +++++++++++-- src/HardwareM5Dial.cpp | 67 +++++++++++++++++++++++++++++++++++++---- src/HardwareM5Dial.hpp | 11 ++++++- src/MenuScene.cpp | 13 ++++++++ src/MultiJogScene.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++ src/Scene.cpp | 9 +++--- src/Scene.h | 1 + 7 files changed, 177 insertions(+), 13 deletions(-) diff --git a/platformio.ini b/platformio.ini index 376e19c..3931a9d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = m5dial +default_envs = m5dial_more_buttons [common] build_flags = @@ -21,7 +21,7 @@ lib_deps = https://github.com/MitchBradley/GrblParser#9108f54 build_src_filter = +<*.c> +<*.h> +<*.cpp> +<*.hpp> - - + - -[env:m5dial] +[env:m5dial_base] ; Pendant based on M5Dial ; http://wiki.fluidnc.com/en/hardware/official/M5Dial_Pendant platform = espressif32 @@ -44,6 +44,23 @@ build_flags = extra_scripts = git-version.py build_src_filter = ${common.build_src_filter} + + +[env:m5dial] +; Pendant with two buttons +extends = env:m5dial_base +lib_deps = + ${env:m5dial_base.lib_deps} + +[env:m5dial_more_buttons] +; Pendant based on M5Dial with more buttons through I2C PCF8574 +extends = env:m5dial_base +lib_deps = + ${env:m5dial_base.lib_deps} + robtillaart/PCF8574@^0.4.1 +build_flags = + ${env:m5dial_base.build_flags} + -DFNC_BAUD=115200 + -DI2C_BUTTONS + [env:cyd_base] ; Pendant based on a 2432S028 "Cheap Yellow Display" and a hand wheel pulse encoder ; http://wiki.fluidnc.com/en/hardware/official/CYD_Dial_Pendant diff --git a/src/HardwareM5Dial.cpp b/src/HardwareM5Dial.cpp index 6dd1323..56c6a23 100644 --- a/src/HardwareM5Dial.cpp +++ b/src/HardwareM5Dial.cpp @@ -7,6 +7,10 @@ #include "M5GFX.h" #include "Drawing.h" #include "HardwareM5Dial.hpp" +#if I2C_BUTTONS +#include +#include +#endif LGFX_Device& display = M5Dial.Display; LGFX_Sprite canvas(&M5Dial.Display); @@ -15,8 +19,24 @@ m5::Touch_Class& touch = M5Dial.Touch; Stream& debugPort = USBSerial; m5::Button_Class& dialButton = M5Dial.BtnA; +#ifdef I2C_BUTTONS +// Buttons through the I2C expander +m5::Button_Class buttons_i2c[8]; +PCF8574 pcf20(I2C_BUTTONS_ADDR); // I2C expander 8x DIO +// Map the original button objects to the array elements +m5::Button_Class& greenButton = buttons_i2c[0]; +m5::Button_Class& redButton = buttons_i2c[1]; +m5::Button_Class& setXButton = buttons_i2c[2]; +m5::Button_Class& setYButton = buttons_i2c[3]; +m5::Button_Class& setZButton = buttons_i2c[4]; +m5::Button_Class& changeStepButton = buttons_i2c[5]; +m5::Button_Class& futureUse1Button = buttons_i2c[6]; +m5::Button_Class& futureUse2Button = buttons_i2c[7]; +#else +// Two buttons through GPIO pins m5::Button_Class greenButton; m5::Button_Class redButton; +#endif bool round_display = true; @@ -41,12 +61,20 @@ void init_hardware() { init_fnc_uart(FNC_UART_NUM, PND_TX_FNC_RX_PIN, PND_RX_FNC_TX_PIN); + #ifdef I2C_BUTTONS + Wire.begin(I2C_BUTTONS_SDA, I2C_BUTTONS_SCL); + pcf20.begin(); + for (auto& button : buttons_i2c) { + button.setDebounceThresh(5); + } + #else // Setup external GPIOs as buttons lgfx::gpio::command(lgfx::gpio::command_mode_input_pullup, RED_BUTTON_PIN); lgfx::gpio::command(lgfx::gpio::command_mode_input_pullup, GREEN_BUTTON_PIN); greenButton.setDebounceThresh(5); redButton.setDebounceThresh(5); + #endif init_encoder(ENC_A, ENC_B); @@ -69,36 +97,52 @@ void system_background() { } bool switch_button_touched(bool& pressed, int& button) { - if (redButton.wasPressed()) { + if (dialButton.wasPressed()) { button = 0; pressed = true; return true; } - if (redButton.wasReleased()) { + if (dialButton.wasReleased()) { button = 0; pressed = false; return true; } - if (dialButton.wasPressed()) { + #ifdef I2C_BUTTONS + for (int i = 0; i < 8; ++i) { + if (buttons_i2c[i].wasPressed()) { + button = i + 1; // Shift index by 1 + pressed = true; + return true; + } + if (buttons_i2c[i].wasReleased()) { + button = i + 1; // Shift index by 1 + pressed = false; + return true; + } + } + #else + if (greenButton.wasPressed()) { button = 1; pressed = true; return true; } - if (dialButton.wasReleased()) { + if (greenButton.wasReleased()) { button = 1; pressed = false; return true; } - if (greenButton.wasPressed()) { + if (redButton.wasPressed()) { button = 2; pressed = true; return true; } - if (greenButton.wasReleased()) { + if (redButton.wasReleased()) { button = 2; pressed = false; return true; } + #endif + // No button pressed or released return false; } @@ -114,9 +158,18 @@ void update_events() { auto ms = m5gfx::millis(); + #ifdef I2C_BUTTONS + // Read the state of the I2C expander and update button states + auto but_bits = ~pcf20.read8(); // Active low + for (int i = 0; i < 8; ++i) { + bool state = bitRead(but_bits, i); + buttons_i2c[i].setRawState(ms, state); + } + #else // The red and green buttons are active low redButton.setRawState(ms, !m5gfx::gpio_in(RED_BUTTON_PIN)); greenButton.setRawState(ms, !m5gfx::gpio_in(GREEN_BUTTON_PIN)); + #endif } void ackBeep() { @@ -134,6 +187,7 @@ bool ui_locked() { // but that can't work because GPIO42 is not an RTC GPIO and thus // cannot be used as an ext0 wakeup source. void deep_sleep(int us) { +#ifdef WAKEUP_GPIO display.sleep(); rtc_gpio_pullup_en((gpio_num_t)WAKEUP_GPIO); @@ -148,4 +202,5 @@ void deep_sleep(int us) { // esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER); } esp_deep_sleep_start(); +#endif } diff --git a/src/HardwareM5Dial.hpp b/src/HardwareM5Dial.hpp index 33f69aa..196b1c1 100644 --- a/src/HardwareM5Dial.hpp +++ b/src/HardwareM5Dial.hpp @@ -5,6 +5,15 @@ constexpr static const int DIAL_BUTTON_PIN = GPIO_NUM_42; #include "M5Dial.h" constexpr static const int FNC_UART_NUM = 1; +#ifdef I2C_BUTTONS +// PORT B is used for I2C buttons +constexpr static const int I2C_BUTTONS_ADDR = 0x20; +constexpr static const int I2C_BUTTONS_SDA = GPIO_NUM_2; // SDA +constexpr static const int I2C_BUTTONS_SCL = GPIO_NUM_1; // SCL +// PORT A is used for UART +constexpr static const int PND_RX_FNC_TX_PIN = GPIO_NUM_15; +constexpr static const int PND_TX_FNC_RX_PIN = GPIO_NUM_13; +#else #ifdef UART_ON_PORT_B constexpr static const int RED_BUTTON_PIN = GPIO_NUM_13; constexpr static const int GREEN_BUTTON_PIN = GPIO_NUM_15; @@ -27,5 +36,5 @@ constexpr static const int GREEN_BUTTON_PIN = GPIO_NUM_2; constexpr static const int PND_RX_FNC_TX_PIN = GPIO_NUM_15; constexpr static const int PND_TX_FNC_RX_PIN = GPIO_NUM_13; #endif - #define WAKEUP_GPIO RED_BUTTON_PIN +#endif // I2C_BUTTONS \ No newline at end of file diff --git a/src/MenuScene.cpp b/src/MenuScene.cpp index 2024511..05b517d 100644 --- a/src/MenuScene.cpp +++ b/src/MenuScene.cpp @@ -120,6 +120,19 @@ class MenuScene : public PieMenu { } reDisplay(); } +#ifdef I2C_BUTTONS + void onOtherButtonPress() { + extern m5::Button_Class& setXButton; + extern m5::Button_Class& setYButton; + extern m5::Button_Class& setZButton; + // handling other physical buttons for this scene + if ((setXButton.wasPressed() || setYButton.wasPressed() || setZButton.wasPressed()) + && (state == Idle)) { + push_scene(&jogScene); + return; + } + } +#endif } menuScene; Scene* initMenus() { diff --git a/src/MultiJogScene.cpp b/src/MultiJogScene.cpp index a364367..3f04e1a 100644 --- a/src/MultiJogScene.cpp +++ b/src/MultiJogScene.cpp @@ -98,6 +98,14 @@ class MultiJogScene : public Scene { } send_line(cmd.c_str()); } + + void zero_axis(int axis) { + std::string cmd = "G10L20P0"; + cmd += axisNumToChar(axis); + cmd += "0"; + send_line(cmd.c_str()); + } + void onEntry(void* arg) { if (arg && strcmp((const char*)arg, "Confirmed") == 0) { zero_axes(); @@ -108,6 +116,19 @@ class MultiJogScene : public Scene { getPref("DistanceDigit", axis, &_dist_index[axis]); } } + extern m5::Button_Class& setXButton; + extern m5::Button_Class& setYButton; + extern m5::Button_Class& setZButton; + if (setXButton.isPressed()) { + set_axis(0); + } else if (setYButton.isPressed()) { + set_axis(1); + } else if (setZButton.isPressed()) { + set_axis(2); + } else { + unselect_all(); + select(0); + } } int which(int x, int y) { @@ -165,6 +186,7 @@ class MultiJogScene : public Scene { if (++_dist_index[axis] >= max_index()) { _dist_index[axis] = min_index(); } + set_dist_index(axis, _dist_index[axis]); } } } @@ -209,6 +231,10 @@ class MultiJogScene : public Scene { } select(the_axis); } + void set_axis(int axis) { + unselect_all(); + select(axis); + } void touch_top() { prev_axis(); reDisplay(); @@ -368,6 +394,48 @@ class MultiJogScene : public Scene { } } +#ifdef I2C_BUTTONS + void onOtherButtonPress() { + // handling physical buttons for this scene + extern m5::Button_Class& setXButton; + extern m5::Button_Class& setYButton; + extern m5::Button_Class& setZButton; + extern m5::Button_Class& changeStepButton; + if (setXButton.wasPressed()) { + set_axis(0); + reDisplay(); + } + if (setYButton.wasPressed()) { + set_axis(1); + reDisplay(); + } + if (setZButton.wasPressed()) { + set_axis(2); + reDisplay(); + } + if (changeStepButton.wasPressed()) { + rotate_distance(); + reDisplay(); + } + // TODO isHolding action doesnt work + // long press - zero the X axis + if (setXButton.isHolding()) { + zero_axis(0); + reDisplay(); + } + // long press - zero the Y axis + if (setYButton.isHolding()) { + zero_axis(1); + reDisplay(); + } + // long press - zero the Z axis + if (setZButton.isHolding()) { + zero_axis(2); + reDisplay(); + } + } +#endif + void onDROChange() { reDisplay(); } diff --git a/src/Scene.cpp b/src/Scene.cpp index 1975395..91865e4 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -62,16 +62,16 @@ void dispatch_button(bool pressed, int button) { switch (button) { case 0: if (pressed) { - current_scene->onRedButtonPress(); + current_scene->onDialButtonPress(); } else { - current_scene->onRedButtonRelease(); + current_scene->onDialButtonRelease(); } break; case 1: if (pressed) { - current_scene->onDialButtonPress(); + current_scene->onRedButtonPress(); } else { - current_scene->onDialButtonRelease(); + current_scene->onRedButtonRelease(); } break; case 2: @@ -82,6 +82,7 @@ void dispatch_button(bool pressed, int button) { } break; default: + current_scene->onOtherButtonPress(); break; } } diff --git a/src/Scene.h b/src/Scene.h index 327668b..147b475 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -39,6 +39,7 @@ class Scene { virtual void onGreenButtonRelease() {} virtual void onDialButtonPress() {} virtual void onDialButtonRelease() {} + virtual void onOtherButtonPress() {} virtual void onTouchPress() {} virtual void onTouchRelease() {} virtual void onTouchClick() {}