Last active
October 5, 2024 07:42
-
-
Save Spirik/7d028fe10894da584262cb91729f7072 to your computer and use it in GitHub Desktop.
test_73_gem-u8g2_sh1106_encoder_spinner-example.ino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Example of using GEMSpinner menu items (with callback). Using rotary encoder as an input source. | |
U8g2lib library is used to draw menu. | |
KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. | |
Additional info (including the breadboard view) available on GitHub: | |
https://github.com/Spirik/GEM | |
This example code is in the public domain. | |
*/ | |
#include <GEM_u8g2.h> | |
#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; | |
byte chanB = HIGH; // Variable to store Channel B readings | |
// 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); | |
bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers | |
bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times | |
const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms | |
long keyPressTime = 0; // Variable to hold time of the key press event | |
long now; // Variable to hold current time taken with millis() function at the beginning of loop() | |
// Create an instance of the U8g2 library. | |
// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details). | |
// This instance is used to call all the subsequent U8g2 functions (internally from GEM library, | |
// or manually in your sketch if it is required). | |
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected | |
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); | |
// Create variables that will be editable through the menu and assign them initial values | |
// byte spinner | |
byte byteNumber = 50; | |
GEMSpinnerBoundariesByte spinnerByteBoundaries = { .step = 10, .min = 0, .max = 150 }; | |
GEMSpinner spinnerByte(spinnerByteBoundaries); | |
// int spinner | |
int intNumber = 0; | |
GEMSpinnerBoundariesInt spinnerIntBoundaries = { .step = 10, .min = -150, .max = 150 }; | |
GEMSpinner spinnerInt(spinnerIntBoundaries); | |
// float spinner | |
float floatNumber = -3.5; | |
GEMSpinnerBoundariesFloat spinnerFloatBoundaries = { .step = 0.5, .min = -10, .max = 10 }; | |
GEMSpinner spinnerFloat(spinnerFloatBoundaries); | |
// double spinner | |
double doubleNumber = 1.2; | |
GEMSpinnerBoundariesDouble spinnerDoubleBoundaries = { .step = 0.05, .min = -2, .max = 2 }; | |
GEMSpinner spinnerDouble(spinnerDoubleBoundaries); | |
// Corresponding menu items | |
void saveCallback(GEMCallbackData callbackData); // Forward declaration of optional callback | |
GEMItem menuItemByteNumber("Byte:", byteNumber, spinnerByte, saveCallback, GEM_VAL_BYTE); | |
GEMItem menuItemIntNumber("Int:", intNumber, spinnerInt, saveCallback, GEM_VAL_INTEGER); | |
GEMItem menuItemFloatNumber("Float:", floatNumber, spinnerFloat, saveCallback, GEM_VAL_FLOAT); | |
GEMItem menuItemDoubleNumber("Double:", doubleNumber, spinnerDouble, saveCallback, GEM_VAL_DOUBLE); | |
// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. | |
// Menu can have multiple menu pages (linked to each other) with multiple menu items each | |
GEMPage menuPageMain("Spinners!"); // Main page | |
// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier | |
GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); | |
// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): | |
// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); | |
void setup() { | |
// Pin modes | |
pinMode(channelA, INPUT_PULLUP); | |
pinMode(channelB, INPUT_PULLUP); | |
pinMode(buttonPin, INPUT_PULLUP); | |
// Serial communication setup | |
Serial.begin(115200); | |
// U8g2 library init. | |
u8g2.begin(); | |
// Turn inverted order of characters during edit mode on (feels more natural when using encoder) | |
menu.invertKeysDuringEdit(true); | |
// Menu init, setup and draw | |
menu.init(); | |
setupMenu(); | |
menu.drawMenu(); | |
Serial.println("Initialized"); | |
} | |
void setupMenu() { | |
// Set precision for floating point variables | |
menuItemFloatNumber.setPrecision(1); | |
menuItemDoubleNumber.setPrecision(2); | |
// Add menu items to menu page | |
menuPageMain.addMenuItem(menuItemByteNumber); | |
menuPageMain.addMenuItem(menuItemIntNumber); | |
menuPageMain.addMenuItem(menuItemFloatNumber); | |
menuPageMain.addMenuItem(menuItemDoubleNumber); | |
// Add menu page to menu and set it as current | |
menu.setMenuPageCurrent(menuPageMain); | |
} | |
void loop() { | |
// Get current time to use later on | |
now = millis(); | |
// If menu is ready to accept button press... | |
if (menu.readyForKey()) { | |
chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm | |
// ...detect key press using KeyDetector library | |
// and pass pressed button to menu | |
myKeyDetector.detect(); | |
switch (myKeyDetector.trigger) { | |
case KEY_A: | |
// Signal from Channel A of encoder was detected | |
if (chanB == LOW) { | |
// If channel B is low then the knob was rotated CCW | |
if (myKeyDetector.current == KEY_C) { | |
// If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... | |
// Serial.println("Rotation CCW with button pressed"); | |
menu.registerKeyPress(GEM_KEY_LEFT); | |
// Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action | |
secondaryPressed = true; | |
} else { | |
// ...or GEM_KEY_UP otherwise | |
// Serial.println("Rotation CCW"); | |
menu.registerKeyPress(GEM_KEY_UP); | |
} | |
} else { | |
// If channel B is high then the knob was rotated CW | |
if (myKeyDetector.current == KEY_C) { | |
// If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... | |
// Serial.println("Rotation CW with button pressed"); | |
menu.registerKeyPress(GEM_KEY_RIGHT); | |
// Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action | |
secondaryPressed = true; | |
} else { | |
// ...or GEM_KEY_DOWN otherwise | |
// Serial.println("Rotation CW"); | |
menu.registerKeyPress(GEM_KEY_DOWN); | |
} | |
} | |
break; | |
case KEY_C: | |
// Button was pressed | |
// Serial.println("Button pressed"); | |
// Save current time as a time of the key press event | |
keyPressTime = now; | |
break; | |
} | |
switch (myKeyDetector.triggerRelease) { | |
case KEY_C: | |
// Button was released | |
// Serial.println("Button released"); | |
if (!secondaryPressed) { | |
// If button was not used as a modifier to rotation action... | |
if (now <= keyPressTime + keyPressDelay) { | |
// ...and if not enough time passed since keyPressTime, | |
// treat key that was pressed as Ok button | |
menu.registerKeyPress(GEM_KEY_OK); | |
} | |
} | |
secondaryPressed = false; | |
cancelPressed = false; | |
break; | |
} | |
// After keyPressDelay passed since keyPressTime | |
if (now > keyPressTime + keyPressDelay) { | |
switch (myKeyDetector.current) { | |
case KEY_C: | |
if (!secondaryPressed && !cancelPressed) { | |
// If button was not used as a modifier to rotation action, and Cancel action was not triggered yet | |
// Serial.println("Button remained pressed"); | |
// Treat key that was pressed as Cancel button | |
menu.registerKeyPress(GEM_KEY_CANCEL); | |
cancelPressed = true; | |
} | |
break; | |
} | |
} | |
} | |
} | |
void saveCallback(GEMCallbackData callbackData) { | |
// Print variable to Serial | |
Serial.print("Selected value: "); | |
switch (callbackData.valInt) { | |
case GEM_VAL_BYTE: | |
Serial.println(byteNumber); | |
break; | |
case GEM_VAL_INTEGER: | |
Serial.println(intNumber); | |
break; | |
case GEM_VAL_FLOAT: | |
Serial.println(floatNumber); | |
break; | |
case GEM_VAL_DOUBLE: | |
Serial.println(doubleNumber); | |
break; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment