Skip to content

Commit 4af0aa6

Browse files
authored
Merge pull request #281 from Smeedy/master
Generic RotaryEvent
2 parents 30c7435 + 90bafe3 commit 4af0aa6

File tree

4 files changed

+285
-1
lines changed

4 files changed

+285
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ examples/screenNav
2020
/examples/dev
2121
/composition_test
2222
/examples/tiny
23+
24+
**/.DS_Store
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/********************
2+
March 2020 M. Smit - [email protected]
3+
4+
Generic Rotary/Button input
5+
output: SSD1306 OLED
6+
input: clickable rotary encoder
7+
8+
purpose:
9+
10+
Having a generic rotary event-based implementation,
11+
leaving rotary and button libraries up to the user.
12+
Example uses QDEC and AceButton, but could be anything
13+
that suits your particular hardware and/or needs.
14+
15+
TODO: userland rotary/button event mapping to menu actions,
16+
as doubleclick/longpress are now hardcoded to back.
17+
18+
***/
19+
20+
21+
#include <menu.h>
22+
#include <menuIO/u8g2Out.h>
23+
#include <menuIO/chainStream.h>
24+
#include <menuIO/rotaryEventIn.h>
25+
26+
// some example libraries to handle the rotation and clicky part
27+
// of the encoder. These will generate our events.
28+
#include <qdec.h> //https://github.com/SimpleHacks/QDEC
29+
#include <AceButton.h> // https://github.com/bxparks/AceButton
30+
31+
32+
// Encoder
33+
const int ROTARY_PIN_A = 13; // the first pin connected to the rotary encoder
34+
const int ROTARY_PIN_B = 15; // the second pin connected to the rotary encoder
35+
const int ROTARY_PIN_BUT = 25;
36+
37+
using namespace ::ace_button;
38+
using namespace ::SimpleHacks;
39+
QDecoder qdec(ROTARY_PIN_A, ROTARY_PIN_B, true); // rotary part
40+
AceButton button(ROTARY_PIN_BUT); // button part
41+
//--//
42+
43+
44+
// Display
45+
// LOLIN32 I2C SSD1306 128x64 display
46+
// https://github.com/olikraus/u8g2
47+
#define SDA 5
48+
#define SCL 4
49+
50+
#include <Wire.h>
51+
#define fontName u8g2_font_7x13_mf
52+
#define fontX 7
53+
#define fontY 16
54+
#define offsetX 0
55+
#define offsetY 3
56+
#define U8_Width 128
57+
#define U8_Height 64
58+
#define USE_HWI2C
59+
#define fontMarginX 2
60+
#define fontMarginY 2
61+
U8G2_SSD1306_128X64_VCOMH0_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);//allow contrast change
62+
63+
const colorDef<uint8_t> colors[6] MEMMODE={
64+
{{0,0},{0,1,1}},//bgColor
65+
{{1,1},{1,0,0}},//fgColor
66+
{{1,1},{1,0,0}},//valColor
67+
{{1,1},{1,0,0}},//unitColor
68+
{{0,1},{0,0,1}},//cursorColor
69+
{{1,1},{1,0,0}},//titleColor
70+
};
71+
//--//
72+
73+
74+
// AndroidMenu
75+
// https://github.com/neu-rah/ArduinoMenu
76+
#define MAX_DEPTH 1
77+
78+
unsigned int timeOn=10;
79+
unsigned int timeOff=90;
80+
81+
using namespace Menu;
82+
MENU(mainMenu, "Blink menu", Menu::doNothing, Menu::noEvent, Menu::wrapStyle
83+
,FIELD(timeOn,"On","ms",0,1000,10,1, Menu::doNothing, Menu::noEvent, Menu::noStyle)
84+
,FIELD(timeOff,"Off","ms",0,10000,10,1,Menu::doNothing, Menu::noEvent, Menu::noStyle)
85+
,EXIT("<Back")
86+
);
87+
88+
RotaryEventIn reIn(
89+
RotaryEventIn::EventType::BUTTON_CLICKED | // select
90+
RotaryEventIn::EventType::BUTTON_DOUBLE_CLICKED | // back
91+
RotaryEventIn::EventType::BUTTON_LONG_PRESSED | // also back
92+
RotaryEventIn::EventType::ROTARY_CCW | // up
93+
RotaryEventIn::EventType::ROTARY_CW // down
94+
); // register capabilities, see AndroidMenu MenuIO/RotaryEventIn.h file
95+
MENU_INPUTS(in,&reIn);
96+
97+
MENU_OUTPUTS(out,MAX_DEPTH
98+
,U8G2_OUT(u8g2,colors,fontX,fontY,offsetX,offsetY,{0,0,U8_Width/fontX,U8_Height/fontY})
99+
,NONE
100+
);
101+
NAVROOT(nav,mainMenu,MAX_DEPTH,in,out);
102+
//--//
103+
104+
105+
// This is the ISR (interrupt service routine) for rotary events
106+
// We will convert/relay events to the RotaryEventIn object
107+
// Callback config in setup()
108+
void IsrForQDEC(void) {
109+
QDECODER_EVENT event = qdec.update();
110+
if (event & QDECODER_EVENT_CW) { reIn.registerEvent(RotaryEventIn::EventType::ROTARY_CW); }
111+
else if (event & QDECODER_EVENT_CCW) { reIn.registerEvent(RotaryEventIn::EventType::ROTARY_CCW); }
112+
113+
}
114+
115+
// This is the handler/callback for button events
116+
// We will convert/relay events to the RotaryEventIn object
117+
// Callback config in setup()
118+
void handleButtonEvent(AceButton* /* button */, uint8_t eventType, uint8_t buttonState) {
119+
120+
switch (eventType) {
121+
case AceButton::kEventClicked:
122+
reIn.registerEvent(RotaryEventIn::EventType::BUTTON_CLICKED);
123+
break;
124+
case AceButton::kEventDoubleClicked:
125+
reIn.registerEvent(RotaryEventIn::EventType::BUTTON_DOUBLE_CLICKED);
126+
break;
127+
case AceButton::kEventLongPressed:
128+
reIn.registerEvent(RotaryEventIn::EventType::BUTTON_LONG_PRESSED);
129+
break;
130+
}
131+
}
132+
133+
134+
void setup() {
135+
Serial.begin(115200);
136+
while(!Serial);
137+
138+
// setup rotary encoder
139+
qdec.begin();
140+
attachInterrupt(digitalPinToInterrupt(ROTARY_PIN_A), IsrForQDEC, CHANGE);
141+
attachInterrupt(digitalPinToInterrupt(ROTARY_PIN_B), IsrForQDEC, CHANGE);
142+
143+
// setup rotary button
144+
pinMode(ROTARY_PIN_BUT, INPUT);
145+
ButtonConfig* buttonConfig = button.getButtonConfig();
146+
buttonConfig->setEventHandler(handleButtonEvent);
147+
buttonConfig->setFeature(ButtonConfig::kFeatureClick);
148+
buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick);
149+
buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
150+
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressClickBeforeDoubleClick);
151+
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterClick);
152+
buttonConfig->setFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick);
153+
154+
// setup OLED disaply
155+
Wire.begin(SDA,SCL);
156+
u8g2.begin();
157+
u8g2.setFont(fontName);
158+
159+
do {
160+
u8g2.drawStr(0,fontY,"RotaryEventIn demo");
161+
} while(u8g2.nextPage());
162+
// appear
163+
for(int c=255;c>0;c--) {
164+
u8g2.setContrast(255-255.0*log(c)/log(255));
165+
delay(12);
166+
}
167+
168+
}
169+
170+
void loop() {
171+
// put your main code here, to run repeatedly:
172+
button.check(); // acebutton check, rotary is on ISR
173+
nav.doInput(); // menu check
174+
175+
if (nav.changed(0)) {//only draw if menu changed for gfx device
176+
u8g2.firstPage();
177+
do nav.doOutput(); while(u8g2.nextPage());
178+
}
179+
180+
}

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=ArduinoMenu library
2-
version=4.19.1
2+
version=4.19.1-dev
33
author=Rui Azevedo, [email protected]
44
maintainer=neu-rah, [email protected]
55
sentence=Generic menu/interactivity system

