Controlling stepper motors via Bluetooth (using millis() instead of delay())

Hello dear forum users!

So I'm working on a robot car that has to follow a black line, the well known 'line follower'. However, I don't use DC motors, but stepper motors. I use two NEMA17 motors in conjunction with two DRV8825 stepper drivers. I also would like to be able to control the car manually via Bluetooth.

I'm aware that delay() should be avoided if possible, so I'm looking into other ways to control my steppers either manually or automatically by a triplet of digital IR-sensors.

This is a simple experimentation sketch that I've been using so far:

byte directionPin = 12;
byte stepPin = 11;
byte directionPin2 = 6;
byte stepPin2 = 5;

unsigned long curMillis;
unsigned long prevStepMillis = 0;
unsigned long millisBetweenSteps = 2; // milliseconds

void setup() {
  Serial.begin(9600);
  Serial3.begin(9600);
  pinMode(directionPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(directionPin2, OUTPUT);
  pinMode(stepPin2, OUTPUT);
}

void loop() {
  StepForward();
  curMillis = millis();
}
void StepForward() {
  if (curMillis - prevStepMillis >= millisBetweenSteps) {
    prevStepMillis = curMillis;
    digitalWrite(directionPin, LOW);
    digitalWrite(directionPin2, LOW);
    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);
    digitalWrite(stepPin2, HIGH);
    digitalWrite(stepPin2, LOW);
  }
}

This works. But I still can't control the steppers and their direction myself. So I've implemented a switch case control structure to be able to control the car over BT (or serial, for BT I'm using the hardware serial ports in the Arduino Mega).

That code looks like this:

byte directionPin = 12;
byte stepPin = 11;
byte directionPin2 = 6;
byte stepPin2 = 5;

unsigned long curMillis;
unsigned long prevStepMillis = 0;
unsigned long millisBetweenSteps = 2; // milliseconds

void setup() {
  Serial.begin(9600);
  Serial3.begin(9600);
  pinMode(directionPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  pinMode(directionPin2, OUTPUT);
  pinMode(stepPin2, OUTPUT);
}

void loop() {
  if (Serial3.available() > 0) {
    char data = Serial3.read();
    switch (data) {
      case 'F':
      StepForward();
      break;
      default:
      break;
    }
  }
  //StepForward();
  curMillis = millis();
}
void StepForward() {
  if (curMillis - prevStepMillis >= millisBetweenSteps) {
    prevStepMillis = curMillis;
    //digitalWrite(directionPin, LOW);
    //digitalWrite(directionPin2, LOW);
    digitalWrite(stepPin, HIGH);
    digitalWrite(stepPin, LOW);
    digitalWrite(stepPin2, HIGH);
    digitalWrite(stepPin2, LOW);
  }
}

"

This doesn't work, and it's probably something I'm missing in the code. The steppers step way slower that they did with the previous code. The Android app is sending the 'F' every 50ms.

I've also made other functions for going backwards, left, right and to stop. They all have the same issue.

How can I get the steppers to drive the same way they do by directly running 'StepForward()' in the first code?

Ukn0wn1:
The Android app is sending the 'F' every 50ms.

That is not a good way to do things. A better way would be to send 'F' when the move is to start and (say) 'f' when it is to end.

Then the Arduino can get on with moving the motor.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data.

...R

Okay, thanks for your reply.

I've corrected what you've said, and now I can start the function with 'F' and stop it with 'f'.
I used the following code for that:

void loop() {
  curMillis = millis();
  char data = Serial.read();

  switch (data) {
    case 'F':
      var = true;
      break;
    case 'f':
      var = false;
      break;
  }
  switch (var) {
    case true:
      StepForward();
      break;
    case false:
      StepStop();
      break;
    default:
      break;
  }
}

But say I have a button in my Android App, and if I click this button and it sends 'F', the steppers will run but I can't really control it 'real time'. Since they'll keep running until they're stopped. I would like to be able to hold the button, and when I release the steppers stop. I know how to do this with the 'conventional' 'delay()-way' of controlling the steppers, but not with millis().

Any suggestions? :slight_smile:

Ukn0wn1:
I would like to be able to hold the button, and when I release the steppers stop.

The normal way to do that is for the Android app to send the 'f' when you release the button.

Computer keyboards have been working on the keyDown and keyUp principle for decades. I'm sure the Android system has keyDown and keyUp events - though they may use a different name for them.

...R

Have you checked out the AccelStepper library? Its a non-blocking library for steppers with speed ramping,
position and speed modes.

So I've solved it by creating a custom library containing all the stepper-commands, using only one 'delayMicroseconds' per direction function (forward, backward etc.).

It works perfect now! I'm using the 'RC Car Bluetooth' - app (from google play). Which sends a capital char while pressing a button, and releasing the same low cap char when I release the button.

Now, I would like to be able to switch between line follower mode, which is the Linetrack() function, and manual mode. Which is the Manual() function. I've tried doing it by creating a switch case for the variable 'Data', and setting the capital 'V' to Manual() and the lowercase 'v' to Linetrack(). This doesn't work unfortunately, I do hear the steppers do something when I click the button. It's like they 'engage' when I toggle between the two functions.

Should I use a totally different char for both functions? Or did I miss something in the switch case.

Here's the working code:

#include <Move.h>

Move Move(6, 5, 9, 10, 4, 8); //dirpin, steppin, dirpin2, steppin2, enable, enable2

int stepTime = 2000;

char Data;

const int LS = 13;
const int RS = 12;

void setup() {
  Serial.begin(9600);
}

void Manual() {
  switch (Data) {
    case ('F'):
      Move.Forward(stepTime);
      break;
    case ('B'):
      Move.Reverse(stepTime);
      break;
    case ('S'):
      Move.Stop();
      break;
    case ('L'):
      Move.hardLeft(stepTime);
      break;
    case ('R'):
      Move.hardRight(stepTime);
      break;
    case ('I'):
      Move.Right(stepTime);
      break;
    case ('G'):
      Move.Left(stepTime);
      break;
    default: Move.Stop();
      break;
  }
}


void Linetrack() {
  bool a = digitalRead(LS);
  bool b = digitalRead(RS);

  if ((a == HIGH) && (b == HIGH)) {
    Move.Forward(stepTime);
  }

  else if ((a == LOW) && (b == HIGH)) {
    Move.Right(stepTime);
  }

  else if ((a == HIGH) && (b == LOW)) {
    Move.Left(stepTime);
  }

  else if ((a == LOW) && (b == LOW)) {
    Move.Stop();
  }

}

void loop() {
  if (Serial.available()) {

    Data = Serial.read();
    }
   Linetrack();
   // Manual();
  }

Ukn0wn1:
Here's the working code:

I suspect it is the non-working code that you need help with.

How do the upper and lower 'V's get to the Arduino?

...R

Yes, but I reverted all changes from the non-working code back to this one. But the only difference was a switch case-statement in the main loop.

Which looked like this:

  switch (Data) {
    case ('V'):
      Linetrack();
      break;
    case ('v'):
      Manual();
      break;
    default:
      Move.Stop();
      break;
  }

The arduino gets the char via the HC06 BT module, the same way the steppers are controlled in the Manual() function.

Ukn0wn1:
Yes, but I reverted all changes from the non-working code back to this one.

Please post the complete program that is causing a problem and describe in as much detail as possible what happens when you run it.

Help us to help you.

...R