Pulse a (dc) motor while receiving serial.

I am working on a project which will involve controlling the speed of a motor in various patterns. I’m currently using pwm (on pin 3) and a whack of delay()'s (ick, i know…) to create these patterns which is working just fine. The problem comes in when I try to control which pattern is active via serial. There’s a few long patterns (that can run from minutes to hours) which causes problems trying to read the serial. Essentially, when the pattern is running, I cannot issue a ‘stop’ command to halt the pattern.

I know the first obvious solution would be something along the lines of the ‘blink without delay’ tutorial (not sure of the name of the method used). I’ve done the same thing many times in simpler projects, but I just can’t wrap my head around how to go about doing it in this case. There’s many different patterns, with varying timing ranging from seconds to over an hour. (relevant code below)

My second idea is to use a second atmega to handle the serial (it will be a cellular chip in the final version) and ping the motor ‘node’ via a hardware interrupt. However I think that I’d still have the same problem due to the delay()'s.

On a side (but forum related) topic. How feasible is it to use bluetooth to pair the arduino to a cell phone? Would it be specific to the phone or is there some sort of standards in place? The project will be controlled via text messages, so I don’t really need much from the phone; only a notice of a new message, number and content.

Thanks as always for any help!

Code:

#define motor_PIN 3
#define NUM_motor_SPEEDS 4
#define motor_HIGH 255
#define motor_MEDHIGH 200
#define motor_MED 150
#define motor_LOW 100

#define MILLIS_IN_MINUTE 60000

int speeds[NUM_motor_SPEEDS] = {motor_LOW, motor_MED, motor_MEDHIGH, motor_HIGH};

void motorOff() {digitalWrite(motor_PIN, 0);}
void motorOn(byte spd) {analogWrite(motor_PIN, spd);}
void motorRampUp(byte maxSpeed, int dly) {
  for (int i=0;i<=maxSpeed;i+=5) {
    analogWrite(motor_PIN, i);
    delay(dly);
  }
}
void motorRampDown(byte maxSpeed, int dly) {
  for (int i=maxSpeed;i>0;i-=5) {
    analogWrite(motor_PIN, i);
    delay(dly);
  }
  motorOff();
}
void motorGrowingPulse(byte spd, byte pulseSpd, int onDly, int offDly) {
  motorRampUp(spd, pulseSpd);
  delay(onDly);
  motorRampDown(spd, pulseSpd);
  delay(offDly);
}
void motorQuickPulse() {
  motorOn(motor_HIGH);
  delay(150);
  motorOff();
  delay(50);
}
void motorQuickPulse(byte cnt) {
  for (byte i=0;i<cnt;i++) {
    motorQuickPulse();
  }
}
void motorIncreasingQuickPulses(byte cnt, long dly) {
  for (byte i=1;i<=cnt;i++) {
    for (byte x=0;x<i;x++) {
      motorQuickPulse();
    }
    delay(dly);
  }
}

void motorModeGrowingPulsesShort(byte cnt, byte spd) {
  for (byte i=0;i<cnt;i++) {
    motorGrowingPulse(spd, 25, 2000, 2000);
  }
}
void motorModeGrowingPulsesLong(byte cnt, byte spd) {
  for (byte i=0;i<cnt;i++) {
    motorGrowingPulse(spd, 100, 5000, 1000);
  }
}
void motorPatternA(byte spd, int totMinutes) {

  motorOn(motor_HIGH);
  delay(2000);
  motorOff();
  delay(150);
  motorQuickPulse(3);
  long totalMillis = totMinutes * MILLIS_IN_MINUTE;
  long current = 0;
  randomSeed(analogRead(0));
  delay(random(5000,30000));
  unsigned long startTime = millis();
  while (current <= totalMillis) {
    current = millis() - startTime;
    motorQuickPulse(random(1, 6));
    delay(random(100, ((totalMillis - current) * 0.1)));
  }
}
void motorPatternB(byte maxSpeed, int totMinutes) {
  unsigned long startTime = millis();
  long totalMillis = totMinutes * MILLIS_IN_MINUTE;
  long current = 0;
  long remaining = totalMillis - current;
  int minDly = 5000;
  byte cycle = 1;
  randomSeed(analogRead(0));
  while (current <= totalMillis) {
    current = millis() - startTime;
    remaining = totalMillis - current;
    if (remaining < 10000) minDly = 500;

    motorRampCycle(cycle++, motor_HIGH, 2, 5, 0);

    delay(random(minDly, remaining * 0.1));
  }
}
void motorModePatternC(byte cycles) {
  int bigPulseDly = 500;
  int miniPulseDly = 0;
  for (byte i=0;i<=cycles;i++) {
    Serial.print("Cyc: ");Serial.print(i, DEC);Serial.print(" big: ");Serial.print(bigPulseDly, DEC);Serial.print (", mini: ");Serial.println(miniPulseDly, DEC);
    motorRampCycle(1, motor_HIGH, 5, bigPulseDly, 100);
    motorRampCycle(i, motor_HIGH, 2, miniPulseDly, 0);
    if ((bigPulseDly+=100) > 3500) bigPulseDly = 3500;
    if ((miniPulseDly+=5) > 125) miniPulseDly = 125;
    delay(1000);
  }
}
void motorRampCycle(byte numCycles, byte spd, byte dly, int upDly, int downDly) {
  for (byte i=0;i<numCycles;i++) {
    motorRampUp(spd, dly);
    delay(upDly);
    motorRampDown(spd, dly);
    delay(downDly);
  }
  motorOff();
}

