For a project I'm using an Arduino Mega2560 to control a couple of stepper motors with the AccelStepper library. It's the first time I'm using Arduino and I'm writing everything in Arduino IDE 2.0 (I also tried it with IDE 1.8 but no change). It's coming along quite nicely and it's a lot of fun! Everything seems to work as intended. Except for 1 thing.
I'm using 4 buttons to trigger certain events in my code, using the interrupt function. I'm doing this because the accelStepper library asks a lot of proccesing from the arduino and I need as many steps/sec as I can get. When 1 of the interrupts is activated it somehow calls another with it. I've removed everything from the code except the interrupt part to see if just the interrupts would work, but I still have the same issue. With this code I would expect to see a print in the serial of the button I pressed. However, when I press button one it sometimes prints both button 1 and button 2. When I press button 2 it always calls button 1 as well. Sometimes button 1 is printed first, sometimes button 2 is printed first. When I press button 3, button 2 is always called and when I press button 4, button 3 is always called.
As you can see in the code, I'm using port 18 through 21 as interrupt ports and their grounds are all connected to the same GND port on the Arduino. I tried disconnecting all the grounds and only connected button 1 with the Arduino GND. But somehow it still triggers button 2 as long as the cable of the button is still plugged into pin 19. Even though the ground of button 2 is not connected.
What am I doing wrong? Did I misunderstand how the interrupts with input_pullup works? Or am I doing something wrong in my code?
I'm running out of ideas so any help is appreciated!
// Define pins numbers
const int button1 = 18;
const int button2 = 19;
const int button3 = 20;
const int button4 = 21;
// Define variables
volatile bool button1Pressed = false;
volatile bool button2Pressed = false;
volatile bool button3Pressed = false;
volatile bool button4Pressed = false;
float timeTrigger1 = 0;
float timeTrigger2 = 0;
float timeTrigger3 = 0;
float timeTrigger4 = 0;
void setup() {
Serial.begin(9600);
Serial.println("Startup begins");
pinMode(button1, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button1), interrupt1, FALLING);
pinMode(button2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button2), interrupt2, FALLING);
pinMode(button3, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button3), interrupt3, FALLING);
pinMode(button4, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button4), interrupt4, FALLING);
Serial.println("Startup finished");
}
void loop() {
if (button1Pressed) {
Serial.println("button 1 was pressed");
button1Pressed = false;
}
if (button2Pressed) {
Serial.println("button 2 was pressed");
button2Pressed = false;
}
if (button3Pressed) {
Serial.println("button 3 was pressed");
button3Pressed = false;
}
if (button4Pressed) {
Serial.println("button 4 was pressed");
button4Pressed = false;
}
}
void interrupt1() {
if (millis() - timeTrigger1 >= 500) {
timeTrigger1 = millis();
button1Pressed = true;
}
}
void interrupt2() {
if (millis() - timeTrigger2 >= 500) {
timeTrigger2 = millis();
button2Pressed = true;
}
}
void interrupt3() {
if (millis() - timeTrigger3 >= 500) {
timeTrigger3 = millis();
button3Pressed = true;
}
}
void interrupt4() {
if (millis() - timeTrigger4 >= 500) {
timeTrigger4 = millis();
button4Pressed = true;
}
}
EDIT: checking the button state once the ISR is called did the trick. Like the code below.
void interrupt1() {
if (millis() - timeTrigger1 >= 500 && digitalRead(button1)==LOW) {
timeTrigger1 = millis();
button1Pressed = true;
}
}
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.
a few days ago I started working with the Arduino. I've set up a small project with a DHT22 to read the temperature and humidity and write it to an LCD. That works without a problem. Now I want to only turn on the backlight of the LCD when I press a button. That mostly works too:
void loop() {
buttonState = digitalRead(BUTTONPIN);
currentMillisScreen = millis();
if (buttonState == HIGH) {
screenOn = true;
lcd.backlight();
}
// DHT22 related code in here
if (currentMillisScreen - previousMillisScreen >= SCREEN_ON_TIME) {
previousMillisScreen = currentMillisScreen;
screenOn = false;
lcd.noBacklight();
}
}
The problem is that with this code is that the Backlight won't always stay on for exactly 5 seconds. I thought putting the currentMillisScreen = millis() in the following if-Statement would fix it:
if (buttonState == HIGH) {
currentMillisScreen = millis();
screenOn = true;
lcd.backlight();
}
But if I do that, the Backlight won't turn off again and I don't understand why.
You are not updating currentMillisScreen in the loop and that is your problem. You just need to find different between currentTime (equal to millis()) and previous time that light turned on and if it reaches above the threshold then turn it off. Something like this:
#define SCREEN_ON_TIME 5000
bool screenOn = false;
void setup()
{
//setup
}
void loop()
{
buttonState = digitalRead(BUTTONPIN);
if (buttonState == HIGH)
{
previousMillisScreen = millis();
lcd.backlight();
screenOn = true;
}
// DHT22 related code in here
// if time passed above SCREEN_ON_TIME after we turned on light
if ((screenOn) && (millis() - previousMillisScreen >= SCREEN_ON_TIME))
{
lcd.noBacklight();
screenOn = false;
}
}
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 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.
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
}
}