0.3.0 I2CKeyPad8x8

This commit is contained in:
Rob Tillaart 2024-07-18 19:55:04 +02:00
parent 2979b19695
commit 109407ab7e
9 changed files with 266 additions and 107 deletions

View File

@ -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/). 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 ## [0.2.0] - 2023-11-09
- simplify begin() - simplify begin()
- update readme.md - update readme.md
- minor edits. - minor edits
----
## [0.1.1] - 2022-11-12 ## [0.1.1] - 2022-11-12
- Add RP2040 support to build-CI. - Add RP2040 support to build-CI.
- Add CHANGELOG.md - Add CHANGELOG.md
## [0.1.0] - 2022-09-29 ## [0.1.0] - 2022-09-29
- initial version - initial version

View File

@ -1,7 +1,7 @@
// //
// FILE: I2CKeyPad8x8.cpp // FILE: I2CKeyPad8x8.cpp
// AUTHOR: Rob Tillaart // AUTHOR: Rob Tillaart
// VERSION: 0.2.0 // VERSION: 0.3.0
// PURPOSE: Arduino library for 8x8 or smaller KeyPad connected to an I2C PCF8575. // PURPOSE: Arduino library for 8x8 or smaller KeyPad connected to an I2C PCF8575.
// URL: https://github.com/RobTillaart/I2CKeyPad8x8 // URL: https://github.com/RobTillaart/I2CKeyPad8x8
@ -14,6 +14,8 @@ I2CKeyPad8x8::I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire *wire)
_lastKey = I2C_KEYPAD8x8_NOKEY; _lastKey = I2C_KEYPAD8x8_NOKEY;
_address = deviceAddress; _address = deviceAddress;
_wire = wire; _wire = wire;
_debounceThreshold = 0;
_lastTimeRead = 0;
} }
@ -33,6 +35,106 @@ bool I2CKeyPad8x8::isConnected()
uint8_t I2CKeyPad8x8::getKey() 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 // key = row + 8 x col
uint8_t key = 0; uint8_t key = 0;
@ -66,68 +168,9 @@ uint8_t I2CKeyPad8x8::getKey()
else if (cols == 0x007F) key += 56; else if (cols == 0x007F) key += 56;
else return I2C_KEYPAD8x8_FAIL; else return I2C_KEYPAD8x8_FAIL;
_lastKey = key;
return key; // 0..65 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 -- // -- END OF FILE --

View File

@ -2,7 +2,7 @@
// //
// FILE: I2CKeyPad8x8.h // FILE: I2CKeyPad8x8.h
// AUTHOR: Rob Tillaart // AUTHOR: Rob Tillaart
// VERSION: 0.2.0 // VERSION: 0.3.0
// PURPOSE: Arduino library for 8x8 or smaller KeyPad connected to an I2C PCF8575. // PURPOSE: Arduino library for 8x8 or smaller KeyPad connected to an I2C PCF8575.
// URL: https://github.com/RobTillaart/I2CKeyPad // URL: https://github.com/RobTillaart/I2CKeyPad
@ -11,17 +11,18 @@
#include "Wire.h" #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_NOKEY 64
#define I2C_KEYPAD8x8_FAIL 65 #define I2C_KEYPAD8x8_FAIL 65
#define I2C_KEYPAD8x8_THRESHOLD 255
class I2CKeyPad8x8 class I2CKeyPad8x8
{ {
public: public:
I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire *wire = &Wire); I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire *wire = &Wire);
// call Wire.begin() first!
bool begin(); bool begin();
bool isConnected(); bool isConnected();
@ -30,22 +31,31 @@ public:
uint8_t getLastKey(); uint8_t getLastKey();
bool isPressed(); bool isPressed();
// get 'translated' keys // get 'translated' keys
// user must load KeyMap, there is no check. // user must load KeyMap, there is no check.
uint8_t getChar(); uint8_t getChar();
uint8_t getLastChar(); uint8_t getLastChar();
void loadKeyMap(char * keyMap); // char[65] 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: protected:
uint8_t _address; uint8_t _address;
uint8_t _lastKey; uint8_t _lastKey;
uint16_t _read(uint16_t mask); uint16_t _read(uint16_t mask);
uint16_t _debounceThreshold;
uint32_t _lastTimeRead;
uint8_t _getKey8x8();
TwoWire* _wire; TwoWire* _wire;
char * _keyMap = NULL; char * _keyMap = NULL;
}; };

View File

