diff --git a/libraries/I2CKeyPad8x8/CHANGELOG.md b/libraries/I2CKeyPad8x8/CHANGELOG.md index 71826f3a..ffd937a8 100644 --- a/libraries/I2CKeyPad8x8/CHANGELOG.md +++ b/libraries/I2CKeyPad8x8/CHANGELOG.md @@ -6,16 +6,29 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.3.0] - 2024-07-15 +- sync with I2CKeyPad 0.5.0 +- implement **debounceThreshold** +- add constant **I2C_KEYPAD8x8_THRESHOLD** +- add **uint32_t getLastTimeRead()** +- update **getChar()** to support **I2C_KEYPAD8x8_THRESHOLD** +- update readme.md +- update unit test +- update keywords.txt +- minor edits + +---- + ## [0.2.0] - 2023-11-09 - simplify begin() - update readme.md -- minor edits. +- minor edits +---- ## [0.1.1] - 2022-11-12 - Add RP2040 support to build-CI. - Add CHANGELOG.md - ## [0.1.0] - 2022-09-29 - initial version diff --git a/libraries/I2CKeyPad8x8/I2CKeyPad8x8.cpp b/libraries/I2CKeyPad8x8/I2CKeyPad8x8.cpp index e73cf9db..4d0801da 100644 --- a/libraries/I2CKeyPad8x8/I2CKeyPad8x8.cpp +++ b/libraries/I2CKeyPad8x8/I2CKeyPad8x8.cpp @@ -1,7 +1,7 @@ // // FILE: I2CKeyPad8x8.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.2.0 +// VERSION: 0.3.0 // PURPOSE: Arduino library for 8x8 or smaller KeyPad connected to an I2C PCF8575. // URL: https://github.com/RobTillaart/I2CKeyPad8x8 @@ -14,6 +14,8 @@ I2CKeyPad8x8::I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire *wire) _lastKey = I2C_KEYPAD8x8_NOKEY; _address = deviceAddress; _wire = wire; + _debounceThreshold = 0; + _lastTimeRead = 0; } @@ -33,6 +35,106 @@ bool I2CKeyPad8x8::isConnected() uint8_t I2CKeyPad8x8::getKey() +{ + uint32_t now = millis(); + if (_debounceThreshold > 0) + { + if (now - _debounceThreshold < _lastTimeRead) + { + return I2C_KEYPAD8x8_THRESHOLD; + } + } + + uint8_t key = _getKey8x8(); + if (key == I2C_KEYPAD8x8_FAIL) return key; // propagate error. + // valid keys + NOKEY + _lastKey = key; + _lastTimeRead = now; + return key; +} + + +uint8_t I2CKeyPad8x8::getLastKey() +{ + return _lastKey; +} + + +// to check "press any key" +bool I2CKeyPad8x8::isPressed() +{ + uint16_t a = _read(0xFF00); + if (a == 0xFF00) return false; + return (a != 0xFF00); +} + + +uint8_t I2CKeyPad8x8::getChar() +{ + uint8_t key = getKey(); + if (key != I2C_KEYPAD8x8_THRESHOLD) + { + return _keyMap[key]; + } + return I2C_KEYPAD8x8_THRESHOLD; +} + + +uint8_t I2CKeyPad8x8::getLastChar() +{ + return _keyMap[_lastKey]; +} + + +void I2CKeyPad8x8::loadKeyMap(char * keyMap) +{ + _keyMap = keyMap; +} + + +void I2CKeyPad8x8::setDebounceThreshold(uint16_t value) +{ + _debounceThreshold = value; +} + + +uint16_t I2CKeyPad8x8::getDebounceThreshold() +{ + return _debounceThreshold; +} + + +uint32_t I2CKeyPad8x8::getLastTimeRead() +{ + return _lastTimeRead; +} + + +////////////////////////////////////////////////////// +// +// PROTECTED +// +uint16_t I2CKeyPad8x8::_read(uint16_t mask) +{ + // improve the odds that IO will not interrupted. + yield(); + + _wire->beginTransmission(_address); + _wire->write(mask >> 8); + _wire->write(mask & 0xFF); + if (_wire->endTransmission() != 0) + { + // set communication error + return 0xFFFF; + } + _wire->requestFrom(_address, (uint8_t)2); + uint16_t value = _wire->read() << 8; + value += _wire->read(); + return value; +} + + +uint8_t I2CKeyPad8x8::_getKey8x8() { // key = row + 8 x col uint8_t key = 0; @@ -66,68 +168,9 @@ uint8_t I2CKeyPad8x8::getKey() else if (cols == 0x007F) key += 56; else return I2C_KEYPAD8x8_FAIL; - _lastKey = key; - return key; // 0..65 } -uint8_t I2CKeyPad8x8::getLastKey() -{ - return _lastKey; -}; - - -// to check "press any key" -bool I2CKeyPad8x8::isPressed() -{ - uint16_t a = _read(0xFF00); - if (a == 0xFF00) return false; - return (a != 0xFF00); -} - - -uint8_t I2CKeyPad8x8::getChar() -{ - return _keyMap[getKey()]; -}; - - -uint8_t I2CKeyPad8x8::getLastChar() -{ - return _keyMap[_lastKey]; -}; - - -void I2CKeyPad8x8::loadKeyMap(char * keyMap) -{ - _keyMap = keyMap; -} - - -////////////////////////////////////////////////////// -// -// PROTECTED -// -uint16_t I2CKeyPad8x8::_read(uint16_t mask) -{ - // improve the odds that IO will not interrupted. - yield(); - - _wire->beginTransmission(_address); - _wire->write(mask >> 8); - _wire->write(mask & 0xFF); - if (_wire->endTransmission() != 0) - { - // set communication error - return 0xFFFF; - } - _wire->requestFrom(_address, (uint8_t)2); - uint16_t value = _wire->read() << 8; - value += _wire->read(); - return value; -} - - // -- END OF FILE -- diff --git a/libraries/I2CKeyPad8x8/I2CKeyPad8x8.h b/libraries/I2CKeyPad8x8/I2CKeyPad8x8.h index 1010a6d1..5ea7f21c 100644 --- a/libraries/I2CKeyPad8x8/I2CKeyPad8x8.h +++ b/libraries/I2CKeyPad8x8/I2CKeyPad8x8.h @@ -2,7 +2,7 @@ // // FILE: I2CKeyPad8x8.h // AUTHOR: Rob Tillaart -// VERSION: 0.2.0 +// VERSION: 0.3.0 // PURPOSE: Arduino library for 8x8 or smaller KeyPad connected to an I2C PCF8575. // URL: https://github.com/RobTillaart/I2CKeyPad @@ -11,17 +11,18 @@ #include "Wire.h" -#define I2C_KEYPAD8x8_LIB_VERSION (F("0.2.0")) +#define I2C_KEYPAD8x8_LIB_VERSION (F("0.3.0")) #define I2C_KEYPAD8x8_NOKEY 64 #define I2C_KEYPAD8x8_FAIL 65 - +#define I2C_KEYPAD8x8_THRESHOLD 255 class I2CKeyPad8x8 { public: I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire *wire = &Wire); + // call Wire.begin() first! bool begin(); bool isConnected(); @@ -30,22 +31,31 @@ public: uint8_t getLastKey(); bool isPressed(); - // get 'translated' keys // user must load KeyMap, there is no check. uint8_t getChar(); uint8_t getLastChar(); void loadKeyMap(char * keyMap); // char[65] + // value in milliseconds, max 65535 ms + void setDebounceThreshold(uint16_t value = 0); + uint16_t getDebounceThreshold(); + uint32_t getLastTimeRead(); + protected: uint8_t _address; uint8_t _lastKey; uint16_t _read(uint16_t mask); + uint16_t _debounceThreshold; + uint32_t _lastTimeRead; + + uint8_t _getKey8x8(); TwoWire* _wire; char * _keyMap = NULL; + }; diff --git a/libraries/I2CKeyPad8x8/README.md b/libraries/I2CKeyPad8x8/README.md index 302a0a61..3668822c 100644 --- a/libraries/I2CKeyPad8x8/README.md +++ b/libraries/I2CKeyPad8x8/README.md @@ -21,11 +21,20 @@ EXPERIMENTAL (first tests ==> OK) The I2CKeyPad8x8 library implements the reading of a 8x8 keypad by means of a PCF8575. Smaller keypads, meaning less columns or rows (e.g. 5x4) can be read with it too. -#### Related +### Breaking change -Relates strongly to https://github.com/RobTillaart/I2CKeyPad. which is an 8x8 version using **PCF8574**. +Since 0.3.0 the library can set a debounce threshold. +If this is set (> 0) the **getKey()** and **getChar()** functions +can return **I2C_KEYPAD_THRESHOLD** (255). + +### Related + +Relates strongly to https://github.com/RobTillaart/I2CKeyPad. which is an 4x4 version using **PCF8574**. + +- https://github.com/RobTillaart/PCF8575 - https://github.com/RobTillaart/AnalogKeypad +- https://github.com/RobTillaart/I2CKeyPad4x4 - https://github.com/RobTillaart/I2CKeyPad8x8 @@ -36,30 +45,59 @@ See the conceptual schema below. It might take some trying to get the correct pins connected. ``` - PROC PCF8575 KEYPAD - +--------+ +--------+ +--------+ - | | | 0|----------|R | - | SDA |--------| .|----------|O | - | SCL |--------| .|----------|W | - | | | 7|----------|S | - | | | .| | | - | | | .| | | - | | | 8|----------|C | - | | | .|----------|O | - | | | .|----------|L | - | | | 15|----------|S | - +--------+ +--------+ +--------+ + PROC PCF8575 KEYPAD + +--------+ +---------+ +---------+ + | | | 0 |<-------->| R | + | SDA |<------>| . |<-------->| O | + | SCL |<------>| . |<-------->| W | + | | | 7 |<-------->| S | + | | | | | | + | | | 8 |<-------->| C | + | | | . |<-------->| O | + | | | . |<-------->| L | + | | | 15 |<-------->| S | + +--------+ +---------+ +---------+ ``` +## I2C + +### I2C addresses + +This library uses a PCF8575 chip. + +| Type | Address-range | Notes | +|:-----------|:---------------:|:-------------------------:| +| PCF8575 | 0x20 to 0x27 | same range as PCF8574 ! | + +Be careful to select an unique I2C address for every device on the bus. + + +### I2C multiplexing + +Sometimes you need to control more devices than possible with the default +address range the device provides. +This is possible with an I2C multiplexer e.g. TCA9548 which creates up +to eight channels (think of it as I2C subnets) which can use the complete +address range of the device. + +Drawback of using a multiplexer is that it takes more administration in +your code e.g. which device is on which channel. +This will slow down the access, which must be taken into account when +deciding which devices are on which channel. +Also note that switching between channels will slow down other devices +too if they are behind the multiplexer. + +- https://github.com/RobTillaart/TCA9548 + + ## Interface ```cpp #include "I2CKeyPad8x8.h" ``` - -#### Base +### Base - **I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire \*wire = &Wire)** The constructor sets the device address and optionally @@ -68,23 +106,28 @@ allows to selects the I2C bus to use. Call wire.begin() first! - **bool isConnected()** returns false if the PCF8575 cannot be connected to. - **uint8_t getKey()** Returns default 0..63 for regular keys, -Returns 64 if no key is pressed and 65 in case of an error. -- **uint8_t getLastKey()** Returns the last **valid** key pressed 0..63. Initially it will return 64 (NOKEY). -- **bool isPressed()** Returns true if one or more keys of the keyPad is pressed, -however it is not checked if multiple keys are pressed. +Returns **I2C_KEYPAD8X8_NOKEY** (64) if no key is pressed and and **I2C_KEYPAD8X8_FAIL** +(17) in case of an error, e.g. multiple keys pressed. +If a debounce delay is set, it might return **I2C_KEYPAD8X8_THRESHOLD** if called too fast. +- **uint8_t getLastKey()** Returns the last **valid** key pressed 0..63, or **I2C_KEYPAD8X8_NOKEY** (64) which is also the initial value. +- **bool isPressed()** Returns true if one or more keys of the keyPad are pressed, +however there is no check if multiple keys are pressed. -#### KeyMap functions +### KeyMap functions -**loadKeyMap()** must be called before **getChar()** and **getLastChar()**! +Note: **loadKeyMap()** must be called before **getChar()** and **getLastChar()**! - **char getChar()** returns the char corresponding to mapped key pressed. +It returns **I2C_KEYPAD_THRESHOLD** if called too fast. - **char getLastChar()** returns the last char pressed. +This function is not affected by the debounce threshold. - **bool loadKeyMap(char \* keyMap)** keyMap should point to a (global) char array of length 66. This array maps index 0..63 on a char and index \[64\] maps to **I2CKeyPad8x8_NOKEY** (typical 'N') and index \[65\] maps **I2CKeyPad8x8_FAIL** (typical 'F'). index 66 is the null char. **WARNING** + If there is no key map loaded the user should **NOT** call **getChar()** or **getLastChar()** as these would return meaningless bytes. @@ -93,14 +136,49 @@ Note: a keyMap char array may be longer than 66 characters, but only the first 6 The length is **NOT** checked upon loading (as it may contain a NULL char). -#### Basic working +### Debouncing threshold + +**Experimental** + +Since version 0.3.0, the library implements an experimental debounce threshold +which is non-blocking. + +If a key bounces, it can trigger multiple interrupts, while the purpose is to +act like only one keypress. The debounce threshold results in a fast return +of **getKey()** (with **I2C_KEYPAD8x8_THRESHOLD**) if called too fast. + +The default value of the debounce threshold is zero to be backwards compatible. +The value is set in milliseconds, with a maximum of 65535 ==> about 65 seconds or 1 minute. +A value of 1 still allows ~1000 **getKey()** calls per second (in theory). +A value of 65535 can be used e.g. for a delay after entering a wrong key code / password. +Setting a high value might result in missed keypresses so use with care. + +The default value of the debounce threshold is zero to be backwards compatible. + +- **void setDebounceThreshold(uint16_t value = 0)** set the debounce threshold, +value in milliseconds, max 65535. +The default value is zero, to reset its value. +- **uint16_t getDebounceThreshold()** returns the set debounce threshold. +- **uint32_t getLastTimeRead()** returns the time stamp of the last valid read key +(or NOKEY). This variable is used for the debounce, and may be used for other +purposes too. E.g. track time between keypresses. + +If a debounce threshold is set, and **getKey()** or **getChar()** is called too fast, +these functions will return **I2C_KEYPAD8x8_THRESHOLD** (255). + +Feedback welcome! + + +### Basic working After the **keypad.begin()** the sketch calls the **keyPad.getKey()** to read values from the keypad. - If no key is pressed **I2C_KEYPAD8x8_NOKEY** code (16) is returned. -- If the read value is not valid, e.g. two keys pressed, **I2CKeyPad8x8_FAIL** code (17) is returned. +- If the read value is not valid, e.g. two keys pressed, **I2C_KEYPAD8x8_FAIL** code (17) is returned. +- If a debounce threshold is set, **I2C_KEYPAD8x8_THRESHOLD** might be returned. +See section above. - Otherwise a number 0..63 is returned. -Note NOKEY and FAIL bot have bit 8 set, all valid keys don't. +Note NOKEY and FAIL both have bit 8 set, all valid keys don't. This allows fast checking for valid keys. Only if a key map is loaded, the user can call **getChar()** and **getLastChar()** to get mapped keys. @@ -109,19 +187,12 @@ Only if a key map is loaded, the user can call **getChar()** and **getLastChar() ## Interrupts The library enables the PCF8575 to generate interrupts on the PCF8575 when a key is pressed. -This makes checking the keypad far more efficient as one does not need to poll over I2C. - -See examples. (TODO) - - -## Operation - -See examples +This makes checking the keypad far more efficient as one does not need to poll the device over I2C. +See examples. ## Future - #### Must - update documentation @@ -134,6 +205,8 @@ See examples - interrupts - keymapping - performance +- improve error handling? + - **I2C_KEYPAD_ERR_MODE** #### Could diff --git a/libraries/I2CKeyPad8x8/examples/I2Ckeypad8x8_demo01/I2Ckeypad8x8_demo01.ino b/libraries/I2CKeyPad8x8/examples/I2Ckeypad8x8_demo01/I2Ckeypad8x8_demo01.ino index fccb49ce..03531e3b 100644 --- a/libraries/I2CKeyPad8x8/examples/I2Ckeypad8x8_demo01/I2Ckeypad8x8_demo01.ino +++ b/libraries/I2CKeyPad8x8/examples/I2Ckeypad8x8_demo01/I2Ckeypad8x8_demo01.ino @@ -13,7 +13,7 @@ #include "Wire.h" #include "I2CKeyPad8x8.h" -const uint8_t KEYPAD_ADDRESS = 0x38; +const uint8_t KEYPAD_ADDRESS = 0x20; I2CKeyPad8x8 keyPad(KEYPAD_ADDRESS); diff --git a/libraries/I2CKeyPad8x8/keywords.txt b/libraries/I2CKeyPad8x8/keywords.txt index 81ed5b57..2c889420 100644 --- a/libraries/I2CKeyPad8x8/keywords.txt +++ b/libraries/I2CKeyPad8x8/keywords.txt @@ -16,6 +16,9 @@ getChar KEYWORD2 getLastChar KEYWORD2 loadKeyMap KEYWORD2 +setDebounceThreshold KEYWORD2 +getDebounceThreshold KEYWORD2 + # Instances (KEYWORD2) @@ -25,4 +28,5 @@ I2C_KEYPAD8x8_LIB_VERSION LITERAL1 I2C_KEYPAD8x8_NOKEY LITERAL1 I2C_KEYPAD8x8_FAIL LITERAL1 +I2C_KEYPAD8x8_THRESHOLD LITERAL1 diff --git a/libraries/I2CKeyPad8x8/library.json b/libraries/I2CKeyPad8x8/library.json index b190f073..a0de3255 100644 --- a/libraries/I2CKeyPad8x8/library.json +++ b/libraries/I2CKeyPad8x8/library.json @@ -1,7 +1,7 @@ { "name": "I2CKeyPad8x8", "keywords": "I2C,KeyPad, 8x8, PCF8574", - "description": "Arduino library for a KeyPad connected to a PCF8575. 8x8 or smaller.", + "description": "Arduino library for a 8x8 (or smaller) keypad connected to an I2C PCF8575.", "authors": [ { @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/I2CKeyPad8x8.git" }, - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "frameworks": "*", "platforms": "*", diff --git a/libraries/I2CKeyPad8x8/library.properties b/libraries/I2CKeyPad8x8/library.properties index 59288c9d..98f34056 100644 --- a/libraries/I2CKeyPad8x8/library.properties +++ b/libraries/I2CKeyPad8x8/library.properties @@ -1,9 +1,9 @@ name=I2CKeyPad8x8 -version=0.2.0 +version=0.3.0 author=Rob Tillaart maintainer=Rob Tillaart -sentence=Arduino library for a KeyPad connected to a PCF8575. -paragraph=8x8 or smaller. +sentence=Arduino library for a 8x8 (or smaller) keypad connected to an I2C PCF8575. +paragraph= category=Signal Input/Output url=https://github.com/RobTillaart/I2CKeyPad8x8 architectures=* diff --git a/libraries/I2CKeyPad8x8/test/unit_test_001.cpp b/libraries/I2CKeyPad8x8/test/unit_test_001.cpp index 9142ccc6..cf401761 100644 --- a/libraries/I2CKeyPad8x8/test/unit_test_001.cpp +++ b/libraries/I2CKeyPad8x8/test/unit_test_001.cpp @@ -43,6 +43,7 @@ unittest(test_constants) { assertEqual(64, I2C_KEYPAD8x8_NOKEY); assertEqual(65, I2C_KEYPAD8x8_FAIL); + assertEqual(255, I2C_KEYPAD8x8_THRESHOLD); } @@ -59,6 +60,21 @@ unittest(test_constructor) } +unittest(test_debounce_threshold) +{ + const uint8_t KEYPAD_ADDRESS = 0x38; + I2CKeyPad8x8 keyPad(KEYPAD_ADDRESS); + + // default 0 + assertEqual(0, keyPad.getDebounceThreshold()); + + for (uint16_t th = 5000; th < 60000; th += 5000) + { + keyPad.setDebounceThreshold(th); + assertEqual(th, keyPad.getDebounceThreshold()); + } +} + // unittest(test_KeyMap) // {