create a timed 3 state push button in arduino - button

Due to a shortage of pins on a esp8266 in arduino, I need a way to detect a button where;
momentary press runs snooze()
15 sec press runs conf_Desk()
30 sec press runs calibration()
the preconfig;
int buttonPin = D7;
pinMode( buttonPin , INPUT_PULLUP);
All while allowing the main loop to function.
If I trap an interrupt it stops cycling the loop(), a few millisec delays are OK but seconds of delay is too much.
The functions are already written I just can't seem to come up how to track and confirm the hold length to call the right function based on the right timing without stopping other process that must stay cycling.

Using an interrupt is, IMHO, overkill. Interrupts are made for when you need to reply to a stimulus quickly, and a button press is something slow. Unless your loop is blocking, thing that I highly discourage.
ADDITION: as Patrick pointed out in the comments, there is in fact another reason to use interrupts: sleep mode. In fact, if you want to go into sleep mode and wake with a button, you have to use interrupts to wake later. However usually you have to do something continuously and not only reply to the button inputs. If you can't go into sleep mode, using an interrupt for button detection is still overkill in my opinion.
So, if you properly designed your loop not to block, here is a brief part of code doing what I think you should implement:
uint8_t buttonState;
unsigned long lastPressTime;
void setup()
{
...
buttonState = digitalRead(buttonPin);
lastPressTime = 0;
}
void loop()
{
uint8_t currRead = digitalRead(buttonPin);
if (buttonState != currRead)
{ // Button transition
buttonState = currRead;
if (buttonState == LOW)
{ // Button pressed, start tracking
lastPressTime = millis();
}
else
{ // Button released, check which function to launch
if (lastPressTime < 100)
{} // Discard (it is just a bounce)
else if (lastPressTime < 15000)
snooze();
else if (lastPressTime < 30000)
conf_Desk();
else
calibration();
}
}
...
}
Since you made three very distant intervals though, I think that this part better suits your needs:
if ((lastPressTime > 100) && (lastPressTime < 7000))
snooze();
else if ((lastPressTime > 12000) && (lastPressTime < 20000))
conf_Desk();
else if ((lastPressTime > 26000) && (lastPressTime < 40000))
calibration();
So you define validity ranges, so if someone presses the button for 10 seconds nothing happens (this is useful because if someone presses the button for 14.9 seconds in the previous code it will trigger the snooze function).

i would use a simple state machine structure with two global vars to avoid complex nested logic:
int buttonDown = 0;
unsigned long buttonStart;
void loop(){
int snapshot = digitalRead(buttonPin);
if(!buttonDown && snapshot ){ //pressed, reset time
buttonDown = 1; // no longer unpressed
buttonStart = millis(); // when it was pressed
}
if(buttonDown && !snapshot ){ //released, count time
buttonDown = 0; // no longer pressed
int duration = millis() - buttonStart; // how long since pressed?
// now the "event part"
if(duration>30000) return calibration();
if(duration>15000) return conf_Desk();
snooze();
}
sleep(1); // or whatever
}

Interrupt service routines should be a short as possible. You don't have to wait inside the ISR and suspend your main loop for seconds.
Just use two different ISRs for a rising and a falling edge.
When the button is pressed, ISR1 starts a timer, when it is released ISR2 stop it and triggers whatever is necessary depending on the time passed.
Make sure your button is debounced.
https://www.arduino.cc/en/Reference/attachInterrupt

Another way to do this is with a pointer-to-function based state machine.
The advantage on this is that you can easily introduce more functionalities to your button (say, another function called at 45 seconds).
try this:
typedef void(*state)();
#define pressed (millis() - lastPressed)
void waitPress();
void momentPress();
void shortPress();
void longPress();
state State = waitPress;
unsigned long lastPressed;
int buttonState;
int buttonPin = 7;// or whathever pin you use
void snooze(){} // stubs for your functions
void conf_Desk(){}
void callibration(){}
void waitPress()
{
if (buttonState == HIGH)
{
lastPressed = millis();
State = momentPress;
return;
}
else
return;
}
void momentPress()
{
if (buttonState == LOW)
{
snooze();
State = waitPress;
return;
}
if (pressed > 15000)
State = shortPress;
return;
return;
}
void shortPress()
{
if (buttonState == LOW)
{
conf_Desk();
return;
}
if (pressed > 30000)
State = longPress;
return;
return;
}
void longPress()
{
if (buttonState == LOW)
{
callibration();
return;
}
return;
}
void loop()
{
buttonState = digitalRead(buttonPin);
State();
}

