Skip to content

Commit

Permalink
add key mapping (#11)
Browse files Browse the repository at this point in the history
* add key mapping
  • Loading branch information
RobTillaart authored Nov 8, 2021
1 parent c034de5 commit e0bad44
Show file tree
Hide file tree
Showing 16 changed files with 498 additions and 129 deletions.
74 changes: 46 additions & 28 deletions I2CKeyPad .cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//
// FILE: I2CKeyPad.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.2.1
// VERSION: 0.3.0
// PURPOSE: Arduino library for 4x4 KeyPad connected to an I2C PCF8574
// URL: https://github.com/RobTillaart/I2CKeyPad
//
Expand All @@ -14,7 +14,7 @@
// 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.


#include "I2CKeyPad.h"
Expand All @@ -41,38 +41,15 @@ bool I2CKeyPad::begin(uint8_t sda, uint8_t scl)
bool I2CKeyPad::begin()
{
_wire->begin();
_read(0xF0); // enable interrupts
// enable interrupts
_read(0xF0);
return isConnected();
}


uint8_t I2CKeyPad::getKey()
{
// key = row + 4 x col
uint8_t key = 0;

// mask = 4 rows as input pull up, 4 columns as output
uint8_t rows = _read(0xF0);
// check if single line has gone low.
if (rows == 0xF0) return I2C_KEYPAD_NOKEY;
else if (rows == 0xE0) key = 0;
else if (rows == 0xD0) key = 1;
else if (rows == 0xB0) key = 2;
else if (rows == 0x70) key = 3;
else return I2C_KEYPAD_FAIL;

// 4 columns as input pull up, 4 rows as output
uint8_t cols = _read(0x0F);
// check if single line has gone low.
if (cols == 0x0F) return I2C_KEYPAD_NOKEY;
else if (cols == 0x0E) key += 0;
else if (cols == 0x0D) key += 4;
else if (cols == 0x0B) key += 8;
else if (cols == 0x07) key += 12;
else return I2C_KEYPAD_FAIL;

_lastKey = key;
return key; // 0..15
return _getKey4x4();
}


Expand All @@ -92,6 +69,16 @@ bool I2CKeyPad::isConnected()
}


void I2CKeyPad::loadKeyMap(char * keyMap)
{
_keyMap = keyMap;
}


//////////////////////////////////////////////////////
//
// PRIVATE
//
uint8_t I2CKeyPad::_read(uint8_t mask)
{
yield(); // improve the odds that IO will not interrupted.
Expand All @@ -108,5 +95,36 @@ uint8_t I2CKeyPad::_read(uint8_t mask)
}


uint8_t I2CKeyPad::_getKey4x4()
{
// key = row + 4 x col
uint8_t key = 0;

// mask = 4 rows as input pull up, 4 columns as output
uint8_t rows = _read(0xF0);
// check if single line has gone low.
if (rows == 0xF0) return I2C_KEYPAD_NOKEY;
else if (rows == 0xE0) key = 0;
else if (rows == 0xD0) key = 1;
else if (rows == 0xB0) key = 2;
else if (rows == 0x70) key = 3;
else return I2C_KEYPAD_FAIL;

// 4 columns as input pull up, 4 rows as output
uint8_t cols = _read(0x0F);
// check if single line has gone low.
if (cols == 0x0F) return I2C_KEYPAD_NOKEY;
else if (cols == 0x0E) key += 0;
else if (cols == 0x0D) key += 4;
else if (cols == 0x0B) key += 8;
else if (cols == 0x07) key += 12;
else return I2C_KEYPAD_FAIL;

_lastKey = key;

return key; // 0..15
}


// -- END OF FILE --

21 changes: 16 additions & 5 deletions I2CKeyPad.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// FILE: I2CKeyPad.h
// AUTHOR: Rob Tillaart
// VERSION: 0.2.1
// VERSION: 0.3.0
// PURPOSE: Arduino library for 4x4 KeyPad connected to an I2C PCF8574
// URL: https://github.com/RobTillaart/I2CKeyPad

Expand All @@ -11,7 +11,7 @@
#include "Wire.h"


#define I2C_KEYPAD_LIB_VERSION (F("0.2.1"))
#define I2C_KEYPAD_LIB_VERSION (F("0.3.0"))

#define I2C_KEYPAD_NOKEY 16
#define I2C_KEYPAD_FAIL 17
Expand All @@ -27,17 +27,28 @@ class I2CKeyPad
#endif
bool begin();

// get raw key's 0..15
uint8_t getKey();
uint8_t getLastKey() { return _lastKey; };
uint8_t getLastKey() { return _lastKey; };

bool isPressed();
bool isConnected();

private:
// 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]

protected:
uint8_t _address;
uint8_t _lastKey;
uint8_t _read(uint8_t mask);

uint8_t _getKey4x4();

TwoWire* _wire;

char * _keyMap = NULL;
};


Expand Down
79 changes: 43 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

# I2CKeyPad

Arduino libray for 4x4 KeyPad connected to an I2C PCF8574
Arduino library for 4x4 KeyPad connected to an I2C PCF8574


## Description

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.
A 5x3 keypad would require modification (issue pending to support this).


