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+ }
0 commit comments