Related

How do I check if a button was pressed in a time interval?

I'm doing a project on Arduino that involves traffic lights.
If the crossing button is pressed within a 7 second interval, after waitng for 3 seconds(delay3000), I will call a function. If it's not pressed, the loop will resume as normal.
I have tried with a for loop but can't seem to get around it. Help?
This is the base code that I have. How could I use a millis() fucntion in this problem? Or are there any possible solutions to this problem?
//Street1-Estado Inicial
void loop() {
digitalWrite(str1_verd,HIGH);
digitalWrite(str1_ama,LOW);
digitalWrite(str1_verm,LOW);
digitalWrite(str2_verd,LOW);
digitalWrite(str2_ama,LOW);
digitalWrite(str2_verm,HIGH);
digitalWrite(ped1_verd,LOW);
digitalWrite(ped1_verm,HIGH);
digitalWrite(ped2_verd,HIGH);
digitalWrite(ped2_verm,LOW);
//Verifica se o botao foi pressionado dps de 3s -------ISSUE
delay(3000);
int stateButton1=digitalRead(button_street1);
for (int t=7;t>=1;t--){
if(stateButton1 == false){
B_change_1();
}}
delay(1000);
digitalWrite(str1_verd,LOW);
digitalWrite(str2_ama,HIGH);
digitalWrite(str1_ama,HIGH);
delay(5000);
digitalWrite(str1_ama,LOW);
digitalWrite(str2_ama,LOW);
digitalWrite(str2_verm,LOW);
digitalWrite(str1_verm,HIGH);
digitalWrite(ped2_verd,LOW);
digitalWrite(str2_verd,HIGH);
digitalWrite(ped2_verm,HIGH);
digitalWrite(ped1_verm,LOW);
digitalWrite(ped1_verd,HIGH);
//Verifica se o botao foi pressionado dps de 3s --------ISSUE
delay(3000);
for (int t=7;t>=1;t--){
if(button_street2 == true){
B_change_2();
}}
delay(1000);
digitalWrite(str2_verd,LOW);
digitalWrite(str1_ama,HIGH);
digitalWrite(str2_ama,HIGH);
delay(5000);
loop();
}
//Funcao b_change1
void B_change_1(){
digitalWrite(str1_verd,LOW);
digitalWrite(str2_ama,HIGH);
digitalWrite(str1_ama,HIGH);
delay(5000);
digitalWrite(str1_ama,LOW);
digitalWrite(str2_ama,LOW);
digitalWrite(str2_verm,LOW);
digitalWrite(str1_verm,HIGH);
digitalWrite(str2_verd,HIGH);
digitalWrite(ped2_verm,HIGH);
digitalWrite(ped1_verm,LOW);
digitalWrite(ped1_verd,HIGH);
buzzer_alert();
digitalWrite(ped2_verd,LOW);
}
//Funcao bchange2
void B_change_2(){
digitalWrite(str2_verd,LOW);
digitalWrite(str1_ama,HIGH);
digitalWrite(str2_ama,HIGH);
delay(5000);
digitalWrite(str2_ama,LOW);
digitalWrite(str1_ama,LOW);
digitalWrite(str1_verm,LOW);
digitalWrite(str2_verm,HIGH);
digitalWrite(str1_verd,HIGH);
digitalWrite(ped1_verm,HIGH);
digitalWrite(ped2_verm,LOW);
digitalWrite(ped2_verd,HIGH);
}}}
First of all, you are repeatedly checking button press variables that can't change (though this may be intentional?) :
int stateButton1=digitalRead(button_street1);
for (int t=7; t>=1; t--) {
if(stateButton1 == false) { <---- stateButton1 will never change
B_change_1();
}
}
and
delay(3000);
for (int t=7;t>=1;t--){
if(button_street2 == true) { <---- this isn't set, and won't change
B_change_2();
}
}
You need to update stateButton1 by reading from digitalRead in the loop, or at least every time you test it.
To test if a certain amount of time has passed, you could do something similar to
long button_wait_timeout = 7000; // Maximum time to wait for button press
long button_wait_allowed = 3000; // If pressed within three seconds do action
long starttime = millis();
while (digitalRead(button_street1) == LOW && ((millis() - starttime) < button_wait_timeout)) {
delay(10); // how man millilseconds before trying the button again
}
if ((millis() - starttime) < button_wait_allowed) {
do_action_1();
}
You can remove the timeout bits if you want to wait indefinitely.

