Using millis() to control 2W car turning

Currently, I'm working on using one of these cars:Car Chassis link

I'm using these encoders to record the Car's RPM Encoder Link

I'm controlling the car via Bluetooth and using X, Y coordinates to control it. There is only one problem with it. The turning is a bit inaccurate.

How it works is, I take the X and Y values, compute the angle and how long it would take the car to turn that much and then use millis to run the function until the interval is up. The thing is, the wheels spin kind of fast and the interval is low (maybe 30ms) and the arduino usually runs the function for too long sometimes twice as long.

This isn't all the code but the parts to do with the turning.

angle = atan2 ( yVal, xVal) * ( 180/M_PI);
distance = angle/360;
dist_to_travel = (CarDiameter * M_PI) * distance;

DistSecond = ((WheelDiameter * M_PI) * RPM / 60000);
interval = dist_to_travel * DistSecond;

currentMillis = Millis();
if ((currentMillis - PreviousMillis) >= interval){
StrDrive(); //Function to drive the car in a straight line after turning
} else {
digitalWrite(rForward, HIGH);
digitalWrite(lBack, HIGH);
}

Is this feasible or would there better ways to controlling turning? I'm using the 2 interupt pins for the encoders so I don't think I can make the wheel spin a specific number of times and I'm also using TimerOne to calculate the RPM periodically.

Hi,
What are you using as a motor speed controller?

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Please post your entire code.

Thanks.. Tom.. :slight_smile:

rx92v2:
How it works is, I take the X and Y values, compute the angle and how long it would take the car to turn that much and then use millis to run the function until the interval is up. The thing is, the wheels spin kind of fast and the interval is low (maybe 30ms) and the arduino usually runs the function for too long sometimes twice as long.

That does not seem a practical way to manage turning. I reckon you need a compass so you can command a turn of X degrees and get feedback from the compass to let you know when you have done that.

...R

Sorry for taking so long to reply. My code and a drawing at at the bottom. Currently, I'm not using any special motor speed control. I thought about using PMW on the Arduino but those are disabled due to using the Timer to calculate the RPM.

I reckon you need a compass so you can command a turn of X degrees and get feedback from the compass to let you know when you have done that.

I thought about that but most Compass modules I see are quite expensive.

#include <math.h>
#include <TimerOne.h>

int wheelDia = 66;
int CarDia = 125;

int rForward = 10;
int rBack = 11;
int lForward = 6;
int lBack = 5;
int rEncoder = 3;
int lEncoder = 2;

int rSpeed, lSpeed;

int xVal, yVal;

unsigned int count1, count2;
float angle;

unsigned long previousMillis;

typedef enum {  NONE, GOT_X, GOT_Y,END } states;
states state = NONE;
unsigned int currentValue;

bool WaitForCo = true;
int CarState = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(rForward, OUTPUT);
  pinMode(rBack, OUTPUT);
  pinMode(lForward, OUTPUT);
  pinMode(lBack, OUTPUT);
  pinMode(lEncoder, INPUT);
  pinMode(rEncoder, INPUT);

  state = NONE;

  Timer1.initialize(1000000);
  attachInterrupt(0, do_count, RISING);
  attachInterrupt(1, do_count2, RISING);
  Timer1.attachInterrupt(timerIsr);
}

void do_count(){
  count1++;
}

void do_count2(){
  count2++;
}

void timerIsr(){
  Timer1.detachInterrupt();
  rSpeed = (count2/20)*60;
  lSpeed = (count1/20)*60;
  count1 = 0; count2 = 0;
  Timer1.attachInterrupt(timerIsr);
}

void StrDrive(){
  
  long currentMillis = millis();
  int SqRoot = sqrt(square(xVal) + square(yVal));
  long interval = DrvCalc(rSpeed, SqRoot) ;

  if ((currentMillis - previousMillis) >= interval){
    StopDrive();
    previousMillis = millis();
  } else {
    digitalWrite(rForward, HIGH);
    digitalWrite(lForward, HIGH);
  }
  
}

void LftTurn(){
  long currentMillis = millis();

  long interval = DrvCalc(lSpeed, DrvCalc(yVal, xVal)) ;
  if ((currentMillis - previousMillis) >= interval){
    StrDrive(); //Function to drive the car in a straight line after turning
    previousMillis = millis();
  } else {
    digitalWrite(rForward, HIGH);
    digitalWrite(lBack, HIGH);
  }
}

void RghtTurn(){
  long currentMillis = millis();
  long interval = DrvCalc(rSpeed, DrvCalc(yVal, xVal)) ;
  if ((currentMillis - previousMillis) >= interval){
    StrDrive(); //Function to drive the car in a straight line after turning
    previousMillis = millis();
  } else {
    digitalWrite(lForward, HIGH);
    digitalWrite(rBack, HIGH);
  }
}

void StopDrive(){
  digitalWrite(lForward, LOW);
  digitalWrite(lBack, LOW);
  digitalWrite(rForward, LOW);
  digitalWrite(rBack, LOW);
}

long DrvCalc(long RPM, long WantDis){
  float DisPerSec = ((wheelDia * M_PI) * RPM / 60000);
  float interval = WantDis *DisPerSec;
  return interval;
} 

long AngleCalc(int x, int y){
  angle = atan2 (y, x) * ( 180/M_PI);
  float distance = angle/360;
  float dist_to_travel = (CarDia * M_PI) * distance;
  return dist_to_travel;
} 

void handlePreviousState (){
  switch (state){
  case GOT_X:
    xVal = currentValue;
    break;
  case GOT_Y:
    yVal = currentValue;
    break;
  case END:
    if (xVal > 0) {
      CarState = 3; 
    } else if (xVal < 0){
      CarState = 1;
    } else {
      CarState = 2;
    }
    break;
  }    

  currentValue = 0; 
}  // end of handlePreviousState

void processIncomingByte (const byte c)
{
  if (isdigit (c)){
    currentValue *= 10;
    currentValue += c - '0';
  } else {
    handlePreviousState ();

    switch (c){
    case 'X':
      state = GOT_X;
      break;
    case 'Y':
      state = GOT_Y;
      break;
    case 'E':
      state = END;
      break;
    default:
      state = NONE;
      break;
    } 
  } 
}

void loop (){
  if (WaitForCo == true){
    while (Serial.available ())
      processIncomingByte (Serial.read ());
  } else {
    switch (CarState) {
      case 1:
        LftTurn();
        break;
      case 2:
        StrDrive();
        break;
      case 3:
        RghtTurn();
        break;
      case 0:
        StopDrive();
        break;
    }
  }
}

rx92v2:
I thought about using PMW on the Arduino but those are disabled due to using the Timer to calculate the RPM.

On an Uno there are 3 hardware Timers. Each one of them can operate PWM on two I/O pins. If your RPM detector is using hardware Timer1 then PWM will still be available on the pins controlled by Timer0 and Timer2

...R

The Timer0 pins are used for the right wheel but one of the timer2 pins (pin 3) is used for an interrupt, so unless I have the forward movement controlled by PWM pins and the back movement by regular digitalWrite(..., HIGH), I can't have both wheels controlled via PWM.

Hi,

I have the forward movement controlled by PWM pins and the back movement by regular digitalWrite(..., HIGH), I can't have both wheels controlled via PWM.

Why aren't you using the ENABLE pin on the L293.

If you PWM the enable pin you will be PWM controlling the motor, it doesn't matter what direction it is being asked to go.
Also PWM of zero will make the motor STOP.


Tom... :slight_smile:

Thanks, I'll make the changes to the circuit.

So, then would you say the best way to control the turning better is to slow down the wheel or are there any other way I could address the problem?