src/menuIO/rotaryEventIn.h

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* -*- C++ -*- */
2+
/**************
3+
4+
RotaryEventIn.h
5+
6+
March 2020 M. Smit - [email protected]
7+
Generic Rotary/Button input
8+
9+
purpose:
10+
11+
Having a generic rotary event-based implementation,
12+
leaving rotary and button libraries up to the user.
13+
14+
TODO: userland rotary/button event mapping to menu actions,
15+
as doubleclick/longpress are now hardcoded to back.
16+
17+
***/
18+
19+
20+
// not sure where to put this..
21+
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
22+
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
23+
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
24+
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
25+
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
26+
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
27+
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }
28+
29+
30+
#ifndef __rotaryEventIn_h__
31+
#define __rotaryEventIn_h__
32+
#include "../menuDefs.h"
33+
34+
namespace Menu {
35+
36+
class RotaryEventIn:public menuIn {
37+
38+
public:
39+
40+
enum EventType {
41+
BUTTON_CLICKED = 1 << 0,
42+
BUTTON_DOUBLE_CLICKED = 1 << 1,
43+
BUTTON_LONG_PRESSED = 1 << 2,
44+
45+
ROTARY_CW = 1 << 3,
46+
ROTARY_CCW = 1 << 4,
47+
};
48+
49+
EventType config;
50+
EventType events; // we could do a fifo if we miss events
51+
52+
RotaryEventIn(EventType c)
53+
:config(c) {
54+
// config for future use. we could raise if
55+
// we are missing essential stuff
56+
// and we need to absorb an arg anyway...
57+
}
58+
59+
void registerEvent(EventType e) {
60+
events |= e; // add it to the current events
61+
}
62+
63+
int peek(void) override { return events; }
64+
int available(void) override {return peek() != 0;}
65+
66+
int read() override {
67+
// enterCmd
68+
if (events & EventType::BUTTON_CLICKED) {
69+
events &= ~EventType::BUTTON_CLICKED; // remove from events
70+
return options->navCodes[enterCmd].ch;
71+
}
72+
// escCmd
73+
else if (events & (EventType::BUTTON_DOUBLE_CLICKED|EventType::BUTTON_LONG_PRESSED)) {
74+
events &= ~(EventType::BUTTON_DOUBLE_CLICKED|EventType::BUTTON_LONG_PRESSED); // remove
75+
return options->navCodes[escCmd].ch;
76+
}
77+
// downCmd
78+
else if (events & EventType::ROTARY_CW) {
79+
events &= ~EventType::ROTARY_CW; // remove from events
80+
return options->navCodes[upCmd].ch; // down sends up on menu? bug?
81+
}
82+
// upCmd
83+
else if (events & EventType::ROTARY_CCW) {
84+
events &= ~EventType::ROTARY_CCW; // remove from events
85+
return options->navCodes[downCmd].ch; // up sends down on menu? bug?
86+
}
87+
88+
else
89+
return -1;
90+
}
91+
92+
void flush() override {}
93+
size_t write(uint8_t v) override {return 0;}
94+
95+
}; // class
96+
97+
98+
}//namespace Menu
99+
100+
101+
102+
#endif /* __rotaryEventIn_h__ */

0 commit comments

Comments
 (0)