diff --git a/I2CKeyPad .cpp b/I2CKeyPad .cpp index 07b0be4..f66e3ad 100644 --- a/I2CKeyPad .cpp +++ b/I2CKeyPad .cpp @@ -1,7 +1,7 @@ // // FILE: I2CKeyPad.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.3.1 +// VERSION: 0.3.2 // PURPOSE: Arduino library for 4x4 KeyPad connected to an I2C PCF8574 // URL: https://github.com/RobTillaart/I2CKeyPad // @@ -14,8 +14,12 @@ // 0.2.0 2021-05-06 MultiWire ... (breaking interface) // 0.2.1 2021-05-06 add _read(0xF0) to begin() to enable PCF8574 // interrupts. (#5 thanks to JohnMac1234) +// // 0.3.0 2021-11-04 add key mapping functions. // 0.3.1 2021-12-19 update library.json, license, minor edits +// 0.3.2 2022-09-19 experimental version +// add 5x3, 6x2 and 8x1 support +// moved all code to .cpp file. #include "I2CKeyPad.h" @@ -26,6 +30,7 @@ I2CKeyPad::I2CKeyPad(const uint8_t deviceAddress, TwoWire *wire) _lastKey = I2C_KEYPAD_NOKEY; _address = deviceAddress; _wire = wire; + _mode = I2C_KEYPAD_4x4; } @@ -51,10 +56,20 @@ bool I2CKeyPad::begin() uint8_t I2CKeyPad::getKey() { + if (_mode == I2C_KEYPAD_5x3) return _getKey5x3(); + if (_mode == I2C_KEYPAD_6x2) return _getKey6x2(); + if (_mode == I2C_KEYPAD_8x1) return _getKey8x1(); + // default. return _getKey4x4(); } +uint8_t I2CKeyPad::getLastKey() +{ + return _lastKey; +}; + + // to check "press any key" bool I2CKeyPad::isPressed() { @@ -71,15 +86,46 @@ bool I2CKeyPad::isConnected() } +uint8_t I2CKeyPad::getChar() +{ + return _keyMap[getKey()]; +}; + + +uint8_t I2CKeyPad::getLastChar() +{ + return _keyMap[_lastKey]; +}; + + void I2CKeyPad::loadKeyMap(char * keyMap) { _keyMap = keyMap; } +void I2CKeyPad::setKeyPadMode(uint8_t mode) +{ + if ((mode == I2C_KEYPAD_5x3) || + (mode == I2C_KEYPAD_6x2) || + (mode == I2C_KEYPAD_8x1)) + { + _mode = mode; + return; + } + _mode = I2C_KEYPAD_4x4; +} + + +uint8_t I2CKeyPad::getKeyPadMode() +{ + return _mode; +} + + ////////////////////////////////////////////////////// // -// PRIVATE +// PROTECTED // uint8_t I2CKeyPad::_read(uint8_t mask) { @@ -129,5 +175,95 @@ uint8_t I2CKeyPad::_getKey4x4() } +// not tested +uint8_t I2CKeyPad::_getKey5x3() +{ + // key = row + 5 x col + uint8_t key = 0; + + // mask = 5 rows as input pull up, 3 columns as output + uint8_t rows = _read(0xF8); + // check if single line has gone low. + if (rows == 0xF8) return I2C_KEYPAD_NOKEY; + else if (rows == 0xF0) key = 0; + else if (rows == 0xE8) key = 1; + else if (rows == 0xD8) key = 2; + else if (rows == 0xB8) key = 3; + else if (rows == 0x78) key = 4; + else return I2C_KEYPAD_FAIL; + + // 3 columns as input pull up, 5 rows as output + uint8_t cols = _read(0x07); + // check if single line has gone low. + if (cols == 0x07) return I2C_KEYPAD_NOKEY; + else if (cols == 0x06) key += 0; + else if (cols == 0x05) key += 5; + else if (cols == 0x03) key += 10; + else return I2C_KEYPAD_FAIL; + + _lastKey = key; + + return key; // 0..14 +} + + +// not tested +uint8_t I2CKeyPad::_getKey6x2() +{ + // key = row + 6 x col + uint8_t key = 0; + + // mask = 6 rows as input pull up, 2 columns as output + uint8_t rows = _read(0xFC); + // check if single line has gone low. + if (rows == 0xFC) return I2C_KEYPAD_NOKEY; + else if (rows == 0xF8) key = 0; + else if (rows == 0xF4) key = 1; + else if (rows == 0xEC) key = 2; + else if (rows == 0xDC) key = 3; + else if (rows == 0xBC) key = 4; + else if (rows == 0x7C) key = 5; + else return I2C_KEYPAD_FAIL; + + // 2 columns as input pull up, 6 rows as output + uint8_t cols = _read(0x03); + // check if single line has gone low. + if (cols == 0x03) return I2C_KEYPAD_NOKEY; + else if (cols == 0x02) key += 0; + else if (cols == 0x01) key += 6; + else return I2C_KEYPAD_FAIL; + + _lastKey = key; + + return key; // 0..11 +} + + +// not tested +uint8_t I2CKeyPad::_getKey8x1() +{ + // key = row + uint8_t key = 0; + + // mask = 8 rows as input pull up, 0 columns as output + uint8_t rows = _read(0xFF); + // check if single line has gone low. + if (rows == 0xFF) return I2C_KEYPAD_NOKEY; + else if (rows == 0xFE) key = 0; + else if (rows == 0xFD) key = 1; + else if (rows == 0xFB) key = 2; + else if (rows == 0xF7) key = 3; + else if (rows == 0xEF) key = 4; + else if (rows == 0xDF) key = 5; + else if (rows == 0xBF) key = 6; + else if (rows == 0x7F) key = 7; + else return I2C_KEYPAD_FAIL; + + _lastKey = key; + + return key; // 0..7 +} + + // -- END OF FILE -- diff --git a/I2CKeyPad.h b/I2CKeyPad.h index d875cf4..9aa2268 100644 --- a/I2CKeyPad.h +++ b/I2CKeyPad.h @@ -2,7 +2,7 @@ // // FILE: I2CKeyPad.h // AUTHOR: Rob Tillaart -// VERSION: 0.3.1 +// VERSION: 0.3.2 // PURPOSE: Arduino library for 4x4 KeyPad connected to an I2C PCF8574 // URL: https://github.com/RobTillaart/I2CKeyPad @@ -11,11 +11,17 @@ #include "Wire.h" -#define I2C_KEYPAD_LIB_VERSION (F("0.3.1")) +#define I2C_KEYPAD_LIB_VERSION (F("0.3.2")) #define I2C_KEYPAD_NOKEY 16 #define I2C_KEYPAD_FAIL 17 +// experimental +#define I2C_KEYPAD_4x4 44 +#define I2C_KEYPAD_5x3 53 +#define I2C_KEYPAD_6x2 62 +#define I2C_KEYPAD_8x1 81 + class I2CKeyPad { @@ -29,26 +35,36 @@ class I2CKeyPad // get raw key's 0..15 uint8_t getKey(); - uint8_t getLastKey() { return _lastKey; }; + uint8_t getLastKey(); - bool isPressed(); + bool isPressed(); bool isConnected(); // get 'translated' keys - // user must load KeyMap self, there is no check. - uint8_t getChar() { return _keyMap[getKey()]; }; - uint8_t getLastChar() { return _keyMap[_lastKey]; }; - void loadKeyMap(char * keyMap); // char[19] + // user must load KeyMap, there is no check. + 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(); protected: uint8_t _address; uint8_t _lastKey; + uint8_t _mode; uint8_t _read(uint8_t mask); uint8_t _getKey4x4(); + // experimental - could be public ?! + uint8_t _getKey5x3(); + uint8_t _getKey6x2(); + uint8_t _getKey8x1(); + TwoWire* _wire; - + char * _keyMap = NULL; }; diff --git a/README.md b/README.md index 151a690..3a102bb 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,17 @@ Arduino library for 4x4 KeyPad connected to an I2C PCF8574. The I2CKeyPad library implements the reading of a 4x4 keypad by means of a PCF8574. Smaller keypads, meaning less columns or rows (4x3) can be read with it too. -A 5x3 keypad would require modification (issue pending to support this). + +Since 0.3.2 the library allows a 5x3, 6x2 or 8x1 or smaller keypad to be connected too. + +Relates to https://github.com/RobTillaart/I2CKeyPad8x8. which is an 8x8 version using PCF8575. ## Connection -The PCF8574 is connected between the processor and the 4x4 keypad, see the conceptual -below. It might take some trying to get the correct pins connected. +The PCF8574 is connected between the processor and the (default) 4x4 keypad. +See the conceptual schema below. +It might take some trying to get the correct pins connected. ``` PROC PCF8574 KEYPAD @@ -41,23 +45,45 @@ below. It might take some trying to get the correct pins connected. ## Interface -- **I2CKEYPAD keypad(const uint8_t deviceAddress, TwoWire \*wire = &Wire)** +- **I2CKEYPAD(const uint8_t deviceAddress, TwoWire \*wire = &Wire)** The constructor sets the device address and optionally allows to selects the I2C bus to use. -- **bool keyPad.begin()** The return value shows if the PCF8574 with the given address is connected properly. +- **bool begin()** The return value shows if the PCF8574 with the given address is connected properly. - **bool begin(uint8_t sda, uint8_t scl)** for ESP32. The return value shows if the PCF8574 with the given address is connected properly. -- **keyPad.isConnected()** returns false if the PCF8574 cannot be connected to. -- **uint8_t keyPad.getKey()** Returns 0..15 for regular keys, 16 if no key is pressed -and 17 in case of an error. -- **keyPad.getLastKey()** Returns the last **valid** key pressed 0..15. Initially it will return 16 (noKey). -- **keyPad.isPressed()** Returns true if one or more keys of the keyPad is pressed, +- **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. +#### Mode functions + +Note: 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. +- **uint8_t getKeyPadMode()** returns the current mode. + +**Supported modi** + +There are 4 modi supported, and every mode also supports 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 | +| 8x1 | 81 | I2C_KEYPAD_8x1 | not real matrix, connect pins to switch to GND. + + #### KeyMap functions -**loadKeyMap()** must be called first! +**loadKeyMap()** must be called before **getChar()** and **getLastChar()**! - **char getChar()** returns the char corresponding to mapped key pressed. - **char getLastChar()** returns the last char pressed. @@ -79,11 +105,13 @@ char diag_keymap[19] = "1 2 3 4NF"; // diagonal keys only In the examples above a 'space' key might be just meant to ignore. However functionality there is no limit how one wants to use the key mapping. -It is even possible to change the mapping runtime. +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. + #### Basic working @@ -92,6 +120,9 @@ After the **keypad.begin()** the sketch calls the **keyPad.getKey()** to read va - If the read value is not valid, e.g. two keys pressed, **I2CKEYPAD_FAIL** code (17) is returned. - Otherwise a number 0..15 is returned. +Note NOKEY and FAIL bot 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. @@ -111,5 +142,4 @@ See examples ## Future - update documentation -- investigate 5x3 keypad and other 'formats' - test key mapping functions. diff --git a/examples/I2Ckeypad_5x3/I2Ckeypad_5x3.ino b/examples/I2Ckeypad_5x3/I2Ckeypad_5x3.ino new file mode 100644 index 0000000..d282799 --- /dev/null +++ b/examples/I2Ckeypad_5x3/I2Ckeypad_5x3.ino @@ -0,0 +1,65 @@ +// +// FILE: I2Ckeypad_5x3.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo 5x3 or smaller keypad +// URL: https://github.com/RobTillaart/I2CKeyPad +// +// PCF8574 +// pin p0-p4 rows +// pin p5-p7 columns + + +#include "Wire.h" +#include "I2CKeyPad.h" + +const uint8_t KEYPAD_ADDRESS = 0x38; + +I2CKeyPad keyPad(KEYPAD_ADDRESS); + +uint32_t start, stop; +uint32_t lastKeyPressed = 0; + + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + + Wire.begin(); + Wire.setClock(400000); + if (keyPad.begin() == false) + { + Serial.println("\nERROR: cannot communicate to keypad.\nPlease reboot.\n"); + while(1); + } + + keyPad.setKeyPadMode(I2C_KEYPAD_5x3); +} + + +void loop() +{ + uint32_t now = millis(); + // adjust keymap if needed + char keys[] = "1234567890ABCDE NF"; // N = NoKey, F = Fail + + if (now - lastKeyPressed >= 100) + { + lastKeyPressed = now; + + start = micros(); + uint8_t index = keyPad.getKey(); + stop = micros(); + + Serial.print(millis()); + Serial.print("\t"); + Serial.print(index); + Serial.print("\t"); + Serial.print(keys[index]); + Serial.print("\t"); + Serial.println(stop - start); + } +} + + +// -- END OF FILE -- diff --git a/examples/I2Ckeypad_6x2/I2Ckeypad_6x2.ino b/examples/I2Ckeypad_6x2/I2Ckeypad_6x2.ino new file mode 100644 index 0000000..e05874b --- /dev/null +++ b/examples/I2Ckeypad_6x2/I2Ckeypad_6x2.ino @@ -0,0 +1,65 @@ +// +// FILE: I2Ckeypad_6x2.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo 6x2 or smaller keypad +// URL: https://github.com/RobTillaart/I2CKeyPad +// +// PCF8574 +// pin p0-p5 rows +// pin p6-p7 columns + + +#include "Wire.h" +#include "I2CKeyPad.h" + +const uint8_t KEYPAD_ADDRESS = 0x38; + +I2CKeyPad keyPad(KEYPAD_ADDRESS); + +uint32_t start, stop; +uint32_t lastKeyPressed = 0; + + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + + Wire.begin(); + Wire.setClock(400000); + if (keyPad.begin() == false) + { + Serial.println("\nERROR: cannot communicate to keypad.\nPlease reboot.\n"); + while (1); + } + + keyPad.setKeyPadMode(I2C_KEYPAD_6x2); +} + + +void loop() +{ + uint32_t now = millis(); + // adjust keymap if needed + char keys[] = "1234567890ABC NF"; // N = NoKey, F = Fail + + if (now - lastKeyPressed >= 100) + { + lastKeyPressed = now; + + start = micros(); + uint8_t index = keyPad.getKey(); + stop = micros(); + + Serial.print(millis()); + Serial.print("\t"); + Serial.print(index); + Serial.print("\t"); + Serial.print(keys[index]); + Serial.print("\t"); + Serial.println(stop - start); + } +} + + +// -- END OF FILE -- diff --git a/examples/I2Ckeypad_8x1/I2Ckeypad_8x1.ino b/examples/I2Ckeypad_8x1/I2Ckeypad_8x1.ino new file mode 100644 index 0000000..ce7655f --- /dev/null +++ b/examples/I2Ckeypad_8x1/I2Ckeypad_8x1.ino @@ -0,0 +1,65 @@ +// +// FILE: I2Ckeypad_8x1.ino +// AUTHOR: Rob Tillaart +// PURPOSE: demo 8x1 or smaller keypad +// URL: https://github.com/RobTillaart/I2CKeyPad +// +// PCF8574 +// pin p0-p7 rows +// + + +#include "Wire.h" +#include "I2CKeyPad.h" + +const uint8_t KEYPAD_ADDRESS = 0x38; + +I2CKeyPad keyPad(KEYPAD_ADDRESS); + +uint32_t start, stop; +uint32_t lastKeyPressed = 0; + + +void setup() +{ + Serial.begin(115200); + Serial.println(__FILE__); + + Wire.begin(); + Wire.setClock(400000); + if (keyPad.begin() == false) + { + Serial.println("\nERROR: cannot communicate to keypad.\nPlease reboot.\n"); + while(1); + } + + keyPad.setKeyPadMode(I2C_KEYPAD_8x1); +} + + +void loop() +{ + uint32_t now = millis(); + // adjust keymap if needed + char keys[] = "12345678 NF"; // N = NoKey, F = Fail + + if (now - lastKeyPressed >= 100) + { + lastKeyPressed = now; + + start = micros(); + uint8_t index = keyPad.getKey(); + stop = micros(); + + Serial.print(millis()); + Serial.print("\t"); + Serial.print(index); + Serial.print("\t"); + Serial.print(keys[index]); + Serial.print("\t"); + Serial.println(stop - start); + } +} + + +// -- END OF FILE -- diff --git a/keywords.txt b/keywords.txt index 76af00a..cd1a8bc 100644 --- a/keywords.txt +++ b/keywords.txt @@ -15,6 +15,8 @@ loadKeyMap KEYWORD2 getChar KEYWORD2 getLastChar KEYWORD2 +setKeyPadMode KEYWORD2 +getKeyPadMode KEYWORD2 # Instances (KEYWORD2) @@ -24,3 +26,9 @@ I2C_KEYPAD_LIB_VERSION LITERAL1 I2C_KEYPAD_NOKEY LITERAL1 I2C_KEYPAD_FAIL LITERAL1 +I2C_KEYPAD_4x4 LITERAL1 +I2C_KEYPAD_5x3 LITERAL1 +I2C_KEYPAD_6x2 LITERAL1 +I2C_KEYPAD_8x1 LITERAL1 + + diff --git a/library.json b/library.json index 6a0d78a..c4a940b 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "I2CKeyPad", - "keywords": "I2C,KeyPad, 4x4, PCF8574", - "description": "Arduino library for 4x4 KeyPad connected to a PCF8574. 4x4 or smaller only.", + "keywords": "I2C,KeyPad, 4x4, 5x3, 6x2, 8x1, PCF8574", + "description": "Arduino library for a KeyPad connected to a PCF8574. 4x4, 5x3, 6x2, 8x1 or smaller.", "authors": [ { @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/I2CKeyPad.git" }, - "version": "0.3.1", + "version": "0.3.2", "license": "MIT", "frameworks": "arduino", "platforms": "*", diff --git a/library.properties b/library.properties index 163475e..2890a3b 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ -name=I2CKeyPad -version=0.3.1 +name=I2CKeyPad +version=0.3.2 author=Rob Tillaart maintainer=Rob Tillaart -sentence=Arduino library for 4x4 KeyPad connected to a PCF8574 -paragraph=4x4 or smaller only. +sentence=Arduino library for a KeyPad connected to a PCF8574. +paragraph=4x4, 5x3, 6x2, 8x1 or smaller. category=Signal Input/Output url=https://github.com/RobTillaart/I2CKeyPad architectures=* diff --git a/test/unit_test_001.cpp b/test/unit_test_001.cpp index 53bc1d5..239d791 100644 --- a/test/unit_test_001.cpp +++ b/test/unit_test_001.cpp @@ -43,6 +43,11 @@ unittest(test_constants) { assertEqual(16, I2C_KEYPAD_NOKEY); assertEqual(17, I2C_KEYPAD_FAIL); + + assertEqual(44, I2C_KEYPAD_4x4); + assertEqual(53, I2C_KEYPAD_5x3); + assertEqual(62, I2C_KEYPAD_6x2); + assertEqual(81, I2C_KEYPAD_8x1); } @@ -59,6 +64,42 @@ unittest(test_constructor) } +unittest(test_mode) +{ + const uint8_t KEYPAD_ADDRESS = 0x38; + I2CKeyPad keyPad(KEYPAD_ADDRESS); + + assertEqual(I2C_KEYPAD_4x4, keyPad.getKeyPadMode()); + + keyPad.setKeyPadMode(I2C_KEYPAD_5x3); + assertEqual(I2C_KEYPAD_5x3, keyPad.getKeyPadMode()); + + keyPad.setKeyPadMode(I2C_KEYPAD_4x4); + assertEqual(I2C_KEYPAD_4x4, keyPad.getKeyPadMode()); + + keyPad.setKeyPadMode(I2C_KEYPAD_6x2); + assertEqual(I2C_KEYPAD_6x2, keyPad.getKeyPadMode()); + + keyPad.setKeyPadMode(I2C_KEYPAD_8x1); + assertEqual(I2C_KEYPAD_8x1, keyPad.getKeyPadMode()); + + // invalid are mapped to 4x4 + keyPad.setKeyPadMode(00); + assertEqual(I2C_KEYPAD_4x4, keyPad.getKeyPadMode()); +} + + +unittest(test_KeyMap) +{ + const uint8_t KEYPAD_ADDRESS = 0x38; + I2CKeyPad keyPad(KEYPAD_ADDRESS); + + char keymap[19] = "123A456B789C*0#DNF"; + keyPad.loadKeyMap(keymap); + assertEqual('N', keyPad.getLastChar()); +} + + // Issues with Wire - to be investigated... // // unittest(test_read)