You need to use the blink without delay to implement a state machine and get rid of all those delays . This means that your loop will check the serial to see if you have to stop. Then it will look to see what sequence is currently running and jump to it. The sequence will set the time to do the next step, then check if that time is up. If it is not it will return without doing anything. When the time is up it will do the change and then set the time for that. Draw it out as a flow diagram to see how it will operate. Look up state machines and state variables.

Thanks for the push in the right direction. After a bunch of reading and testing, I settled on using the FiniteStateMachine library to handle the bulk of it. I ended up modifying it in order to add sub-state functionality and parameters, but it is working beautifully!

The code has been separated into individual files which contain a single function per state. The states are written so that they can be nested in order to build up more complex states with ease. For those who may be interested, the new code is below (100% rewrite).

#include <Messenger.h>
#include <FiniteStateMachine.h>
//#include <MemoryFree.h>

//#define DEBUG

#define MILLIS_IN_MINUTE 60000
#define MOTOR_PIN 3

State on = State("On", on_Update);
State off = State("Off", off_Update);
State idle = State("Idle", idle_Update);
State rampUp = State("RU", rampUp_Update);
State rampDown = State("RD", rampDown_Update);
State rampCycle = State("RC", rampCycle_Update);
State longPulse = State("LP", longPulse_Update);
State quickPulse = State("QP", quickPulse_Update);
State growingPulse = State("GP", growingPulse_Update);
State driveYouNuts = State("DYN", driveYouNuts_Update);
State totalInsanity = State("TI", totalInsanity_Update);
State incQuickPulses = State("IQP", incQuickPulses_Update);
State growingInsanity = State("GI", growingInsanity_Update);

FSM sm = FSM(on); // start in the off state
Messenger message = Messenger(); 
int curVal = 0;

void setup() {
  Serial.begin(38400);
#ifdef DEBUG
  Serial.println("Init...");
#endif
  randomSeed(analogRead(0));
  message.attach(messageCompleted);
#ifdef DEBUG
  Serial.println("End Init...");  
#endif
}
void loop(){
  sm.update();
  if(Serial.available() > 0) message.process(Serial.read());
  outputChange();  
}

