#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args) write(args);
#else
#define printByte(args) print(args,BYTE);
#endif
uint8_t arrow[8] = {0x00, 0x04, 0x06, 0x1F, 0x1F, 0x06, 0x04, 0x00};
/////Rotary Encoder Pin Assignment/////
#define rotaryPin_A 5
#define rotaryPin_B 6
#define rotaryButtonPin 7
#define EN_PIN 9 // LOW: Driver enabled. HIGH: Driver disabled
#define STEP_PIN 11 // Step on rising edge
#define RX_PIN 10 // SoftwareSerial pins
#define TX_PIN 8 //
#define DIR_PIN 12 //
#define Buzzer_PIN 13 //
#include <TMC2208Stepper.h>
TMC2208Stepper driver = TMC2208Stepper(RX_PIN, TX_PIN);
int currentLimit = 500;
int microstep = 2;
unsigned int maxMenuItems; //number of menu items
unsigned char encoder_A = 0;
unsigned char encoder_B = 0;
unsigned char encoder_A_prev = 0;
unsigned char encoder_C = 1; //encoder button
unsigned char encoder_C_prev = 1;
unsigned long currentTime;
unsigned long loopTime;
/////End of Rotary Encoder Declarations/////
//////Menu declarations///////
unsigned int menuPos = 0; //current menu position
unsigned int lastMenuPos = 0; //previous menu position
unsigned int parentMenuPos = 0; //parent menu position
bool humanInputActive = false; //flag to indicate if input session is active
unsigned subMenuActive = 0; //flag to indicate a sub selection marduarduenu session is active: 0 - main menu; 1 - number selection 1- 255; 2 - binary selection yes/no; 3 - time setting;
unsigned int subMenuPos = 0; //sub menu rotary position
unsigned int subMenuClick = 0; //sub menu click counter
unsigned long CurrentTimeS;
unsigned long CurrentTimeE;
typedef struct menu_type
{
menu_type()
: code(0), text("")
{
//do nothing
}
unsigned int code; //code that indicate address (position) in the menu tree
String text; //text to be displayed for the menu selection
} menu_type;
menu_type menu[30] = {}; //initilizing menu array, use a number >= than the number of menu options
/////End of Menu declarations////
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
menuInit();
Serial.begin(9600);
Wire.begin();
rotaryEncoderInit();
lcd.init();
lcd.backlight();
lcd.createChar(1, arrow);
lcd.setCursor(3, 0);
lcd.print("Bucky-et AC");
lcd.setCursor(5, 1);
lcd.print("V 1.00");
pinMode(rotaryPin_A, INPUT_PULLUP);
pinMode(rotaryPin_B, INPUT_PULLUP);
pinMode(rotaryButtonPin, INPUT_PULLUP);
pinMode(Buzzer_PIN, OUTPUT);
driver.beginSerial(115200);
// Push at the start of setting up the driver resets the register to default
driver.push();
// Prepare pins
pinMode(EN_PIN, OUTPUT);
pinMode(STEP_PIN, OUTPUT);
driver.pdn_disable(true); // Use PDN/UART pin for communication
driver.I_scale_analog(false); // Use internal voltage reference
driver.rms_current(currentLimit); // Set driver current = 500mA, 0.5 multiplier for hold current and RSENSE = 0.11.
driver.toff(2); // Enable driver in software
driver.mstep_reg_select(true);
driver.microsteps(microstep);
driver.dedge(false);
digitalWrite(EN_PIN, LOW); // Enable driver in hardware
// motorReset();
}
void loop() {
rotaryEncoderUpdate();
// if (micros() < NextTime)
// NextTime = micros();
// if (micros() - NextTime > interval) {
// digitalWrite(STEP_PIN, !digitalRead(STEP_PIN));
// NextTime = micros(); // reset for next pulse
// digitalWrite(STEP_PIN, !digitalRead(STEP_PIN));
// delay(1);
// }
digitalWrite(STEP_PIN, !digitalRead(STEP_PIN));
delay(1);
}
void rotaryEncoderInit() {
//Rotary encoder initialization
currentTime = millis();
loopTime = currentTime;
}
void rotaryEncoderUpdate() {
//rotary encoder update, to be called from main loop()
currentTime = millis();
if (currentTime >= (loopTime + 1) ) {
encoder_A = digitalRead(rotaryPin_A);
encoder_B = digitalRead(rotaryPin_B);
encoder_C = digitalRead(rotaryButtonPin);
//handling knob rotation
if ( (encoder_A == 0) && (encoder_A_prev == 1) ) { //encoder changed position
beep();
if (encoder_B == 1) { //B is high, so encoder moved clockwise
//Serial.println("encoder rotated CW");
switch (subMenuActive) { //send encoder action to appropriate menu handler
case 0: //main menu
menuUpdate(2);
break;
case 1: //subMenu1
subMenu1Update(2);
break;
case 2: //subMenu1
subMenu2Update(2);
break;
default:
menuUpdate(2);
break;
}
} else { //else, encoder moved counter-clockwise
//Serial.println("encoder rotated CCW");
switch (subMenuActive) { //call the appropriate menuupdater depending on which sub menu is active
case 0:
menuUpdate(3);
break;
case 1:
subMenu1Update(3);
break;
case 2: //subMenu1
subMenu2Update(3);
break;
default:
menuUpdate(3);
break;
}
}
}
//handling push button
if ( (encoder_C == 0) && (encoder_C_prev == 1) ) { //button pushed
//Serial.println("encoder button closed.");
longBeep();
switch (subMenuActive) { //call the appropriate menuupdater depending on which sub menu is active
case 0:
menuUpdate(1);
break;
case 1:
subMenu1Update(1);
break;
case 2: //subMenu1
subMenu2Update(1);
break;
default:
menuUpdate(1);
break;
}
} else if ( (encoder_C == 1) && (encoder_C_prev == 0) ) { //button
//Serial.println("encoder button opened.");
}
encoder_A_prev = encoder_A;
encoder_C_prev = encoder_C;
loopTime = currentTime;
}
}
unsigned int menuVerifyPos(unsigned int menuCode) {
//accepts a code that represents position in the menu
//checks against the menu, verify it exist, and returns it
//if the menu code given does not exist,
//returns the closest code smaller than the one given
bool confirmCode = false; //flag to keep track of whether code has been confirmed in menu tree
for (unsigned int k = 0; k <= (maxMenuItems - 1); k++) {
if (menuCode == menu[k].code) { //found exact code, returns it
menuPos = menu[k].code;
confirmCode = true;
lastMenuPos = menuCode;
return menuPos;
}
}
if (confirmCode == false) {
menuPos = lastMenuPos;
return menuPos; //cannot find menu option, go back to previous menu option
}
}
void updateMenuDisplay(unsigned int menuCode) {
//prints menu selection to the LCD display
//in order to have a scrolling menu effect, this code looks at item before and after current menu item and display them in a row
String curMenu;
String nextMenu;
curMenu = findMenuTextFromCode(menuCode);
nextMenu = findMenuTextFromCode((menuCode + 1));
//starts printing to LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.printByte(1);
//char index, row index, on LCD
lcd.print(curMenu);
lcd.setCursor(1, 1);
lcd.print(nextMenu); //print the next menu text in the 3rd row
}
String findMenuTextFromCode(unsigned int menuCode) {
//accepts a code representing the code in menu, and returns the corresponding text
for (unsigned int j = 0; j <= (maxMenuItems - 1); j++ ) {
if (menuCode == menu[j].code) {
return menu[j].text;
break;
}
}
}
void beep()
{
digitalWrite(LED_BUILTIN, HIGH);
delay(1);
digitalWrite(LED_BUILTIN, LOW);
}
void longBeep()
{
digitalWrite(LED_BUILTIN, HIGH);
delay(20);
digitalWrite(LED_BUILTIN, LOW);
}
void menuAction(unsigned int menuCode) {
//gets called when rotary encoder is clicked,
//check the current location inside the menu,
//execute approriate actions
//returns true if some action is executed
//returns false if nothing is done
//Serial.print("menuAction called with menuCode = ");
//Serial.println(menuCode);
switch (menuCode) {
case 0:
{
}
break;
case 111: //set frame rpm
if (subMenuActive != 1) { //initialize the subMenu3
lcd.clear();
subMenuActive = 1;
subMenuPos = 0;
subMenuClick = 0;
subMenu1Update(0); //calls subMenu3Update with 0 (no action)
} else if (subMenuActive == 1) { //already initialized
subMenuActive = 0; //deactivate submenu, activate main menu
lcd.clear();
menuPos = 11;
updateMenuDisplay(menuPos);
//code to set time variables
}
break;
case 12:
digitalWrite(EN_PIN, !digitalRead(EN_PIN));
break;
case 13:
digitalWrite(DIR_PIN, !digitalRead(DIR_PIN));
break;
case 141:
if (subMenuActive != 2) { //initialize the subMenu3
lcd.clear();
subMenuActive = 2;
subMenuPos = 0;
subMenuClick = 0;
subMenu2Update(0); //calls subMenu3Update with 0 (no action)
} else if (subMenuActive == 2) { //already initialized
subMenuActive = 0; //deactivate submenu, activate main menu
lcd.clear();
menuPos = 14;
updateMenuDisplay(menuPos);
//code to set time variables
}
break;
}
}
void menuInit() {
//menu text display
unsigned int i = 0;
menu[i].code = 0;
menu[i++].text = "EMERGENCY STOP";
menu[i].code = 1;
menu[i++].text = "MOTOR CONTROL";
menu[i].code = 10;
menu[i++].text = "RETURN";
menu[i].code = 11;
menu[i++].text = "SET INTERVAL";
menu[i].code = 111;
menu[i++].text = "INTERVAL:";
menu[i].code = 12;
menu[i++].text = "TOGGLE MOTOR";
menu[i].code = 13;
menu[i++].text = "REVERSE DIR";
menu[i].code = 14;
menu[i++].text = "SET MICROSTEPS";
menu[i].code = 141;
menu[i++].text = "MICROSTEPS:";
menu[i].code = 2;
menu[i++].text = "RESET";
menu[i].code = 3;
menu[i++].text = "SAVE SETTINGS";
maxMenuItems = i + 1;
}
void menuUpdate(unsigned char encoderAction) { //main menu navigation updater
//called on by rotary encoder actions
//parameter 0: no action from encoder
//parameter 1: encoder click
//parameter 2: encoder CW rotation
//parameter 3: encoder CCW rotation
//unsigned int verifiedMenuPos;
if (encoderAction == 0) {
//no action
} else { //triggered by action
if (encoderAction == 1) { //encoder click button
//code below takes menu position to the next sub branch
if (menuPos % 10 == 0) { //if menu selection is at XX0 (zero at the end)
menuPos = menuPos / 10; //go back up one level of menu (we use zero as the 'return' menu selection)
} else {
menuPos = menuPos * 10 + 1; //else, go further in one level of menu from the current selection
}
menuPos = menuVerifyPos(menuPos);
menuAction(menuPos); //click execution
updateMenuDisplay(menuPos);
} else if (encoderAction == 2) { //encoder CW rotation
if (menuPos % 10 == 9) { //if current selection is at XX9
//do nothing
} else {
menuPos ++;
}
menuPos = menuVerifyPos(menuPos);
updateMenuDisplay(menuPos);
} else if (encoderAction == 3) { //encoder CCW rotation
if (menuPos % 10 == 0) { //if current selection is at XX0
//do nothing
} else {
menuPos --;
}
menuPos = menuVerifyPos(menuPos);
updateMenuDisplay(menuPos);
}
lastMenuPos = menuPos;
//Serial.print("menuPos = ");
//Serial.println(menuPos);
}
}
void subMenu1Update(unsigned char encoderAction) { //sub menu type 1 updater: number selection 1 - 1000
//called on by rotary encoder actions with a parameter to indicate:
//parameter 0: no action from encoder
//parameter 1: encoder click
//parameter 2: encoder CW rotation
//parameter 3: encoder CCW rotation
if (encoderAction == 0) {
// no action
} else {
if (encoderAction == 1) { //encoder click
subMenuClick++;
//Serial.print("subMenu1Update: click++; click = ");
//Serial.println(subMenuClick);
if (subMenuClick == 1) { //single click to exit sub menu
lcd.clear();
menuAction(lastMenuPos); //calls menuAction with last main menu tree position
return;
}
} else if (encoderAction == 2) { //CW rotation
if (interval >= 1000)
interval = 1000;
else
interval += 1;
} else if (encoderAction == 3) { //CCW rotation
if (interval <= 1)
interval = 1;
else
interval -= 1;
}
//update 4th row of LCD to display sub menu position
lcd.setCursor(0, 1);
if (interval % 9 == 0 )
{
lcd.print(" ");
lcd.setCursor(0, 1);
}
lcd.print(interval);
}
}
void subMenu2Update(unsigned char encoderAction) { //sub menu type 1 updater: number selection 1 - 1000
//called on by rotary encoder actions with a parameter to indicate:
//parameter 0: no action from encoder
//parameter 1: encoder click
//parameter 2: encoder CW rotation
//parameter 3: encoder CCW rotation
if (encoderAction == 0) {
// no action
} else {
if (encoderAction == 1) { //encoder click
subMenuClick++;
//Serial.print("subMenu1Update: click++; click = ");
//Serial.println(subMenuClick);
if (subMenuClick == 1) { //single click to exit sub menu
lcd.clear();
menuAction(lastMenuPos); //calls menuAction with last main menu tree position
return;
}
} else if (encoderAction == 2) { //CW rotation
if (microstep >= 256)
microstep = 256;
else
microstep += microstep;
} else if (encoderAction == 3) { //CCW rotation
if (microstep <= 1)
microstep = 1;
else
microstep = microstep / 2;
}
//update 4th row of LCD to display sub menu position
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(microstep);
driver.microsteps(microstep);
}
}
Comments