I am trying to implement three different functions for one button in an Arduino project. Click, double click and hold.
I have to use interrupts and let the system sleep as much as possible, because the final product will have to run on a coin cell for a few months.
#include <Ports.h>
#include <RF12.h>
#include <avr/sleep.h>
#include <PinChangeInt.h>
#include <VirtualWire.h>
ISR(WDT_vect) { Sleepy::watchdogEvent(); }
char *controller;
const int buttonPin = 3;
bool stateSingle = false;
bool stateDouble = false;
bool stateLong = false;
void setup() {
pinMode(13, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(5, OUTPUT);
// vw_set_ptt_inverted(true);
// vw_set_tx_pin(12);
// vw_setup(4000);
//
Serial.begin(9600);
PCintPort::attachInterrupt(buttonPin, wakeUp, HIGH);
}
void wakeUp() {
}
void loop() {
cli();
int i = 0;
while (digitalRead(buttonPin) == HIGH) { // Wait until button is LOW, or has been high for more than 600ms
Sleepy::loseSomeTime(50);
if (i > 12)
break;
i++;
}
if (digitalRead(buttonPin) == HIGH)
longTapAction();
else {
i = 0;
while (digitalRead(buttonPin) == LOW) { // Wait for possible double press
Sleepy::loseSomeTime(50);
if (i > 8)
break;
i++;
}
if (digitalRead(buttonPin) == HIGH) {
doubleTapAction();
while (digitalRead(buttonPin) == HIGH)
Sleepy::loseSomeTime(50);
} else
singleTapAction();
}
}
void singleTapAction() {
stateSingle = !stateSingle;
digitalWrite(5, stateSingle ? HIGH : LOW);
sei();
Sleepy::powerDown();
}
void doubleTapAction() {
stateDouble = !stateDouble;
digitalWrite(6, stateDouble ? HIGH : LOW);
sei();
Sleepy::powerDown();
}
void longTapAction() {
stateLong = !stateLong;
digitalWrite(7, stateLong ? HIGH : LOW);
sei();
Sleepy::powerDown();
}
The problem is that this is not always correctly working.
Because I'm using interrupts, millis() inside void loop() is not reliable, for some reason.
For any double click, and for any hold action, the single click function also gets called. I suspect this is due to multiple interrupts firing, but I have no way to test this. Also, sometimes, the double click seems to need only one click. Is my thinking wrong, did I forget something?
If you are seeing singleTapAction and doubleTapAction triggering too often, the problem could be that your method doesn't really debounce the button inputs, meaning you may read spurious noise on any click as a single press or double press. E.G your first while loop will exit almost immediately if there is a noisy input, which makes the following behavior difficult to predict.
https://www.arduino.cc/en/Tutorial/Debounce
If you have a look at the linked example on the arduino site - a possible solution is to record the period of time an input has been present and ignore any inputs of less than a certain period. Modifying your code to do this could stop the spurious calls.
Related
I have a button connect to my arduino board and i want the serial monitor to display pressed when the button is pressed and released when the button is not pressed.
My problem is that i want it to be print only once but with my code it prints it non stop.
I already tried writing the code in void setup but i cant seem to make it work.
Does anyone have any suggestions?
I would really appreciate the help.
const int pinButton = 8;
void setup() {
pinMode(pinButton, INPUT);
Serial.begin(9600);
}
void loop() {
int stateButton = digitalRead(pinButton);
if(stateButton == 1) {
Serial.println("PRESSED");
} else {
Serial.println("RELEASED");
}
delay(20);
}
this is my first answer on stack overflow.
Anyway, the solution I suggest is to save previous state of the button in another variable, compare it to the new state, if they are diffrent you print the message, else you don't.
Here's a code example :
const int pinButton = 8;
int previous_state;
void setup() {
pinMode(pinButton, INPUT);
Serial.begin(9600);
previous_state = digitalRead(pinButton);
}
void loop() {
int new_state = digitalRead(pinButton);
if(new_state == 1 && previous_state==0) {
Serial.println("PRESSED");
} if(new_state == 0 && previous_state==1) {
Serial.println("RELEASED");
}
previous_state=new_state;
delay(20);
}
This is not the optimal solution, but it should work. Chek out interruptions on Arduino to see how to do it better.
I've just started tinkering with the arduino and i'm getting my head around the basics. I have a push button hooked up so so i get a serial print when it's pushed.
int button = 3;
void setup() {
Serial.begin(9600);
pinMode(button, INPUT_PULLUP);
}
void loop() {
if (digitalRead(button) == LOW) {
Serial.print("pressed\n");
}
}
Now when the button is pressed it'll print pressed a bunch until released. Now my next step is hook up an LED and I want to use the button as a toggle. Press it the first time, it'll come on, press it a second, it'll turn off. But this will run hundreds of times while the button is pressed. How do I get around this? Thanks
int led = 5;
int button = 3;
void setup() {
Serial.begin(9600);
pinMode(button, INPUT_PULLUP);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
}
void loop() {
if (digitalRead(button) == LOW) {
Serial.print("pressed\n");
toggleLed(led);
}
}
int toggleLed(int led){
if (digitalRead(led) == LOW) {
Serial.print("set on");
digitalWrite(led, HIGH);
} else {
Serial.print("set off");
digitalWrite(led, LOW);
}
}
There is an example code that comes with the Arduino IDE called the State Change Example. Study it. Basically you need to have a variable to remember the state of the button the last time you pressed it and you only react to changes in the button state. Instead of running your code anytime the button pin IS low, you run your code anytime the button pin changes from high to low.
currently am working on project to open a door with access code using arduino UNO and a servo motor. Normal operation requires entering access code using keypad which is working fine. Another option requires pressing a button that causes an interrupt to rotate the servo motor. My problem is my interrupt only works once and never works again. Plus how do i put the for-loop to rotate the servo motor inside the interrupt function with a delay. I know that is not possible but am calling another function that has the delayMicroseconds but all this is not working. Below is my implementation please help
#include <Keypad.h>
#include <LiquidCrystal.h>
#include <Servo.h>
Servo servo;
const int openButtonPin = 2;
void setup() {
// put your setup code here, to run once:
servo.attach(5);
pinMode(openButtonPin, INPUT); //Pin 2 is input
attachInterrupt(0, enforceOpenAccess, HIGH); // PIN 2
}
void(* resetFunc)(void) = 0;
void loop()
{
//My other keypad implementations go here
}
void myDelay(int x) // function to cause delay in the interrupt
{
for(int i = 0; i<x; i++)
{
delayMicroseconds(1000);
}
}
void enforceOpenAccess() // ISR
{
for(int k =0; k<=180; k+=2)
{
servo.write(k); //rotate the servo
myDelay(30); //delay the rotation of the servo
}
}
The code above is run on arduino UNO being simulated in proteus and the interrupt button is a push button. Please if there is other ways of implementing that but with the same behaviour as I have described above help out. Thanks a lot
There are a couple of problems in the slice of code you posted. Just for completeness, you should post the loop function, since we can't guess what you wrote inside.
Just one comment: did you put a pullup? Otherwise use INPUT_PULLUP instead of INPUT for the button pinmode.
The main one is that you attached the interrupt for the HIGH mode, which will trigger the interrupt any time the pin is up, not on the rising edge. And please use the macro digitalPinToInterrupt to map to the correct pin:
attachInterrupt(digitalPinToInterrupt(openButtonPin), enforceOpenAccess, RISING);
Then.. Let's improve the code. You really should use the interrupts only when strictly necessary when you have to respond IMMEDIATELY (= less than a couple of milliseconds) to an input. Here you don't have to, so it's MUCH better to check for the button in the loop (more on turning the motor following)
uint8_t lastState;
void setup()
{
...
lastState = LOW;
}
void loop()
{
uint8_t currentState = digitalRead(openButtonPin);
if ((currentState != lastState) && (currentState == HIGH))
{
// Start turning the motor
}
lastState = currentState;
...
}
This will enable you to properly debounce the button too:
#include <Bounce2.h>
Bounce debouncer = Bounce();
void setup()
{
...
pinMode(openButtonPin, INPUT); //Pin 2 is input
debouncer.attach(openButtonPin);
debouncer.interval(5); // interval in ms
}
void loop()
{
debouncer.update();
if (debouncer.rose())
{
// Start turning the motor
}
...
}
If, on the other way, you REALLY want to use the interrupts (because waiting for a couple of milliseconds is too much for you), you should do something like this:
#include <Bounce2.h>
Bounce debouncer = Bounce();
void setup()
{
...
pinMode(openButtonPin, INPUT);
attachInterrupt(digitalPinToInterrupt(openButtonPin), enforceOpenAccess, RISING);
}
void loop()
{
...
}
void enforceOpenAccess() // ISR
{
// Start turning the motor
}
It looks like your code? No, because now we'll speak about turning the motor
You should NOT use delays to make steps, because otherwise you will wait for 30ms * 180 steps = 5.4s before being able to do anything else.
You can, however, make a sort of reduced state machine. You want your servo to move from 0 to 180 in steps of 1. So let's code the "don't move" state with any value greater than 180, and consequently we can do something like this in the loop:
unsigned long lastServoTime;
uint8_t servoPosition = 255;
const int timeBetweenSteps_in_ms = 30;
void loop()
{
...
if (servoPosition <= 180)
{ // servo should move
if ((millis() - lastServoTime) >= timeBetweenSteps_in_ms)
{
lastServoTime += timeBetweenSteps_in_ms;
servoPosition++;
if (servoPosition <= 180)
servo.write(servoPosition);
}
}
}
Then, using any of the previous examples, instead of // Start turning the motor write
lastServoTime = millis();
servoPosition = 0;
servo.write(servoPosition);
This way you won't block the main loop even when the button is pressed
This is what is in my loop()
char key = keypad.getKey();
if(key)
{
if(j < 10)
{
studentNumber[j] = key;
//holdMaskedNumber[j] = '*';
lcd.setCursor(0,2);
lcd.print(String(studentNumber));
if(j == 9)
{
studentNumber[9] = '\0';
//holdMaskedNumber[9] = 0;
lcd.clear();
//String number = String(studentNumber);
//lcd.print(number);
//delay(1000);
//lcd.clear();
lcd.print("Access Code");
}
j++;
}
else
{
if(i < 5)
{
accessCode[i] = key;
holdMaskedCode[i] = '*';
lcd.setCursor(1,2);
lcd.print(String(holdMaskedCode));
if(i == 4)
{
holdMaskedCode[5] = '\0';
accessCode[5] = '\0';
//lcd.clear();
//lcd.setCursor(0,0);
//accessCodeString = String(accessCode);
//lcd.print(accessCodeString);
//delay(1000);
lcd.clear();
for(int i =0; i<6; i++)
{
lcd.print("Please wait.");
delay(500);
lcd.clear();
lcd.print("Please wait..");
delay(500);
lcd.clear();
lcd.print("Please wait...");
delay(500);
lcd.clear();
}
digitalWrite(4, HIGH);
lcd.print("Access Granted");
for(int k =0; k<=180; k+=2)
{
servo.write(k);
delay(30);
}
resetFunc();
}
i++;
}
}
}
I was using Arduino but I encountered problems with my code. I want a button to illuminate certain lights, and every time you press it, the lights will illuminate in a different pattern. Right now, the code does not keep track of how many times the button has been pressed. I would really appreciate any help!
const int buttonPin = 4; // pushbutton 1 pin
int ledPins[] = {5,6,7,8,9,10};
int buttonCounter = 0;
int buttonState = digitalRead(buttonPin);
void setup(){
pinMode(buttonPin, INPUT);
Serial.begin(9600);
// The for loop refers to each pin number
// by their position in the array
for(int i = 0; i < 6; i++){
pinMode(ledPins[i], OUTPUT);
}
}
int countButtonPresses()
{
if (buttonState == 0)
{
buttonCounter = buttonCounter + 1;
}
return buttonCounter;
}
void loop(){
int displayTime = 800;
int pattern = countButtonPresses();
Serial.println(pattern);
switch(pattern) {
case 1:
for(int i = 0; i <=5; i++){
digitalWrite(ledPins[i], HIGH);
delay(displayTime);
digitalWrite(ledPins[i], LOW);
}
break;
case 2:
for(int i = 0; i <=5; i += 2){
digitalWrite(ledPins[i], HIGH);
delay(displayTime);
digitalWrite(ledPins[i], LOW);
}
break;
default:
Serial.println(countButtonPresses());
}
}
In my opinion, the problem is that you don't have a clear idea of what you want to do :-)
The first error is logical: you cannot count the number of button presses, because when you have pressed the button once, the program returns "one", and executes the pattern one. The next time you press the button, the program returns "one" again, and so on. So, you have to change your logic (for example, when you press the first time, execute pattern one, when you press the second time, execute the pattern two).
The second error is the handling of the button press. Assuming that your button is pressed if it returns zero (we say "active low"), probably your program will count a few dozens of times before you release it. The cause of this is in the fact you don't have "debounced" it.
The "bouncing" effect is that your Arduino is faster than you, and if you press a button for a tenth of a second, Arduino loops several times finding the button pressed and counting many "presses".
To avoid this, when you find it pressed (buttonstate = 0), you must perform the illumination task, and wait until buttonstate becomes != 0. Also you must move the instruction "buttonstate = digitalRead(buttonPin);" within the loop() function in order to read continuously the status of the button.
I "dumped" some ideas to make it work; try to write it better, and if there are problems, ask again.
I modified your code a bit. I added a simple interrupt which modifies the count no matter where your code pointer is currently in (this will enable you to store number of presses even if you are inside a loop).
int buttonPin = 2; // pushbutton 1 pin
int ledPins[] = {5,6,7,8,9,10};
volatile int buttonCounter = 0;
int buttonState = 0;
void countButtonPresses();
void ExecuteIllumination(int ledPins[6],int DelayTime,int increments);
void setup(){
pinMode(buttonPin, INPUT);
Serial.begin(9600);
// The for loop refers to each pin number
// by their position in the array
for(int i = 0; i < 6; i++){
pinMode(ledPins[i], OUTPUT);
}
//Here is much better
buttonState=digitalRead(buttonPin);
//Attach interrupt to button pin
attachInterrupt(0,countButtonPresses,FALLING);
}
void loop(){
int displayTime = 800;
Serial.println(buttonCounter);
switch(buttonCounter) {
case 1:
ExecuteIllumination(ledPins,displayTime,1);
break;
case 2:
ExecuteIllumination(ledPins,displayTime,2);
break;
}
}
void countButtonPresses()
{
buttonCounter++;
// modify this condition with number of patterns
if (buttonCounter>2)
buttonCounter=1;
}
void ExecuteIllumination(int ledPins[6],int DelayTime,int increments)
{
for(int i = 0; i <=5; i += increments){
digitalWrite(ledPins[i], HIGH);
delay(DelayTime);
digitalWrite(ledPins[i], LOW);
}
}
You may want to pay attention to bouncing effect of button:
Button debouncing
I've got a stepper motor that I'm using to power a lift on an automated scanner (if you're interested in getting a larger description of it's full functionality I'm happy to oblige).
Anyways, the problem I'm having currently is that when the lift gets to the top of the scanner it pushes a button that triggers the camera to take a picture, and should then lower and repeat the process.
However, the problem is that once the motor triggers the button it seems to lose track of where it was, and instead of going to the preset distance I had constructed for it either stays in place, or lowers a small fraction of the intended distance.
My thoughts on the matter would be that I believe it to either be a problem with my code and the way I'm controlling the button, or a problem of needing to debounce the button so that I'm reading it as a constant value.
The code is as follows:
//Global Variables
//-----------------------------------------------------------------------------
const int BUTTON_PIN = 4; // number of the pushbutton pin
const int CAMERA_SHOOT_PIN = 2; // Pin that controls the shoot function
const int MOTOR_DIRECTION_PIN = 8;
const int MOTOR_STEP_PIN = 9;
//Function Prototypes
//-----------------------------------------------------------------------------
//If the red button is press, the camera will take a shot
void checkIfButtonIsPressedAndTakePictureWithCamera();
//Moves platform tray down lead screw down to zero???
void moveDown(int clicks);
//Moves platform tray up lead screw up to "maxDistance"
void moveUp(int clicks);
//Presses the camera's shoot button and takes a picture
void shootCamera();
//Steps the motor one click
void stepMotorOneClick();
//Steps the motor N clicks
void stepMotorNClicks(int n);
//Changes the current motor direction clcokwise
void toggleDirectionClockwise();
//Changes the current motor direction counterclockwise
void toggleDirectionCounterClockwise();
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//Called once when the Arduino is powered up or reset button is hit
/*****************************************************************************/
void setup()
/*****************************************************************************/
{
Serial.begin(9600); //Initializes serial port at baud rate of 9600 bps
pinMode(BUTTON_PIN, INPUT); //set that the button is an input
pinMode(CAMERA_SHOOT_PIN, OUTPUT); // set the pin that controls the shoot function
//Setup motor pins
pinMode(MOTOR_DIRECTION_PIN, OUTPUT);
pinMode(MOTOR_STEP_PIN, OUTPUT);
digitalWrite(MOTOR_DIRECTION_PIN, LOW);
digitalWrite(MOTOR_STEP_PIN, LOW);
//moveUp(3600);
}
int clicks = 0;
int moveDirection = 1;
//Called over and over again after setup() executes
/*****************************************************************************/
void loop()
/*****************************************************************************/
{
clicks = clicks + 1;
if(clicks > 7000)
{
moveDirection = -moveDirection;
clicks = 0;
}
switch(moveDirection)
{
case 1: moveUp(1); break;
case -1: moveDown(1); break;
case 0: break;
default: break;
};
checkIfButtonIsPressedAndTakePictureWithCamera();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//Function Implemented
//-----------------------------------------------------------------------------
/*****************************************************************************/
void checkIfButtonIsPressedAndTakePictureWithCamera()
/*****************************************************************************/
{
if (digitalRead(BUTTON_PIN) == HIGH) //If the button is pressed, run through the my functions
{
//START CRAPPY HACK
if (moveDirection == 0)
moveDirection = 1;
else
{
moveDirection = 0;
shootCamera();
moveDirection=-1;
}
//END CRAPPY HACK
}
}
/*****************************************************************************/
void toggleDirectionClockwise()
/*****************************************************************************/
{
digitalWrite(8, LOW);
}
/*****************************************************************************/
void toggleDirectionCounterClockwise()
/*****************************************************************************/
{
digitalWrite(8, HIGH);
}
/*****************************************************************************/
void stepMotorOneClick()
/*****************************************************************************/
{
digitalWrite(MOTOR_STEP_PIN, HIGH);
delayMicroseconds(40);
digitalWrite(MOTOR_STEP_PIN, LOW);
delayMicroseconds(40);
}
/*****************************************************************************/
void stepMotorNClicks(int n)
/*****************************************************************************/
{
for(int c = 0; c < n; c++)
stepMotorOneClick();
}
/*****************************************************************************/
void moveDown(int clicks)
/*****************************************************************************/
{
//counterclock
toggleDirectionCounterClockwise();
stepMotorNClicks(clicks);
}
/*****************************************************************************/
void moveUp(int clicks)
/*****************************************************************************/
{
//clockwise
toggleDirectionClockwise();
stepMotorNClicks(clicks);
}
/*****************************************************************************/
void shootCamera()
/*****************************************************************************/
{
digitalWrite(CAMERA_SHOOT_PIN,HIGH); //SHOOT
delay(500);
digitalWrite(CAMERA_SHOOT_PIN,LOW);
delay(1);
}
When it reaches the top and presses the button, your code takes a picture with the camera (0.5 seconds), then sets the direction to move down - but doesn't actually move down. So in the next loop, the direction is down so it moves one step down, but the button may still be pressed because one step is so small. So it takes another picture (0.5 seconds), etc etc.
It ends up taking several pictures at the top because the switch is held down.
You may want to move the system down some steps after a picture is taken. Or do not take a picture of the direction is down.
void checkIfButtonIsPressedAndTakePictureWithCamera()
/*****************************************************************************/
{
if (digitalRead(BUTTON_PIN) == HIGH) //If the button is pressed, run through the my functions
{
//START CRAPPY HACK
if (moveDirection == 0)
moveDirection = 1;
else if(moveDirection != -1) //If it's not moving down already
{
moveDirection = 0;
shootCamera();
moveDirection=-1;
}
//END CRAPPY HACK
}
}