I'm working on a project where I need to run 3 24v DC motors for 30 seconds when a button is pressed. I am using Pololu motor drivers (Link: Pololu - DRV8256E Single Brushed DC Motor Driver Carrier) to drive the motors and an arcade button from Adafruit. I have everything wired correctly however I'm running into some issue with my code. I was initially using this code which uses a delay:
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
void setup() {
pinMode(motorPin, OUTPUT); //Set motorPin as an Output
pinMode(buttonPin, INPUT); //Set buttonPin as an Input
}
void loop() {
if (digitalRead (buttonPin) == HIGH){ //When button is pressed
digitalWrite (motorPin, HIGH);
delay(30000); //run motor for 30000ms (3s)
}
else {
digitalWrite(motorPin, LOW); //otherwise turn motor off
}
}
But I was having issues with what I believe was button debounce — after the initial 30 seconds the motor would trigger again immediately without and additional button press. To solve this I switched to a modified version of the code I found in this post: Making somthing run for X amount of time - #6 by Blackfin.
const uint32_t K_MAXIMUM_DURATION = 30000ul; //mS 30-sec duration motors will run
const uint32_t K_PAUSE_DURATION = 2000ul; //mS 2-sec pause after motors stop before allowing them to start again
const uint32_t K_BTN_READ_TIME = 20ul; //mS 20mS button read period
const uint8_t pinButton = 2; //button input (wire so press grounds button; no-press is open circuit
const uint8_t pinMotor1 = 9; //motor 1
const uint8_t pinLED = LED_BUILTIN; //"ready" light
uint8_t
lastButton;
//button press return values
typedef enum {
NO_BUTTON = 0, //no button state change
BTN_PRESSED, //button went from not-pressed to pressed
BTN_RELEASED //button went from pressed to not-pressed
} eButtonStates_t;
//motor control states
typedef enum {
ST_INIT = 0, //initialize motors to "off"
ST_IDLE, //waiting for button press
ST_RUNNING, //motors running; waiting for button release or timeout
ST_PAUSE //after-run pause before re-enabling
} eMotorStates_t;
void setup(void) {
Serial.begin(115200); //debug/information messages
pinMode(pinMotor1, OUTPUT);
pinMode(pinLED, OUTPUT);
digitalWrite(pinLED, HIGH); //turn on LED indicating "ready"
//
pinMode(pinButton, INPUT_PULLUP);
lastButton = digitalRead(pinButton);
} //setup
void loop(void) {
MotorControl();
} //loop
uint8_t chkButton(void) {
static uint32_t
timeButton = 0ul;
uint32_t
timeNow;
//read the button at periodic intervals (once every K_BTN_READ_TIME milliseconds)
timeNow = millis();
if ((timeNow - timeButton) < K_BTN_READ_TIME)
return NO_BUTTON; //if not time for a read just return "no button"
timeButton = timeNow;
uint8_t nowButton = digitalRead(pinButton); //read the state of the button now
//different than last read?
if (nowButton != lastButton) {
//this read wasn't the same as the last
lastButton = nowButton;
if (nowButton == LOW) {
//if LOW now the button has been pressed
Serial.println("Button pressed");
return BTN_PRESSED;
} //if
else {
//if HIGH now button has been released
Serial.println("Button released");
return BTN_RELEASED;
} //else
} //if
//if we get here, button has not changed state so return "no button"
return NO_BUTTON;
} //chkButton
void MotorControl(void) {
static uint8_t
stateMotor = ST_INIT;
static uint32_t
timeMotor;
uint32_t timeNow;
uint8_t
btnCond;
timeNow = millis(); //get current millis count
btnCond = chkButton(); //and check the button
switch (stateMotor) {
case ST_INIT:
//initial state; turn off the motors and proceed to idle
digitalWrite(pinMotor1, LOW);
stateMotor = ST_IDLE;
break;
case ST_IDLE:
if (btnCond == BTN_PRESSED) {
//if we detect button pressed, start motors
digitalWrite(pinMotor1, HIGH);
//turn OFF the LED to indicate system is busy
digitalWrite(pinLED, LOW);
//save this time so we can measure the run duration
timeMotor = timeNow;
Serial.println("Motors running");
stateMotor = ST_RUNNING;
} //if
break;
case ST_RUNNING:
if ((btnCond == BTN_RELEASED) || ((timeNow - timeMotor) >= K_MAXIMUM_DURATION)) {
//button was released or we saw a time-out; turn off the motors
digitalWrite(pinMotor1, LOW);
//save the time so we can measure the pause duration
timeMotor = timeNow;
Serial.println("Motors halted");
stateMotor = ST_PAUSE;
} //if
break;
case ST_PAUSE:
//pause after running the motors
if (timeNow - timeMotor >= K_PAUSE_DURATION) {
//when pause is complete, turn on the LED indicating
//ready for the another dispense
Serial.println("Ready");
digitalWrite(pinLED, HIGH);
//and return to IDLE state
stateMotor = ST_IDLE;
} //if
break;
} //switch
} //MotorControl
It solved the motor triggering issue, however now I'm not getting the full 30 seconds run time. Sometimes it seems to work and other times it will run for shorter periods. I'm not exactly sure what's going on, but I suspect it may have something to do with the timeout. I've poured over the code but everything seems to be right. Is there something I'm missing?
button switches are typically connected between the pin and ground with the pin configured as INPUT_PULL, enabling the internal pullup resistor which pulls the pin HIGH and pressing the button pulls the pin the ground (LOW)
consider
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
void setup() {
pinMode(motorPin, OUTPUT); //Set motorPin as an Output
pinMode(buttonPin, INPUT_PULLUP); //Set buttonPin as an Input
}
void loop() {
if (digitalRead (buttonPin) == LOW) { //When button is pressed
digitalWrite (motorPin, HIGH);
delay(30000); //run motor for 30000ms (3s)
digitalWrite(motorPin, LOW); //otherwise turn motor off
}
}
bool debounce() {
static uint16_t state = 0;
state = (state<<1) | digitalRead(btn) | 0xfe00;
return (state == 0xff00);
}
into the code you posted. He mentions that the button needs to be connected between a GPIO and ground with the GPIO setup using INPUT_PULLUP. So maybe something like this?
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
bool debounce() {
static uint16_t state = 0;
state = (state << 1) | digitalRead(buttonPin) | 0xfe00;
return (state == 0xff00);
}
void setup() {
pinMode(motorPin, OUTPUT); //Set motorPin as an Output
pinMode(buttonPin, INPUT_PULLUP); //Set buttonPin as an Input
}
void loop() {
if (debounce()) {
digitalWrite(motorPin, !digitalRead(motorPin));
delay(30000);
}
}
I'm pretty new to this so I'd love some help understanding if I put the debounce code in the correct place and if I did if I used the debounce command correctly. Thanks!
Yes, I did. When I tried it immediately started running my motor but the motor wouldn't stop. If my initial code didn't need debounce I'm wondering what my issue was. Why would my motor run for the delay time but then immediately start again without a button press?
The forum can be a source of great help to learn the details that are needed to make a microcontrollerproject work the way you want it to. My offer is to point to that things that will "teach you fishing" to beeing able to find bugs yourself.
Your first approach to get the needed functionality seems to be like
"I google for the cheatcode to enter platinum-level of the game"
This works well for a simple cheatcode. But as you have encountered is does not work for a microcontroller. You encountered problems. This problems are caused by a lack of knowledge.
One important thing to know is that even such a simple thing as a button needs a little bit knowledge how to connect the button to wires to make the button work reliable.
A wire to an input without a pullup-resistor is an antenna for electromagnetic noise.
clear conditions through a pull-up resistor:
A microcontroller inputpin is very sensistive. Even the all over electromagnetic noise in the air can make the input-pin switch randomly between LOW and HIGH.
A pullup-resistor makes the input-pin less sensitive.
Result: clear detection of button unpressed / button pressed.
That is the recommendation already given. In combination with the pinMode INPUT_PULLUP the button is wired between input-pin and ground.
If the button is used with INPUT_PULLUP and the button wired between input-pin and ground.
If button is pressed the logic level of the input-pin is LOW
a second important thing is to learn how to make visible what your code is doing.
This is done with the serial monitor.
At this point I need some input from you. Do you know what the serial monitor is and have you used the serial monitor already or not?
Hey Stefan, thank you for your detailed answer. Sorry for my slow reply. I've been swamped so haven't had time to respond until now. I understand the need to have a resistor between the input pin and the I previously had a resistor on my breadboard which is why I was using "pinMode(buttonPin, INPUT)". After understanding this post more and reading up on INPUT_PULLUP I simplified my wiring and I'm now using the internal pullup resistor.
This is the code I'm now attempting to use:
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
void setup() {
pinMode(motorPin, OUTPUT); //Set motorPin as an Output
pinMode(buttonPin, INPUT_PULLUP); //Set buttonPin as an Input
}
void loop() {
if (digitalRead(buttonPin) == LOW) { //When button is pressed
digitalWrite(motorPin, HIGH);
delay(3000); //run motor for 30000ms (3s)
digitalWrite(motorPin, LOW); //otherwise turn motor off
}
}
When I press my button it runs the motor, but the motor doesn't stop spinning after 3000ms.
I tried to use the serial monitor to understand what's happening, and it printed "1" then after another 3 seconds printed "1" again, so I suspect the button is somehow continuously being triggered. Admittedly, I don't fully understand how to implement the serial monitor so that information could be misleading.
Should I be using some other syntax instead of "digitalWrite(motorPin, LOW);" after my delay?
Here is the code I used when trying to use the serial monitor:
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
void setup() {
pinMode(motorPin, OUTPUT); //Set motorPin as an Output
pinMode(buttonPin, INPUT_PULLUP); //Set buttonPin as an Input
Serial.begin(9600); //Start serial monitor
}
void loop() {
if (digitalRead(buttonPin) == LOW) { //When button is pressed
digitalWrite(motorPin, HIGH);
Serial.println(digitalRead (motorPin), DEC);
delay(3000); //run motor for 30000ms (3s)
digitalWrite(motorPin, LOW); //otherwise turn motor off
}
}
whenever something in the code is not timecritical that things must be executed superfast
you can add as much serial output as you want.
This makes visible what your code is really doing and what values variables really have
and what state IO-Pins really have
the code below has three macros for comfortable debug-output,
at first concentrate on how to use the macros
dbg("my text",variable); // prints everytime
dbgi("my text",variable,1234); / prints only once every 1234 milliseconds
dbgc("my text",variable); // prints only once on every CHANGE of the value of the variable
// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298
#define dbg(myFixedText, variableName) \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope
#define dbgi(myFixedText, variableName,timeInterval) \
do { \
static unsigned long intervalStartTime; \
if ( millis() - intervalStartTime >= timeInterval ){ \
intervalStartTime = millis(); \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName); \
} \
} while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// print only once when value has CHANGED
#define dbgc(myFixedText, variableName) \
do { \
static long lastState; \
if ( lastState != variableName ){ \
Serial.print( F(#myFixedText " " #variableName" changed from ") ); \
Serial.print(lastState); \
Serial.print( F(" to ") ); \
Serial.println(variableName); \
lastState = variableName; \
} \
} while (false);
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("Setup-Start");
pinMode(motorPin, OUTPUT); //Set motorPin as an Output
pinMode(buttonPin, INPUT_PULLUP); //Set buttonPin as an Input
dbg("setup",digitalRead(motorPin) );
dbg("setup",digitalRead(buttonPin) );
}
void loop() {
// dbgi: print only ONCE every 1002 milliseconds
dbgi("top of loop",digitalRead(buttonPin),1002);
if (digitalRead(buttonPin) == LOW) { //When button is pressed
dbg("if is true",digitalRead(buttonPin) );
dbg("right before motorpin HIGH",digitalRead(motorPin) );
digitalWrite(motorPin, HIGH);
delay(3000); //run motor for 30000ms (3s)
dbg("right before motorpin LOW",digitalRead(motorPin) );
digitalWrite(motorPin, LOW); //otherwise turn motor off
dbg("right before motorpin LOW",digitalRead(motorPin) );
}
}
Thank you this worked! My motor is now running as intended, when I press the button it runs for 3 seconds then stops. I'm using motor drivers and ultimately decided to use "analogWrite" so I could slow them down a little using PWM.
Here is the code I'm running:
int motorPin = 9; //Define motor input pin
int buttonPin = 2; //Define button input pin
void setup() {
Serial.begin(9600);
delay(1000);
Serial.println("Setup-Start");
pinMode(motorPin, OUTPUT); //Set motorPin as an Output.
pinMode(buttonPin, INPUT_PULLUP); //Set buttonPin as an Input.
}
void loop() {
byte but = digitalRead(buttonPin);
Serial.print("but ");
Serial.println(but);
if (but == LOW) { // *** LOW replaces HIGH
analogWrite(motorPin, 170); //start motor at 75% speed
delay(3000); //delay (run motor) for 3000ms (3s).
analogWrite(motorPin, 0); //stop motor
}
}
Can you explain what the "byte but = digitalRead(buttonPin)" command did to prevent my button from triggering more than once? I'm seeing in the serial monitor that its continuously writing 1 until I press the button at which point it writes 0 which triggers the motor. But I don't fully understand how that command works.
Thank you for the debugging code! My button was indeed triggering immediately after the first press. I'm not exactly sure why, I tried another button and it didn't have the same issue. ie. I pressed it once and the motor spun then stopped.
It is easy to understand and has a good mixture between explaining important concepts and example-codes to get you going. So give it a try and report your opinion about this tutorial.