Skip to content

Commit

Permalink
Merge pull request #4 from Spirik/secondary-press-support
Browse files Browse the repository at this point in the history
Support for secondary key press; `triggerRelease` property
  • Loading branch information
Spirik authored Sep 22, 2023
2 parents 24e624b + 049612c commit cffe3c0
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 33 deletions.
68 changes: 60 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ KeyDetector

Arduino library for detecting state change on analog and digital pins. Listens for specified values on the analog input and sets `KeyDetector` object state accordingly. Detects digital pin state change as well.

![KeyDetector](https://github.com/Spirik/KeyDetector/wiki/images/KeyDetector_hero.png)

Can be used to detect key press events that were assigned distinctive levels of the single analog signal ("multiplexed" to analog signal), e.g. by using DAC to "encode" multiple digital signals to a single analog line.

* [When to Use](#when-to-use)
* [How to Use](#how-to-use)
Allows detection of up to two simultaneously pressed keys connected to digital pins (since version 1.2.0).

* [When to use](#when-to-use)
* [How to use](#how-to-use)
* [Reference](#reference)
* [Simultaneous presses](#simultaneous-presses)
* [Timing diagrams](#timing-diagrams)
* [Examples](#examples)
* [License](#license)
* [**Wiki**](https://github.com/Spirik/KeyDetector/wiki)
Expand All @@ -16,11 +22,13 @@ When to use
-----------
E.g. if you find yourself in a situation when you need to execute some code based on values passed through single analog input. You may want to do that in case if you are running low on a free pin budget, but still need a way to receive multiple control signals from another Arduino (and don’t want to use Serial or another interface for some reason). Then you use simplest DAC on the end of transmitting Arduino and multiplex the encoded control signals into single analog signal that will be decoded on the receiving Arduino. See [Example 3](https://github.com/Spirik/KeyDetector/wiki/Example-03:-Multiplexed-signal-readings) provided with the library to learn how to multiplex digital signals and demultiplex them back using KeyDetector.

And since nature of the analog signal isn’t important for the library, you may use it to detect specific values of any analog source you connect to Arduino, be it some sensor or simple potentiometer. That way you may execute some code based on, e.g., temperature readings of the sensor or rotation of the knob. See [Example 1](https://github.com/Spirik/KeyDetector/wiki/Example-01:-Analog-signal-readings) provided with the library to learn how to build absolute rotary encoder using potentiometer and KeyDetector library.
And since origin of the analog signal isn’t important for the library, you may use it to detect specific values of any analog source you connect to Arduino, be it some sensor or simple potentiometer. That way you may execute some code based on, e.g., temperature readings of the sensor or rotation of the knob. See [Example 1](https://github.com/Spirik/KeyDetector/wiki/Example-01:-Analog-signal-readings) provided with the library to learn how to build absolute rotary encoder using potentiometer and KeyDetector library.

Of course you may do this without any fancy library whatsoever, but KeyDetector will provide you with convenient way of tracking previous reading and executing desired code once per signal change (i.e. once per key press, ignoring the duration of it being in a pressed state).

And it will detect state changes on the digital pins as well. See [Example 2](https://github.com/Spirik/KeyDetector/wiki/Example-02:-Digital-signal-readings) provided with the library to learn how to detect momentary push-buttons single and continuous presses.
And it will detect state changes on the digital pins as well. See [Example 2](https://github.com/Spirik/KeyDetector/wiki/Example-02:-Digital-signal-readings) provided with the library to learn how to detect momentary push-buttons single, continuous and simultaneous presses.

[Example 4](https://github.com/Spirik/KeyDetector/wiki/Example-04:-Rotary-encoder) shows how to use KeyDetector with rotary encoder equipped with push-button to determine rotation of the knob and simultaneous presses of the button.

KeyDetector used as a means to detect push-buttons presses in order to navigate and interact with graphic multi-level menu in examples provided with the [GEM](https://github.com/Spirik/GEM) library.

Expand Down Expand Up @@ -227,19 +235,63 @@ KeyDetector myKeyDetector(keysArray, keysArrayLength[, debounceDelay[, analogThr
* **trigger**
*Type*: `byte`
*Initial value*: `KEY_NONE`, `0`
Populated once per key press and holds the identifier of the button being pressed. Use it to detect key press event.
Populated once per key press (at the beginning of press event) with the identifier of the button being pressed. Use it to detect key press event (both primary and secondary).
* **triggerRelease**
*Type*: `byte`
*Initial value*: `KEY_NONE`, `0`
Populated once per key press (at the end of press event) with the identifier of the button being released. Use it to detect key release event (both primary and secondary).
* **current**
*Type*: `byte`
*Initial value*: `KEY_NONE`, `0`
Populated during key press and holds the identifier of the button currently being in pressed state. Unlike `trigger`, it will be populated with the key identifier not only once per button press but for the whole period of it being in this state (i.e. while signal maintains its value from the previous reading). Use it to detect key hold event.
Populated during key press and holds the identifier of the primary button currently being in pressed state. Unlike `trigger` and `triggerRelease`, it will be populated with the key identifier not only once per button press but for the whole period of it being in this state (i.e. while signal maintains its value from the previous reading). Use it to detect primary key hold event.
* **previous**
*Type*: `byte`
*Initial value*: `KEY_NONE`, `0`
Holds the identifier of the button that was pressed when `detect()` method was previously called.
Holds the identifier of the primary button that was pressed when `detect()` method was previously called.
> Each of these properties (`trigger`, `current`, `previous`) ends up storing value of `KEY_NONE` when no press events are detected for user defined buttons.
* **secondary**
*Type*: `byte`
*Initial value*: `KEY_NONE`, `0`
Populated during key press and holds the identifier of the secondary button currently being in pressed state. Unlike `trigger` and `triggerRelease`, it will be populated with the key identifier not only once per button press but for the whole period of it being in this state (i.e. while signal maintains its value from the previous reading). Use it to detect secondary key hold event or simultaneously pressed combinations of two keys (primary and secondary).
* **previousSecondary**
*Type*: `byte`
*Initial value*: `KEY_NONE`, `0`
Holds the identifier of the secondary button that was pressed when `detect()` method was previously called.
> Each of these properties (`trigger`, `triggerRelease`, `current`, `previous`, `secondary`, `previousSecondary`) ends up storing value of `KEY_NONE` when no press events are detected for user defined buttons.
Simultaneous presses
-----------
Since version 1.2.0 it is possible to detect simultaneous presses of two keys connected to digital pins. In that case first pressed key considered to be "primary" (with its identifier stored in `current` property) and the second key - "secondary" (with its identifier stored in `secondary` property).
Simultaneous presses detection has limited support for the case when analog and digital signal detection mixed in the same Keys array passed to the constructor. As a result, analog readings are detected as a primary key press (stored in `current` property) if no other primary key presses were detected, and can not be detected as a secondary key press (stored in `secondary` property). When digital Key objects present in the same Keys array as analog Key objects, they will be detected either as primary (when placed before analog Key objects in constructor) or secondary (when placed after analog Key objects in constructor).
Timing diagrams
-----------
For the following diagrams, consider you have two push-buttons (with identifiers `KEY_A` and `KEY_B` associated with them) wired with pull-down resistors (so the `HIGH` level of signal means that button is pressed). For the case of buttons wired with pull-up resistors (so the `LOW` level of signal means that button is pressed) just assume the signal levels inverted on the rows corresponding for each key.
Marks on the X axis indicate points in time when call to `detect()` method was made (generally it will happen once per sketch `loop()` iteration). Top rows, named `KEY_A` and `KEY_B`, show key state (in this case signal level of pin that button is connected to). Other rows show value of corresponding property of KeyDetector object.
### Single key press
Button `KEY_A` pressed, held for some time and then released:
![Single key press](https://github.com/Spirik/KeyDetector/wiki/images/KeyDetector_timing-diagram_01.png)
### Simultaneous key presses
Button `KEY_A` pressed, then button `KEY_B` pressed, then the buttons released in reverse order:
![Simultaneous key presses 1](https://github.com/Spirik/KeyDetector/wiki/images/KeyDetector_timing-diagram_02.png)
Button `KEY_A` pressed, then button `KEY_B` pressed, then the buttons released in the same order:
![Simultaneous key presses 2](https://github.com/Spirik/KeyDetector/wiki/images/KeyDetector_timing-diagram_03.png)
Examples
-----------
Expand Down
3 changes: 2 additions & 1 deletion examples/Example-01_Analog/Example-01_Analog.ino
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ char* state; // Variable to hold string describing direction of rotation
Key keys[] = {{KEY_25, potPin, 255}, {KEY_50, potPin, 511}, {KEY_75, potPin, 767}, {KEY_100, potPin, 1023}};

// Create KeyDetector object
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 0, 129);
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 0, /* analogThreshold= */ 129);
// Notes:
// Increasing of 'debounceDelay' (set to 0) can cause skip of detection of adjacent values if pot is rotated quickly enough, but helps
// to account for any transient processes or ripple that may occur during pot rotation and value readings, try value of 1
Expand All @@ -44,6 +44,7 @@ KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 0, 129);
// press event of KEY_25, because its Key object was placed before Key object of KEY_50 in the keys[] array

void setup() {
// Serial communications setup
Serial.begin(115200);

// Set potPin to input
Expand Down
50 changes: 43 additions & 7 deletions examples/Example-02_Digital/Example-02_Digital.ino
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Digital signal readings using KeyDetector library.
Demonstrates how to use KeyDetector to trigger action based on digital signal readings from momentary push-buttons.
Pressing button once or keeping it in pressed state continuously will print corresponding message.
Pressing button once or keeping it in a pressed state continuously will print corresponding message. Pressing another
button in combination with the one already in a pressed state will be detected as well.
Additional info (including the breadboard view) available on GitHub:
https://github.com/Spirik/KeyDetector
Expand Down Expand Up @@ -34,10 +35,11 @@ Key keys[] = {{KEY_A, pinA}, {KEY_B, pinB}, {KEY_C, pinC}};
// Create KeyDetector object
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key));
// To account for switch bounce effect of the buttons (if occur) you may whant to specify debounceDelay
// as the second argument to KeyDetector constructor:
// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10);
// as the third argument to KeyDetector constructor:
// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10);

void setup() {
// Serial communications setup
Serial.begin(115200);

// Set button pins to input
Expand All @@ -57,22 +59,56 @@ void loop() {
// and save current time as a time of the key press event
switch (myKeyDetector.trigger) {
case KEY_A:
Serial.println("Button A pressed!");
// Determine whether button A was pressed in combination with another one already in a pressed state (B or C)
switch (myKeyDetector.current) {
case KEY_B:
Serial.println("Button A pressed simultaneously with Button B!");
break;
case KEY_C:
Serial.println("Button A pressed simultaneously with Button C!");
break;
default:
Serial.println("Button A pressed!");
break;
}
keyPressTime = now;
break;

case KEY_B:
Serial.println("Button B pressed!");
// Determine whether button B was pressed in combination with another one already in a pressed state (A or C)
switch (myKeyDetector.current) {
case KEY_A:
Serial.println("Button B pressed simultaneously with Button A!");
break;
case KEY_C:
Serial.println("Button B pressed simultaneously with Button C!");
break;
default:
Serial.println("Button B pressed!");
break;
}
keyPressTime = now;
break;

case KEY_C:
Serial.println("Button C pressed!");
// Determine whether button C was pressed in combination with another one already in a pressed state (A or B)
switch (myKeyDetector.current) {
case KEY_A:
Serial.println("Button C pressed simultaneously with Button A!");
break;
case KEY_B:
Serial.println("Button C pressed simultaneously with Button B!");
break;
default:
Serial.println("Button C pressed!");
break;
}
keyPressTime = now;
break;
}

// After keyPressDelay passed since keyPressTime...
if (now > keyPressTime + keyPressDelay) {

// ...determine currently pressed button (i.e. button being in a pressed state)
// and print corresponding message, followed by keyPressRepeatDelay
switch (myKeyDetector.current) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,16 @@ KeyDetector key(keys, sizeof(keys)/sizeof(Key));
// Note that you can increase threshold value to achieve more accurate detection (e.g. in case of high signal ripple
// or inaccuracy of the DAC used to encode signals). To do so, use the following line instead (where 24 is the custom
// value of threshold):
// KeyDetector key(keys, sizeof(keys)/sizeof(Key), 0, 24);
// KeyDetector key(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 0, /* analogThreshold= */ 24);
// Additionally you can specify debounceDelay value to account for any transient process that may occur when adjusting
// the source level of analog signal:
// KeyDetector key(keys, sizeof(keys)/sizeof(Key), 5, 24);
// KeyDetector key(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 5, /* analogThreshold= */ 24);

void setup() {
// Configure the reference voltage used for analog input (i.e. the value used as the top of the input range)
analogReference(EXTERNAL);


// Serial communications setup
Serial.begin(115200);

// Set signal pins to input
Expand Down
107 changes: 107 additions & 0 deletions examples/Example-04_Encoder/Example-04_Encoder.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
Rotary encoder readings using KeyDetector library.
Demonstrates how to use KeyDetector to trigger action based on digital signal readings from rotary encoder with button.
Rotating the knob and pressing button at the same time will print corresponding message.
Additional info (including the breadboard view) available on GitHub:
https://github.com/Spirik/KeyDetector
This example code is in the public domain.
*/

#include <KeyDetector.h>

// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button)
#define KEY_A 1
#define KEY_B 2
#define KEY_C 3

// Pins encoder is connected to
const byte channelA = 2;
const byte channelB = 3;
const byte buttonPin = 4;

// Array of Key objects that will link GEM key identifiers with dedicated pins
// (it is only necessary to detect signal change on a single channel of the encoder, either A or B;
// order of the channel and push-button Key objects in an array is not important)
Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}};
//Key keys[] = {{KEY_C, buttonPin}, {KEY_A, channelA}};

// Create KeyDetector object
// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key));
// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay
// as the third argument to KeyDetector constructor.
// Make sure to adjust debounce delay to better fit your rotary encoder.
// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case).
// Analog threshold is not necessary for this example and is set to default value 16.
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 5, /* analogThreshold= */ 16, /* pullup= */ true);

void setup() {
// Serial communications setup
Serial.begin(115200);

// Pin modes set to INPUT_PULLUP with internal pullup resistors activated
// (alternatively it is possible to connect terminals using external pullup 10kOhm resistors and INPUT mode)
pinMode(channelA, INPUT_PULLUP);
pinMode(channelB, INPUT_PULLUP);
pinMode(buttonPin, INPUT_PULLUP);

Serial.println("Sketch loaded. Interact with encoder.");
}

void loop() {
myKeyDetector.detect();

// Press event (e.g. when button was pressed or channel A signal chaged to HIGH)
switch (myKeyDetector.trigger) {
case KEY_A:
//Signal from Channel A of encoder was detected
if (digitalRead(channelB) == LOW) {
// If channel B is LOW then the knob was rotated CW
if (myKeyDetector.current == KEY_C) {
Serial.println("Rotation CW with button pressed");
} else {
Serial.println("Rotation CW");
}
} else {
// If channel B is HIGH then the knob was rotated CCW
if (myKeyDetector.current == KEY_C) {
Serial.println("Rotation CCW with button pressed");
} else {
Serial.println("Rotation CCW");
}
}
break;
case KEY_C:
//Button was pressed
Serial.println("Button pressed");
break;
}

// Release event (e.g. when button was released or channel A signal changed to LOW)
switch (myKeyDetector.triggerRelease) {
case KEY_A:
//Signal from Channel A of encoder was detected
if (digitalRead(channelB) == LOW) {
// If channel B is LOW then the knob was rotated CCW
if (myKeyDetector.current == KEY_C) {
Serial.println("Rotation CCW with button pressed (release)");
} else {
Serial.println("Rotation CCW (release)");
}
} else {
// If channel B is HIGH then the knob was rotated CW
if (myKeyDetector.current == KEY_C) {
Serial.println("Rotation CW with button pressed (release)");
} else {
Serial.println("Rotation CW (release)");
}
}
break;
case KEY_C:
// Button was released
Serial.println("Button released");
break;
}
}
Loading

0 comments on commit cffe3c0

Please sign in to comment.