void messageCompleted() {
  if (message.checkString("off")) {
    sm.transitionTo(off, true);
  } else if (message.checkString("menu")) {
    showMenu();
  } else if (message.checkString("run")) {
    switch (message.readChar()) {
      case 'o':case 'O':
        sm.transitionTo(off, true);
      break;
      case 'n':case 'N':
        sm.transitionTo(on, true);
      break;
      case 'u':case 'U':
        sm.transitionTo(rampUp, true);
      break;
      case 'd':case 'D':
        sm.transitionTo(rampDown, true);
      break;
      case 'r':case 'R':
        sm.transitionTo(rampCycle, true, message.readInt());
      break;
      case 'q':case 'Q':
        sm.transitionTo(quickPulse, true, message.readInt());
      break;
      case 'l':case 'L':
        sm.transitionTo(longPulse, true, message.readInt());
      break;
      case 'g':case 'G':
        sm.transitionTo(growingPulse, true);
      break;
      case 'i':case 'I':
        sm.transitionTo(incQuickPulses, true, message.readInt());
      break;
      case 'y':case 'Y':
        sm.transitionTo(driveYouNuts, true, message.readInt());
      break;
      case 's':case 'S':
        sm.transitionTo(totalInsanity, true, message.readInt());
      break;
      case 'a':case 'A':
        sm.transitionTo(growingInsanity, true, message.readInt());
      break;
    }
  }
}
void driveYouNuts_Update(int totMinutes) {
  static int step = 0;
  static long next = 0;
  const long totMillis = totMinutes * MILLIS_IN_MINUTE;

  if (step > 0 && sm.timeSinceSave() >= totMillis) {
    step = -1;
    next = 0;
    sm.transitionTo(off);
  } else if (millis() >= next) {
    switch (step) {
      case 0:
        sm.saveTime();
        sm.subTransitionTo(on);                        // ON
        next = millis() + 2000;
      break;
      case 1:
        sm.subTransitionTo(off);                       // OFF
        next = millis() + 150;
      break;
      case 2: case 3: case 4:
        sm.subTransitionTo(quickPulse);                // 3 QUICK PULSES
      break;
      case 5:
        next = millis() + random(5000,30000);          // DELAY BETWEEN 5 AND 30 SECONDS
      break;
      default:
        sm.subTransitionTo(quickPulse);
        next = millis() + random(100, ((totMillis - sm.timeSinceSave()) * 0.1));
      break;
    }
    step++;
  }
}
void growingInsanity_Update(int cycles) {
  static int bigPulseDly = 100;
  static int miniPulseDly = 0;
  static int step = 0;
  static int cnt = 0;
  static long next = 0;

  if (cnt >= cycles) {
    step = -1;
    miniPulseDly = cnt = next = 0;
    bigPulseDly = 500;
    sm.transitionTo(off);
  } else if (millis() >= next) {
    if ((bigPulseDly+=100) > 1500) bigPulseDly = 1500;
    if ((miniPulseDly+=5) > 125) miniPulseDly = 125;
    switch (step) {
      case 0:
        sm.subTransitionTo(longPulse, cnt);
        next = millis() + bigPulseDly + (550 * cnt);
      break;
      case 1:
        sm.subTransitionTo(quickPulse, cnt);
        next = millis() + miniPulseDly + (200 * cnt);
      break;
      default:
        next = millis() + 1000;
        cnt++;
      break;
    }
    step = (step+1) % 3;
  }
}
void growingPulse_Update(int param) {
  static int step = 0;
  static long next = 0;
  
  if (millis() >= next) {
    switch (step) {
      case 0:
        sm.subTransitionTo(rampUp);
        next = millis() + 25;
      break;
      case 1:
        sm.subTransitionTo(rampDown);
        next = millis() + 25;
      break;
      default:
        step = -1;
        next = 0;
        sm.transitionTo(idle);
      break;
    }
    step++;
  }
}
void incQuickPulses_Update(int param) {
  static long next = 0;
  static int cnt = 0;

  if (cnt == param) {
    next = 0;
    cnt = 0;
    sm.transitionTo(idle);
  } else if (millis() >= next) {
    cnt++;
    next = millis() + 1000 + (cnt * 200); // includes time of pulses
    sm.subTransitionTo(quickPulse, cnt);
  }
}
void longPulse_Update(int param) {
  static int step = 0;
  static int cnt = 0;
  static long next = 0;
  
  if (param == 0) param = 1;
  
  if (cnt == param) {
    next = 0;
    step = cnt = -1;
    sm.transitionTo(idle);
  }
  if (millis() >= next) {
    switch (step) {
      case 0:
        sm.subTransitionTo(on);
        next = millis() + 500;
      break;
      case 1:
        sm.subTransitionTo(off);
        next = millis() + 50;
      break;
      default:
        step = -1;
        cnt++;
      break;
    }
    step++;
  }
}
void quickPulse_Update(int param) {
  static int step = 0;
  static int cnt = 0;
  static long next = 0;
  
  if (param == 0) param = 1;
  
  if (cnt == param) {
    next = 0;
    step = cnt = -1;
    sm.transitionTo(idle);
  }
  if (millis() >= next) {
    switch (step) {
      case 0:
        sm.subTransitionTo(on);
        next = millis() + 150;
      break;
      case 1:
        sm.subTransitionTo(off);
        next = millis() + 50;
      break;
      default:
        step = -1;
        cnt++;
      break;
    }
    step++;
  }
}
void rampCycle_Update(int numCycles) {
  static int num = 0;
  static int step = 0;
  static long next = 0;

  if (num < numCycles) {
    if (millis() >= next) {
      switch (step) {
        case 0:
          sm.subTransitionTo(rampUp);
          next = millis() + 1000;
        break;
        case 1:
          num++;
          sm.subTransitionTo(rampDown);
          next = millis() + 500;
        break;
        default:
          step = -1;
          next = 0;
        break;
      }
      step++;
    }
  } else {
    num = 0;
    step = -1;
    sm.transitionTo(off);
  }
}
void rampDown_Update(int param) {
  static long next = 0;
  if (millis() >= next) {
    if ((curVal -= 5) >= 0) {
      next = millis() + 25;
    } else {
      next = curVal = 0; // just to make sure it didn't 'overflow'
      sm.transitionTo(idle);
    }
  } 
}
void rampUp_Update(int param) {
  static long next = 0;

  if (millis() >= next) {
    if ((curVal += 5) <= 255) {
      next = millis() + 25;
    } else {
      curVal = 255; // just to make sure it didn't 'overflow'
      next = 0;
      sm.transitionTo(idle);
    }
  } 
}