Arduino Project

So, I am making a project wherein I will be using an Arduino Uno. What I want to do is, whenever the switch is on, the Arduino will make the led blink. But there is a twist. The LED will start blinking after 10 seconds of the switch status becoming high. But what is happening is that the led is switched off for 10 seconds and then turns on for 0.5 seconds, then again turns off for 10 seconds. What I want it to do is, after remaining in Off condition for 10 seconds, it will keep blinking continuously.
Here's the code
const int upperSwitch=2;
int buttonState;
const int ledPin=13;
unsigned long startMillis;
unsigned long currentMillis;
const unsigned long period = 10000;
void setup()
{
pinMode(upperSwitch,INPUT);
pinMode(ledPin,OUTPUT);
startMillis=millis();
}
void loop()
{
buttonState=digitalRead(upperSwitch);
if(buttonState==HIGH)
{
delay(10000);
currentMillis = millis();
if (currentMillis - startMillis >= period)
{
digitalWrite(ledPin,HIGH);
delay(500);
digitalWrite(ledPin,LOW);
delay(500);
}
}
}
Where am I going wrong??
Where am I going wrong?
Your logic is flawed, and the code exhibits a number design flaws. If you press the button and release it it will enter the if(buttonState==HIGH) section once, so flash once. If you press and hold the button (or use a toggle switch), it will re-enter the if(buttonState==HIGH) section, but repeat the delay before flashing.
Your code lacks "state" - a means of remembering what you were doing in the previous loop() iteration to inform what to do in the next.
The design flaws include:
Unnecessary use of global variables - it is a common bad-habit in Arduino code encouraged by numerous examples on the Arduino website. You only need globals for those variables shared between setup() and loop() - and even then that is not strictly necessary, but I'll let that go - Arduino code seldom gets complicated enough for that to be an issue. In general allow variables the narrowest scope necessary.
Use of delay() in loop(). This makes your code unresponsive and is a waste of CPU resource - doing nothing. In this case once you have started the delay, there is no means of cancelling it or doing other work until it is complete. You had the right idea with if (currentMillis - startMillis >= period), but rendered it pointless by preceding it with a delay and initialising startMillis at the start of the program, rather then when the button was pressed. After the delay, currentMillis - startMillis >= period will certainly be true so the test serves no purpose.
The following code implements press-on/press-off toggle semantics for the button with necessary debouncing. The button state can be toggled on/off at any time (no delays where the button will not be read).
When toggled-on, the delay starts by timestamping the event, and testing time passed since the timestamp. When the delay expires, it starts flashing - timestamping each LED state toggle to effect the flash timing.
When toggled-off, none of the delay/flash code is executed so you can cancel the delay and flashing at any time. The LED is forced off in this state. It seems likely that this is the semantics you intended.
You can run a simulation of this here. I have included debug output - click the "Code" button and expand the "Serial Monitor" to see the debug output, then click "Start Simulation", and click the simulated tact switch. The simulation timing is not accurate, about 50% longer than nominal - YMMV. Debug is output when the button state is toggled and while the LED is flashing. The voltmeter I added to verify the button was wired correctly.
const int UPPER_SWITCH = 2 ;
const int LED_PIN = 13 ;
void setup()
{
pinMode( UPPER_SWITCH, INPUT ) ;
pinMode( LED_PIN, OUTPUT ) ;
}
void loop()
{
// Get initial button state
static int button_toggle_state = digitalRead( UPPER_SWITCH ) ;
// Indicator timing
static unsigned long delay_start_timestamp = 0 ;
// Get current time
unsigned long current_millis = millis();
// Toggle button state on press (press-on/press-off) with debounce
static const unsigned long DEBOUNCE_MILLIS = 20 ;
static unsigned long debounce_timestamp = 0 ;
static int previous_button_state = digitalRead( UPPER_SWITCH ) ;
int current_button_state = digitalRead( UPPER_SWITCH ) ;
if( current_millis - debounce_timestamp > DEBOUNCE_MILLIS &&
current_button_state == HIGH && previous_button_state == LOW )
{
debounce_timestamp = current_millis ;
if( button_toggle_state == LOW )
{
button_toggle_state = HIGH ;
delay_start_timestamp = current_millis ;
}
else
{
button_toggle_state = LOW ;
}
}
previous_button_state = current_button_state ;
// If button toggle state has remained HIGH for DELAY_PERIOD...
static const unsigned long DELAY_PERIOD = 10000 ;
if( button_toggle_state == HIGH &&
current_millis - delay_start_timestamp > DELAY_PERIOD)
{
// ... start flashing
static const int FLASH_PERIOD = 500 ;
static int led_state = LOW ;
static unsigned long flash_toggle_timestamp = 0 ;
if( current_millis - flash_toggle_timestamp > FLASH_PERIOD )
{
flash_toggle_timestamp = current_millis ;
led_state = led_state == HIGH ? LOW : HIGH ;
}
digitalWrite( LED_PIN, led_state ) ;
}
else
{
// LED off
digitalWrite( LED_PIN, LOW ) ;
}
}

