#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Stepper.h>
#define SSD1306_NO_SPLASH
#define ENCODER_CLK 15
#define ENCODER_DT 2
#define ENCODER_BTN 4
#define OLEDC 0 // oled clock pin (set to 0 for default)
#define OLEDD 0 // oled data pin
#define OLEDE 0 // oled enable pin
// oLED
#define OLED_ADDR 0x3C // OLED i2c address
#define SCREEN_WIDTH 128 // OLED display width, in pixels (usually 128)
#define SCREEN_HEIGHT 64 // OLED display height, in pixels (64 for larger oLEDs)
#define OLED_RESET -1 // Reset pin gpio (or -1 if sharing Arduino reset pin)
// Misc
const int serialDebug = 1;
const int iLED = 2; // onboard indicator led gpio pin
#define BUTTONPRESSEDSTATE 0 // rotary encoder gpio pin logic level when the button is pressed (usually 0)
#define DEBOUNCEDELAY 20 // debounce delay for button inputs
const int menuTimeout = 10; // menu inactivity timeout (seconds)
const bool menuLargeText = 0; // show larger text when possible (if struggling to read the small text)
const int maxmenuItems = 12; // max number of items used in any of the menus (keep as low as possible to save memory)
const int itemTrigger = 2; // rotary encoder - counts per tick (varies between encoders usually 1 or 2)
const int topLine = 18; // y position of lower area of the display (18 with two colour displays)
const byte lineSpace1 = 9; // line spacing for textsize 1 (small text)
const byte lineSpace2 = 17; // line spacing for textsize 2 (large text)
const int displayMaxLines = 5; // max lines that can be displayed in lower section of display in textsize1 (5 on larger oLeds)
const int MaxmenuTitleLength = 10; // max characters per line when using text size 2 (usually 10)
const int stepsPerRevolution = 200;
Stepper myStepper(stepsPerRevolution, 27, 26, 25, 33);
// forward declarations
void doEncoder();
void demoMenu();
void menuActions();
void value1();
void menuValues();
void reUpdateButton();
void serviceMenu();
int serviceValue(bool _blocking);
void createList(String _title, int _noOfElements, String *_list);
void displayMessage(String _title, String _message);
void resetMenu();
// menus
// modes that the menu system can be in
enum menuModes {
off, // display is off
menu, // a menu is active
value, // 'enter a value' none blocking is active
message, // displaying a message
blocking // a blocking procedure is in progress (see enter value)
};
menuModes menuMode = off; // default mode at startup is off
struct oledMenus {
// menu
String menuTitle = ""; // the title of active mode
int noOfmenuItems = 0; // number if menu items in the active menu
int selectedMenuItem = 0; // when a menu item is selected it is flagged here until actioned and cleared
int highlightedMenuItem = 0; // which item is curently highlighted in the menu
String menuItems[maxmenuItems+1]; // store for the menu item titles
uint32_t lastMenuActivity = 0; // time the menu last saw any activity (used for timeout)
// 'enter a value'
int mValueEntered = 0; // store for number entered by value entry menu
int mValueLow = 0; // lowest allowed value
int mValueHigh = 0; // highest allowed value
int mValueStep = 0; // step size when encoder is turned
};
oledMenus oledMenu;
struct rotaryEncoders {
volatile int encoder0Pos = 0; // current value selected with rotary encoder (updated by interrupt routine)
volatile bool encoderPrevA; // used to debounced rotary encoder
volatile bool encoderPrevB; // used to debounced rotary encoder
uint32_t reLastButtonChange = 0; // last time state of button changed (for debouncing)
bool encoderPrevButton = 0; // used to debounce button
int reButtonDebounced = 0; // debounced current button state (1 when pressed)
const bool reButtonPressedState = BUTTONPRESSEDSTATE; // the logic level when the button is pressed
const uint32_t reDebounceDelay = DEBOUNCEDELAY; // button debounce delay setting
bool reButtonPressed = 0; // flag set when the button is pressed (it has to be manually reset)
};
rotaryEncoders rotaryEncoder;
// oled SSD1306 display connected to I2C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Start the default menu
void defaultMenu() {
demoMenu();
}
// when an item is selected it is actioned in menuActions()
void demoMenu() {
resetMenu(); // clear any previous menu
menuMode = menu; // enable menu mode
oledMenu.noOfmenuItems = 8; // set the number of items in this menu
oledMenu.menuTitle = "demo_menu"; // menus title (used to identify it)
oledMenu.menuItems[1] = "Set stepper speed";
oledMenu.menuItems[2] = "Set servo angle";
oledMenu.menuItems[3] = "Help";
oledMenu.menuItems[4] = "Menus Off";
}
void menuActions() {
if (oledMenu.menuTitle == "demo_menu") { // actions when an item is selected in "demo_menu"
if (oledMenu.selectedMenuItem == 1) {
if (serialDebug) Serial.println("demo_menu: set stepper motor speed");
resetMenu();
value1(); // enter a value
}
if (oledMenu.selectedMenuItem == 2) {
if (serialDebug) Serial.println("demo_menu: set stepper motor speed");
resetMenu();
value2(); // enter a value
}
// demonstrate usage of message
if (oledMenu.selectedMenuItem == 3) {
if (serialDebug) Serial.println("demo_menu: helps");
displayMessage("Helps", "Untuk pilih menu\ntekan tombol\nUntuk up,\nputar kanan.\nUntuk down,\nputar kiri."); // 21 chars per line, "\n" = next line
}
// turn menu/oLED off
else if (oledMenu.selectedMenuItem == 4) {
if (serialDebug) Serial.println("demo_menu: menu off");
resetMenu(); // turn menus off
}
oledMenu.selectedMenuItem = 0; // clear menu item selected flag as it has been actioned
}
} // menuActions
void setup() {
Serial.begin(115200);
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
pinMode(ENCODER_BTN, INPUT_PULLUP);
myStepper.setSpeed(60);
Serial.begin(9600);
servoMotor.attach(SERVO_PIN); // attaches the servo on ESP32 pin
}
int lastClk = HIGH;
void loop() {
int newClk = digitalRead(ENCODER_CLK);
if (newClk != lastClk) {
// There was a change on the CLK pin
lastClk = newClk;
int dtValue = digitalRead(ENCODER_DT);
if (newClk == LOW && dtValue == HIGH) {
Serial.println("Rotated clockwise");
}
if (newClk == LOW && dtValue == LOW) {
Serial.println("Rotated counterclockwise");
}
}
if (digitalRead(ENCODER_BTN) == LOW) {
digitalWrite(LED_BUILTIN, HIGH);
} else {
digitalWrite(LED_BUILTIN, LOW);
}
// step one revolution in one direction:
Serial.println("clockwise");
myStepper.step(stepsPerRevolution);
delay(500);
// step one revolution in the other direction:
Serial.println("counterclockwise");
myStepper.step(-stepsPerRevolution);
delay(500);
for (int pos = 0; pos <= 180; pos += 1) {
// in steps of 1 degree
servoMotor.write(pos);
delay(15); // waits 15ms to reach the position
}
// rotates from 180 degrees to 0 degrees
for (int pos = 180; pos >= 0; pos -= 1) {
servoMotor.write(pos);
delay(15); // waits 15ms to reach the position
}
}
void setup() {
}
void loop() {
// rotates from 0 degrees to 180 degrees
for (int pos = 0; pos <= 180; pos += 1) {
// in steps of 1 degree
servoMotor.write(pos);
delay(15); // waits 15ms to reach the position
}
// rotates from 180 degrees to 0 degrees
for (int pos = 180; pos >= 0; pos -= 1) {
servoMotor.write(pos);
delay(15); // waits 15ms to reach the position
}
}