Skip to content

Commit 694c682

Browse files
committed
Init
0 parents  commit 694c682

File tree

12 files changed

+602
-0
lines changed

12 files changed

+602
-0
lines changed

LICENSE.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BSD 2-Clause License
2+
3+
Copyright (c) 2023, codingABI All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8+
9+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10+
11+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# KY040
2+
An Arduino library for KY-040 rotary encoders. The library has debouncing and works in polling mode, with pin change interrupts or normal interrupts. In polling or pin change interrupt mode you can attach more then one rotary encoder to your Arduino Uno/Nano.
3+
4+
Examples how to use the library
5+
- [pollingNoInterrupts](/examples/pollingNoInterrupts/pollingNoInterrupts.ino)
6+
- [pinChangeInterrupt](/examples/pinChangeInterrupt/pinChangeInterrupt.ino)
7+
- [pinChangeInterruptPowerSave](/examples/pinChangeInterruptPowerSave/pinChangeInterruptPowerSave.ino)
8+
- [pinChangeInterruptDualEncoders](/examples/pinChangeInterruptDualEncoders/pinChangeInterruptDualEncoders.ino)
9+
- [withInterrupt](/examples/withInterrupt/withInterrupt.ino)
10+
11+
## License and copyright
12+
This library is licensed under the terms of the 2-Clause BSD License [Copyright (c) 2023 codingABI](LICENSE.txt).
13+
14+
## Appendix
15+
### Background
16+
KY040 is library for KY-040 rotary encoders. There are a lot of libraries existing for KY-040, but I found no library (at least 12/2023) which
17+
18+
1. works without the need of interrupt enabled pins for the CLK (aka. A) and DT (aka. B)
19+
2. has a stable debouncing using a signal state table (without debouncing the KY-040 rotary encoder is a mess)
20+
21+
So I wrote my own library KY040, which was designed to work on an Arduino Uno/Nano or ATmega328 and could work on other Arduino compatible MCUs too.
22+
23+
The KY040 library can:
24+
- be used without interrupts in polling mode
25+
- be used with pin change interrupts
26+
- control more than one rotary encoders in polling or pin change interrupt mode on an Arduino Uno/Nano
27+
- use any common pin digital pins for CLK and DT in polling or pin change interrupt mode
28+
- be used with normal *attachInterrupt* interrupts (in this case could have to use Pins 2 and 3 on your Arduino Uno/Nano)
29+
- be used with SLEEP_MODE_PWR_SAVE/SLEEP_MODE_PWR_DOWN sleep mode in combination with pin change interrupts
30+
- debounce the rotary encoder by filtering out invalid signal sequences
31+
32+
### Valid clockwise sequence
33+
34+
```
35+
0 1 2 3
36+
--+ +---- High
37+
CLK | |
38+
+---+ Low
39+
40+
----+ +-- High
41+
DT | |
42+
+---+ Low
43+
```
44+
| Step | Signal level for CLK/DT |
45+
| ------------- | ------------- |
46+
| 0 | Low/High |
47+
| 1 | Low/Low |
48+
| 2 | High/Low |
49+
| 3 | High/High |
50+
51+
### Valid counter-clockwise sequence
52+
53+
```
54+
0 1 2 3
55+
----+ +--- High
56+
CLK | |
57+
+---+ Low
58+
59+
--+ +----- High
60+
DT | |
61+
+---+ Low
62+
```
63+
64+
| Step | Signal level for CLK/DT |
65+
| ------------- | ------------- |
66+
| 0 | High/Low |
67+
| 1 | Low/Low |
68+
| 2 | Low/High |
69+
| 3 | High/High |
70+
71+
### KY-040 Hardware
72+
![Frontside](assets/images/KY-040_Frontside.jpg)
73+
74+
| Pins | Comment |
75+
| ------------- | ------------- |
76+
| GND | Ground |
77+
| + | Vcc |
78+
| SW | Switch button, not covered by this library. You can use *digitalRead* statements to check SW. Pin is pulled up to Vcc via the 10k pullup resistor R3 |
79+
| DT | aka. B, Pin is pulled up to Vcc via the 10k pullup resistor R2 |
80+
| CLK | aka. A, Pin is pulled up to Vcc via the 10k pullup resistor R1 |
81+
82+
![Backside](assets/images/KY-040_Backside.jpg)
83+
84+
The three 10k resistors R1,R2 and R3 pulls up the SW, CLK and DT pins up to Vcc.

assets/images/KY-040_Backside.jpg

133 KB
Loading

assets/images/KY-040_Frontside.jpg

175 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Example for using the rotary encoder with pin change interrupts
3+
*/
4+
5+
#include <KY040.h>
6+
7+
#define CLK_PIN 5 // aka. A
8+
#define DT_PIN 4 // aka. B
9+
KY040 g_rotaryEncoder(CLK_PIN,DT_PIN);
10+
11+
// Rotary encoder value (will be set in ISR)
12+
volatile int v_value=0;
13+
14+
// Enable pin change interrupt
15+
void pciSetup(byte pin) {
16+
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
17+
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
18+
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
19+
}
20+
21+
// ISR to handle pin change interrupt for D0 to D7 here
22+
ISR (PCINT2_vect) {
23+
// Process pin state
24+
switch (g_rotaryEncoder.getRotation()) {
25+
case KY040::CLOCKWISE:
26+
v_value++;
27+
break;
28+
case KY040::COUNTERCLOCKWISE:
29+
v_value--;
30+
break;
31+
}
32+
}
33+
34+
void setup() {
35+
Serial.begin(9600);
36+
37+
// Set pin change interrupt for CLK and DT
38+
pciSetup(CLK_PIN);
39+
pciSetup(DT_PIN);
40+
}
41+
42+
void loop() {
43+
static int lastValue = 0;
44+
int value;
45+
46+
// Get rotary encoder value set in ISR
47+
cli();
48+
value = v_value;
49+
sei();
50+
51+
// Show, if value has changed
52+
if (lastValue != value) {
53+
Serial.println(value);
54+
lastValue = value;
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Example for using two rotary encoders with pin change interrupts
3+
*/
4+
5+
#include <KY040.h>
6+
7+
// First rotary encoder
8+
#define X_CLK_PIN 5 // aka. A
9+
#define X_DT_PIN 4 // aka. B
10+
KY040 g_rotaryEncoderX(X_CLK_PIN,X_DT_PIN);
11+
12+
// Second rotary encoder
13+
#define Y_CLK_PIN 6 // aka. A
14+
#define Y_DT_PIN 7 // aka. B
15+
KY040 g_rotaryEncoderY(Y_CLK_PIN,Y_DT_PIN);
16+
17+
// Rotary encoder values (will be set in ISR)
18+
volatile int v_valueX=0;
19+
volatile int v_valueY=0;
20+
21+
// Enable pin change interrupt
22+
void pciSetup(byte pin) {
23+
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
24+
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
25+
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
26+
}
27+
28+
// ISR to handle pin change interrupts for D0 to D7 here
29+
ISR (PCINT2_vect) {
30+
// Read pin states with PIND (Faster replacement for digitalRead, better for fast interrupts, but harder to read)
31+
byte state = PIND;
32+
byte stateX = ((state & 0b00110000)>>4);
33+
byte stateY = ((state & 0b11000000)>>6);
34+
35+
// Process first rotary encoder
36+
g_rotaryEncoderX.setState(stateX); // Store CLK/DT states
37+
// Process stored state
38+
switch (g_rotaryEncoderX.checkRotation()) {
39+
case KY040::CLOCKWISE:
40+
v_valueX++;
41+
break;
42+
case KY040::COUNTERCLOCKWISE:
43+
v_valueX--;
44+
break;
45+
}
46+
47+
// Process second rotary encoder
48+
g_rotaryEncoderY.setState(stateY); // Store CLK/DT states
49+
// Process stored state
50+
switch (g_rotaryEncoderY.checkRotation()) {
51+
case KY040::CLOCKWISE:
52+
v_valueY++;
53+
break;
54+
case KY040::COUNTERCLOCKWISE:
55+
v_valueY--;
56+
break;
57+
}
58+
}
59+
60+
void setup() {
61+
Serial.begin(9600);
62+
63+
// Set pin change interrupt for CLK and DT
64+
pciSetup(X_CLK_PIN);
65+
pciSetup(X_DT_PIN);
66+
pciSetup(Y_CLK_PIN);
67+
pciSetup(Y_DT_PIN);
68+
}
69+
70+
void loop() {
71+
static int lastValueX = 0;
72+
static int lastValueY = 0;
73+
74+
int valueX, valueY;
75+
76+
// Get rotary encoder values set in ISR
77+
cli();
78+
valueX = v_valueX;
79+
valueY = v_valueY;
80+
sei();
81+
82+
// Show, if value has changed
83+
if ((lastValueX != valueX) || (lastValueY != valueY)) {
84+
Serial.print("X:");
85+
Serial.print(valueX);
86+
Serial.print(" Y:");
87+
Serial.println(valueY);
88+
lastValueX = valueX;
89+
lastValueY = valueY;
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Example for using the rotary encoder with pin change interrupts and
3+
* SLEEP_MODE_PWR_SAVE sleep mode
4+
*/
5+
6+
#include <avr/sleep.h>
7+
#include <KY040.h>
8+
9+
#define CLK_PIN 5 // aka. A
10+
#define DT_PIN 4 // aka. B
11+
KY040 g_rotaryEncoder(CLK_PIN,DT_PIN);
12+
13+
// Rotary encoder value (will be set in ISR)
14+
volatile int v_value=0;
15+
16+
// Enable pin change interrupt
17+
void pciSetup(byte pin) {
18+
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
19+
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
20+
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
21+
}
22+
23+
// ISR to handle pin change interrupt for D0 to D7 here
24+
ISR (PCINT2_vect) {
25+
// Faster replacement for digitalRead, better for interrupts, but harder to read
26+
byte state = ((PIND & 0b00110000)>>4);
27+
g_rotaryEncoder.setState(state); // Store CLK/DT states
28+
// Process stored state
29+
switch (g_rotaryEncoder.checkRotation()) {
30+
case KY040::CLOCKWISE:
31+
v_value++;
32+
break;
33+
case KY040::COUNTERCLOCKWISE:
34+
v_value--;
35+
break;
36+
}
37+
}
38+
39+
void setup() {
40+
Serial.begin(9600);
41+
42+
// Set pin change interrupt for CLK and DT
43+
pciSetup(CLK_PIN);
44+
pciSetup(DT_PIN);
45+
46+
// Set sleep mode to SLEEP_MODE_PWR_SAVE
47+
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
48+
}
49+
50+
void loop() {
51+
static int lastValue = 0;
52+
int value;
53+
54+
// Go to sleep when rotary encoder has no rotation for ~100 milliseconds
55+
if (g_rotaryEncoder.readyForSleep()) sleep_mode();
56+
57+
// Get rotary encoder value set in ISR
58+
cli();
59+
value = v_value;
60+
sei();
61+
62+
// Show, if value has changed
63+
if (lastValue != value) {
64+
Serial.println(value);
65+
Serial.flush();
66+
lastValue = value;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Example for using the rotary encoder without interrupts
3+
* in polling mode
4+
*/
5+
6+
#include <KY040.h>
7+
8+
#define CLK_PIN 6 // aka. A
9+
#define DT_PIN 5 // aka. B
10+
KY040 g_rotaryEncoder(CLK_PIN,DT_PIN);
11+
12+
void setup() {
13+
Serial.begin(9600);
14+
// If you rotary encoder has no builtin pullup resistors for CLK (aka. A) and DT (aka. B) uncomment the following two lines
15+
// pinMode(CLK_PIN,INPUT_PULLUP);
16+
// pinMode(DT,INPUT_PULLUP);
17+
}
18+
19+
void loop() {
20+
static int value=0;
21+
22+
// You have to run getRotation() very frequently in loop to prevent missing rotary encoder signals
23+
// If this is not possible take a look at the pinChangeInterrupt examples
24+
switch (g_rotaryEncoder.getRotation()) {
25+
case KY040::CLOCKWISE:
26+
value++;
27+
Serial.println(value);
28+
break;
29+
case KY040::COUNTERCLOCKWISE:
30+
value--;
31+
Serial.println(value);
32+
break;
33+
}
34+
}

0 commit comments

Comments
 (0)