0.5.0 I2CKeyPad

This commit is contained in:
Rob Tillaart 2024-07-18 17:15:18 +02:00
parent 13df84efc4
commit 2979b19695
8 changed files with 264 additions and 105 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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": "*",

View File

@ -1,8 +1,8 @@
name=I2CKeyPad
version=0.4.0
version=0.5.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
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

View File

@ -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)