Continuation of code block:

void totalInsanity_Update(int totMinutes) {
  static int step = 0;
  static long next = 0;
  int minDly = 5000;
  long totMillis = totMinutes * MILLIS_IN_MINUTE;
  long remaining = totMillis - sm.timeSinceSave();

  if (step > 0 && sm.timeSinceSave() >= totMillis) {
    step = -1;
    next = 0;
    sm.transitionTo(off);
  } else if (millis() >= next) {
    switch (step) {
      case 0:
        sm.saveTime();
      break;
      default:
        if (remaining < 10000) minDly = 500;
        sm.subTransitionTo(rampCycle, step);
        next = millis() + (4000 * step) + random(minDly, remaining * 0.1); // includes offset for rampcycle duration (~4000mS)
      break;
    }
    step++;
  }
}
void showMenu() {
  Serial.println("Commands are NOT case sensitive:\n\nM - Show menu         O - Off\nN - On                U - Ramp Up\nD - Ramp Down         Q - Quick Pulse\nG - Growing Pulse     I - Inc. Quick Pulses\nR - Ramp Cycle        Y - Drive You Nuts\nS - Total Insanity    A - Growing Insanity");
}
void outputChange() {
  static int oldVal = -1;
  if (curVal != oldVal) { // && abs(oldVal-curVal) > 10) {
    oldVal = curVal;
    analogWrite(MOTOR_PIN, curVal);
//    int tmp = curVal / 10;
//    String str = "Update: ";for (int i=0;i<=tmp;i++) str += "*";
//    Serial.println(str);
  }
}
/*
  The functions below are helper functions for the states of the program
*/
void on_Update(int param) {
  curVal = 255;
  sm.transitionTo(idle);
}
void off_Update(int param) {
  curVal = 0;
  sm.transitionTo(idle);
}
void idle_Update(int param) {
  static boolean state = 0;
  digitalWrite(13, (state ^= 1));
}

Diff’s of my changes to the Finite State Machine library are below.

cpp:

