I've done a few projects, but I'm no pro with the Arduino. It seems on nearly every pseudo complicated project my biggest hurdle is the interface. I'm wondering if anyone knows a clean way to stay in a subroutine and not have loop() continuously call it?
I'm interfacing with several sensors and a couple small steppers. I'm using a rotary encoder with switch along with a 16x2 LCD for interfacing.
What I'd like to do is when I first power on, it defaults into 'run mode' where it just does it's thing (read sensors, pulse steppers when required...); but if I toggle a switch, I'd like to jump to 'setup mode' where I can select from a list of options. Depending on the option I'll run a subroutine, such as 'calibrate sensor #1', or 'manually jog stepper #3'. After I scroll through and adjust all of the setups, I can simply toggle the switch back and it'll jump back to 'run mode'
where setupmode(); has the bulk of the code doing things like reading the encoder, calling subroutines etc... I can't seem to make this work cleanly. loop() will continuously call setupmode().
Is there a way to call setupmode() and stay there until I flip the switch back? Or is there a better way to handle this situation?
Sure. Use a "for" loop to drive the function and code the for loop to watch a boolean to change from true to false. Somewhere in your function, change the boolean to false and the loop will end.
Paul
By this snippet, I infer that you have a bistable switch, in addition to the pushbutton on the rotary encoder, is that correct? If you were referring to the pushbutton that comes with the encoder, that's a temporary switch, which stays closed only as long as you keep it pressed.
Either write setupmode() so that it works correctly while repeatedly called (this is possible, but I am not sure if you have the skills as you are asking the question)
loop() is a subroutine, that is continuously called by main(). Everything of interest can be done in loop(), so there is no obvious reason to shove the problem one level down.
Thanks all for the suggestions. I tried tinkering and still getting stuck.
Using the if or the while (tried both) seems to jump to the menuselect() portion. But it seems to be getting stuck there. The LCD readout in the code below displays "rotate 0" and the encoder nor the button seem to have any effect.
I've tried putting the rotate() and the buttonPressed() in several places, as it seems that neither is being called no matter where I put them. I had both working before when it was just the two, but when trying to integrate into a larger program, they seem to be getting ignored. That tells me it has something to do with where I put them or how I'm calling them.
If anyone has any insight, I'd greatly appreciate it.
/*
* This is to be the basis for a menu select for a LCD screen and a rotary encoder with button select
*
* Arduino pins
* 0 - (FREE)
* 1 - Rotary Encoder CLK
* 2 - Rotary Encoder DT
* 3 - Rotary Encoder SW
* 4 - (FREE)
* 5 - (FREE)
* 6 - (FREE)
* 7 - (FREE)
* 8 - (FREE)
* 9 - (FREE)
* 10- (FREE)
* 11- 2 Position Selector Switch
* 12- (FREE)
* 13- (FREE)
* A0- (FREE)
* A1- (FREE)
* A2- (FREE)
* A3- (FREE)
* A4- (FREE)
* A5- (FREE)
* SCL - I2C / LCD SCL line
* SDA - I2C / LCD SDA line
*
*/
#include <Wire.h> //library for LCD / I2C
#include <LiquidCrystal_I2C.h> //library for LCD / I2c
LiquidCrystal_I2C lcd(0x27,16,2); //I2C ADDRESS
#define CLK 1 //define pin 1 as CLK of rotary encoder
#define DT 2 //define pin 2 as DT of rotary encoder
#define SW 3 //define pin 3 as the select switch on rotary encoder
#define setupPin 11 //switch to select between setup mode and run mode
int ButtonCounter = 0; //counts the button clicks
int RotateCounter = 0; //counts the rotation clicks
bool rotated = true; //state of the rotation
bool ButtonPressed = false; //state of the button
//variables to track the encoder:
int CLKNow;
int CLKPrevious;
int DTNow;
int DTPrevious;
// Timers for tracking encoder clicks:
float TimeNow1;
float TimeNow2;
void setup() {
lcd.begin(); //initalize LCD display
lcd.backlight(); //turn on LCD backlight
lcd.clear(); //clear the LCD for a fresh start
lcd.setCursor(1,0); // set the cursor to position (2,0)
lcd.print("menu test V0.1"); // print out start up message to LCD
pinMode(CLK,INPUT); // setup rotary encoder CLK as an input
pinMode(DT,INPUT); // setup rotary encoder DT as an input
pinMode(SW,INPUT_PULLUP); // setup rotary encoder SW as an input
pinMode(setupPin, INPUT);
//Setup states for the encoder:
CLKPrevious = digitalRead(CLK);
DTPrevious = digitalRead(DT);
attachInterrupt(digitalPinToInterrupt(CLK), rotate, CHANGE);
attachInterrupt(digitalPinToInterrupt(SW), buttonPressed, FALLING);
TimeNow1 = millis(); //Start timer 1
}
void buttonPressed(){
//This timer is an attempt at a software debounce
TimeNow2 = millis();
if((TimeNow2 - TimeNow1 > 500) && (digitalRead(SW) == HIGH)){
ButtonPressed = true;
lcd.clear();
lcd.setCursor(1,0);
lcd.print("button press");
}
TimeNow1 = millis(); //reset timer for the next press
}
void rotate(){
CLKNow = digitalRead(CLK); //Read the state of the CLK pin
// If last and current state of CLK are different, then a pulse occurred
if (CLKNow != CLKPrevious && CLKNow == 1)
{
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so increase
if (digitalRead(CLK) != CLKNow){
RotateCounter++;
if(RotateCounter > 4){
RotateCounter = 0;
}
}
else{
RotateCounter--;
if(RotateCounter < 0){
RotateCounter = 4;
}
}
}
CLKPrevious = CLKNow; // Store last CLK state
rotated = true;
lcd.setCursor(0,1);
lcd.print("rotate ");
lcd.print(RotateCounter);
}
void menuSelect(){
rotate();
buttonPressed();
if(ButtonPressed == true)
{
switch(RotateCounter)
{
case 0:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("menu 0");
break;
case 1:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("menu 1");
break;
case 2:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("menu 2");
break;
case 3:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("menu 3");
break;
case 4:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("menu 4");
break;
}
}
ButtonPressed = false; //reset this variable
}
void loop() {
//menu select programming:
if (digitalRead(setupPin) == HIGH){
menuSelect();
while (digitalRead(setupPin) == HIGH);
}
else {
//run mode program
}
}
You reset the button press variable 'TimeNow1' every time ButtonPress() is called, not just when a button press is detected. That is not right.
Usually it would have a descriptive name like "previousPress" or "lastPress". There can only be one time now, this Now1 and Now2 obfuscates the meaning.
Also 500 milliseconds is far too long for a debounce interval.
Well, you only call menuselect() once, then you enter an endless loop. So no surprise there...
Here is how you might stay in a loop during setup:
bool runningSetup = false;
void loop() {
//menu select programming:
if (digitalRead(setupPin) == HIGH and runningSetup == false){
runningSetup = true;
if (runningSetup == true){
menuSelect();
}
else {
//run mode program
}
}
If you want to remain frozen in menuSelect, you can perform that action there, or you have the option of running your program by setting
runningSetup = false;
again
Programmatically, it seems like you have designed things "bottom up" and then had an assortment of parts that don't fit together into what you want. By designing "top down" you can avoid that.
Actually, this solution was already given in reply #2.
Thanks all for contributions.
I started over and I think I have a reasonable template to use for menu selecting with an encoder / LCD.
The only thing that's not quite working right yet is when you switch back over to run mode (pin 11), it doesn't seem to be running the clearScreen(); inside loop(); but that's relatively minor at this point.
Below is the code, incase anyone can benefit from it:
/*
* Arduino pins
* 0 - (FREE)
* 1 - Rotary Encoder CLK
* 2 - Rotary Encoder DT
* 3 - Rotary Encoder Switch
* 4 - (FREE)
* 5 - (FREE)
* 6 - (FREE)
* 7 - (FREE)
* 8 - (FREE)
* 9 - (FREE)
* 10- (FREE)
* 11- Two Position Switch to select Run Mode or Setup
* 12- (FREE)
* 13- (FREE)
* A0- (FREE)
* A1- (FREE)
* A2- (FREE)
* A3- (FREE)
* A4- (FREE)
* A5- (FREE)
* SCL - I2C / LCD SCL line
* SDA - I2C / LCD SDA line
*
*/
#include <Wire.h> //library for LCD / I2C
#include <LiquidCrystal_I2C.h> //library for LCD / I2c
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27,16,2); //I2C ADDRESS
#define CLK 1 //define pin 1 as CLK of rotary encoder
#define DT 2 //define pin 2 as DT of rotary encoder
#define SW 3 //define pin 3 as the select on rotary encoder
#define setupPin 11 //switch to select between setup and run mode
int counter = 0; //variable for counting the rotary encoder movement
int menu = 0; //vairable for redistributing the counter clicks (2 per detent) into menu items
int currentStateCLK; // records CLK current position on rotary encoder
int lastStateCLK; // records CLK last position on rotary encoder
unsigned long lastButtonPress = 0; //records button press on rotary encoder
bool clearscrn = false; // switch to clear screen only once in loop()
int numScrn = 4; // number of setup screens to use
void setup() {
lcd.begin(); //initalize LCD display
lcd.backlight(); //turn on LCD backlight
lcd.clear(); //clear the LCD for a fresh start
lcd.setCursor(1,0); // set the cursor to position (2,0)
lcd.print("menu two V0.1"); // print out start up message to LCD
delay(3000);
lcd.clear();
pinMode(CLK,INPUT); // setup rotary encoder CLK as an input
pinMode(DT,INPUT); // setup rotary encoder DT as an input
pinMode(SW,INPUT_PULLUP); // setup rotary encoder SW as an input
pinMode(setupPin, INPUT_PULLUP); // setup selector switch
lastStateCLK = digitalRead(CLK); // read inital state of CLK, assign to previousStateCLK
}
void menuSelect(){
currentStateCLK = digitalRead(CLK); // read current state of CLK on rotary encoder
// the following compares last and current state of CLK to see if pulse occured
if (currentStateCLK != lastStateCLK){
// if the DT state is different than CLK state then CCW rotation, so decrement
if (digitalRead(DT) != currentStateCLK) {
counter ++;
menu = counter/2;
if(menu > numScrn){ //this rolls back over to zero if counter gets over number of screens
counter = 0;
}
} else {
// encoder is rotating CW so incriment
counter --;
menu = counter/2;
if (menu <0){ //this rolls back over to the max number of screens if counter gets below zero
counter = numScrn;
}
}
// the following is to display the encoder state on the LCD screen
lcd.clear(); // clear LCD screen
lcd.setCursor(0,0); // set cursor to beginning
lcd.print("Select Operation"); // print out rotation on line 1
lcd.setCursor(1,1); // set cursor to second row
switch(menu){
case 0:
lcd.print("operation 1");
break;
case 1:
lcd.print("operation 2");
break;
case 2:
lcd.print("operation 3");
break;
case 3:
lcd.print("operation 4");
break;
case 4:
lcd.print("operation 5");
break;
}
}
lastStateCLK = currentStateCLK; //remember last CLK state
int btnState = digitalRead(SW); //read button state
//if btnState LOW, then button is pressed
if (btnState == LOW) {
if (millis() - lastButtonPress > 50) {
switch(menu){
case 0:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("running op 1...");
//run menu 1 program here
break;
case 1:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("running op 2...");
//run menu 2 program here
break;
case 2:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("running op 3...");
//run menu 3 program here
break;
case 3:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("running op 4...");
//run menu 4 program here
break;
case 4:
lcd.clear();
lcd.setCursor(0,1);
lcd.print("running op 5...");
//run menu 5 program here
break;
}
}
lastButtonPress = millis(); // remember last button press
}
clearscrn = false;
}
void clearScreen(){
lcd.clear();
clearscrn = true;
}
void loop() {
if (digitalRead(setupPin) == HIGH){
menuSelect();
}
if (digitalRead(setupPin) == LOW){
if (clearscrn = false){
clearScreen();
}
//run mode
lcd.setCursor(2,0);
lcd.print("run mode"); // trouble shooting remove this and insert the run mode program call
}
}
I never miss an opportunity to look at debouncing code or strategy. Here we see something a bit different. The code in #14 stripped down to show just the debounce logic
#define SW 3 //define pin 3 as the select on rotary encoder
# define LED 4 // just an LED for showing
unsigned long lastButtonPress = 0; //records button press on rotary encoder
void setup() {
pinMode(SW,INPUT_PULLUP); // setup rotary encoder SW as an input
pinMode(LED, OUTPUT); // setup selector switch
}
void menuSelect(){
int btnState = digitalRead(SW); //read button state
//if btnState LOW, then button is pressed
if (btnState == LOW) {
if (millis() - lastButtonPress > 500) {
digitalWrite(LED, !digitalRead(LED)); // toggle the LED
}
lastButtonPress = millis(); // remember last button press
}
}
void loop() {
menuSelect();
}
is a demonstration of getting it right, albeit uncommon. The button is reacted to immediately, debouncing is handled by not reacting again until (at least) the debounce period has elapsed.
In the wokwi and above, I set the debounce period to an absurd 500 ms, just to show the effect. Most debouncing code waits that period before reacting. Replace 500 with 50 and you have an instant acting debounced button.