From b57e5d6a23004773c39396a5ab788e3520e5f6fd Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Sun, 27 Apr 2025 21:23:07 +0200 Subject: [PATCH 1/8] Toggle leds when pushing buttons --- .../Buttons_Basic/Buttons_Basic.ino | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino b/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino index d69f13f..f29f96b 100644 --- a/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino +++ b/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino @@ -11,9 +11,9 @@ // Create a ModulinoButtons object ModulinoButtons buttons; -bool button_a = false; -bool button_b = false; -bool button_c = false; +bool button_a = true; +bool button_b = true; +bool button_c = true; void setup() { Serial.begin(9600); @@ -28,12 +28,19 @@ void loop() { // Check for new button events, returns true when button state changes if (buttons.update()) { // Check which button was pressed (0=A, 1=B, 2=C) + // Also toggle the corresponding LED, for each of the three buttons if (buttons.isPressed(0)) { Serial.println("Button A pressed!"); + button_a = !button_a; } else if (buttons.isPressed(1)) { Serial.println("Button B pressed!"); + button_b = !button_b; } else if (buttons.isPressed(2)) { Serial.println("Button C pressed!"); + button_c = !button_c; } + + // Update the LEDs above buttons, depending on the variables value + buttons.setLeds(button_a, button_b, button_c); } } \ No newline at end of file From 3c3673277049c6d2247ee45e09bb3cf750b391a7 Mon Sep 17 00:00:00 2001 From: Antonio Bernardini <60107274+AntonioBerna@users.noreply.github.com> Date: Fri, 9 May 2025 12:18:16 +0200 Subject: [PATCH 2/8] Add `getDirection()` support to `ModulinoKnob` and example :zap: (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(buttons): allow isPressed() to accept 'A', 'B', 'C' as input Added support for character and string-based input to `isPressed()` in `ModulinoButtons`, so it now accepts both index (0–2) and letter identifiers (`'A'`, `'B'`, `'C'`) matching the physical button labeling. Closes #3 * Update example to document support for both numeric and letter indices :recycle: * Add getDirection() support to ModulinoKnob and example :zap: * Fix: :fire: * Fix: restore files :fire: * Minor fixes :recycle: * Update API docs :recycle: * Update API docs :recycle: * Update examples/Modulino_Knob/Knob_Basic/Knob_Basic.ino --------- Co-authored-by: Leonardo Cavagnis <45899760+leonardocavagnis@users.noreply.github.com> --- docs/api.md | 6 +++++ .../Modulino_Knob/Knob_Basic/Knob_Basic.ino | 11 ++++++++- src/Modulino.h | 24 ++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/api.md b/docs/api.md index 7c463c0..225181b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -90,6 +90,12 @@ Represents a Modulino Knob module. - **`bool isPressed()`** Returns `true` if the button on the knob is pressed, `false` otherwise. +- **`int8_t getDirection()`** + Returns the direction of the knob rotation. + - `1` for clockwise + - `-1` for counter-clockwise + - `0` if no movement is detected + - **`void set(int16_t value)`** Sets the knob value. diff --git a/examples/Modulino_Knob/Knob_Basic/Knob_Basic.ino b/examples/Modulino_Knob/Knob_Basic/Knob_Basic.ino index b933002..60dc248 100644 --- a/examples/Modulino_Knob/Knob_Basic/Knob_Basic.ino +++ b/examples/Modulino_Knob/Knob_Basic/Knob_Basic.ino @@ -24,6 +24,8 @@ void loop(){ int position = knob.get(); // Check if the knob has been pressed (clicked) bool click = knob.isPressed(); + // Get the rotation direction + int8_t direction = knob.getDirection(); Serial.print("Current position is: "); Serial.println(position); @@ -32,4 +34,11 @@ void loop(){ Serial.println("Clicked!"); } -} \ No newline at end of file + if (direction == 1) { + Serial.println("Rotated clockwise"); + } else if (direction == -1) { + Serial.println("Rotated counter-clockwise"); + } + + delay(10); // optional small delay to reduce serial spam +} \ No newline at end of file diff --git a/src/Modulino.h b/src/Modulino.h index 6f62aaf..365d3c1 100644 --- a/src/Modulino.h +++ b/src/Modulino.h @@ -243,8 +243,9 @@ class ModulinoKnob : public Module { bool begin() { auto ret = Module::begin(); if (ret) { - // check for set() bug auto _val = get(); + _lastPosition = _val; + _lastDebounceTime = millis(); set(100); if (get() != 100) { _bug_on_set = true; @@ -277,6 +278,24 @@ class ModulinoKnob : public Module { get(); return _pressed; } + int8_t getDirection() { + unsigned long now = millis(); + if (now - _lastDebounceTime < DEBOUNCE_DELAY) { + return 0; + } + int16_t current = get(); + int8_t direction = 0; + if (current > _lastPosition) { + direction = 1; + } else if (current < _lastPosition) { + direction = -1; + } + if (direction != 0) { + _lastDebounceTime = now; + _lastPosition = current; + } + return direction; + } virtual uint8_t discover() { for (unsigned int i = 0; i < sizeof(match)/sizeof(match[0]); i++) { if (scan(match[i])) { @@ -288,6 +307,9 @@ class ModulinoKnob : public Module { private: bool _pressed = false; bool _bug_on_set = false; + int16_t _lastPosition = 0; + unsigned long _lastDebounceTime = 0; + static constexpr unsigned long DEBOUNCE_DELAY = 30; protected: uint8_t match[2] = { 0x74, 0x76 }; }; From 9c948fefc0de011add921fb36a4cf06687dc4871 Mon Sep 17 00:00:00 2001 From: Antonio Bernardini <60107274+AntonioBerna@users.noreply.github.com> Date: Fri, 9 May 2025 12:29:09 +0200 Subject: [PATCH 3/8] Add support for character input (`'A'`, `'B'`, `'C'`) in `isPressed()` (#33) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(buttons): allow isPressed() to accept 'A', 'B', 'C' as input Added support for character and string-based input to `isPressed()` in `ModulinoButtons`, so it now accepts both index (0–2) and letter identifiers (`'A'`, `'B'`, `'C'`) matching the physical button labeling. Closes #3 * Update example to document support for both numeric and letter indices :recycle: * Update API docs :recycle: * Delete .gitignore * Update examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino --------- Co-authored-by: Leonardo Cavagnis <45899760+leonardocavagnis@users.noreply.github.com> --- docs/api.md | 6 ++++++ .../Buttons_Basic/Buttons_Basic.ino | 14 ++++++++------ src/Modulino.h | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/api.md b/docs/api.md index 225181b..f52c482 100644 --- a/docs/api.md +++ b/docs/api.md @@ -33,6 +33,12 @@ Represents a Modulino Buttons module. - **`PinStatus isPressed(int index)`** Returns the press status (HIGH/LOW) of the button at the specified index (_0-A, 1-B, 2-C_). +- **`PinStatus isPressed(char button)`** + Returns the press status (HIGH/LOW) of the button specified by its character ('A', 'B', 'C'). + +- **`PinStatus isPressed(const char *button)`** + Returns the press status (HIGH/LOW) of the button specified by its string ("A", "B", "C"). + - **`bool update()`** Updates the button status. Returns `true` if the status has changed, `false` otherwise. diff --git a/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino b/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino index f29f96b..9d5e4da 100644 --- a/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino +++ b/examples/Modulino_Buttons/Buttons_Basic/Buttons_Basic.ino @@ -24,22 +24,24 @@ void setup() { // Turn on the LEDs above buttons A, B, and C buttons.setLeds(true, true, true); } + void loop() { // Check for new button events, returns true when button state changes if (buttons.update()) { - // Check which button was pressed (0=A, 1=B, 2=C) - // Also toggle the corresponding LED, for each of the three buttons - if (buttons.isPressed(0)) { + // You can use either index (0=A, 1=B, 2=C) or letter ('A', 'B', 'C') to check buttons + // Below we use the letter-based method for better readability + + if (buttons.isPressed('A')) { Serial.println("Button A pressed!"); button_a = !button_a; - } else if (buttons.isPressed(1)) { + } else if (buttons.isPressed("B")) { Serial.println("Button B pressed!"); button_b = !button_b; - } else if (buttons.isPressed(2)) { + } else if (buttons.isPressed('C')) { Serial.println("Button C pressed!"); button_c = !button_c; } - + // Update the LEDs above buttons, depending on the variables value buttons.setLeds(button_a, button_b, button_c); } diff --git a/src/Modulino.h b/src/Modulino.h index 365d3c1..f723e24 100644 --- a/src/Modulino.h +++ b/src/Modulino.h @@ -127,6 +127,17 @@ class ModulinoButtons : public Module { PinStatus isPressed(int index) { return last_status[index] ? HIGH : LOW; } + PinStatus isPressed(char button) { + int index = buttonToIndex(button); + if (index < 0) return LOW; + return isPressed(index); + } + PinStatus isPressed(const char *button) { + if (button == nullptr || button[0] == '\0' || button[1] != '\0') { + return LOW; + } + return isPressed(button[0]); + } bool update() { uint8_t buf[3]; auto res = read((uint8_t*)buf, 3); @@ -154,6 +165,14 @@ class ModulinoButtons : public Module { } private: bool last_status[3]; + int buttonToIndex(char button) { + switch (toupper(button)) { + case 'A': return 0; + case 'B': return 1; + case 'C': return 2; + default: return -1; + } + } protected: uint8_t match[1] = { 0x7C }; // same as fw main.c }; From 71ca9fb84b097af1fa82f3058dcae7b21c6fc56d Mon Sep 17 00:00:00 2001 From: Giovanni Bruno Date: Tue, 1 Jul 2025 15:42:22 +0200 Subject: [PATCH 4/8] fix: error if nano esp32 and selected Arduino pin number --- src/Modulino.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Modulino.h b/src/Modulino.h index f723e24..a79d4b5 100644 --- a/src/Modulino.h +++ b/src/Modulino.h @@ -4,6 +4,11 @@ #ifndef ARDUINO_LIBRARIES_MODULINO_H #define ARDUINO_LIBRARIES_MODULINO_H +#if defined(ESP32) && defined(BOARD_HAS_PIN_REMAP) && defined(tone) + #error "The current configuration is unsupported, switch Pin Numbering to "By GPIO number" or #undef tone and #undef noTone in the beginning of your sketch." + #error "Learn more at: https://support.arduino.cc/hc/en-us/articles/10483225565980-Select-pin-numbering-for-Nano-ESP32-in-Arduino-IDE" +#endif + #include "Wire.h" #include // from stm32duino #include // from stm32duino From 10bda6d6d3139c505bbd65b8025b1a0502664bae Mon Sep 17 00:00:00 2001 From: kunningKing11 <150028328+kunningKing11@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:22:29 +0200 Subject: [PATCH 5/8] Update AddressChanger.ino spelling (#37) Corrected a minor typo --- examples/Utilities/AddressChanger/AddressChanger.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Utilities/AddressChanger/AddressChanger.ino b/examples/Utilities/AddressChanger/AddressChanger.ino index abe4181..4005574 100644 --- a/examples/Utilities/AddressChanger/AddressChanger.ino +++ b/examples/Utilities/AddressChanger/AddressChanger.ino @@ -29,7 +29,7 @@ void setup() { Serial.print("Found device at "); Serial.println(i); address = i; - Serial.println("Press 'c' to configure te new address"); + Serial.println("Press 'c' to configure the new address"); } } } From 2f3a683c7411bcfa20ac6ccc7b2d065e319fbecf Mon Sep 17 00:00:00 2001 From: Xayton <30591904+Xayton@users.noreply.github.com> Date: Thu, 24 Jul 2025 09:51:05 +0200 Subject: [PATCH 6/8] Make AddressChanger interactive --- .../AddressChanger/AddressChanger.ino | 264 +++++++++++++++--- 1 file changed, 230 insertions(+), 34 deletions(-) diff --git a/examples/Utilities/AddressChanger/AddressChanger.ino b/examples/Utilities/AddressChanger/AddressChanger.ino index 4005574..d6fe0a9 100644 --- a/examples/Utilities/AddressChanger/AddressChanger.ino +++ b/examples/Utilities/AddressChanger/AddressChanger.ino @@ -8,60 +8,256 @@ #include "Wire.h" -// Setting new_address to 0 means that the module will get back its original address -const uint8_t new_address = 0; +struct DetectedModulino { + uint8_t addr; + String modulinoType; + String pinstrap; + String defaultAddr; +}; + +#define MAX_DEVICES 16 +DetectedModulino rows[MAX_DEVICES]; +int numRows = 0; -uint8_t address; void setup() { Wire1.begin(); Serial.begin(115200); - delay(1000); - if (new_address != 0 && (new_address < 8 || new_address > 0x77)) { - Serial.println("Address outside valid range"); - while (1); + + delay(600); + discoverDevices(); +} + +bool waitingInput = false; +void loop() { + if (numRows == 0) return; + if (Serial.available() == 0 && waitingInput) return; + + if (Serial.available() > 0) { + String hex1 = Serial.readStringUntil(' '); // Read until space (or other delimiter) + String hex2 = Serial.readStringUntil('\n'); // Read until newline + Serial.println("> " + hex1 + " " + hex2); // Print what the user inserted. + + int num1 = parseHex(hex1); // Parse the first hex number + int num2 = parseHex(hex2); // Parse the second hex number + if (num1 == -1 || num2 == -1) { + Serial.println("Error: Incomplete or invalid input. Please enter two hexadecimal numbers"); + return; + } + + bool success = updateI2cAddress(num1, num2); + if (!success) return; // If the update failed, skip discovery and messages, and wait for input again. + + discoverDevices(); + waitingInput = false; + } + + Serial.println("Enter the current address, space, and new address (ex. \"0x20 0x30\" or \"20 2A\"):"); + Serial.println(" - Enter \" 0\" to reset the device at to its default address."); + Serial.println(" - Enter \"0 0\" to reset all devices to the default address."); + waitingInput = true; +} + +// Updates the device at current address to new address. Supports broadcasting and setting default address (0). +// Returns true if the update was successful, false otherwise. +bool updateI2cAddress(int curAddress, int newAddress) { + uint8_t data[40] = { 'C', 'F', newAddress * 2 }; + memset(data + 3, 0, sizeof(data) - 3); // Zero the rest of the buffer. + + // Validate the current address, it must match a detected device. + if (curAddress != 0 && !findRow(curAddress)) { + Serial.println("Error: current address 0x" + String(curAddress, HEX) + " not found in the devices list\n"); + return false; + } + + if (curAddress != 0 && isFixedAddrDevice(curAddress)) { + Serial.println("Error: address 0x" + String(curAddress, HEX) + " is a non configurable device\n"); + return false; + } + + // Validate the new address. + if (newAddress != 0 && (newAddress < 8 || newAddress > 0x77)) { + Serial.println("Error: new address 0x" + String(newAddress, HEX) + " must be from 0x08 to 0x77\n"); + return false; + } + + if (curAddress == 0) { + Serial.print("Updating all devices (broadcast 0x00) to 0x" + String(newAddress, HEX)); + } else { + Serial.print("Updating the device address from 0x" + String(curAddress, HEX) + " to 0x" + String(newAddress, HEX)); + } + if (newAddress == 0) Serial.print(" (default address)"); + Serial.print("..."); + + Wire1.beginTransmission(curAddress); + Wire1.write(data, 40); + Wire1.endTransmission(); + + delay(500); + + if (newAddress == 0) { + Serial.println(" done\n"); + return true; + } else { + Wire1.requestFrom(newAddress, 1); + if (Wire1.available()) { + Serial.println(" done\n"); + return true; + } else { + Serial.println(" error\n"); + return false; + } + } +} + +// Function to parse hex number (with or without 0x prefix) +int parseHex(String hexStr) { + hexStr.trim(); + + if (hexStr.length() == 0) { + return -1; + } + + if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) { + hexStr = hexStr.substring(2); // Remove the "0x" prefix } - // Search for devices and wait for user confirmation - for (int i = 8; i < 128; i++) { - Wire1.beginTransmission(i); - auto err = Wire1.endTransmission(); - if (err == 0) { - Serial.print("Found device at "); - Serial.println(i); - address = i; - Serial.println("Press 'c' to configure the new address"); + + // Validate that the remaining string contains only valid hexadecimal characters (0-9, A-F, a-f) + for (int i = 0; i < hexStr.length(); i++) { + if (!isHexDigit(hexStr.charAt(i))) { + return -1; + } + } + + return strtol(hexStr.c_str(), NULL, 16); +} + +bool isHexDigit(char c) { + return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')); +} + +void discoverDevices() { + char buffer[64]; + Serial.println("ADDR\tMODULINO\tPINSTRAP\tDEFAULT ADDR"); // Table heading. + + numRows = 0; + + // Discover all modulino devices connected to the I2C bus. + for (int addr = 8; addr < 128; addr++) { + Wire1.beginTransmission(addr); + if (Wire1.endTransmission() != 0) continue; + + if (numRows >= MAX_DEVICES) { + Serial.println("Too many devices connected, maximum supported is" + String(MAX_DEVICES)); + return; + } + + // Some addresses represent non configurable devices (no MCU on it). Handle them as a special case. + if (isFixedAddrDevice(addr)) { + snprintf(buffer, 64, "0x%02X (cannot change)", addr); + addRow(addr, fixedAddrToName(addr), "-", String(buffer)); + + continue; // Stop here, do not try to communicate with this device. } + + { + uint8_t pinstrap = 0; // Variable to store the pinstrap (device type) + Wire1.beginTransmission(addr); // Begin I2C transmission to the current address + Wire1.write(0x00); // Send a request to the device (assuming 0x00 is the register for device type) + Wire1.endTransmission(); // End transmission + + delay(50); // Delay to allow for the device to respond + + Wire1.requestFrom(addr, 1); // Request 1 byte from the device at the current address + if (Wire1.available()) { + pinstrap = Wire1.read(); // Read the device type (pinstrap) + } else { + // If an error happens in the range 0x78 to 0x7F, ignore it. + if (addr >= 0x78) continue; + Serial.println("Failed to read device type at address 0x" + String(addr, HEX)); + } + + snprintf(buffer, 64, "0x%02X", pinstrap); + auto hexPinstrap = String(buffer); + + snprintf(buffer, 64, "0x%02X", pinstrap / 2); // Default address is half pinstrap. + auto defaultAddr = String(buffer); + if (addr != pinstrap / 2) defaultAddr += " *"; // Mark devices with modified address. + + addRow(addr, pinstrapToName(pinstrap), hexPinstrap, defaultAddr); + } + } + + // Print the results. + for (int i = 0; i < numRows; i++) { + char buffer[16]; + snprintf(buffer, 16, "0x%02X", rows[i].addr); + + Serial.print(fixedWidth(buffer, 8)); + Serial.print(fixedWidth(rows[i].modulinoType, 16)); + Serial.print(fixedWidth(rows[i].pinstrap, 16)); + Serial.println(fixedWidth(rows[i].defaultAddr, 12)); } } +void addRow(uint8_t address, String modulinoType, String pinstrap, String defaultAddr) { + if (numRows >= MAX_DEVICES) return; + + rows[numRows].addr = address; + rows[numRows].modulinoType = modulinoType; + rows[numRows].pinstrap = pinstrap; + rows[numRows].defaultAddr = defaultAddr; + numRows++; // Increment the row counter +} + +bool findRow(uint8_t address) { + for (int i = 0; i < numRows; i++) { + if (rows[i].addr == address) return true; + } + return false; +} + + +// Function to add padding to the right to ensure each field has a fixed width +String fixedWidth(String str, int width) { + for (int i = str.length(); i < width; i++) str += ' '; + return str; +} + String pinstrapToName(uint8_t pinstrap) { switch (pinstrap) { case 0x3C: - return "BUZZER"; + return "Buzzer"; case 0x7C: - return "BUTTONS"; + return "Buttons"; case 0x76: case 0x74: - return "ENCODER"; + return "Encoder"; case 0x6C: - return "SMARTLEDS"; + return "Smartleds"; } return "UNKNOWN"; } -void loop() { - // put your main code here, to run repeatedly: - if (Serial.available()) { - if (Serial.read() == 'c') { - Serial.print("Assigning new address to "); - Serial.println(address); - uint8_t data[40] = { 'C', 'F', new_address * 2 }; - Wire1.beginTransmission(address); - Wire1.write(data, 40); - Wire1.endTransmission(); - delay(1000); - Wire1.requestFrom(new_address, 1); - Serial.println("Device type " + pinstrapToName(Wire1.read()) + " at new address " + String(new_address)); - } +String fixedAddrToName(uint8_t address) { + switch (address) { + case 0x29: + return "Distance"; + case 0x44: + return "Thermo"; + case 0x6A: + case 0x6B: + return "Movement"; + } + return "UNKNOWN"; +} + +bool isFixedAddrDevice(uint8_t addr) { + // List of non-configurable devices, recognized by their fixed I2C address. + const uint8_t fixedAddr[] = { 0x29, 0x44, 0x6A, 0x6B }; + + for (int i = 0; i < sizeof(fixedAddr) / sizeof(fixedAddr[0]); i++) { + if (addr == fixedAddr[i]) return true; } + return false; } From 532ead92da1ea7fab89c034e415a017c25b6f271 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 24 Jul 2025 09:56:49 +0200 Subject: [PATCH 7/8] Assign Wire1 as default for NanoR4 --- src/Modulino.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modulino.h b/src/Modulino.h index a79d4b5..745061f 100644 --- a/src/Modulino.h +++ b/src/Modulino.h @@ -26,7 +26,7 @@ void __increaseI2CPriority(); class ModulinoClass { public: -#ifdef ARDUINO_UNOR4_WIFI +#if defined(ARDUINO_UNOR4_WIFI) || defined(ARDUINO_NANO_R4) void begin(HardwareI2C& wire = Wire1) { #else void begin(HardwareI2C& wire = Wire) { From e9e816a0b41b44718cef522058e3ab3ecea44411 Mon Sep 17 00:00:00 2001 From: Martino Facchin Date: Thu, 24 Jul 2025 16:45:10 +0200 Subject: [PATCH 8/8] Update library version to 0.5.1 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 8989efb..e78f90c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Modulino -version=0.5.0 +version=0.5.1 author=Arduino maintainer=Arduino sentence=Arduino Library for Modulinos