@ -21,11 +21,20 @@ EXPERIMENTAL (first tests ==> OK)
The I2CKeyPad8x8 library implements the reading of a 8x8 keypad by means of a PCF8575. 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. 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/AnalogKeypad
- https://github.com/RobTillaart/I2CKeyPad4x4
- https://github.com/RobTillaart/I2CKeyPad8x8 - 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. It might take some trying to get the correct pins connected.
``` ```
PROC PCF8575 KEYPAD PROC PCF8575 KEYPAD
+--------+ +--------+ +--------+ +--------+ +---------+ +---------+
| | | 0|----------|R | | | | 0 |<-------->| R |
| SDA |--------| .|----------|O | | SDA |<------>| . |<-------->| O |
| SCL |--------| .|----------|W | | SCL |<------>| . |<-------->| W |
| | | 7|----------|S | | | | 7 |<-------->| S |
| | | .| | | | | | | | |
| | | .| | | | | | 8 |<-------->| C |
| | | 8|----------|C | | | | . |<-------->| O |
| | | .|----------|O | | | | . |<-------->| L |
| | | .|----------|L | | | | 15 |<-------->| S |
| | | 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 ## Interface
```cpp ```cpp
#include "I2CKeyPad8x8.h" #include "I2CKeyPad8x8.h"
``` ```
### Base
#### Base
- **I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire \*wire = &Wire)** - **I2CKeyPad8x8(const uint8_t deviceAddress, TwoWire \*wire = &Wire)**
The constructor sets the device address and optionally The constructor sets the device address and optionally
@ -68,23 +106,28 @@ allows to selects the I2C bus to use.
Call wire.begin() first! Call wire.begin() first!
- **bool isConnected()** returns false if the PCF8575 cannot be connected to. - **bool isConnected()** returns false if the PCF8575 cannot be connected to.
- **uint8_t getKey()** Returns default 0..63 for regular keys, - **uint8_t getKey()** Returns default 0..63 for regular keys,
Returns 64 if no key is pressed and 65 in case of an error. Returns **I2C_KEYPAD8X8_NOKEY** (64) if no key is pressed and and **I2C_KEYPAD8X8_FAIL**
- **uint8_t getLastKey()** Returns the last **valid** key pressed 0..63. Initially it will return 64 (NOKEY). (17) in case of an error, e.g. multiple keys pressed.
- **bool isPressed()** Returns true if one or more keys of the keyPad is pressed, If a debounce delay is set, it might return **I2C_KEYPAD8X8_THRESHOLD** if called too fast.
however it is not checked if multiple keys are pressed. - **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. - **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. - **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. - **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') 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. and index \[65\] maps **I2CKeyPad8x8_FAIL** (typical 'F'). index 66 is the null char.
**WARNING** **WARNING**
If there is no key map loaded the user should **NOT** call **getChar()** or If there is no key map loaded the user should **NOT** call **getChar()** or
**getLastChar()** as these would return meaningless bytes. **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). 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. 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 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. - 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. 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. 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 ## Interrupts
The library enables the PCF8575 to generate interrupts on the PCF8575 when a key is pressed. 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. This makes checking the keypad far more efficient as one does not need to poll the device over I2C.
See examples.
See examples. (TODO)
## Operation
See examples
## Future ## Future
#### Must #### Must
- update documentation - update documentation
@ -134,6 +205,8 @@ See examples
- interrupts - interrupts
- keymapping - keymapping
- performance - performance
- improve error handling?
- **I2C_KEYPAD_ERR_MODE**
#### Could #### Could

View File

@ -13,7 +13,7 @@
#include "Wire.h" #include "Wire.h"
#include "I2CKeyPad8x8.h" #include "I2CKeyPad8x8.h"
const uint8_t KEYPAD_ADDRESS = 0x38; const uint8_t KEYPAD_ADDRESS = 0x20;
I2CKeyPad8x8 keyPad(KEYPAD_ADDRESS); I2CKeyPad8x8 keyPad(KEYPAD_ADDRESS);

View File

@ -16,6 +16,9 @@ getChar KEYWORD2
getLastChar KEYWORD2 getLastChar KEYWORD2
loadKeyMap KEYWORD2 loadKeyMap KEYWORD2
setDebounceThreshold KEYWORD2
getDebounceThreshold KEYWORD2
# Instances (KEYWORD2) # Instances (KEYWORD2)
@ -25,4 +28,5 @@ I2C_KEYPAD8x8_LIB_VERSION LITERAL1
I2C_KEYPAD8x8_NOKEY LITERAL1 I2C_KEYPAD8x8_NOKEY LITERAL1
I2C_KEYPAD8x8_FAIL LITERAL1 I2C_KEYPAD8x8_FAIL LITERAL1
I2C_KEYPAD8x8_THRESHOLD LITERAL1

View File

@ -1,7 +1,7 @@
{ {
"name": "I2CKeyPad8x8", "name": "I2CKeyPad8x8",
"keywords": "I2C,KeyPad, 8x8, PCF8574", "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": "authors":
[ [
{ {
@ -15,7 +15,7 @@
"type": "git", "type": "git",
"url": "https://github.com/RobTillaart/I2CKeyPad8x8.git" "url": "https://github.com/RobTillaart/I2CKeyPad8x8.git"
}, },
"version": "0.2.0", "version": "0.3.0",
"license": "MIT", "license": "MIT",
"frameworks": "*", "frameworks": "*",
"platforms": "*", "platforms": "*",

View File

@ -1,9 +1,9 @@
name=I2CKeyPad8x8 name=I2CKeyPad8x8
version=0.2.0 version=0.3.0
author=Rob Tillaart <rob.tillaart@gmail.com> author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com> maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino library for a KeyPad connected to a PCF8575. sentence=Arduino library for a 8x8 (or smaller) keypad connected to an I2C PCF8575.
paragraph=8x8 or smaller. paragraph=
category=Signal Input/Output category=Signal Input/Output
url=https://github.com/RobTillaart/I2CKeyPad8x8 url=https://github.com/RobTillaart/I2CKeyPad8x8
architectures=* architectures=*

View File

@ -43,6 +43,7 @@ unittest(test_constants)
{ {
assertEqual(64, I2C_KEYPAD8x8_NOKEY); assertEqual(64, I2C_KEYPAD8x8_NOKEY);
assertEqual(65, I2C_KEYPAD8x8_FAIL); 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) // unittest(test_KeyMap)
// { // {