diff --git a/libraries/I2CKeyPad/CHANGELOG.md b/libraries/I2CKeyPad/CHANGELOG.md index 4599cb9c..59938263 100644 --- a/libraries/I2CKeyPad/CHANGELOG.md +++ b/libraries/I2CKeyPad/CHANGELOG.md @@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.5.0] - 2024-07-14 +- Fix #21, add section to readme.md +- Fix #22, implement **debounceThreshold** +- add constant **I2C_KEYPAD_THRESHOLD** +- add **uint32_t getLastTimeRead()** +- update **getChar()** to support **I2C_KEYPAD_THRESHOLD** +- update readme.md +- update unit test +- update keywords.txt +- minor edits + +---- + ## [0.4.0] - 2023-11-09 - simplify begin() - added I2Ckeypad_Wire1_ESP32.ino diff --git a/libraries/I2CKeyPad/I2CKeyPad .cpp b/libraries/I2CKeyPad/I2CKeyPad .cpp index 8ab0abc5..85255d1e 100644 --- a/libraries/I2CKeyPad/I2CKeyPad .cpp +++ b/libraries/I2CKeyPad/I2CKeyPad .cpp @@ -1,7 +1,7 @@ // // FILE: I2CKeyPad.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.4.0 +// VERSION: 0.5.0 // PURPOSE: Arduino library for 4x4 KeyPad connected to an I2C PCF8574 // URL: https://github.com/RobTillaart/I2CKeyPad @@ -15,6 +15,8 @@ I2CKeyPad::I2CKeyPad(const uint8_t deviceAddress, TwoWire *wire) _address = deviceAddress; _wire = wire; _mode = I2C_KEYPAD_4x4; + _debounceThreshold = 0; + _lastTimeRead = 0; } @@ -26,20 +28,42 @@ bool I2CKeyPad::begin() } -uint8_t I2CKeyPad::getKey() +bool I2CKeyPad::isConnected() { - if (_mode == I2C_KEYPAD_5x3) return _getKey5x3(); - if (_mode == I2C_KEYPAD_6x2) return _getKey6x2(); - if (_mode == I2C_KEYPAD_8x1) return _getKey8x1(); - // default. - return _getKey4x4(); + _wire->beginTransmission(_address); + return (_wire->endTransmission() == 0); } -uint8_t I2CKeyPad::getLastKey() -{ +uint8_t I2CKeyPad::getKey() +{ + uint32_t now = millis(); + if (_debounceThreshold > 0) + { + if (now - _debounceThreshold < _lastTimeRead) + { + return I2C_KEYPAD_THRESHOLD; + } + } + + uint8_t key = 0; + if (_mode == I2C_KEYPAD_5x3) key = _getKey5x3(); + else if (_mode == I2C_KEYPAD_6x2) key = _getKey6x2(); + else if (_mode == I2C_KEYPAD_8x1) key = _getKey8x1(); + else key = _getKey4x4(); // default. + + if (key == I2C_KEYPAD_FAIL) return key; // propagate error. + // valid keys + NOKEY + _lastKey = key; + _lastTimeRead = now; + return key; +} + + +uint8_t I2CKeyPad::getLastKey() +{ return _lastKey; -}; +} // to check "press any key" @@ -51,23 +75,21 @@ bool I2CKeyPad::isPressed() } -bool I2CKeyPad::isConnected() +uint8_t I2CKeyPad::getChar() { - _wire->beginTransmission(_address); - return (_wire->endTransmission() == 0); + uint8_t key = getKey(); + if (key != I2C_KEYPAD_THRESHOLD) + { + return _keyMap[key]; + } + return I2C_KEYPAD_THRESHOLD; } -uint8_t I2CKeyPad::getChar() -{ - return _keyMap[getKey()]; -}; - - uint8_t I2CKeyPad::getLastChar() -{ - return _keyMap[_lastKey]; -}; +{ + return _keyMap[_lastKey]; +} void I2CKeyPad::loadKeyMap(char * keyMap) @@ -78,7 +100,7 @@ void I2CKeyPad::loadKeyMap(char * keyMap) void I2CKeyPad::setKeyPadMode(uint8_t mode) { - if ((mode == I2C_KEYPAD_5x3) || + if ((mode == I2C_KEYPAD_5x3) || (mode == I2C_KEYPAD_6x2) || (mode == I2C_KEYPAD_8x1)) { @@ -95,6 +117,24 @@ uint8_t I2CKeyPad::getKeyPadMode() } +void I2CKeyPad::setDebounceThreshold(uint16_t value) +{ + _debounceThreshold = value; +} + + +uint16_t I2CKeyPad::getDebounceThreshold() +{ + return _debounceThreshold; +} + + +uint32_t I2CKeyPad::getLastTimeRead() +{ + return _lastTimeRead; +} + + ////////////////////////////////////////////////////// // // PROTECTED @@ -118,7 +158,7 @@ uint8_t I2CKeyPad::_read(uint8_t mask) uint8_t I2CKeyPad::_getKey4x4() { - // key = row + 4 x col + // key = row + 4 x column uint8_t key = 0; // mask = 4 rows as input pull up, 4 columns as output @@ -141,8 +181,6 @@ uint8_t I2CKeyPad::_getKey4x4() else if (cols == 0x07) key += 12; else return I2C_KEYPAD_FAIL; - _lastKey = key; - return key; // 0..15 } @@ -150,7 +188,7 @@ uint8_t I2CKeyPad::_getKey4x4() // not tested uint8_t I2CKeyPad::_getKey5x3() { - // key = row + 5 x col + // key = row + 5 x column uint8_t key = 0; // mask = 5 rows as input pull up, 3 columns as output @@ -173,8 +211,6 @@ uint8_t I2CKeyPad::_getKey5x3() else if (cols == 0x03) key += 10; else return I2C_KEYPAD_FAIL; - _lastKey = key; - return key; // 0..14 } @@ -182,7 +218,7 @@ uint8_t I2CKeyPad::_getKey5x3() // not tested uint8_t I2CKeyPad::_getKey6x2() { - // key = row + 6 x col + // key = row + 6 x column uint8_t key = 0; // mask = 6 rows as input pull up, 2 columns as output @@ -205,8 +241,6 @@ uint8_t I2CKeyPad::_getKey6x2() else if (cols == 0x01) key += 6; else return I2C_KEYPAD_FAIL; - _lastKey = key; - return key; // 0..11 } @@ -231,8 +265,6 @@ uint8_t I2CKeyPad::_getKey8x1() else if (rows == 0x7F) key = 7; else return I2C_KEYPAD_FAIL; - _lastKey = key; - return key; // 0..7 } diff --git a/libraries/I2CKeyPad/I2CKeyPad.h b/libraries/I2CKeyPad/I2CKeyPad.h index 19b7d84d..044ded05 100644 --- a/libraries/I2CKeyPad/I2CKeyPad.h +++ b/libraries/I2CKeyPad/I2CKeyPad.h @@ -2,7 +2,7 @@ // // FILE: I2CKeyPad.h // AUTHOR: Rob Tillaart -// VERSION: 0.4.0 +// VERSION: 0.5.0 // PURPOSE: Arduino library for 4x4 KeyPad connected to an I2C PCF8574 // URL: https://github.com/RobTillaart/I2CKeyPad @@ -11,10 +11,13 @@ #include "Wire.h" -#define I2C_KEYPAD_LIB_VERSION (F("0.4.0")) +#define I2C_KEYPAD_LIB_VERSION (F("0.5.0")) #define I2C_KEYPAD_NOKEY 16 #define I2C_KEYPAD_FAIL 17 +// +#define I2C_KEYPAD_THRESHOLD 255 + // experimental #define I2C_KEYPAD_4x4 44 @@ -29,37 +32,42 @@ public: I2CKeyPad(const uint8_t deviceAddress, TwoWire *wire = &Wire); // call Wire.begin() first! - bool begin(); + bool begin(); + bool isConnected(); // get raw key's 0..15 - uint8_t getKey(); - uint8_t getLastKey(); - - bool isPressed(); - bool isConnected(); + uint8_t getKey(); + 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[19] + uint8_t getChar(); + uint8_t getLastChar(); + void loadKeyMap(char * keyMap); // char[19] // mode functions - experimental - void setKeyPadMode(uint8_t mode = I2C_KEYPAD_4x4); - uint8_t getKeyPadMode(); + void setKeyPadMode(uint8_t mode = I2C_KEYPAD_4x4); + uint8_t getKeyPadMode(); + + // 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; - uint8_t _mode; - uint8_t _read(uint8_t mask); - uint8_t _getKey4x4(); + uint8_t _address; + uint8_t _lastKey; + uint8_t _mode; + uint8_t _read(uint8_t mask); + uint16_t _debounceThreshold; + uint32_t _lastTimeRead; - // experimental - could be public ?! - uint8_t _getKey5x3(); - uint8_t _getKey6x2(); - uint8_t _getKey8x1(); + uint8_t _getKey4x4(); + uint8_t _getKey5x3(); + uint8_t _getKey6x2(); + uint8_t _getKey8x1(); TwoWire* _wire; diff --git a/libraries/I2CKeyPad/README.md b/libraries/I2CKeyPad/README.md index d6eb2bda..175355f3 100644 --- a/libraries/I2CKeyPad/README.md +++ b/libraries/I2CKeyPad/README.md @@ -11,7 +11,7 @@ # I2CKeyPad -Arduino library for 4x4 KeyPad connected to an I2C PCF8574. +Arduino library for 4x4 (or smaller) keypad connected to an I2C PCF8574. ## Description @@ -21,11 +21,20 @@ Smaller keypads, meaning less columns or rows (4x3) can be read with it too. Since 0.3.2 the library allows a 5x3, 6x2 or 8x1 or smaller keypad to be connected too. -#### Related +### Breaking change + +Since 0.5.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/I2CKeyPad8x8. which is an 8x8 version using **PCF8575**. +- https://github.com/RobTillaart/PCF8574 - https://github.com/RobTillaart/AnalogKeypad +- https://github.com/RobTillaart/I2CKeyPad4x4 - https://github.com/RobTillaart/I2CKeyPad8x8 - https://github.com/WK-Software56/AdvKeyPad (derived work with keyboard alike interface) @@ -37,28 +46,61 @@ See the conceptual schema below. It might take some trying to get the correct pins connected. ``` - PROC PCF8574 KEYPAD - +--------+ +--------+ +--------+ - | | | 0|----------|R | - | SDA |--------| 1|----------|O | - | SCL |--------| 2|----------|W | - | | | 3|----------|S | - | | | | | | - | | | 4|----------|C | - | | | 5|----------|O | - | | | 6|----------|L | - | | | 7|----------|S | - +--------+ +--------+ +--------+ + PROC PCF8574 KEYPAD + +--------+ +---------+ +---------+ + | | | 0 |<-------->| R | + | SDA |<------>| 1 |<-------->| O | + | SCL |------->| 2 |<-------->| W | + | | | 3 |<-------->| S | + | | | | | | + | | | 4 |<-------->| C | + | | | 5 |<-------->| O | + | | | 6 |<-------->| L | + | | | 7 |<-------->| S | + +--------+ +---------+ +---------+ ``` +## I2C + +### I2C addresses + +This library uses a PCF8574 or a PCF8574A chip. +These devices are identical in behaviour although there are two distinct address ranges. + +| Type | Address-range | Notes | +|:-----------|:---------------:|:-------------------------:| +| PCF8574 | 0x20 to 0x27 | same range as PCF8575 ! | +| PCF8574A | 0x38 to 0x3F | + +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 "I2CKeyPad.h" ``` -#### Base +### Base - **I2CKeyPad(const uint8_t deviceAddress, TwoWire \*wire = &Wire)** The constructor sets the device address and optionally @@ -67,45 +109,51 @@ allows to selects the I2C bus to use. Call wire.begin() first! - **bool isConnected()** returns false if the PCF8574 cannot be connected to. - **uint8_t getKey()** Returns default 0..15 for regular keys, -Returns 16 if no key is pressed and 17 in case of an error. -- **uint8_t getLastKey()** Returns the last **valid** key pressed 0..15. Initially it will return 16 (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_KEYPAD_NOKEY** (16) if no key is pressed and **I2C_KEYPAD_FAIL** +(17) in case of an error, e.g. multiple keys pressed. +If a debounce delay is set, it might return **I2C_KEYPAD_THRESHOLD** if called too fast. +- **uint8_t getLastKey()** Returns the last **valid** key pressed 0..15, +or **I2C_KEYPAD_NOKEY** (16) 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. -#### Mode functions +### Mode functions -Note: experimental +**Experimental** - **void setKeyPadMode(uint8_t mode = I2C_KEYPAD_4x4)** sets the mode, default 4x4. -This mode can also be used for 4x3 or 4x2. -Invalid values are mapped to 4x4. +This mode can also be used for 4x3 or 4x2 or 3x3 etc. +Invalid values for mode are mapped to 4x4. - **uint8_t getKeyPadMode()** returns the current mode. **Supported modi** -There are 4 modi supported, and every mode also supports smaller keypads. +There are 4 modi supported, and every mode will also support smaller keypads. E.g. a 4x3 keypad can be read in mode 4x4 or in mode 5x3. | modi | value | definition | notes | |:------:|:-------:|:-----------------|:----------| -| 4x4 | 44 | I2C_KEYPAD_4x4 | default | -| 5x3 | 53 | I2C_KEYPAD_5x3 | -| 6x2 | 62 | I2C_KEYPAD_6x2 | +| 4x4 | 44 | I2C_KEYPAD_4x4 | default, also for 4x3 4x2 4x1 3x3 3x2 3x1 etc. +| 5x3 | 53 | I2C_KEYPAD_5x3 | also for 5x2 or 5x1 etc. +| 6x2 | 62 | I2C_KEYPAD_6x2 | also for 6x1 etc. | 8x1 | 81 | I2C_KEYPAD_8x1 | not real matrix, connect pins to switch to GND. -#### 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 19. This array maps index 0..15 on a char and index \[16\] maps to **I2CKEYPAD_NOKEY** (typical 'N') and index \[17\] maps **I2CKEYPAD_FAIL** (typical 'F'). index 18 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. @@ -124,17 +172,52 @@ It is even possible to change the mapping runtime after each key. Note: a keyMap char array may be longer than 18 characters, but only the first 18 are used. The length is **NOT** checked upon loading. -Note: The 5x3, 6x2 and the 8x1 modi also uses a keymap of length 18. +Note: The 5x3, 6x2 and the 8x1 modi also uses a key map of length 18. -#### Basic working +### Debouncing threshold + +**Experimental** + +Since version 0.5.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_KEYPAD_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_KEYPAD_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 **I2CKEYPAD_NOKEY** code (16) is returned. -- If the read value is not valid, e.g. two keys pressed, **I2CKEYPAD_FAIL** code (17) is returned. +- If no key is pressed **I2C_KEYPAD_NOKEY** code (16) is returned. +- If the read value is not valid, e.g. two keys pressed, **I2C_KEYPAD_FAIL** code (17) is returned. +- If a debounce threshold is set, **I2C_KEYPAD_THRESHOLD** might be returned. +See section above. - Otherwise a number 0..15 is returned. -Note NOKEY and FAIL bot have bit 4 set, all valid keys don't. +Note NOKEY and FAIL both have bit 4 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. @@ -142,17 +225,11 @@ Only if a key map is loaded, the user can call **getChar()** and **getLastChar() ## Interrupts -Since version 0.2.1 the library enables the PCF8574 to generate interrupts -on the PCF8574 when a key is pressed. -This makes checking the keypad far more efficient as one does not need to poll over I2C. +The library enables the PCF8574 to generate interrupts on the PCF8574 when a key is pressed. +This makes checking the keypad far more efficient as one does not need to poll the device over I2C. See examples. -## Operation - -See examples - - ## Future #### Must @@ -161,7 +238,13 @@ See examples #### Should -- test key mapping functions. +- test extensively + - basic working (OK) + - interrupts + - keymapping + - performance +- improve error handling? + - **I2C_KEYPAD_ERR_MODE** #### Could diff --git a/libraries/I2CKeyPad/keywords.txt b/libraries/I2CKeyPad/keywords.txt index cd1a8bc5..44839aaa 100644 --- a/libraries/I2CKeyPad/keywords.txt +++ b/libraries/I2CKeyPad/keywords.txt @@ -6,18 +6,23 @@ I2CKeyPad KEYWORD1 # Methods and Functions (KEYWORD2) begin KEYWORD2 +isConnected() KEYWORD2 + getKey KEYWORD2 getLastKey KEYWORD2 isPressed KEYWORD2 -isConnected() KEYWORD2 -loadKeyMap KEYWORD2 getChar KEYWORD2 getLastChar KEYWORD2 +loadKeyMap KEYWORD2 setKeyPadMode KEYWORD2 getKeyPadMode KEYWORD2 +setDebounceThreshold KEYWORD2 +getDebounceThreshold KEYWORD2 + + # Instances (KEYWORD2) @@ -25,6 +30,7 @@ getKeyPadMode KEYWORD2 I2C_KEYPAD_LIB_VERSION LITERAL1 I2C_KEYPAD_NOKEY LITERAL1 I2C_KEYPAD_FAIL LITERAL1 +I2C_KEYPAD_THRESHOLD LITERAL1 I2C_KEYPAD_4x4 LITERAL1 I2C_KEYPAD_5x3 LITERAL1 diff --git a/libraries/I2CKeyPad/library.json b/libraries/I2CKeyPad/library.json index 974f37d6..2be0d978 100644 --- a/libraries/I2CKeyPad/library.json +++ b/libraries/I2CKeyPad/library.json @@ -1,7 +1,7 @@ { "name": "I2CKeyPad", "keywords": "I2C,KeyPad, 4x4, 5x3, 6x2, 8x1, PCF8574", - "description": "Arduino library for a KeyPad connected to a PCF8574. 4x4, 5x3, 6x2, 8x1 or smaller.", + "description": "Arduino library for 4x4 (or smaller) keypad connected to an I2C PCF8574. 4x4, 5x3, 6x2, 8x1.", "authors": [ { @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/I2CKeyPad.git" }, - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "frameworks": "*", "platforms": "*", diff --git a/libraries/I2CKeyPad/library.properties b/libraries/I2CKeyPad/library.properties index 46716f98..68655570 100644 --- a/libraries/I2CKeyPad/library.properties +++ b/libraries/I2CKeyPad/library.properties @@ -1,8 +1,8 @@ name=I2CKeyPad -version=0.4.0 +version=0.5.0 author=Rob Tillaart maintainer=Rob Tillaart -sentence=Arduino library for a KeyPad connected to a PCF8574. +sentence=Arduino library for 4x4 (or smaller) keypad connected to an I2C PCF8574. paragraph=4x4, 5x3, 6x2, 8x1 or smaller. category=Signal Input/Output url=https://github.com/RobTillaart/I2CKeyPad diff --git a/libraries/I2CKeyPad/test/unit_test_001.cpp b/libraries/I2CKeyPad/test/unit_test_001.cpp index 239d7910..b81d111e 100644 --- a/libraries/I2CKeyPad/test/unit_test_001.cpp +++ b/libraries/I2CKeyPad/test/unit_test_001.cpp @@ -43,6 +43,7 @@ unittest(test_constants) { assertEqual(16, I2C_KEYPAD_NOKEY); assertEqual(17, I2C_KEYPAD_FAIL); + assertEqual(255, I2C_KEYPAD_THRESHOLD); assertEqual(44, I2C_KEYPAD_4x4); assertEqual(53, I2C_KEYPAD_5x3); @@ -100,6 +101,22 @@ unittest(test_KeyMap) } +unittest(test_debounce_threshold) +{ + const uint8_t KEYPAD_ADDRESS = 0x38; + I2CKeyPad 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()); + } +} + + // Issues with Wire - to be investigated... // // unittest(test_read)