Enable esp32 deep sleep mode after a certain period of inactivity

I have a device designed in esp that contains two buttons, each of which performs a function. However, he is constantly monitoring these buttons and running out of battery. How do I implement esp's deep sleep, after a certain period of inactivity? For example, if the device does not read in 30 seconds, activate sleep mode. To exit the mode, just press any of the existing buttons.
I thought of something from the team, but it’s not working, because the milis() parameter is never reset.
void loop()
{
unsigned long timer=millis();
int botaoRfid = digitalRead(butRFID);
int botaoDig = digitalRead(butDIG);
if(botaoRfid == HIGH){
timer=0;
Serial.println("botao RFID pressionado");
checkRFID();
}
else if(botaoDig == HIGH){
timer=0;
Serial.println("botao DIGITAL pressionado");
checkFingerprint();
return;
}
else if(timer >=10000){
Serial.println("Sleep…");
esp_deep_sleep_start();
}
}
Thanks!
It is not really a problem, that millis() is not reset after startup, when you rewrite your code like so:
unsigned long timer = 0;
void setup()
{
}
void loop()
{
int botaoRfid = digitalRead(butRFID);
int botaoDig = digitalRead(butDIG);
if(botaoRfid == HIGH){
timer = millis();
Serial.println("botao RFID pressionado");
checkRFID();
}
else if(botaoDig == HIGH){
timer = millis();
Serial.println("botao DIGITAL pressionado");
checkFingerprint();
return;
}
else if(millis() - timer >= 10000){
Serial.println("Sleep…");
esp_deep_sleep_start();
}
}
This would put the ESP32 into deep sleep after 10 seconds without a reset to the current millis() (which is currently happening in every loop cycle, but you can of course put that code outside the loop...). Beware however, that millis() will overflow after about 49 days and you will have to handle this correctly.
Also in order to wake the ESP32 from DeepSleep you have to define a PIN to listen on using for example esp_sleep_enable_ext1_wakeup(bitmask, mode). There are also other option outlined here.

I'm using interrupts to detect button press lengths, but it will sometimes not detect that I've released the button