31,33c31
< 
< 
< 
---
> //#define DEBUG
35c33
< State::State( void (*updateFunction)() ){
---
> State::State(char n[], void (*updateFunction)(int param) ){
37a36
> 	name = n;
41c40
< State::State( void (*enterFunction)(), void (*updateFunction)(), void (*exitFunction)() ){
---
> State::State( void (*enterFunction)(), void (*updateFunction)(int param), void (*exitFunction)() ){
46a46
> 
55c55
< void State::update(){
---
> void State::update(int param){
57c57
< 		userUpdate();
---
> 		userUpdate(param);
72a73
> 	inSub = false;
74c75,76
< 	stateChangeTime = 0;
---
> 	subCnt = 0;
> 	stateChangeTimes[0] = millis();
85c87
< 			immediateTransitionTo(*nextState);
---
> 			immediateTransitionTo(*nextState, param);
87c89
< 		currentState->update();
---
> 		currentState->update(stateParams[subCnt]);
92c94,99
< FiniteStateMachine& FiniteStateMachine::transitionTo(State& state){
---
> FiniteStateMachine& FiniteStateMachine::transitionTo(State& state, bool bO, int par) {
> 	breakOut = bO;
> 	stateParams[subCnt] = par;
> 	if (!breakOut && subCnt > 0) {
> 		nextState = returnStates[subCnt];
> 	} else {
94c101,104
< 	stateChangeTime = millis();
---
> 	}
> #ifdef DEBUG
> 	Serial.print(subCnt, DEC);Serial.print(": ");Serial.print("QueueTrans To: ");Serial.print(nextState->name);Serial.print(": ");Serial.println(stateParams[subCnt], DEC);
> #endif
98c108
< FiniteStateMachine& FiniteStateMachine::immediateTransitionTo(State& state){
---
> FiniteStateMachine& FiniteStateMachine::immediateTransitionTo(State& state, int par){
99a110,139
> 	
> 	if (par != NULL && stateParams[subCnt] != NULL)
> 		stateParams[subCnt] = par;
> 	
> 	if (!breakOut && subCnt > 0) {
> 		currentState = nextState = returnStates[subCnt];
> 		subCnt--;
> #ifdef DEBUG
> 		Serial.print(subCnt, DEC);Serial.print(": ");Serial.print("Returning To: ");Serial.println(currentState->name);
> #endif
> 	} else {
> 		inSub = breakOut = false;
> 		subCnt = 0;
> #ifdef DEBUG
> 		Serial.print(subCnt, DEC);Serial.print(": ");Serial.print("Transition From: ");Serial.print(currentState->name);Serial.print(" To: ");Serial.print((&state)->name);Serial.print(": ");Serial.println(stateParams[subCnt], DEC);
> #endif
> 		currentState = nextState = &state;
> 		currentState->enter();		
> 		stateChangeTimes[subCnt] = millis();
> 	}	
> 	return *this;
> }
> 
> FiniteStateMachine& FiniteStateMachine::subTransitionTo( State& state, int par ) {
> 	inSub = true;
> 	subCnt++;
> 	returnStates[subCnt] = &getCurrentState();
> 	stateParams[subCnt] = par;
> 	stateChangeTimes[subCnt] = millis();
> 
100a141,143
> #ifdef DEBUG
> 	Serial.print(subCnt, DEC);Serial.print(": ");Serial.print("Subbing To: ");Serial.print(currentState->name);Serial.print(": ");Serial.println(stateParams[subCnt], DEC);
> #endif
102d144
< 	stateChangeTime = millis();
119a162,171
> void FiniteStateMachine::saveTime(byte num) {
> 	timeSaves[num] = millis();
> }
> unsigned long FiniteStateMachine::getTime(byte num) {
> 	return timeSaves[num];
> }
> unsigned long FiniteStateMachine::timeSinceSave(byte num) {
> 	return (millis() - timeSaves[num]);
> }
> 
121c173
< 	millis() - stateChangeTime; 
---
> 	return millis() - stateChangeTimes[subCnt];

header:

44,45c44,49
< 		State( void (*updateFunction)() );
< 		State( void (*enterFunction)(), void (*updateFunction)(), void (*exitFunction)() );
---
> 		// State( void (*updateFunction)() );
> 		State( void (*updateFunction)(int param) );
> 		// State( char n[], void (*updateFunction)() );
> 		State( char n[], void (*updateFunction)(int param) );
> 		// State( void (*enterFunction)(), void (*updateFunction)(), void (*exitFunction)() );
> 		State( void (*enterFunction)(), void (*updateFunction)(int param), void (*exitFunction)() );
50c54,55
< 		void update();
---
> 		// void update();
> 		void update(int param);
51a57,58
> 		void exit(State&);
> 		char *name;
55c62
< 		void (*userUpdate)();
---
> 		void (*userUpdate)(int param);
65,66c72,74
< 		FiniteStateMachine& transitionTo( State& state );
< 		FiniteStateMachine& immediateTransitionTo( State& state );
---
> 		FiniteStateMachine& transitionTo(State& state, bool bO = false, int param = NULL);
> 		FiniteStateMachine& immediateTransitionTo(State& state, int param = NULL);
> 		FiniteStateMachine& subTransitionTo(State& state, int param = NULL);
69a78,80
> 		String name;
> 		
> 		void saveTime(byte num = 0);
71a83,84
> 		unsigned long getTime(byte num = 0);
> 		unsigned long timeSinceSave(byte num = 0);
74a88,92
> 		bool	breakOut;
> 		bool    inSub;
> 		int		subCnt;
> 		int		param;
> 		int		stateParams[20];
77c95,97
< 		unsigned long stateChangeTime;
---
> 		State*  returnStates[20];
> 		unsigned long stateChangeTimes[20];
> 		unsigned long timeSaves[20];