I came up with a function to pulse a digital output:
void pulseOutLow(int pin, long duration){ // pulses a digital output for a certain length of time, meant for active low relays
unsigned long timeStart = millis();
boolean exitThis = false;
digitalWrite(pin, LOW);
while(!exitThis){
if(millis() >= (timeStart + duration)){
digitalWrite(pin, HIGH);
exitThis = true;
} // end if
} // end while()
} // end pulseOutLow
However, I notice it seem to work about the same as delay(). What I mean is that if I do something like:
pulseOutLow(3, 500);
pulseOutLow(4, 500);
The first pin pulses for half a second, then the second pin pulses for half a second, much like if I would have just used delay()s and digitalWrite()s. What I would like to happen is that the events fire in that order, but practically at the same time and then later they both turn off at practically the same time. It seems to me the while() loop is keeping execution there as opposed to executing other code.
Does that happen because the Arduino is single threaded? Did I just reinvent delay()? My intent was to make a pulse function that I could call from within the main loop(). I want to make it a function rather than a copy/paste block of code for ease of use and elegance. I have been able to make a timer of sorts using
if (millis() == (whatTimeIsIt + idleEngineKill) && (!firstTimeOn) ){ // if 5 seconds has passed since entering the ready state, kill the motor and engage the brakes
engineKill();
brakesEngage();
//timeOut = true;
} // end test for timeOut
But I would prefer to make it a function.
Total source of what I am working on:
#include <ServoDecode.h>
char * stateStrings[] = {"NOT_SYNCHED", "ACQUIRING", "READY", "in Failsafe"};
// Define readable non-backwards states for relays.
// This is because relays are active LOW. That means you can ground the pin and activate the relay.
const byte relayOn = 0;
const byte relayOff = 1;
// Give each relay friendly name and map to Arduino Digital I/O pin number
const byte relay1 = 2; // wire 1 of the engine kill actuator. Pulse to allow engine to run.
const byte relay2 = 3; // wire 2 of the engine kill actuator. Pulse to kill engine.
const byte relay3 = 4; // brake controller
const byte relay4 = 5; // currently unused
// For reading/feedback purposes, compare to the on off constants above
byte relay1State;
byte relay2State;
byte relay3State;
byte relay4State;
const unsigned long idleEngineKill = 50000; // delay to kill the motor
const unsigned long engineStartPeriod = 15000; // length of time allowed on a new connection to allow for starting the mower engine
long whatTimeIsIt = 0;
boolean timeOut = false;
// Initialize to zero to give it an obvious out of range starting value
// valid values will be from 1000 - 2000 with maybe some spillage in either direction
int curVal[7] = {0, 0, 0, 0, 0, 0, 0}; // Current values of the channel signals
int prevVal[7] = {0, 0, 0, 0, 0, 0, 0}; // Previous values of the channel signals
int arraySize = 7;
int numChannels = 6; // size of both arrays, used for feedback purposes
const int chChangeThreshold = 15; // Used to determine if there was user input on a given channel
// boolean userInput = false;
boolean firstTimeOn = true;
boolean firstTimeOff = true;
byte throttleChannel = 3; // sets the throttle channel to remove it from idle calculations. Set to 0 to include all channels in idle calculations. This is selectable as the transmitter is programmable.
void setup(){
//ServoDecode setup
ServoDecode.begin();
ServoDecode.setFailsafe(3,1234); // set channel 3 failsafe pulse width
// Relay Setup Initialize Pins so relays are inactive at reset
digitalWrite(relay1, relayOff);
digitalWrite(relay2, relayOff);
digitalWrite(relay3, relayOff);
digitalWrite(relay4, relayOff);
relay1State = relayOff;
relay2State = relayOff;
relay3State = relayOff;
relay4State = relayOff;
// THEN set pins as outputs
pinMode(relay1, OUTPUT);
pinMode(relay2, OUTPUT);
pinMode(relay3, OUTPUT);
pinMode(relay4, OUTPUT);
delay(1000); // Check that all relays are inactive at Reset
// Prevent motor from starting and engage breaks
engineKill();
brakesEngage();
} // end setup()
void loop(){
if(ServoDecode.getState()!= READY_state) { // transmitter is off or out of range
if(firstTimeOff){
engineKill(); // kill motor
brakesEngage(); // engage brakes
whatTimeIsIt = 0; // used as a semaphore
//timeOut = false; // resets this semaphore
firstTimeOff = false;
firstTimeOn = true;
} // end if(firsTimeOff)
} // end if() transmitter not ready
if(ServoDecode.getState()== READY_state) { // transmitter is on
if (firstTimeOn){
whatTimeIsIt = millis(); // if it's the first loop through, make a note of the time
firstTimeOn = false; // resets this semaphore
firstTimeOff = true;
engineAllow(); // allow motor to be cranked
brakesDisengage(); //disengage brakes
} //enf if() firstTimeOn
if (millis() == (whatTimeIsIt + idleEngineKill) && (!firstTimeOn) ){ // if 5 seconds has passed since entering the ready state, kill the motor and engage the brakes
engineKill();
brakesEngage();
//timeOut = true;
} // end test for timeOut
if(testInput()){
pulseOutLow(relay4, 500);
} // end if() testInput()
} // end if() transmitter ready
} // end main loop()
void engineKill(){
if(relay1State == relayOn){ // just in case it was on for some random reason
digitalWrite(relay1, relayOff); // prevents frying relays and/or actuators
relay1State = relayOff;
} // end safety check
if(relay2State == relayOff){ // if it's already on, no need to do anything, so only do something if its off
relay2State = relayOn;
pulseOutLow(relay2, 500);
relay2State = relayOff;
} // end if()
//digitalWrite(relay2, relayOn); // eventually pulse it, but for now just latch it
} // end engineKill
void engineAllow(){
if(relay2State == relayOn){
digitalWrite(relay2, relayOff); // just in case it was on for some random reason; prevents frying relays and/or actuators
relay2State = relayOff;
} // end safety check
if(relay1State == relayOff){ // if it's already on, no need to do anything, so only do something if its off
relay1State = relayOn;
pulseOutLow(relay1, 500);
relay1State = relayOff;
} // end if()
//digitalWrite(relay1, relayOn); // eventually pulse it, but for now just latch it
} // end instantEngineKill
void brakesEngage(){
if(relay3State == relayOn){ // if it's already onn, no need to do anything, so only do something if its off
digitalWrite(relay3, relayOff); // engages the brakes by removing power, allowing the mower to move
relay3State = relayOff;
} // end if()
} // end brakesEngage
void brakesDisengage(){
if(relay3State == relayOff){ // if it's already on, no need to do anything, so only do something if its on
digitalWrite(relay3, relayOn); // disengages the brakes by applying voltage
relay3State = relayOn;
} // end if()
} // end brakesDisengage
void pulseOutLow(int pin, long duration){ // pulses a digital output for a certain length of time, meant for active low relays
unsigned long timeStart = millis();
boolean exitThis = false;
digitalWrite(pin, LOW);
while(!exitThis){
if(millis() >= (timeStart + duration)){
digitalWrite(pin, HIGH);
exitThis = true;
} // end if
} // end while()
} // end pulseOutLow
void pulseOutHigh(int pin, long duration){ // pulses a digital output for a certain length of time
unsigned long timeStart = millis();
boolean exitThis = false;
digitalWrite(pin, HIGH);
while(!exitThis){
if(millis() >= (timeStart + duration)){
digitalWrite(pin, LOW);
exitThis = true;
} // end if
} // end while()
} // end pulseOutHIGH
boolean testInput(){ // tests for user input via the transmitter
boolean userInput = false;
boolean curPrevChange[arraySize]; // used an array to test for ignore channel. May not be needed but makes it simpler
// move current values to previous array, sample current values, populate current array
for ( int i = 1; i <= numChannels; i++ ){
prevVal[i] = curVal[i];
curVal[i] = ServoDecode.GetChannelPulseWidth(i); // GetChannelPulseWidth technically requires a byte, but the number won't get high enough to cause problems
} // end move old values to proper variable
// Compute if there was a change
for ( int i = 1; i <= numChannels; i++ ){
if(i==throttleChannel){
curPrevChange[i] = false; // set to false b/c this is excluded from calculations
} // end if
else{ curPrevChange[i] = (curVal[i] - prevVal[i]) > chChangeThreshold; }
//end else
userInput = userInput | curPrevChange[i]; // if anything changes, the value will go true
} // end for() of compare channel values from each array and populate curPrevChange array
return userInput;
} // end testInput