I'm working on a project using an arduino instead of an expensive PLC or industrial timer relays. I need a 2 hand anti tie down to start the cycle. Before I get chastized for using software for a safety control, understand that the process will not injure the operator, but the operator could interfere with the process quality if their hands were in the wrong place at the right time.
If you're unfamiliar with a 2 hand anti tie down, the operator has to press 2 buttons at the same time (typically a tolerance of 500ms is given since humans can't press two buttons at exactly the same time) and hold the buttons throughout the cycle, then the buttons have to both be released before the cycle can be restarted. Ideally the buttons would have to be held throughout the entire cycle or else the process is interrupted.
I've attached the ladder logic I've used to program a PLC, I'm just not sure how to put this logic into a format that an arduino can understand. The rest of the IO stuff is pretty straightforward but this 2 hand anti-tiedown has me stumped. My mechanical engineer brain doesn't see logic the same way as a programmer's brain.
Here's the code I have so far:
const int LeftButtonPin = 2; // Left palm button attached to pin 2
const int RightButtonPin = 3; // Right palm button attached to pin 3
const int StartRelay = 13; //cycle start relay attached to pin 13
void setup() {
//initialize button pins as input
pinMode (LeftButtonPin, INPUT);
pinMode (RightButtonPin, INPUT);
//initialize relay pins as output
pinMode (StartRelay, OUTPUT);
}
void loop() {
//2 hand anti tiedown code here
digitalWrite (StartRelay, HIGH); //energize start relay
delay (1000) //delay for process to finish
digitalWrite (StartRelay, LOW); //turn off start relay
}
It is considerably easier to program this if you make one of the buttons the master and look for that being pressed first. Save the time it was pressed and start looking for the second one to be pressed. If/when it is pressed measure the time between the two presses and act accordingly.
I would put the button pins in an array, and use a for loop to process them both the same way:
Read the raw button state, apply debounce logic to get the debounced state, if the debounced state is changing from not pressed to pressed record the current time as the button press time; if it changes from pressed to not pressed set the button press time back to zero. Then iterate through the array of button press times to see how recently the other buttons last became pressed. If any button was pressed longer than half a second ago, your result state should be 'disabled'. If all button presses were within the last half second, the result state is 'enabled'.
All the data needed to implement the debouncing and state change detection and record the press time would be held in arrays.
Using arrays might seem like unnecessary complexity when you only have two items but will eliminate code duplication and the resulting risk of copy/paste errors between the implementation of the two buttons.
Thanks for the help, this has certainly gotten my thinking headed in the right direction. As I consider the next steps, I believe I'll need to use an interrupt to restart the loop should either of the buttons be released during the cycle. Of course this means I'll have to use hardware to de-bounce the switches, which should make my sketch a lot simpler.
As I consider the next steps, I believe I'll need to use an interrupt to restart the loop should either of the buttons be released during the cycle.
An interrupt may, or may not be appropriate depending on what the 'cycle' is doing. If you do use one then at the end of the interrupt the program will return to exactly the same place in the program that was being executed when it occurred and you have no control over that. It may be more effective to check that both buttons are still pressed, easy to do with a function, before carrying out the dangerous part(s) of the cycle.
Of course this means I'll have to use hardware to de-bounce the switches, which should make my sketch a lot simpler.
I don't see that one follows the other, but I assume from what you have said that you are more comfortable with hardware than software so it could be your preferred solution.
UKHeliBob:
An interrupt may, or may not be appropriate depending on what the 'cycle' is doing. If you do use one then at the end of the interrupt the program will return to exactly the same place in the program that was being executed when it occurred and you have no control over that. It may be more effective to check that both buttons are still pressed, easy to do with a function, before carrying out the dangerous part(s) of the cycle.
Typically a 2 hand anti tie down is a safety device, used to start a machine only when the operator is clear of the moving parts of the machine and the operator must keep both buttons depressed throughout the entire cycle. If either of the buttons are released the machine should immediately stop so that the operator is not injured if they try to reach into the machine. It's not critical for safety in my application, but I'd prefer if the program stopped and reset if the start buttons are released during the process. The process will have a couple of delays while valves are opening or closing, my understanding is that the only way to do something else while the microcontroller is processing a delay is to use an interrupt. I'll set a boolean variable to true in the ISR, each step in the main loop will have an if statement that will only be processed when that variable is false, the variable will be set back to false at the end of the main loop.
I don't see that one follows the other, but I assume from what you have said that you are more comfortable with hardware than software so it could be your preferred solution.
The interrupt will be looking for a falling state, I don't believe an interrupt will work with a switch that bounces because it would start the ISR while the switch is bouncing. Maybe there's a way to address this with software, but without being able to use delay or millis in the ISR I don't see how it would be possible. I'm definately more comfortable with hardware than software as well
Sample of code so far, it's heavily commented, possibly excessively commented, but because this is the first sketch I've written, and I've done it over several days I find myself forgetting why I used a particular line of code if I don't have some kind of comment:
//define input and output pins
const int LeftButtonPin = 2; // Left palm button attached to pin 2
const int RightButtonPin = 3; // Right palm button attached to pin 3
const int timerPot1 = 0; // potentiometer connected to pin A0
const int timerPot2 = 1; //potentimoeter connected to pin A1
const int SafetyRelay = 10; // Safety relay connected to pin 10
const int UpperRelay = 11; // Upper relay connected to pin 11
const int LowerRelay = 12; // Lower relay connected to pin 12
//define interrupts
int LeftButtonInterrupt = 0; //left button attached to interrupt 0, aka pin 2
int RightButtonInterrupt = 1; //right button attached to interrupt 1, aka pin 3
volatile boolean InterruptTriggered = false; //boolean value to bypass code in main loop if interrupt has been processed
//define miscellaneous variables
int RightButtonState = 0; // variables for reading the pushbutton status
int LeftButtonState = 0;
int PotVal1 = 0; //variables to store incoming value from pot
int PotVal2 = 0;
unsigned long LeftButtonPressTime = 0; //variables for 2 hand anti tie down logic
unsigned long RightButtonPressTime = 0;
byte CurrentLeftButtonState;
byte PreviousLeftButtonState;
byte CurrentRightButtonState;
byte PreviousRightButtonState;
boolean WaitingForRightButton = false;
void setup() {
//start serial communications
Serial.begin(9600);
//initialize button pins as input
pinMode (LeftButtonPin, INPUT);
pinMode (RightButtonPin, INPUT);
//initialize relay pins as output
pinMode (SafetyRelay, OUTPUT);
pinMode (UpperRelay, OUTPUT);
pinMode (LowerRelay, OUTPUT);
//attach interrupts
attachInterrupt(RightButtonInterrupt, Safety, FALLING); //Right button interrupt (interrupt 0 on pin 2) calls interrupt service routine "Safety" when input state is falling
attachInterrupt(LeftButtonInterrupt, Safety, FALLING); //Left button interrupt (interrupt 1 on pin 3) calls interrupt service routine "Safety" when input state is falling
//Both buttons call the same interrupt service routine
}
//interrupt service routine
void Safety()
{
Serial.println("palm switch released, resetting"); //prints debugging information in serial monitor
digitalWrite(UpperRelay, HIGH);
digitalWrite(LowerRelay, HIGH);
InterruptTriggered = true; //set variable true so that functions in the main loop won't run if interrupt is triggered
Serial.println("Interrupt trigger set to true"); //prints debugging information in serial monitor
}
void loop()
{
CurrentLeftButtonState = digitalRead(LeftButtonPin);
if (CurrentLeftButtonState == HIGH && PreviousLeftButtonState == LOW) //Detect that left button has changed from low to high
{
LeftButtonPressTime = millis();
Serial.print("Left button pressed at "); //print message in serial monitor for debugging
Serial.println(LeftButtonPressTime);
WaitingForRightButton = true; //set boolean variable for right button sequence
}
PreviousLeftButtonState = CurrentLeftButtonState; //set previous left button state to current left button state for next cyle
CurrentRightButtonState = digitalRead(RightButtonPin);
if (CurrentRightButtonState == HIGH && PreviousRightButtonState == LOW && WaitingForRightButton == true) //detect that right button has changed from low to high and right button has already been pressed
{
RightButtonPressTime = millis();
Serial.print("Right button pressed at "); //print message in serial monitor for debugging
Serial.println(RightButtonPressTime);
WaitingForRightButton = false; //change boolean variable back to false for next cycle
if (RightButtonPressTime - LeftButtonPressTime <= 500 && WaitingForRightButton == false) //compare left button press time and right button press time, if less than 500ms continue
{
Serial.println("Begin Cycle"); //print message in serial monitor for debugging
//Energize safety relay
Serial.print("Safety solenoid on");
digitalWrite (SafetyRelay, LOW); //relay on when output is LOW
//Energize upper relay
PotVal1 = analogRead(timerPot1); //read pot value and store as variable
if (InterruptTriggered = false) //proceed with this step if interrupt has not been triggered
{
digitalWrite (UpperRelay, LOW); //relay on when output is LOW
delay (PotVal1); //hold relay on time based on potentiometer value
}
if (InterruptTriggered = false) //proceed with this step if interrupt has not been triggered
{
digitalWrite (UpperRelay, HIGH); //relay off when value is HIGH
delay (PotVal1); //delay before starting next operation
}
//Energize lower relay
PotVal2 = analogRead (timerPot2); //read pot value and store as a variable
if (InterruptTriggered = false) //proceed with this step if interrupt has not been triggered
{
digitalWrite (LowerRelay, LOW); //relay on when output is LOW
delay (PotVal2); //hold relay on based on potentiometer value
}
if (InterruptTriggered = false) //proced with this step if interrupt has not been triggered
{
digitalWrite (LowerRelay, HIGH); //relay off when output HIGH
delay (PotVal2); //delay before starting next operation
}
//De-energize safety relay
delay(500); //wait for other solenoids to de-energize
Serial.println("safety solenoid off"); //print message in serial monitor for debugging
digitalWrite (SafetyRelay, HIGH); //de-energize safety solenoid
}
}
PreviousRightButtonState = CurrentRightButtonState;
InterruptTriggered = false;
Serial.println("interrupt trigger set to false");
Serial.println("Finished, return to start.");
}
I don't have an arduino to upload the code to yet, it should be coming next week and I'll find out what I did wrong.
I wouldn't use interrupts and I wouldn't rely on making the sketch or the Arduino reset in order to trigger the cut-off. I would just use the polling approach I outlined previously, and ensure that the loop did not execute any blocking code - this ensures that any 'stop' condition would be detected promptly.
PeterH:
I wouldn't use interrupts and I wouldn't rely on making the sketch or the Arduino reset in order to trigger the cut-off. I would just use the polling approach I outlined previously, and ensure that the loop did not execute any blocking code - this ensures that any 'stop' condition would be detected promptly.
I'll be honest your explanation went over my head as soon as you used the word array. I have no idea how to implement the solution you're suggesting, I have a feeling it's more technically correct, but I don't have the experience to do so. Everything I know about programming I've learned from reading the resources on this site over the last couple days, and a 1/2 semester class in Basic when I was in highschool 20 years ago.
Do I misunderstand the use of an interrupt? I thought an interrupt was intended to be used to process a critical piece of code when the processor may be busy processing a delay. Isn't that what I'm using the interrupt to do?
I don't belive I'm expecting the sketch or the Arduino to reset, although I may have used the word reset incorrectly in a previous post. All I want to happen is to recognize a falling input, and immediately reverse any outputs that may have been changed since the start of the loop then ignore any conditional statements after that point in the loop. My logic is, admittedly, very linear, which may not be the most efficent approach, but it's what makes most sense to me today.
PeterH:
I would put the button pins in an array, and use a for loop to process them both the same way:
Read the raw button state, apply debounce logic to get the debounced state, if the debounced state is changing from not pressed to pressed record the current time as the button press time; if it changes from pressed to not pressed set the button press time back to zero. Then iterate through the array of button press times to see how recently the other buttons last became pressed. If any button was pressed longer than half a second ago, your result state should be 'disabled'. If all button presses were within the last half second, the result state is 'enabled'.
All the data needed to implement the debouncing and state change detection and record the press time would be held in arrays.
Using arrays might seem like unnecessary complexity when you only have two items but will eliminate code duplication and the resulting risk of copy/paste errors between the implementation of the two buttons.
This method seems to me (granted without much thinking about the situation) an ideal technique, and will provide the OP an opportunity to learn about arrays because they are so valuable.
However, in this case I wouldn't expect a little switch bounce to cause a problem. The allowable time between the two buttons being pressed is so much larger than any expected bouncing, who cares if they bounce for a couple ms. Because they both finished bouncing at least 500ms ago it doesn't matter that they were each actually pressed 503ms and 511ms (numbers pulled out of my ass) before the process is allowed to start. This also gives about a half second delay after the operator thinks they pressed the buttons to release one (or both) because they just noticed that widget isn't aligned properly for the operation (or any other reason a last second reflex change of intent).
my understanding is that the only way to do something else while the microcontroller is processing a delay is to use an interrupt
That depends on how you implement the delay. If you use the obvious delay() function then an interrupt would be appropriate but that is not the only way to implement a delay. Take a look at the BlinkWithoutDelay example in the IDE to see how to create a delay that does not stop you doing other things such as polling a switch.
Basically you save the start time of the delay then periodically check whether the required period has elapsed. If not then go and do something else and come back and check again later.
Sembazuru:
The allowable time between the two buttons being pressed is so much larger than any expected bouncing, who cares if they bounce for a couple ms.
You may be right. My thought was that I don't know which way round the switches are being used and whether they're liable to bounce in the active or inactive state, and if they're bouncing in the active state it seemed like a bad idea to have the physical switch bounce cause the sketch output to also bounce. I don't know whether it matters, but it seems inelegant either way. When dealing with switches, I would deal with the issue of bounce at the boundary of the system as a matter of course unless there was a particular reason not to.
Sembazuru:
The allowable time between the two buttons being pressed is so much larger than any expected bouncing, who cares if they bounce for a couple ms.
You may be right. My thought was that I don't know which way round the switches are being used and whether they're liable to bounce in the active or inactive state, and if they're bouncing in the active state it seemed like a bad idea to have the physical switch bounce cause the sketch output to also bounce. I don't know whether it matters, but it seems inelegant either way. When dealing with switches, I would deal with the issue of bounce at the boundary of the system as a matter of course unless there was a particular reason not to.
True, I would consider it "best practices" to automatically count bad or worst case scenarios when coding unless good thought is spent deciding it can be skipped. In this particular case I see the array technique you outlined actually does the debouncing. It would take 500ms of both buttons continuously being high for the operation to start, but would shut down immediately. There are two gotchas that I can think of. While switches don't continue to "bounce" while being held, there may be some contact wiping that might add variable resistances (and might potentially cause a low state at one of the two pins). Also EMI might cause false lows as well. This would cause the operation to halt inappropriately. I don't know the OP's full intent of the project to know if this would be acceptable or not, but it would probably be annoying to say the least to have random half second pauses in the operation. Hmmm... I think I've talked myself back to your argument for individually debouncing the inputs prior to check to see if they are both pressed.
I decided to do a bit of googling and found this very informative pdf file where the author actually gathered empirical data about various switches and their bouncing properties. He also discusses both hardware and software debouncing pros, cons, and techniques.
It's a really good read IMHO. Might be an interesting exercise to convert one or both of his software debouncing methods to Arduino libraries. I'm not volunteering though, I have no experience doing libraries or sketches that muck with timer interrupts. (Both methods fire periodically on a timer interrupt to directly pole the pins, and then the rest of the user's program would just checksthe status state variables instead of the pins directly.)
Granted, coming from a hardware basis (a history of designing full circuits made of combinations 74xx series TTL chips), I'd probably just throw an RC (with schmitt buffer if my I/O pins aren't shmitt triggered already) debounce on the pins and save my uP clock cycles for actually doing what I want, instead of wasting clock cycles on interfacing digital logic to an analog world. All comes down to how one wants to skin their particular cat, and what resources (money vs. clock cycles) are more valuable.
here's the solution I worked up, it will only process startCycle() when both buttons are pressed within 500ms of each other, it doesn't matter which button is pressed first and both buttons have to be held down for at least 500ms. Before startCycle() will be processed again both buttons have to be released. startCycle() won't abort if the buttons are released as that process is running, I'll have to work that in later:
byte rightButtonPin = 2;
byte leftButtonPin = 3;
unsigned long leftTime;
unsigned long rightTime;
byte ledPin = 13;
int previousLeftButtonState = LOW;
int previousRightButtonState = LOW;
void setup(){
pinMode (rightButtonPin, INPUT);
pinMode (leftButtonPin, INPUT);
pinMode (ledPin, OUTPUT);
}
void loop(){
int currentRightButtonState = digitalRead(rightButtonPin);
int currentLeftButtonState = digitalRead(leftButtonPin);
if (currentLeftButtonState == HIGH && currentRightButtonState == LOW && previousLeftButtonState == LOW){ //make sure the left button has been released since last cycle
leftTime = millis();
}
if (currentRightButtonState == HIGH && currentLeftButtonState == LOW && previousRightButtonState == LOW){ //make sure the right button has been released since last cycle
rightTime = millis();
}
unsigned long delayTime = millis();
if ((((delayTime - rightTime) < 500) && currentLeftButtonState == HIGH && currentRightButtonState == HIGH) ||
(((delayTime - leftTime) < 500) && currentLeftButtonState == HIGH && currentRightButtonState == HIGH)){ //if both buttons were pressed within 500 milliseconds of each other start cyle
cycleStart();
}
previousRightButtonState = currentRightButtonState; //reset previous button state for next cycle
previousLeftButtonState = currentLeftButtonState;
}
void cycleStart(){
//do stuff here
digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);
}
Sorry, i know this topic is old. I was looking at this sketch, and as i understand, cycleStart is not activating before button release, is because, while buttons are pressed (delayTime - rightTime) < 500) is being calculated. Would like know, how to get cycleStart on button press. Ideas?
Would like know, how to get cycleStart on button press. Ideas?
I do not know what you mean by "get cycleStart" on button press. cycleStart() is a function that always exists. You don't need to "get" it. If you mean that you want to CALL cycleStart() when a switch is pressed, you have our permission to do just that.
Thanks for your reply. I am so jealous on you, talented arduino gurus, who can read and understand code as well as rude jokes on facebook. I will try to avoid asking anything here.
if (currLBStt == HIGH && currRBStt == LOW && prevLBStt == LOW){ //make sure the left button has been released since last cycle
leftTime = millis();
}
if (currRBStt == HIGH && currLBStt == LOW && prevRBStt == LOW){ //make sure the right button has been released since last cycle
rightTime = millis();
}
unsigned long delayTime = millis();
while ((((delayTime - rightTime) < 500) && currLBStt == HIGH && currRBStt == HIGH) ||
(((delayTime - leftTime) < 500) && currLBStt == HIGH && currRBStt == HIGH)){ //if both buttons were pressed within 500 milliseconds of each other start cyle
digitalWrite(ledPin, HIGH);
currRBStt = digitalRead(rightBPin);
currLBStt = digitalRead(leftBPin);
}
prevRBStt = currRBStt; //reset previous button state for next cycle
prevLBStt = currLBStt;
digitalWrite(ledPin, LOW);
}
I don't see the point of timing or any of that complexity.
Would two buttons wired in series not solve your problem?
Treat it as a single button in software. But both need to be pressed to 'press' it by completing the circuit. Who cares how long it takes to get them both pressed. Releasing either is a release.