#include <DS3231.h>
DS3231 clock;
RTCDateTime dt;
const int INTERRUPT_PIN = 2;
int prevTime, pressedTime, releasedTime, heldTime;
bool settingTimes = false;
int startHour1, startMin1, startHour2, startMin2, endHour1, endMin1, endHour2 = 0;
void setup() {
Serial.begin(9600);
// Initialize DS3231
clock.begin();
clock.setDateTime(__DATE__, __TIME__);
pinMode(INTERRUPT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), actButtonPress, CHANGE);
}
void loop() {
if (!settingTimes) {
dt = clock.getDateTime();
// For leading zero look to DS3231_dateformat example
Serial.print(dt.hour); Serial.print(":");
Serial.println(dt.minute);
delay(1000);
} else {
//Serial.println("Setting Times, Check the buttons");
}
}
void actButtonPress() {
if (prevTime + 100 < millis()) {
Serial.println("This is being called");
if (digitalRead(INTERRUPT_PIN) == LOW) { //Button has been pressed
pressedTime = millis();
} else {
releasedTime = millis();
heldTime = releasedTime - pressedTime;
if (heldTime > 3000) {
settingTimes = !settingTimes;
} else {
Serial.println("Change Page");
}
}
prevTime = millis();
}
}
I want to be able to press the button for 3 seconds to set times, and anything less times, I want to change pages. I need to use interrupts because the ds3231 get date time function doesn't seem to work without the delay statement (I've already tried the millis() function)
Using interrupts is generally not a good idea for checking button presses. Switch inputs should always be considered as rather dirty signals... The initial contact is never clean-cut, there is always some noise before the signal is clean. That's called "bounce".
To debounce your switch input you should either:
wait a little while using a counter, or a time stamp, (using millis) before checking if the button is released.
or only poll the switches at regular intervals.
The duration of the bounce depends on the switch, and will degrade over time as the switch gets older. In practice, 30 ms is a good value for the delay.
I suggest you try this:
#include <DS3231.h>
DS3231 clock;
RTCDateTime dt;
// putting all switch states in this struct to take advantage of bit-sized
// storage, and save RAM space for the rest of the program.
struct SwitchStates
{
unsigned char action : 1; // state of action switch.
unsigned char sw1 : 1; // add one bit for each switch you have.
};
// you need to keep track of previous switch states to detect changes.
SwitchStates previousSwitchState;
#define ACTION_SWITCH_PIN (2)
#define SWITCH_TIME_SET_DELAY (3000) // time to hold switch for setting time in ms.
#define SWITCH_POLLING_DELAY (30) // switch polling delay, in ms.
#define RTC_READ_DELAY (1000) // RTC read delay in ms.
void setup()
{
Serial.begin(9600);
// Initialize DS3231
clock.begin();
clock.setDateTime(__DATE__, __TIME__); // imho, that's not a very good idea...
pinMode(ACTION_SWITCH_PIN, INPUT_PULLUP);
digitalWrite(ACTION_SWITCH_PIN, HIGH); // you must set the pin high
// for pull-up to work.
}
// only used witin loop()
static unsigned char switchPollingTimeStamp = 0; // char because 30 is less than 256.
static bool settingTimes = false;
static unsigned int rtcReadTimeStamp = 0;
static unsigned int modeSwitchTimeStamp = 0;
void loop()
{
// get current timestamp.
unsigned long now = millis();
if ((unsigned char)now - switchPollingTimeStamp >= SWITCH_POLLING_DELAY)
{
switchPollingTimeStamp = (unsigned char)now;
// all switch inputs will be handled within this block.
SwitchStates curSwitchState;
// polling at regular intervals, first read your inputs.
curSwitchState.action = digitalRead(ACTION_SWITCH_PIN);
// curSwitchState.sw1 = digitalRead(SW1_PIN);
// etc...
// check activity on action (mode) switch and do timing, etc...
if (curSwitchState.action && !previousSwitchState.action)
{
// keep track of how long the switch has been held
modeSwitchTimeStamp = (unsigned int)now;
if (settingTimes)
settingTimes = false; // exit time set mode, for example.
else
Serial.pritln("Next Page"); //
}
else if (curSwitchState.action)
{
// after 3000 ms, switch to timeset mode.
if ((unsigned int)now - modeSwitchTimeStamp >= SWITCH_TIME_SET_DELAY)
{
if (!settingTimes)
{
settingTimes = true:
Serial.println("Entering time set mode");
}
}
}
// That was the most complex one, others should be easier
if (settingTimes)
{
if (curState.sw1 && !previousSwitchState.sw1)
{
// whatever this switch would do in set time state.
}
// etc...
}
else
{
// the same when not in set time state.
}
// to keep track of switch activity.
// by first storing current switch states, then saving them
// like this, you cannot forget one, this avoids any 'I missed one' bugs.
previousSwitchState = curSwitchState;
}
// try to keep your code non blocking, so your device stays responsive...
// in other words, avoid calling delay().
if (!settingTimes && (unsigned int)now - rtcReadTimeStamp >= RTC_READ_DELAY)
{
rtcReadTimeStamp - (unsigned int)millis();
dt = clock.getDateTime();
// For leading zero look to DS3231_dateformat example
Serial.print(dt.hour); Serial.print(":");
Serial.println(dt.minute);
}
}
Note: I don't have an arduino with me, and the laptop I have is not set up for it, so I coud not compile and debug it. It should be very close, though.

