From ec6da2160b14da448d64939eb8e9fbb88606108a Mon Sep 17 00:00:00 2001 From: marcosabcarvalho Date: Tue, 2 Jun 2026 18:06:51 -0300 Subject: [PATCH 1/2] fix-MFS_INVERT_DIGIT_SELECT --- .../Alarm_Clock/Alarm_Clock.ino | 267 +++++++++--------- readme-FIX_MFS_INVERT_DIGIT_SELECT.md | 151 ++++++++++ src/MultiFuncShield.cpp | 75 ++++- 3 files changed, 356 insertions(+), 137 deletions(-) create mode 100644 readme-FIX_MFS_INVERT_DIGIT_SELECT.md diff --git a/examples/03 Applications/Alarm_Clock/Alarm_Clock.ino b/examples/03 Applications/Alarm_Clock/Alarm_Clock.ino index 4bbb208..111dcf6 100644 --- a/examples/03 Applications/Alarm_Clock/Alarm_Clock.ino +++ b/examples/03 Applications/Alarm_Clock/Alarm_Clock.ino @@ -1,16 +1,20 @@ -#include #include #include /* - - 24h Alarm Clock sample + 24h Alarm Clock sample - adaptado para hpsaturn/MultiFuncShield button 1 : hold to set time or alarm button 2 : press to view alarm time or cancel alarm if in progress - button 3 : increment hour / minute when setting (alarm) time. Hold to toggle alarm setting. - + button 3 : increment hour / minute when setting (alarm) time. + Hold to toggle alarm setting. + LED1 : on = alarm enabled + + Ajuste feito: + - Removido #include + - Removido Timer1.initialize() + - Trocado MFS.initialize(&Timer1) por MFS.initialize() */ volatile unsigned int clockMilliSeconds = 0; @@ -25,7 +29,6 @@ volatile byte alarmEnabled = false; byte alarmTogglePressed = false; - enum displayModeValues { MODE_CLOCK_TIME, @@ -41,193 +44,195 @@ byte displayMode = MODE_CLOCK_TIME; //-------------------------------------------------------------------------------------------------- void setup() { - Timer1.initialize(); + /* + Na biblioteca hpsaturn, initialize() não recebe TimerOne. + A própria biblioteca já cuida da interrupção interna. + */ MFS.userInterrupt = clockISR; - MFS.initialize(&Timer1); - + MFS.initialize(); + MFS.blinkDisplay(DIGIT_ALL); //MFS.beep(500); } - void loop() { - // put your main code here, to run repeatedly: - byte btn = MFS.getButton(); - + switch (displayMode) { case MODE_CLOCK_TIME: - displayTime(clockHours, clockMinutes); - - if (btn == BUTTON_2_PRESSED) - { - MFS.beep(0); // cancel the alarm. - displayMode = MODE_ALARM_TIME; - } - else if (btn == BUTTON_1_LONG_PRESSED) - { - MFS.blinkDisplay(DIGIT_ALL, OFF); - MFS.blinkDisplay(DIGIT_1 | DIGIT_2); - displayMode = MODE_CLOCK_TIME_SET_HOUR; - clockEnabled = false; - clockMilliSeconds = 0; - clockSeconds = 0; - } - else if (btn == BUTTON_3_LONG_PRESSED && !alarmTogglePressed) - { - alarmTogglePressed = true; - alarmEnabled = !alarmEnabled; - MFS.writeLeds(LED_1, alarmEnabled); - } - else if (btn == BUTTON_3_LONG_RELEASE) - { - alarmTogglePressed = false; - } - break; - + displayTime(clockHours, clockMinutes); + + if (btn == BUTTON_2_PRESSED) + { + MFS.beep(0); // cancel the alarm. + displayMode = MODE_ALARM_TIME; + } + else if (btn == BUTTON_1_LONG_PRESSED) + { + MFS.blinkDisplay(DIGIT_ALL, OFF); + MFS.blinkDisplay(DIGIT_1 | DIGIT_2); + displayMode = MODE_CLOCK_TIME_SET_HOUR; + clockEnabled = false; + clockMilliSeconds = 0; + clockSeconds = 0; + } + else if (btn == BUTTON_3_LONG_PRESSED && !alarmTogglePressed) + { + alarmTogglePressed = true; + alarmEnabled = !alarmEnabled; + MFS.writeLeds(LED_1, alarmEnabled); + } + else if (btn == BUTTON_3_LONG_RELEASE) + { + alarmTogglePressed = false; + } + break; + case MODE_CLOCK_TIME_SET_HOUR: - if (btn == BUTTON_1_PRESSED) + if (btn == BUTTON_1_PRESSED) + { + MFS.blinkDisplay(DIGIT_1 | DIGIT_2, OFF); + MFS.blinkDisplay(DIGIT_3 | DIGIT_4); + displayMode = MODE_CLOCK_TIME_SET_MINUTE; + } + else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + { + clockHours++; + if (clockHours >= 24) { - MFS.blinkDisplay(DIGIT_1 | DIGIT_2, OFF); - MFS.blinkDisplay(DIGIT_3 | DIGIT_4); - displayMode = MODE_CLOCK_TIME_SET_MINUTE; + clockHours = 0; } - else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) - { - clockHours++; - if (clockHours >= 24) - { - clockHours = 0; - } - displayTime(clockHours, clockMinutes); - } - break; - + displayTime(clockHours, clockMinutes); + } + break; + case MODE_CLOCK_TIME_SET_MINUTE: - if (btn == BUTTON_1_PRESSED) - { - MFS.blinkDisplay(DIGIT_3 | DIGIT_4, OFF); - displayMode = MODE_CLOCK_TIME; - clockEnabled = true; - } - else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + if (btn == BUTTON_1_PRESSED) + { + MFS.blinkDisplay(DIGIT_3 | DIGIT_4, OFF); + displayMode = MODE_CLOCK_TIME; + clockEnabled = true; + } + else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + { + clockMinutes++; + if (clockMinutes >= 60) { - clockMinutes++; - if (clockMinutes >= 60) - { - clockMinutes = 0; - } - displayTime(clockHours, clockMinutes); + clockMinutes = 0; } - break; - + displayTime(clockHours, clockMinutes); + } + break; + case MODE_ALARM_TIME: - displayTime(alarmHours, alarmMinutes); + displayTime(alarmHours, alarmMinutes); + + if (btn == BUTTON_2_SHORT_RELEASE || btn == BUTTON_2_LONG_RELEASE) + { + displayMode = MODE_CLOCK_TIME; + } + else if (btn == BUTTON_1_LONG_PRESSED) + { + MFS.blinkDisplay(DIGIT_ALL, OFF); + MFS.blinkDisplay(DIGIT_1 | DIGIT_2); + displayMode = MODE_ALARM_TIME_SET_HOUR; + alarmEnabled = false; + } + break; - if (btn == BUTTON_2_SHORT_RELEASE || btn == BUTTON_2_LONG_RELEASE) - { - displayMode = MODE_CLOCK_TIME; - } - else if (btn == BUTTON_1_LONG_PRESSED) - { - MFS.blinkDisplay(DIGIT_ALL, OFF); - MFS.blinkDisplay(DIGIT_1 | DIGIT_2); - displayMode = MODE_ALARM_TIME_SET_HOUR; - alarmEnabled = false; - } - break; - case MODE_ALARM_TIME_SET_HOUR: - if (btn == BUTTON_1_PRESSED) - { - MFS.blinkDisplay(DIGIT_1 | DIGIT_2, OFF); - MFS.blinkDisplay(DIGIT_3 | DIGIT_4); - displayMode = MODE_ALARM_TIME_SET_MINUTE; - } - else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + if (btn == BUTTON_1_PRESSED) + { + MFS.blinkDisplay(DIGIT_1 | DIGIT_2, OFF); + MFS.blinkDisplay(DIGIT_3 | DIGIT_4); + displayMode = MODE_ALARM_TIME_SET_MINUTE; + } + else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + { + alarmHours++; + if (alarmHours >= 24) { - alarmHours++; - if (alarmHours >= 24) - { - alarmHours = 0; - } - displayTime(alarmHours, alarmMinutes); + alarmHours = 0; } - break; - + displayTime(alarmHours, alarmMinutes); + } + break; + case MODE_ALARM_TIME_SET_MINUTE: - if (btn == BUTTON_1_PRESSED) - { - MFS.blinkDisplay(DIGIT_3 | DIGIT_4, OFF); - displayMode = MODE_CLOCK_TIME; - alarmEnabled = true; - MFS.writeLeds(LED_1, ON); - } - else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + if (btn == BUTTON_1_PRESSED) + { + MFS.blinkDisplay(DIGIT_3 | DIGIT_4, OFF); + displayMode = MODE_CLOCK_TIME; + alarmEnabled = true; + MFS.writeLeds(LED_1, ON); + } + else if (btn == BUTTON_3_PRESSED || btn == BUTTON_3_LONG_PRESSED) + { + alarmMinutes++; + if (alarmMinutes >= 60) { - alarmMinutes++; - if (alarmMinutes >= 60) - { - alarmMinutes = 0; - } - displayTime(alarmHours, alarmMinutes); + alarmMinutes = 0; } - break; + displayTime(alarmHours, alarmMinutes); + } + break; } } - -void displayTime (byte hours, byte minutes) +void displayTime(byte hours, byte minutes) { char time[5]; - + sprintf(time, "%03d", (hours * 100) + minutes); MFS.write(time, 1); } //-------------------------------------------------------------------------------------------------- -void clockISR () +void clockISR() { // Perform ripple count for all time components. if (clockEnabled) { clockMilliSeconds++; + if (clockMilliSeconds >= 1000) { clockMilliSeconds = 0; - + clockSeconds++; + if (clockSeconds >= 60) { clockSeconds = 0; - + clockMinutes++; + if (clockMinutes >= 60) { clockMinutes = 0; - + clockHours++; + if (clockHours >= 24) { clockHours = 0; } } - + // If current time coincides with alarm time, and alarm is enabled, engage the alarm. if (alarmEnabled && (clockMinutes == alarmMinutes) && (clockHours == alarmHours)) { MFS.beep( - 10, // on period - 5, // off period - 4, // number of cycles - 100, // number of loop cycles - 50 // delay between loop cycles - ); + 10, // on period + 5, // off period + 4, // number of cycles + 100, // number of loop cycles + 50 // delay between loop cycles + ); } } } } -} - +} \ No newline at end of file diff --git a/readme-FIX_MFS_INVERT_DIGIT_SELECT.md b/readme-FIX_MFS_INVERT_DIGIT_SELECT.md new file mode 100644 index 0000000..071c863 --- /dev/null +++ b/readme-FIX_MFS_INVERT_DIGIT_SELECT.md @@ -0,0 +1,151 @@ +# readme-fix-MFS_INVERT_DIGIT_SELECT-problem + +## Add compatibility option for active-low digit select displays + +This note documents a compatibility issue found in some Arduino Multi Function Shield clones that use two `74HC595` shift registers to drive the 4-digit display. + +The tested board uses the same display pins as the classic Multi Function Shield: + +```text +DATA = D8 +CLOCK = D7 +LATCH = D4 +``` + +However, the 4-digit display did not work correctly with the original library behavior. The display powered on, but showed malformed characters instead of values such as `1234`. + +After direct tests with the `74HC595` output, the display worked correctly when digit selection was treated as active LOW. + +## Diagnosis + +The tested display configuration is: + +```text +DATA = D8 +CLOCK = D7 +LATCH = D4 +Segments = standard segment mapping +Digit select = active LOW +``` + +The working digit select values are: + +```cpp +digit 0 = 0xFE +digit 1 = 0xFD +digit 2 = 0xFB +digit 3 = 0xF7 +``` + +## Proposed compatibility option + +This patch adds a compile-time compatibility flag: + +```cpp +#ifndef MFS_INVERT_DIGIT_SELECT +#define MFS_INVERT_DIGIT_SELECT 1 +#endif +``` + +Use: + +```cpp +#define MFS_INVERT_DIGIT_SELECT 1 +``` + +for displays that require active-LOW/inverted digit select. + +Use: + +```cpp +#define MFS_INVERT_DIGIT_SELECT 0 +``` + +for the original/classic library behavior. + +## Implementation + +The patch keeps the existing segment maps and adds two helper functions: + +```cpp +static inline byte normalizeSegmentValue(byte value) +{ +#if MFS_INVERT_DIGIT_SELECT + return ~value; +#else + return value; +#endif +} + +static inline byte normalizeDigitSelect(byte segment) +{ +#if MFS_INVERT_DIGIT_SELECT + return (byte)(0xF0 | ((~(1 << segment)) & 0x0F)); +#else + return SEGMENT_SELECT[segment]; +#endif +} +``` + +The low-level display writing routine then sends: + +```cpp +byte valueOut = normalizeSegmentValue(Value); +byte digitOut = normalizeDigitSelect(Segment); +``` + +This keeps the hardware adaptation isolated and documented. + +## Test sketch + +```cpp +#include + +void setup() { + MFS.initialize(); +} + +void loop() { + MFS.write(1234); + delay(1500); + + MFS.write(5678); + delay(1500); + + MFS.write(8888); + delay(1500); + + MFS.write(1111); + delay(1500); + + MFS.write("Hi"); + delay(1500); + + MFS.write("End"); + delay(1500); +} +``` + +Expected output: + +```text +1234 +5678 +8888 +1111 +Hi +End +``` + +## Pull Request title suggestion + +```text +Add compatibility option for active-low digit select displays +``` + +## Notes + +- No hardware modification was required. +- The issue was not caused by changed DATA/CLOCK/LATCH pins. +- The same D8/D7/D4 pin configuration was confirmed by direct tests. +- The compatibility option preserves the classic behavior when disabled. diff --git a/src/MultiFuncShield.cpp b/src/MultiFuncShield.cpp index 9514773..f5bedd3 100644 --- a/src/MultiFuncShield.cpp +++ b/src/MultiFuncShield.cpp @@ -1,5 +1,25 @@ #include "MultiFuncShield.h" +/* + DISPLAY COMPATIBILITY OPTION + ------------------------------------------------------------ + Some Multi Function Shield clones use the same 74HC595 display + pins as the classic board, but their 4-digit display uses + active-LOW digit selection. + + Set to 0 for the classic/original shield behavior. + Set to 1 for displays that show malformed characters and need + inverted/active-LOW digit select values. + + Tested pins: + DATA = D8 + CLOCK = D7 + LATCH = D4 +*/ +#ifndef MFS_INVERT_DIGIT_SELECT +#define MFS_INVERT_DIGIT_SELECT 1 +#endif + #define BUTTON_SAMPLE_INTERVAL_SCALE 20 #define BUTTON_SAMPLE_INTERVAL (1000 / BUTTON_SAMPLE_INTERVAL_SCALE) @@ -17,6 +37,40 @@ const byte SEGMENT_MAP_ALPHA[] = {136, 131, 167, 161, 134, 142, 144, 139 ,207, 2 /* Byte maps to select digit 1 to 4 */ const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8}; + +/* + Display output normalization helpers. + + The original library segment maps are active LOW. On displays that use + inverted/active-LOW digit selection, the segment byte must be normalized + and the digit-select byte must be generated as: + + digit 0 = 0xFE + digit 1 = 0xFD + digit 2 = 0xFB + digit 3 = 0xF7 + + Keeping this logic in helper functions makes the hardware variation clear + and avoids unexplained bit inversions inside the low-level write routine. +*/ +static inline byte normalizeSegmentValue(byte value) +{ +#if MFS_INVERT_DIGIT_SELECT + return ~value; +#else + return value; +#endif +} + +static inline byte normalizeDigitSelect(byte segment) +{ +#if MFS_INVERT_DIGIT_SELECT + return (byte)(0xF0 | ((~(1 << segment)) & 0x0F)); +#else + return SEGMENT_SELECT[segment]; +#endif +} + const char DISPLAY_OVERFLOW_ERROR = 'E'; const byte BLINK_ON_COUNT = 65; @@ -1204,16 +1258,19 @@ int MedianOf9(int s0, int s1, int s2, int s3, int s4, int s5, int s6, int s7, in /* Write a value to one of the 4 digits of the display */ void WriteValueToSegment(byte Segment, byte Value) { + byte valueOut = normalizeSegmentValue(Value); + byte digitOut = normalizeDigitSelect(Segment); + bitClear(PORTD, 4); for (uint8_t i = 0; i < 8; i++) { - bitWrite(PORTB, 0, !!(Value & (1 << (7 - i)))); + bitWrite(PORTB, 0, !!(valueOut & (1 << (7 - i)))); bitSet(PORTD, 7); bitClear(PORTD, 7); } for (uint8_t i = 0; i < 8; i++) { - bitWrite(PORTB, 0, !!(SEGMENT_SELECT[Segment] & (1 << (7 - i)))); + bitWrite(PORTB, 0, !!(digitOut & (1 << (7 - i)))); bitSet(PORTD, 7); bitClear(PORTD, 7); } @@ -1265,16 +1322,19 @@ int MedianOf9(int s0, int s1, int s2, int s3, int s4, int s5, int s6, int s7, in /* Write a value to one of the 4 digits of the display */ void WriteValueToSegment(byte Segment, byte Value) { + byte valueOut = normalizeSegmentValue(Value); + byte digitOut = normalizeDigitSelect(Segment); + bitClear(PORTD, 4); for (uint8_t i = 0; i < 8; i++) { - bitWrite(PORTB, 4, !!(Value & (1 << (7 - i)))); + bitWrite(PORTB, 4, !!(valueOut & (1 << (7 - i)))); bitSet(PORTE, 6); bitClear(PORTE, 6); } for (uint8_t i = 0; i < 8; i++) { - bitWrite(PORTB, 4, !!(SEGMENT_SELECT[Segment] & (1 << (7 - i)))); + bitWrite(PORTB, 4, !!(digitOut & (1 << (7 - i)))); bitSet(PORTE, 6); bitClear(PORTE, 6); } @@ -1336,16 +1396,19 @@ void WriteValueToSegment(byte Segment, byte Value) /* Write a value to one of the 4 digits of the display */ void WriteValueToSegment(byte Segment, byte Value) { + byte valueOut = normalizeSegmentValue(Value); + byte digitOut = normalizeDigitSelect(Segment); + bitClear(PORTG, 5); for (uint8_t i = 0; i < 8; i++) { - bitWrite(PORTH, 5, !!(Value & (1 << (7 - i)))); + bitWrite(PORTH, 5, !!(valueOut & (1 << (7 - i)))); bitSet(PORTH, 4); bitClear(PORTH, 4); } for (uint8_t i = 0; i < 8; i++) { - bitWrite(PORTH, 5, !!(SEGMENT_SELECT[Segment] & (1 << (7 - i)))); + bitWrite(PORTH, 5, !!(digitOut & (1 << (7 - i)))); bitSet(PORTH, 4); bitClear(PORTH, 4); } From 93ef7bbb3ee73193ca5bb65143c6d8487906b208 Mon Sep 17 00:00:00 2001 From: marcosabcarvalho Date: Mon, 8 Jun 2026 12:46:25 -0300 Subject: [PATCH 2/2] Document digit select compatibility --- README.md | 8 ++ readme-FIX_MFS_INVERT_DIGIT_SELECT.md | 151 -------------------------- src/MultiFuncShield.cpp | 2 +- 3 files changed, 9 insertions(+), 152 deletions(-) delete mode 100644 readme-FIX_MFS_INVERT_DIGIT_SELECT.md diff --git a/README.md b/README.md index bf70648..927888a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,14 @@ Here some features contributed by the community regarding the original one: Please see the examples for details. +### Digit select inversion support + +Some Multi Function Shield clones use inverted digit select logic. If the display digits are selected incorrectly on your board, enable the following option before compiling: + +```cpp +#define MFS_INVERT_DIGIT_SELECT 1 +``` + ## PlatformIO full Demo project Please install first [PlatformIO](http://platformio.org/) open source ecosystem for IoT development compatible with **Arduino** IDE and its command line tools (Windows, MacOs and Linux). Also, you may need to install [git](http://git-scm.com/) in your system. diff --git a/readme-FIX_MFS_INVERT_DIGIT_SELECT.md b/readme-FIX_MFS_INVERT_DIGIT_SELECT.md deleted file mode 100644 index 071c863..0000000 --- a/readme-FIX_MFS_INVERT_DIGIT_SELECT.md +++ /dev/null @@ -1,151 +0,0 @@ -# readme-fix-MFS_INVERT_DIGIT_SELECT-problem - -## Add compatibility option for active-low digit select displays - -This note documents a compatibility issue found in some Arduino Multi Function Shield clones that use two `74HC595` shift registers to drive the 4-digit display. - -The tested board uses the same display pins as the classic Multi Function Shield: - -```text -DATA = D8 -CLOCK = D7 -LATCH = D4 -``` - -However, the 4-digit display did not work correctly with the original library behavior. The display powered on, but showed malformed characters instead of values such as `1234`. - -After direct tests with the `74HC595` output, the display worked correctly when digit selection was treated as active LOW. - -## Diagnosis - -The tested display configuration is: - -```text -DATA = D8 -CLOCK = D7 -LATCH = D4 -Segments = standard segment mapping -Digit select = active LOW -``` - -The working digit select values are: - -```cpp -digit 0 = 0xFE -digit 1 = 0xFD -digit 2 = 0xFB -digit 3 = 0xF7 -``` - -## Proposed compatibility option - -This patch adds a compile-time compatibility flag: - -```cpp -#ifndef MFS_INVERT_DIGIT_SELECT -#define MFS_INVERT_DIGIT_SELECT 1 -#endif -``` - -Use: - -```cpp -#define MFS_INVERT_DIGIT_SELECT 1 -``` - -for displays that require active-LOW/inverted digit select. - -Use: - -```cpp -#define MFS_INVERT_DIGIT_SELECT 0 -``` - -for the original/classic library behavior. - -## Implementation - -The patch keeps the existing segment maps and adds two helper functions: - -```cpp -static inline byte normalizeSegmentValue(byte value) -{ -#if MFS_INVERT_DIGIT_SELECT - return ~value; -#else - return value; -#endif -} - -static inline byte normalizeDigitSelect(byte segment) -{ -#if MFS_INVERT_DIGIT_SELECT - return (byte)(0xF0 | ((~(1 << segment)) & 0x0F)); -#else - return SEGMENT_SELECT[segment]; -#endif -} -``` - -The low-level display writing routine then sends: - -```cpp -byte valueOut = normalizeSegmentValue(Value); -byte digitOut = normalizeDigitSelect(Segment); -``` - -This keeps the hardware adaptation isolated and documented. - -## Test sketch - -```cpp -#include - -void setup() { - MFS.initialize(); -} - -void loop() { - MFS.write(1234); - delay(1500); - - MFS.write(5678); - delay(1500); - - MFS.write(8888); - delay(1500); - - MFS.write(1111); - delay(1500); - - MFS.write("Hi"); - delay(1500); - - MFS.write("End"); - delay(1500); -} -``` - -Expected output: - -```text -1234 -5678 -8888 -1111 -Hi -End -``` - -## Pull Request title suggestion - -```text -Add compatibility option for active-low digit select displays -``` - -## Notes - -- No hardware modification was required. -- The issue was not caused by changed DATA/CLOCK/LATCH pins. -- The same D8/D7/D4 pin configuration was confirmed by direct tests. -- The compatibility option preserves the classic behavior when disabled. diff --git a/src/MultiFuncShield.cpp b/src/MultiFuncShield.cpp index f5bedd3..6ebb3b8 100644 --- a/src/MultiFuncShield.cpp +++ b/src/MultiFuncShield.cpp @@ -17,7 +17,7 @@ LATCH = D4 */ #ifndef MFS_INVERT_DIGIT_SELECT -#define MFS_INVERT_DIGIT_SELECT 1 +#define MFS_INVERT_DIGIT_SELECT 0 #endif #define BUTTON_SAMPLE_INTERVAL_SCALE 20