## Connection
Expand All @@ -41,61 +41,68 @@ below. It might take some trying to get the correct pins connected.

## Interface

**I2CKEYPAD keypad(const uint8_t deviceAddress, TwoWire \*wire = &Wire)**

- **I2CKEYPAD keypad(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(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,
however it is not checked if multiple keys are pressed.

**keyPad.begin()**

First call that needs to be done is **keyPad.begin()**.
For the ESP32 **begin(uint8_t sda, uint8_t scl)** is provided.
The return value shows if the PCF8574 with the given address is connected properly.
#### KeyMap functions

**keyPad.getKey()**
**loadKeyMap()** must be called first!

Then the user can use the **keyPad.getKey()** to read values from the keypad.
The read is done in the following way:
First it scans all rows at once by setting all rows to input and all columns to output.
If no row is pressed **I2CKEYPAD_NOKEY** code is returned.
- **char getChar()** returns the char corresponding to mapped key pressed.
- **char getLastChar()** returns the last char pressed.
- **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.

If a row is pressed the row is determined by checking the read value against valid values.
If the read value is not valid a **I2CKEYPAD_FAIL** code is returned.
(e.g. double key pressed)
**WARNING**
If there is no key map loaded the user should **NOT** call **getChar()** or
**getLastChar()** as these would return meaningless bytes.

Then all columns are scanned at once by setting the columns to input and rows to output.
The column is determined by checking the read value against valid values.
If the read value is not valid a **I2CKEYPAD_FAIL** code is returned.

Given the row and column, a number 0..15 is returned.
```cpp
char normal_keymap[19] = "123A456B789C*0#DNF"; // typical normal key map (phone layout)
char repeat_keymap[19] = "1234123412341234NF"; // effectively 4 identical columns
char partial_keymap[19] = "1234 NF"; // top row
char diag_keymap[19] = "1 2 3 4NF"; // diagonal keys only
```

**keyPad.getLastKey()**
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.

returns the last valid key pressed 0..15 or **I2C_KEYPAD_NOKEY** = 16.
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.

**keyPad.isPressed()**

returns true if one or more keys of the keyPad is pressed,
however it is not checked if multiple keys are pressed.
#### Basic working

**keyPad.isConnected()**
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.
- Otherwise a number 0..15 is returned.

returns false if the PCF8574 cannot be connected to.
Only if a key map is loaded, the user can call **getChar()** and **getLastChar()** to get mapped keys.


## 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.
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.
See examples.


## Char mapping

The code does not map the index on a character or digit as that depends on the application.
It returns 0..15 if one key is pressed, 16 for **I2CKEYPAD_NOKEY** and 17 for **I2CKEYPAD_FAIL**.


## Operation

See examples
Expand All @@ -105,4 +112,4 @@ See examples

- update documentation
- investigate 5x3 keypad and other 'formats'
-
- test key mapping functions.
22 changes: 12 additions & 10 deletions examples/I2CKeypad_interrupts_1/I2CKeypad_interrupts_1.ino
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
// polling at e.g 10Hz (to stay reactive) adds up both in CPU time used
// and also in occupation of the I2C bus.
//
// The PCF8574 will generate an irq both on press and release.
// The PCF8574 will generate an interrupt both on press and release.
// So this code reads the keypad on both signals!
//
// Note: depending on keypad used some bouncing may occur
// (saw it only during release)
// can be solved by tracking lastinterrupt in the ISR routine
// can be solved by tracking the last interrupt in the ISR routine
// however it is more efficient to reset the flag only after the
// keypress is handled.
//
Expand All @@ -54,7 +54,7 @@

const uint8_t KEYPAD_ADDRESS = 0x20;
I2CKeyPad keyPad(KEYPAD_ADDRESS);
char keys[] = "123A456B789C*0#DNF"; // N = Nokey, F = Fail (eg >1 keys pressed)
char keys[] = "123A456B789C*0#DNF"; // N = NoKey, F = Fail (e.g. >1 keys pressed)

// volatile for IRQ var
volatile bool keyChange = false;
Expand Down Expand Up @@ -95,13 +95,13 @@ void loop()
{
if (keyChange)
{
uint8_t idx = keyPad.getKey();
// only after keychange is handled it is time reset the flag
uint8_t index = keyPad.getKey();
// only after keyChange is handled it is time reset the flag
keyChange = false;
if (idx != 16)
if (index != 16)
{
Serial.print("press: ");
Serial.println(keys[idx]);
Serial.println(keys[index]);
}
else
{
Expand Down Expand Up @@ -133,19 +133,21 @@ void measurePolling()
{
// reference time for keyPressed check UNO ~
uint32_t start = micros();
uint8_t idx = keyPad.isPressed();
uint8_t index = keyPad.isPressed();
uint32_t stop = micros();

Serial.print(clock);
Serial.print("\t");
Serial.print(idx);
Serial.print(index);
Serial.print("\t");
Serial.print(keys[idx]);
Serial.print(keys[index]);
Serial.print("\t");
Serial.println(stop - start);
delay(10);
}
}
}


// -- END OF FILE --

Loading

0 comments on commit e0bad44

Please sign in to comment.