Having trouble getting my stepper motor to respond to my button sensor in Arduino sketch

I'm using the AccelStepper library to control my stepper motor, and I'm having difficulty figuring out how to get my motor to stop when my button is pushed.
I can get the motor to stop once it completes its entire movement from the moveTo command, but I can't get it to stop before it finishes. I've tried using an if statement nested within the while loop I'm using to get the motor to run, but no dice.
The code as it stands is as follows:
#include <AccelStepper.h>
const int buttonPin=4; //number of the pushbutton pin
int buttonState=0;
int motorSpeed = 9600; //maximum steps per second (about 3rps / at 16 microsteps)
int motorAccel = 80000; //steps/second/second to accelerate
int motorDirPin = 8; //digital pin 8
int motorStepPin = 9; //digital pin 9
int state = 0;
int currentPosition=0;
//set up the accelStepper intance
//the "1" tells it we are using a driver
AccelStepper stepper(1, motorStepPin, motorDirPin);
void setup(){
pinMode(buttonPin,INPUT);
stepper.setMaxSpeed(motorSpeed);
stepper.setSpeed(motorSpeed);
stepper.setAcceleration(motorAccel);
stepper.moveTo(-12000); //move 2000 steps (gets close to the top)
}
void loop(){
while( stepper.currentPosition()!=-10000)
stepper.run();
// move to next state
buttonState = digitalRead(buttonPin);
currentPosition=stepper.currentPosition();
// check if the pushbutton is pressed.
// if it is, the buttonState is HIGH:
//if stepper is at desired location
if (buttonState == HIGH ){//need to find a way to alter current move to command
stepper.stop();
stepper.runToPosition();
stepper.moveTo(12000);
}
if(stepper.distanceToGo() == 0)
state=1;
if(state==1){
stepper.stop();
stepper.runToPosition();
stepper.moveTo(12000);
}
//these must be called as often as possible to ensure smooth operation
//any delay will cause jerky motion
stepper.run();
}
I don't know if you've figured it out already, but I stumbled upon this thread and noticed something which might or might not solve your problem. I'm working with accelstepper too at the moment.
I'm having the feeling that even though you use .stop to stop the motor, you're still assigning a new destination (stepper.moveTo(12000)), after which you still run the stepper.run() command at the bottom, causing the stepper to 'run towards 12000 steps'. Maybe try this?
uint8_t button_state = 0; // cf problem 1.
void loop() {
if (digitalRead(buttonPin) == HIGH) {
if (button_state == 0) {
stepper.stop();
stepper.moveTo(12000);
button_state = 1;
}
} else {
button_state = 0;
}
if (stepper.distanceToGo() == 0) {
stepper.stop();
stepper.moveTo(12000);
}
if(button_state = 0) {
stepper.run();
}
}
I don't know if this is going to work, but this way, if the button is pressed, the button_state variable should prevent the stepper from running when it's set on 1.
Hope this helps,
Just a passing by student
I see three problems in your code:
when you push the button, its state will be set to HIGH for the whole time you're pressing it, and it can be several loops. You'd better use a state variable that triggers what you want to do on button press only once.
looking at the documentation, you're using stepper.runToPosition(), which blocks until it reaches the destination. So the more you click, the more it could get blocked. You'd better use only the stepper.moveTo() and stepper.run() method that enables to use a few cycles for interactions.
at the beginning of your loop, you make your code block until stepper.currentPosition gets to -10000. So you surely are blocking until it gets there, removing all reactivity on every loop() iteration.
you may better work your loop() function out as follows:
uint8_t button_state = 0; // cf problem 1.
void loop() {
if (digitalRead(buttonPin) == HIGH) {
if (button_state == 0) {
stepper.stop();
stepper.moveTo(12000);
button_state = 1;
}
} else {
button_state = 0;
}
if (stepper.distanceToGo() == 0) {
stepper.stop();
stepper.moveTo(12000);
}
stepper.run();
}

Resources